From: Jason Merrill <jason@redhat.com>
To: Marek Polacek <polacek@redhat.com>,
GCC Patches <gcc-patches@gcc.gnu.org>,
Joseph Myers <joseph@codesourcery.com>
Subject: Re: [PATCH] c: Implement C23 nullptr (N3042)
Date: Mon, 15 Aug 2022 16:03:13 -0400 [thread overview]
Message-ID: <38054995-48c3-8e98-623a-c2057ec70fb3@redhat.com> (raw)
In-Reply-To: <20220813213504.568937-1-polacek@redhat.com>
On 8/13/22 14:35, Marek Polacek wrote:
> This patch implements the C23 nullptr literal:
> <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3042.htm>, which is
> intended to replace the problematic definition of NULL which might be
> either of integer type or void*.
>
> Since C++ has had nullptr for over a decade now, it was relatively easy
> to just move the built-in node definitions from the C++ FE to the C/C++
> common code. Also, our DWARF emitter already handles NULLPTR_TYPE by
> emitting DW_TAG_unspecified_type. However, I had to handle a lot of
> contexts such as ?:, comparison, conversion, etc.
>
> There are some minor differences, e.g. in C you can do
>
> bool b = nullptr;
>
> but in C++ you have to use direct-initialization:
>
> bool b{nullptr};
>
> And I think that
>
> nullptr_t n = 0;
>
> is only valid in C++.
>
> Of course, C doesn't have to handle mangling, RTTI, substitution,
> overloading, ...
>
> This patch also defines nullptr_t in <stddef.h>. I'm uncertain about
> the __STDC_VERSION__ version I should be checking. Also, I'm not
> defining __STDC_VERSION_STDDEF_H__ yet, because I don't know what value
> it should be defined to. Do we know yet?
>
> Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?
The C++ changes are OK, but you probably want a comment in
c_common_nodes_and_builtins that we aren't setting the alignment there
for C++ backward ABI bug compatibility. Or perhaps set it there and
then break it in the C++ front end when abi < 9.
> gcc/c-family/ChangeLog:
>
> * c-common.cc (c_common_reswords): Enable nullptr in C.
> (c_common_nodes_and_builtins): Create the built-in node for nullptr.
> * c-common.h (enum c_tree_index): Add CTI_NULLPTR, CTI_NULLPTR_TYPE.
> (nullptr_node): Define.
> (nullptr_type_node): Define.
> (NULLPTR_TYPE_P): Define.
> * c-pretty-print.cc (c_pretty_printer::simple_type_specifier): Handle
> NULLPTR_TYPE.
> (c_pretty_printer::direct_abstract_declarator): Likewise.
> (c_pretty_printer::constant): Likewise.
>
> gcc/c/ChangeLog:
>
> * c-convert.cc (c_convert) <case POINTER_TYPE>: Handle NULLPTR_TYPE.
> Give a better diagnostic when converting to nullptr_t.
> * c-decl.cc (c_init_decl_processing): Perform C-specific nullptr
> initialization.
> * c-parser.cc (c_parser_postfix_expression): Handle RID_NULLPTR.
> * c-typeck.cc (null_pointer_constant_p): Return true for NULLPTR_TYPE_P.
> (build_unary_op) <case TRUTH_NOT_EXPR>: Handle NULLPTR_TYPE.
> (build_conditional_expr): Handle the case when the second/third operand
> is NULLPTR_TYPE and third/second operand is POINTER_TYPE.
> (convert_for_assignment): Handle converting an expression of type
> nullptr_t to pointer/bool.
> (build_binary_op) <case TRUTH_XOR_EXPR>: Handle NULLPTR_TYPE.
> <case EQ_EXPR>: Likewise.
>
> gcc/cp/ChangeLog:
>
> * cp-tree.h (enum cp_tree_index): Remove CTI_NULLPTR, CTI_NULLPTR_TYPE.
> Move it to c_tree_index.
> (nullptr_node): No longer define here.
> (nullptr_type_node): Likewise.
> (NULLPTR_TYPE_P): Likewise.
> * decl.cc (cxx_init_decl_processing): Only keep C++-specific nullptr
> initialization; move the shared code to c_common_nodes_and_builtins.
>
> gcc/ChangeLog:
>
> * ginclude/stddef.h: Define nullptr_t.
>
> gcc/testsuite/ChangeLog:
>
> * gcc.dg/Wcxx-compat-2.c: Remove nullptr test.
> * gcc.dg/c2x-nullptr-1.c: New test.
> * gcc.dg/c2x-nullptr-2.c: New test.
> * gcc.dg/c2x-nullptr-3.c: New test.
> * gcc.dg/c2x-nullptr-4.c: New test.
> * gcc.dg/c2x-nullptr-5.c: New test.
> ---
> gcc/c-family/c-common.cc | 13 +-
> gcc/c-family/c-common.h | 8 +
> gcc/c-family/c-pretty-print.cc | 7 +
> gcc/c/c-convert.cc | 19 ++-
> gcc/c/c-decl.cc | 6 +
> gcc/c/c-parser.cc | 8 +
> gcc/c/c-typeck.cc | 55 +++++-
> gcc/cp/cp-tree.h | 8 -
> gcc/cp/decl.cc | 8 +-
> gcc/ginclude/stddef.h | 8 +
> gcc/testsuite/gcc.dg/Wcxx-compat-2.c | 1 -
> gcc/testsuite/gcc.dg/c2x-nullptr-1.c | 239 +++++++++++++++++++++++++++
> gcc/testsuite/gcc.dg/c2x-nullptr-2.c | 9 +
> gcc/testsuite/gcc.dg/c2x-nullptr-3.c | 62 +++++++
> gcc/testsuite/gcc.dg/c2x-nullptr-4.c | 10 ++
> gcc/testsuite/gcc.dg/c2x-nullptr-5.c | 11 ++
> 16 files changed, 448 insertions(+), 24 deletions(-)
> create mode 100644 gcc/testsuite/gcc.dg/c2x-nullptr-1.c
> create mode 100644 gcc/testsuite/gcc.dg/c2x-nullptr-2.c
> create mode 100644 gcc/testsuite/gcc.dg/c2x-nullptr-3.c
> create mode 100644 gcc/testsuite/gcc.dg/c2x-nullptr-4.c
> create mode 100644 gcc/testsuite/gcc.dg/c2x-nullptr-5.c
>
> diff --git a/gcc/c-family/c-common.cc b/gcc/c-family/c-common.cc
> index 6e41ceb38e9..809e7ff5804 100644
> --- a/gcc/c-family/c-common.cc
> +++ b/gcc/c-family/c-common.cc
> @@ -500,7 +500,7 @@ const struct c_common_resword c_common_reswords[] =
> { "namespace", RID_NAMESPACE, D_CXXONLY | D_CXXWARN },
> { "new", RID_NEW, D_CXXONLY | D_CXXWARN },
> { "noexcept", RID_NOEXCEPT, D_CXXONLY | D_CXX11 | D_CXXWARN },
> - { "nullptr", RID_NULLPTR, D_CXXONLY | D_CXX11 | D_CXXWARN },
> + { "nullptr", RID_NULLPTR, D_CXX11 | D_CXXWARN },
> { "operator", RID_OPERATOR, D_CXXONLY | D_CXXWARN },
> { "private", RID_PRIVATE, D_CXX_OBJC | D_CXXWARN },
> { "protected", RID_PROTECTED, D_CXX_OBJC | D_CXXWARN },
> @@ -4723,6 +4723,17 @@ c_common_nodes_and_builtins (void)
> null_node = make_int_cst (1, 1);
> TREE_TYPE (null_node) = c_common_type_for_size (POINTER_SIZE, 0);
>
> + /* Create the built-in nullptr node. This part of its initialization is
> + common to C and C++. The front ends can further adjust its definition
> + in {c,cxx}_init_decl_processing. */
> + nullptr_type_node = make_node (NULLPTR_TYPE);
> + TYPE_SIZE (nullptr_type_node) = bitsize_int (GET_MODE_BITSIZE (ptr_mode));
> + TYPE_SIZE_UNIT (nullptr_type_node) = size_int (GET_MODE_SIZE (ptr_mode));
> + TYPE_UNSIGNED (nullptr_type_node) = 1;
> + TYPE_PRECISION (nullptr_type_node) = GET_MODE_BITSIZE (ptr_mode);
> + SET_TYPE_MODE (nullptr_type_node, ptr_mode);
> + nullptr_node = build_int_cst (nullptr_type_node, 0);
> +
> /* Since builtin_types isn't gc'ed, don't export these nodes. */
> memset (builtin_types, 0, sizeof (builtin_types));
> }
> diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
> index c06769b6f0b..d30174334c2 100644
> --- a/gcc/c-family/c-common.h
> +++ b/gcc/c-family/c-common.h
> @@ -375,6 +375,8 @@ enum c_tree_index
> CTI_DEFAULT_FUNCTION_TYPE,
>
> CTI_NULL,
> + CTI_NULLPTR,
> + CTI_NULLPTR_TYPE,
>
> /* These are not types, but we have to look them up all the time. */
> CTI_FUNCTION_NAME_DECL,
> @@ -534,6 +536,9 @@ extern const unsigned int num_c_common_reswords;
>
> /* The node for C++ `__null'. */
> #define null_node c_global_trees[CTI_NULL]
> +/* The nodes for `nullptr'. */
> +#define nullptr_node c_global_trees[CTI_NULLPTR]
> +#define nullptr_type_node c_global_trees[CTI_NULLPTR_TYPE]
>
> extern GTY(()) tree c_global_trees[CTI_MAX];
>
> @@ -1009,6 +1014,9 @@ extern void c_parse_final_cleanups (void);
> #define DECL_UNNAMED_BIT_FIELD(NODE) \
> (DECL_C_BIT_FIELD (NODE) && !DECL_NAME (NODE))
>
> +/* True iff TYPE is cv decltype(nullptr). */
> +#define NULLPTR_TYPE_P(TYPE) (TREE_CODE (TYPE) == NULLPTR_TYPE)
> +
> extern tree do_case (location_t, tree, tree);
> extern tree build_stmt (location_t, enum tree_code, ...);
> extern tree build_real_imag_expr (location_t, enum tree_code, tree);
> diff --git a/gcc/c-family/c-pretty-print.cc b/gcc/c-family/c-pretty-print.cc
> index 71a0cb51093..efa1768f4d6 100644
> --- a/gcc/c-family/c-pretty-print.cc
> +++ b/gcc/c-family/c-pretty-print.cc
> @@ -321,6 +321,7 @@ pp_c_pointer (c_pretty_printer *pp, tree t)
> _Bool -- C99
> _Complex -- C99
> _Imaginary -- C99
> + nullptr_t -- C23
> struct-or-union-specifier
> enum-specifier
> typedef-name.
> @@ -424,6 +425,9 @@ c_pretty_printer::simple_type_specifier (tree t)
> else
> translate_string ("<anonymous>");
> break;
> + case NULLPTR_TYPE:
> + pp_c_ws_string (this, "nullptr_t");
> + break;
>
> default:
> pp_unsupported_tree (this, t);
> @@ -678,6 +682,7 @@ c_pretty_printer::direct_abstract_declarator (tree t)
> case COMPLEX_TYPE:
> case TYPE_DECL:
> case ERROR_MARK:
> + case NULLPTR_TYPE:
> break;
>
> default:
> @@ -1219,6 +1224,8 @@ c_pretty_printer::constant (tree e)
> pp_c_character_constant (this, e);
> else if (TREE_CODE (type) == ENUMERAL_TYPE)
> pp_c_enumeration_constant (this, e);
> + else if (NULLPTR_TYPE_P (type))
> + pp_string (this, "nullptr");
> else
> pp_c_integer_constant (this, e);
> }
> diff --git a/gcc/c/c-convert.cc b/gcc/c/c-convert.cc
> index 18083d59618..013fe6b2a53 100644
> --- a/gcc/c/c-convert.cc
> +++ b/gcc/c/c-convert.cc
> @@ -133,6 +133,14 @@ c_convert (tree type, tree expr, bool init_const)
> (loc, type, c_objc_common_truthvalue_conversion (input_location, expr));
>
> case POINTER_TYPE:
> + /* The type nullptr_t may be converted to a pointer type. The result is
> + a null pointer value. */
> + if (NULLPTR_TYPE_P (TREE_TYPE (e)))
> + {
> + ret = build_int_cst (type, 0);
> + goto maybe_fold;
> + }
> + gcc_fallthrough ();
> case REFERENCE_TYPE:
> ret = convert_to_pointer (type, e);
> goto maybe_fold;
> @@ -180,7 +188,16 @@ c_convert (tree type, tree expr, bool init_const)
> return ret;
> }
>
> - error ("conversion to non-scalar type requested");
> + /* If we are converting to nullptr_t, don't say "non-scalar type" because
> + the nullptr_t type is a scalar type. Only nullptr_t shall be converted
> + to nullptr_t. */
> + if (code == NULLPTR_TYPE)
> + {
> + error ("conversion from %qT to %qT", TREE_TYPE (e), type);
> + inform (input_location, "only %qT can be converted to %qT", type, type);
> + }
> + else
> + error ("conversion to non-scalar type requested");
> return error_mark_node;
> }
>
> diff --git a/gcc/c/c-decl.cc b/gcc/c/c-decl.cc
> index ae8990c138f..ac4394c9dc5 100644
> --- a/gcc/c/c-decl.cc
> +++ b/gcc/c/c-decl.cc
> @@ -4531,6 +4531,12 @@ c_init_decl_processing (void)
> pushdecl (build_decl (UNKNOWN_LOCATION, TYPE_DECL, get_identifier ("_Bool"),
> boolean_type_node));
>
> + /* C-specific nullptr initialization. */
> + record_builtin_type (RID_MAX, "nullptr_t", nullptr_type_node);
> + /* The size and alignment of nullptr_t is the same as for a pointer to
> + character type. */
> + SET_TYPE_ALIGN (nullptr_type_node, GET_MODE_ALIGNMENT (ptr_mode));
> +
> input_location = save_loc;
>
> make_fname_decl = c_make_fname_decl;
> diff --git a/gcc/c/c-parser.cc b/gcc/c/c-parser.cc
> index 759f200a7eb..2af3a614fb9 100644
> --- a/gcc/c/c-parser.cc
> +++ b/gcc/c/c-parser.cc
> @@ -10243,6 +10243,14 @@ c_parser_postfix_expression (c_parser *parser)
> "%<depend%> clause");
> expr.set_error ();
> break;
> + /* C23 'nullptr' literal. */
> + case RID_NULLPTR:
> + c_parser_consume_token (parser);
> + expr.value = nullptr_node;
> + set_c_expr_source_range (&expr, tok_range);
> + pedwarn_c11 (loc, OPT_Wpedantic,
> + "ISO C does not support %qs before C2X", "nullptr");
> + break;
> default:
> c_parser_error (parser, "expected expression");
> expr.set_error ();
> diff --git a/gcc/c/c-typeck.cc b/gcc/c/c-typeck.cc
> index d37de2a313b..2ba48345a1b 100644
> --- a/gcc/c/c-typeck.cc
> +++ b/gcc/c/c-typeck.cc
> @@ -133,6 +133,13 @@ null_pointer_constant_p (const_tree expr)
> /* This should really operate on c_expr structures, but they aren't
> yet available everywhere required. */
> tree type = TREE_TYPE (expr);
> +
> + /* An integer constant expression with the value 0, such an expression
> + cast to type void*, or the predefined constant nullptr, are a null
> + pointer constant. */
> + if (NULLPTR_TYPE_P (type))
> + return true;
> +
> return (TREE_CODE (expr) == INTEGER_CST
> && !TREE_OVERFLOW (expr)
> && integer_zerop (expr)
> @@ -4575,7 +4582,7 @@ build_unary_op (location_t location, enum tree_code code, tree xarg,
> case TRUTH_NOT_EXPR:
> if (typecode != INTEGER_TYPE && typecode != FIXED_POINT_TYPE
> && typecode != REAL_TYPE && typecode != POINTER_TYPE
> - && typecode != COMPLEX_TYPE)
> + && typecode != COMPLEX_TYPE && typecode != NULLPTR_TYPE)
> {
> error_at (location,
> "wrong type argument to unary exclamation mark");
> @@ -5515,6 +5522,13 @@ build_conditional_expr (location_t colon_loc, tree ifexp, bool ifexp_bcp,
> }
> result_type = type2;
> }
> + /* 6.5.15: "if one is a null pointer constant (other than a pointer) or has
> + type nullptr_t and the other is a pointer, the result type is the pointer
> + type." */
> + else if (code1 == NULLPTR_TYPE && code2 == POINTER_TYPE)
> + result_type = type2;
> + else if (code1 == POINTER_TYPE && code2 == NULLPTR_TYPE)
> + result_type = type1;
>
> if (!result_type)
> {
> @@ -7613,9 +7627,10 @@ convert_for_assignment (location_t location, location_t expr_loc, tree type,
> error_at (location, msg);
> return error_mark_node;
> }
> - else if (codel == POINTER_TYPE && coder == INTEGER_TYPE)
> + else if (codel == POINTER_TYPE
> + && (coder == INTEGER_TYPE || coder == NULLPTR_TYPE))
> {
> - /* An explicit constant 0 can convert to a pointer,
> + /* An explicit constant 0 or nullptr can convert to a pointer,
> or one that results from arithmetic, even including
> a cast to integer type. */
> if (!null_pointer_constant)
> @@ -7691,7 +7706,10 @@ convert_for_assignment (location_t location, location_t expr_loc, tree type,
>
> return convert (type, rhs);
> }
> - else if (codel == BOOLEAN_TYPE && coder == POINTER_TYPE)
> + else if (codel == BOOLEAN_TYPE
> + /* The type nullptr_t may be converted to bool. The
> + result is false. */
> + && (coder == POINTER_TYPE || coder == NULLPTR_TYPE))
> {
> tree ret;
> bool save = in_late_binary_op;
> @@ -12107,10 +12125,10 @@ build_binary_op (location_t location, enum tree_code code,
> case TRUTH_XOR_EXPR:
> if ((code0 == INTEGER_TYPE || code0 == POINTER_TYPE
> || code0 == REAL_TYPE || code0 == COMPLEX_TYPE
> - || code0 == FIXED_POINT_TYPE)
> + || code0 == FIXED_POINT_TYPE || code0 == NULLPTR_TYPE)
> && (code1 == INTEGER_TYPE || code1 == POINTER_TYPE
> || code1 == REAL_TYPE || code1 == COMPLEX_TYPE
> - || code1 == FIXED_POINT_TYPE))
> + || code1 == FIXED_POINT_TYPE || code1 == NULLPTR_TYPE))
> {
> /* Result of these operations is always an int,
> but that does not mean the operands should be
> @@ -12418,6 +12436,31 @@ build_binary_op (location_t location, enum tree_code code,
> result_type = type1;
> pedwarn (location, 0, "comparison between pointer and integer");
> }
> + else if (null_pointer_constant_p (orig_op0)
> + && null_pointer_constant_p (orig_op1))
> + {
> + /* 6.5.9: One of the following shall hold:
> + -- both operands have type nullptr_t; */
> + if (code0 == NULLPTR_TYPE && code1 == NULLPTR_TYPE)
> + {
> + result_type = nullptr_type_node;
> + /* No need to convert the operands to result_type later. */
> + converted = 1;
> + }
> + /* -- one operand has type nullptr_t and the other is a null pointer
> + constant. We will have to convert the former to the type of the
> + latter, because during gimplification we can't have mismatching
> + comparison operand type. We convert from nullptr_t to the other
> + type, since only nullptr_t can be converted to nullptr_t. Also,
> + even a constant 0 is a null pointer constant, so we may have to
> + create a pointer type from its type. */
> + else if (code0 == NULLPTR_TYPE)
> + result_type = (INTEGRAL_TYPE_P (type1)
> + ? build_pointer_type (type1) : type1);
> + else
> + result_type = (INTEGRAL_TYPE_P (type0)
> + ? build_pointer_type (type0) : type0);
> + }
> if ((TREE_CODE (TREE_TYPE (orig_op0)) == BOOLEAN_TYPE
> || truth_value_p (TREE_CODE (orig_op0)))
> ^ (TREE_CODE (TREE_TYPE (orig_op1)) == BOOLEAN_TYPE
> diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> index 3278b4114bd..eb461bf8374 100644
> --- a/gcc/cp/cp-tree.h
> +++ b/gcc/cp/cp-tree.h
> @@ -187,9 +187,6 @@ enum cp_tree_index
> CPTI_NOEXCEPT_FALSE_SPEC,
> CPTI_NOEXCEPT_DEFERRED_SPEC,
>
> - CPTI_NULLPTR,
> - CPTI_NULLPTR_TYPE,
> -
> CPTI_ANY_TARG,
>
> CPTI_MODULE_HWM,
> @@ -254,8 +251,6 @@ extern GTY(()) tree cp_global_trees[CPTI_MAX];
> #define conv_op_marker cp_global_trees[CPTI_CONV_OP_MARKER]
> #define abort_fndecl cp_global_trees[CPTI_ABORT_FNDECL]
> #define current_aggr cp_global_trees[CPTI_AGGR_TAG]
> -#define nullptr_node cp_global_trees[CPTI_NULLPTR]
> -#define nullptr_type_node cp_global_trees[CPTI_NULLPTR_TYPE]
> /* std::align_val_t */
> #define align_type_node cp_global_trees[CPTI_ALIGN_TYPE]
>
> @@ -4405,9 +4400,6 @@ get_vec_init_expr (tree t)
> || TREE_CODE (TYPE) == REAL_TYPE \
> || TREE_CODE (TYPE) == COMPLEX_TYPE)
>
> -/* True iff TYPE is cv decltype(nullptr). */
> -#define NULLPTR_TYPE_P(TYPE) (TREE_CODE (TYPE) == NULLPTR_TYPE)
> -
> /* [basic.types]
>
> Arithmetic types, enumeration types, pointer types,
> diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
> index ff56fddba54..ba4fb21d36a 100644
> --- a/gcc/cp/decl.cc
> +++ b/gcc/cp/decl.cc
> @@ -4793,16 +4793,10 @@ cxx_init_decl_processing (void)
> }
> }
>
> - nullptr_type_node = make_node (NULLPTR_TYPE);
> - TYPE_SIZE (nullptr_type_node) = bitsize_int (GET_MODE_BITSIZE (ptr_mode));
> - TYPE_SIZE_UNIT (nullptr_type_node) = size_int (GET_MODE_SIZE (ptr_mode));
> - TYPE_UNSIGNED (nullptr_type_node) = 1;
> - TYPE_PRECISION (nullptr_type_node) = GET_MODE_BITSIZE (ptr_mode);
> + /* C++-specific nullptr initialization. */
> if (abi_version_at_least (9))
> SET_TYPE_ALIGN (nullptr_type_node, GET_MODE_ALIGNMENT (ptr_mode));
> - SET_TYPE_MODE (nullptr_type_node, ptr_mode);
> record_builtin_type (RID_MAX, "decltype(nullptr)", nullptr_type_node);
> - nullptr_node = build_int_cst (nullptr_type_node, 0);
> }
>
> if (! supports_one_only ())
> diff --git a/gcc/ginclude/stddef.h b/gcc/ginclude/stddef.h
> index 79e296d4a66..2ccf411ed88 100644
> --- a/gcc/ginclude/stddef.h
> +++ b/gcc/ginclude/stddef.h
> @@ -443,6 +443,14 @@ typedef struct {
> #endif
> #endif /* C++11. */
>
> +#if (defined (__STDC_VERSION__) && __STDC_VERSION__ >= 202000L)
> +#ifndef _GCC_NULLPTR_T
> +#define _GCC_NULLPTR_T
> + typedef __typeof__(nullptr) nullptr_t;
> +/* ??? This doesn't define __STDC_VERSION_STDDEF_H__ yet. */
> +#endif
> +#endif /* C23. */
> +
> #endif /* _STDDEF_H was defined this time */
>
> #endif /* !_STDDEF_H && !_STDDEF_H_ && !_ANSI_STDDEF_H && !__STDDEF_H__
> diff --git a/gcc/testsuite/gcc.dg/Wcxx-compat-2.c b/gcc/testsuite/gcc.dg/Wcxx-compat-2.c
> index 4578bece109..61d33a0b028 100644
> --- a/gcc/testsuite/gcc.dg/Wcxx-compat-2.c
> +++ b/gcc/testsuite/gcc.dg/Wcxx-compat-2.c
> @@ -18,7 +18,6 @@ int friend; /* { dg-warning "5:keyword" } */
> int mutable; /* { dg-warning "5:keyword" } */
> int namespace; /* { dg-warning "5:keyword" } */
> int new; /* { dg-warning "5:keyword" } */
> -int nullptr; /* { dg-warning "5:keyword" } */
> int operator; /* { dg-warning "5:keyword" } */
> int private; /* { dg-warning "5:keyword" } */
> int protected; /* { dg-warning "5:keyword" } */
> diff --git a/gcc/testsuite/gcc.dg/c2x-nullptr-1.c b/gcc/testsuite/gcc.dg/c2x-nullptr-1.c
> new file mode 100644
> index 00000000000..49e7031c4d9
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/c2x-nullptr-1.c
> @@ -0,0 +1,239 @@
> +/* Test basic usage of C23 nullptr. */
> +/* { dg-do run } */
> +/* { dg-options "-std=c2x -pedantic-errors -Wall -Wextra -Wno-unused-variable" } */
> +
> +#include <stdarg.h>
> +
> +typedef __typeof__(nullptr) nullptr_t;
> +
> +void f1 (nullptr_t) { }
> +void f2 (int *) { }
> +void f3 (_Bool) { }
> +nullptr_t cmp (void) { return nullptr; }
> +
> +/* The type nullptr_t shall not be converted to any type other than bool or
> + a pointer type. No type other than nullptr_t shall be converted to nullptr_t. */
> +void
> +test1 (void)
> +{
> + const nullptr_t nptr = nullptr;
> + static nullptr_t static_nptr;
> + int *p1 = nullptr;
> + void *p2 = nullptr;
> + float *p3 = nullptr;
> + void (*p4)(int) = nullptr;
> + int (*p5)[10] = nullptr;
> + int *p6 = nptr;
> + void *p7 = nptr;
> + float *p8 = nptr;
> + void (*p9)(int) = nptr;
> + int (*p10)[10] = nptr;
> + int *p11 = (int *) nullptr;
> + int *p12 = (int *) nptr;
> + if (nullptr || p1 || p2 || p3 || p4 || p5 || p6 || p7 || p8 || p9 || p10
> + || p11 || p12)
> + __builtin_abort ();
> +
> + _Bool b1 = nullptr;
> + _Bool b2 = (_Bool) nullptr;
> + _Bool b3 = nptr;
> + _Bool b4 = (_Bool) nptr;
> + if (b1 || b2 || b3 || b4 || (_Bool) nullptr || (_Bool) nptr)
> + __builtin_abort ();
> +
> + __auto_type a1 = nullptr;
> + __auto_type a2 = nptr;
> +
> + /* We can convert nullptr_t to nullptr_t. */
> + __typeof__(nullptr) x = nullptr;
> + f1 (x);
> + f1 (nullptr);
> + f2 (x);
> + f2 (nullptr);
> + f3 (nullptr);
> +
> + const nullptr_t np1 = nullptr;
> + const nullptr_t np2 = np1;
> +}
> +
> +/* Test valid comparison. */
> +void
> +test2 (int *p)
> +{
> + /* If both operands have type nullptr_t or one operand has type nullptr_t
> + and the other is a null pointer constant, they compare equal. */
> + const nullptr_t nptr = nullptr;
> + int r = 0;
> +
> + r |= nullptr != nullptr;
> + r |= cmp () != nullptr;
> + r |= nullptr != cmp ();
> + r |= !(nullptr == nullptr);
> + r |= !(cmp () == nullptr);
> + r |= !(nullptr == cmp ());
> + r |= nullptr != (void *) 0;
> + r |= !(nullptr == (void *) 0);
> + r |= (void *) 0 != nullptr;
> + r |= !((void *) 0 == nullptr);
> + r |= nullptr != 0;
> + r |= 0 != nullptr;
> + r |= !(nullptr == 0);
> + r |= !(0 == nullptr);
> + r |= nullptr != 0u;
> + r |= 0u != nullptr;
> + r |= !(nullptr == 0u);
> + r |= !(0u == nullptr);
> +
> + r |= nptr != nptr;
> + r |= cmp () != nptr;
> + r |= nptr != cmp ();
> + r |= !(nptr == nptr);
> + r |= !(cmp () == nptr);
> + r |= !(nptr == cmp ());
> + r |= nptr != (void *) 0;
> + r |= !(nptr == (void *) 0);
> + r |= (void *) 0 != nptr;
> + r |= !((void *) 0 == nptr);
> + r |= nptr != 0;
> + r |= 0 != nptr;
> + r |= !(nptr == 0);
> + r |= !(0 == nptr);
> + r |= nptr != 0u;
> + r |= 0u != nptr;
> + r |= !(nptr == 0u);
> + r |= !(0u == nptr);
> + if (r)
> + __builtin_abort ();
> +
> + (void) (p == nullptr);
> + (void) (p != nullptr);
> + (void) (nullptr == p);
> + (void) (nullptr != p);
> +}
> +
> +/* Test ?:. */
> +void
> +test3 (int *p, _Bool b)
> +{
> + int x = nullptr ? 1 : 2;
> + (void) x;
> + const nullptr_t nptr = nullptr;
> + /* One of the following shall hold for the second and third operands:
> + -- both operands have nullptr_t type. */
> + __auto_type r1 = b ? nullptr : nullptr;
> + __auto_type r2 = b ? nptr : nptr;
> + /* -- one operand is a pointer and the other is a null pointer constant
> + or has type nullptr_t; */
> + __auto_type r3 = b ? p : nullptr;
> + __auto_type r4 = b ? nullptr : p;
> + __auto_type r5 = b ? nptr : p;
> + __auto_type r6 = b ? p : nptr;
> + __auto_type r7 = b ? 0 : p;
> + __auto_type r8 = b ? p : 0;
> + __auto_type r9 = b ? p : cmp ();
> + __auto_type r10 = b ? cmp () : p;
> +}
> +
> +/* Simple assignment. */
> +void
> +test4 (void)
> +{
> + /* -- the left operand has an atomic, qualified, or unqualified version of
> + the nullptr_t type and the type of the right is nullptr_t; */
> + nullptr_t n1;
> + n1 = nullptr;
> + const nullptr_t n2 = nullptr;
> + _Atomic nullptr_t n3 = nullptr;
> + volatile nullptr_t n4 = nullptr;
> + /* -- the left operand is an atomic, qualified, or unqualified pointer,
> + and the type of the right is nullptr_t; */
> + int *p1 = cmp ();
> + _Atomic int *p2 = cmp ();
> + const int *volatile p3 = cmp ();
> + const int *const *const p4 = cmp ();
> + double (*const p5)(void) = n1;
> + /* -- the left operand is an atomic, qualified, or unqualified bool, and
> + the type of the right is nullptr_t; */
> + _Bool b1;
> + b1 = cmp ();
> + const _Bool b2 = nullptr;
> + _Atomic _Bool b3;
> + b3 = n1;
> + (void) b1;
> + (void) b3;
> +}
> +
> +/* var_arg etc. */
> +static void
> +test5 (int i, ...)
> +{
> + va_list ap;
> + va_start (ap, i);
> + if (va_arg (ap, void *))
> + __builtin_abort ();
> +}
> +
> +/* Operand of alignas, sizeof or typeof operators. */
> +void
> +test6 (void)
> +{
> + _Static_assert (sizeof (nullptr) == sizeof (void *), "sizeof (nullptr)");
> + _Static_assert (sizeof (nullptr_t) == sizeof (void *), "sizeof (nullptr_t)");
> + _Static_assert (sizeof (nullptr) == sizeof (char *), "sizeof (nullptr)");
> + _Static_assert (sizeof (nullptr_t) == sizeof (char *), "sizeof (nullptr_t)");
> + _Static_assert (_Alignof (nullptr_t) == _Alignof (char *), "_Alignof (nullptr_t)");
> + __typeof__(nullptr) t = nullptr;
> + f1 (t);
> + _Alignas (nullptr_t) char i1 = 'q';
> +
> + _Static_assert (_Generic (nullptr, nullptr_t: 1, default: 0) == 1, "_Generic");
> + _Static_assert (_Generic (t, nullptr_t: 1, default: 0) == 1, "_Generic");
> + _Static_assert (_Generic (cmp (), nullptr_t: 1, default: 0) == 1, "_Generic");
> + _Static_assert (_Generic (0, nullptr_t: 1, int: 2, default: 0) == 2, "_Generic");
> + _Static_assert (_Generic ((void *)0, nullptr_t: 1, void *: 2, default: 0) == 2, "_Generic");
> + _Static_assert (_Generic (nullptr, nullptr_t: 1, void *: 2, default: 0) == 1, "_Generic");
> +}
> +
> +/* Play with !, ||, &&. */
> +void
> +test7 (void)
> +{
> + if (nullptr)
> + __builtin_abort ();
> + if (1 && nullptr)
> + __builtin_abort ();
> + if (0 || nullptr)
> + __builtin_abort ();
> + if (nullptr && 1)
> + __builtin_abort ();
> + if (nullptr || 0)
> + __builtin_abort ();
> + if (!nullptr)
> + {
> + }
> + else
> + __builtin_abort ();
> + while (nullptr)
> + __builtin_abort ();
> + int i = 0;
> + do
> + ++i;
> + while (nullptr);
> + if (i != 1)
> + __builtin_abort ();
> + for (;nullptr;)
> + __builtin_abort ();
> +}
> +
> +int
> +main (void)
> +{
> + int i = 42;
> + test1 ();
> + test2 (&i);
> + test3 (&i, 0);
> + test4 ();
> + test5 (42, nullptr);
> + test6 ();
> + test7 ();
> +}
> diff --git a/gcc/testsuite/gcc.dg/c2x-nullptr-2.c b/gcc/testsuite/gcc.dg/c2x-nullptr-2.c
> new file mode 100644
> index 00000000000..2cc88353581
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/c2x-nullptr-2.c
> @@ -0,0 +1,9 @@
> +/* Test nullptr_t from <stddef.h.. */
> +/* { dg-do compile } */
> +/* { dg-options "-std=c2x -pedantic-errors" } */
> +
> +#include <stddef.h>
> +
> +void f(nullptr_t);
> +_Static_assert (sizeof (nullptr_t) == sizeof (char *), "sizeof (nullptr_t)");
> +_Static_assert (_Alignof (nullptr_t) == _Alignof (char *), "_Alignof (nullptr_t)");
> diff --git a/gcc/testsuite/gcc.dg/c2x-nullptr-3.c b/gcc/testsuite/gcc.dg/c2x-nullptr-3.c
> new file mode 100644
> index 00000000000..3f37c9b17ed
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/c2x-nullptr-3.c
> @@ -0,0 +1,62 @@
> +/* Test wrong usage of C23 nullptr. */
> +/* { dg-do compile } */
> +/* { dg-options "-std=c2x -pedantic-errors -Wall -Wextra -Wno-unused-variable" } */
> +
> +typedef __typeof__(nullptr) nullptr_t;
> +
> +void g (nullptr_t); /* { dg-message "expected .nullptr_t. but argument is of type .int." } */
> +
> +void
> +test1 (int *p)
> +{
> + (void) (p > nullptr); /* { dg-error "ordered comparison" } */
> + (void) (p >= nullptr); /* { dg-error "ordered comparison" } */
> + (void) (p < nullptr); /* { dg-error "ordered comparison" } */
> + (void) (p <= nullptr); /* { dg-error "ordered comparison" } */
> + (void) (nullptr == 1); /* { dg-error "invalid operands" } */
> + (void) (1 == nullptr); /* { dg-error "invalid operands" } */
> + (void) (nullptr != 1); /* { dg-error "invalid operands" } */
> + (void) (1 != nullptr); /* { dg-error "invalid operands" } */
> + (void) (1 > nullptr); /* { dg-error "invalid operands" } */
> +}
> +
> +void
> +test2 (void)
> +{
> + const nullptr_t nptr = nullptr;
> + int p = nullptr; /* { dg-error "incompatible types" } */
> + float d = nullptr; /* { dg-error "incompatible types" } */
> + char arr[10] = { nullptr }; /* { dg-error "incompatible types" } */
> +
> + /* No type other than nullptr_t shall be converted to nullptr_t. */
> + const nullptr_t n = 0; /* { dg-error "invalid initializer" } */
> + +(nullptr_t) 0; /* { dg-error "conversion from .int. to .nullptr_t." } */
> +
> + g (0); /* { dg-error "incompatible type" } */
> +
> + int i = 42 + nullptr; /* { dg-error "invalid operands" } */
> +
> + /* The assignment of an object of type nullptr_t with a value of another
> + type, even if the value is a null pointer constant, is a constraint
> + violation. */
> + nullptr_t m;
> + m = 0; /* { dg-error "incompatible types" } */
> + (void) m;
> + nullptr_t o = 0; /* { dg-error "invalid initializer" } */
> +
> + switch (nullptr); /* { dg-error "switch quantity not an integer" } */
> +}
> +
> +/* If a second or third operand of type nullptr_t is used that is not a null
> + pointer constant and the other operand is not a pointer or does not have
> + itself nullptr_t, a constraint is violated even if that other operand is
> + a null pointer constant such as 0. */
> +void
> +test3 (_Bool b, int i)
> +{
> + const nullptr_t nptr = nullptr;
> + __auto_type a1 = b ? nptr : i; /* { dg-error "type mismatch" } */
> + __auto_type a2 = b ? i : nptr; /* { dg-error "type mismatch" } */
> + __auto_type a3 = b ? nptr : 0; /* { dg-error "type mismatch" } */
> + __auto_type a4 = b ? 0 : nptr; /* { dg-error "type mismatch" } */
> +}
> diff --git a/gcc/testsuite/gcc.dg/c2x-nullptr-4.c b/gcc/testsuite/gcc.dg/c2x-nullptr-4.c
> new file mode 100644
> index 00000000000..5b15e75d159
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/c2x-nullptr-4.c
> @@ -0,0 +1,10 @@
> +/* Test that we warn about `nullptr' pre-C2X. */
> +/* { dg-do compile } */
> +/* { dg-options "-std=c17 -pedantic-errors" } */
> +
> +int *
> +fn (int *p)
> +{
> + p = nullptr; /* { dg-error "ISO C does not support .nullptr. before C2X" } */
> + return p;
> +}
> diff --git a/gcc/testsuite/gcc.dg/c2x-nullptr-5.c b/gcc/testsuite/gcc.dg/c2x-nullptr-5.c
> new file mode 100644
> index 00000000000..7479ab4ea1d
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/c2x-nullptr-5.c
> @@ -0,0 +1,11 @@
> +/* Test that -Wc11-c2x-compat issues a warning (not a pedwarn) about
> + `nullptr' in C2X. */
> +/* { dg-do compile } */
> +/* { dg-options "-std=c2x -pedantic-errors -Wc11-c2x-compat" } */
> +
> +int *
> +fn (int *p)
> +{
> + p = nullptr; /* { dg-warning "ISO C does not support .nullptr. before C2X" } */
> + return p;
> +}
>
> base-commit: 4991e20923b658ce9fbdf5621cab39f71b98fbc2
next prev parent reply other threads:[~2022-08-15 20:03 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2022-08-13 21:35 Marek Polacek
2022-08-15 17:48 ` Joseph Myers
2022-08-24 18:24 ` [PATCH v2] " Marek Polacek
2022-08-25 17:28 ` Joseph Myers
2022-08-25 20:51 ` [PATCH v3] " Marek Polacek
2022-08-25 21:12 ` Joseph Myers
2022-08-25 22:14 ` Marek Polacek
2022-08-15 20:03 ` Jason Merrill [this message]
2022-08-24 18:24 ` [PATCH] " Marek Polacek
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=38054995-48c3-8e98-623a-c2057ec70fb3@redhat.com \
--to=jason@redhat.com \
--cc=gcc-patches@gcc.gnu.org \
--cc=joseph@codesourcery.com \
--cc=polacek@redhat.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).