From: Bart Van Assche <bvanassche@acm.org>
To: linux-sparse@vger.kernel.org
Cc: Linus Torvalds <torvalds@linux-foundation.org>,
Bart Van Assche <bvanassche@acm.org>
Subject: [PATCH 44/73] c23: Add support for nullptr and nullptr_t
Date: Mon, 9 Nov 2026 12:32:30 -0800 [thread overview]
Message-ID: <20261109203259.362845-45-bvanassche@acm.org> (raw)
In-Reply-To: <20261109203259.362845-1-bvanassche@acm.org>
Introduce the reserved C23 nullptr keyword and a dedicated nullptr_ctype
base type representing nullptr_t per ISO C23 6.2.5 and 6.3.2.3:
- Evaluate nullptr as an address constant of type nullptr_ctype rather
than an integer constant expression, rejecting nullptr in array bounds,
enumerators, and switch controlling expressions.
- Support implicit and explicit conversions from nullptr_t expressions to
any pointer type and bool, equality comparisons with pointers and null
pointer constants, conditional expressions, _Generic associations, and
variadic arguments, while rejecting relational comparisons, arithmetic,
and non-bool/non-void/non-pointer casts.
- Lower nullptr_ctype as a pointer-sized null value in linearize and
sparse-llvm.
Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
evaluate.c | 186 +++++++++++++++++++++++++++++--
expression.c | 10 ++
ident-list.h | 1 +
linearize.c | 2 +
parse.c | 7 ++
show-parse.c | 1 +
sparse-llvm.c | 26 +++--
symbol.c | 12 +-
symbol.h | 13 ++-
validation/backend/c23-nullptr.c | 21 ++++
validation/c23-nullptr-assign.c | 35 ++++++
validation/c23-nullptr-error.c | 107 ++++++++++++++++++
validation/c23-nullptr.c | 107 ++++++++++++++++++
validation/linear/c23-nullptr.c | 20 ++++
14 files changed, 528 insertions(+), 20 deletions(-)
create mode 100644 validation/backend/c23-nullptr.c
create mode 100644 validation/c23-nullptr-assign.c
create mode 100644 validation/c23-nullptr-error.c
create mode 100644 validation/c23-nullptr.c
create mode 100644 validation/linear/c23-nullptr.c
diff --git a/evaluate.c b/evaluate.c
index ec02e5bbc4fe..c0d039105881 100644
--- a/evaluate.c
+++ b/evaluate.c
@@ -857,6 +857,8 @@ const char *type_difference(struct ctype *c1, struct ctype *c2,
return "different address spaces";
if (base1 != base2)
return "different base types";
+ if (!base1 && t1->bit_size != t2->bit_size)
+ return "different base types";
if (t1->rank != t2->rank)
return "different type sizes";
diff = (mod1 ^ mod2) & ~MOD_IGNORE;
@@ -1106,7 +1108,11 @@ enum null_constant_type {
static inline int is_null_pointer_constant(struct expression *e)
{
- if (e->ctype == &null_ctype)
+ if (e->ctype == &null_ctype ||
+ (e->ctype && e->ctype->type == SYM_NODE && e->ctype->ctype.base_type == &null_ctype))
+ return NULL_PTR;
+ if (is_nullptr_type(e->ctype) &&
+ (e->type == EXPR_VALUE || (e->flags & CEF_ADDR)))
return NULL_PTR;
if (!(e->flags & CEF_ICE))
return NON_NULL;
@@ -1148,6 +1154,87 @@ static struct symbol *evaluate_compare(struct expression *expr)
goto OK;
}
+ /* nullptr_t equality comparisons [C23 6.5.9] */
+ if (ltype == &nullptr_ctype || rtype == &nullptr_ctype) {
+ int is_null1, is_null2;
+
+ if (expr->op != SPECIAL_EQUAL && expr->op != SPECIAL_NOTEQUAL)
+ return bad_expr_type(expr);
+
+ is_null1 = is_null_pointer_constant(left);
+ is_null2 = is_null_pointer_constant(right);
+ if (ltype == &nullptr_ctype && rtype == &nullptr_ctype) {
+ if (is_null1 && is_null2) {
+ expr->type = EXPR_VALUE;
+ expr->value = (expr->op == SPECIAL_EQUAL);
+ expr->flags |= CEF_ACE;
+ }
+ goto OK;
+ }
+ if (ltype == &nullptr_ctype) {
+ if (rclass & TYPE_PTR) {
+ if (is_null1 && is_null2) {
+ if (is_null2 == NULL_ZERO)
+ bad_null(right);
+ expr->type = EXPR_VALUE;
+ expr->value = (expr->op == SPECIAL_EQUAL);
+ expr->flags |= CEF_ACE;
+ goto OK;
+ }
+ if (rtype == &null_ctype) {
+ expr->left = cast_to(left, &ptr_ctype);
+ expr->right = cast_to(right, &ptr_ctype);
+ } else {
+ expr->left = cast_to(left, rtype);
+ }
+ goto OK;
+ }
+ if (is_null2) {
+ if (is_null2 == NULL_ZERO)
+ bad_null(right);
+ if (is_null1) {
+ expr->type = EXPR_VALUE;
+ expr->value = (expr->op == SPECIAL_EQUAL);
+ expr->flags |= CEF_ACE;
+ } else {
+ expr->right = cast_to(right, &nullptr_ctype);
+ }
+ goto OK;
+ }
+ return bad_expr_type(expr);
+ }
+ if (lclass & TYPE_PTR) {
+ if (is_null1 && is_null2) {
+ if (is_null1 == NULL_ZERO)
+ bad_null(left);
+ expr->type = EXPR_VALUE;
+ expr->value = (expr->op == SPECIAL_EQUAL);
+ expr->flags |= CEF_ACE;
+ goto OK;
+ }
+ if (ltype == &null_ctype) {
+ expr->left = cast_to(left, &ptr_ctype);
+ expr->right = cast_to(right, &ptr_ctype);
+ } else {
+ expr->right = cast_to(right, ltype);
+ }
+ goto OK;
+ }
+ if (is_null1) {
+ if (is_null1 == NULL_ZERO)
+ bad_null(left);
+ if (is_null2) {
+ expr->type = EXPR_VALUE;
+ expr->value = (expr->op == SPECIAL_EQUAL);
+ expr->flags |= CEF_ACE;
+ } else {
+ expr->left = cast_to(left, &nullptr_ctype);
+ }
+ goto OK;
+ }
+ return bad_expr_type(expr);
+ }
+
/* at least one must be a pointer */
if (!((lclass | rclass) & TYPE_PTR))
return bad_expr_type(expr);
@@ -1276,6 +1363,29 @@ static struct symbol *evaluate_conditional_expression(struct expression *expr)
goto out;
}
+ if (ltype == &nullptr_ctype || rtype == &nullptr_ctype) {
+ if (ltype == &nullptr_ctype && rtype == &nullptr_ctype) {
+ ctype = &nullptr_ctype;
+ goto out;
+ }
+ if (ltype == &nullptr_ctype && (rclass & TYPE_PTR)) {
+ ctype = (rtype == &null_ctype) ? &ptr_ctype : rtype;
+ *cond = cast_to(*cond, ctype);
+ if (rtype == &null_ctype)
+ expr->cond_false = cast_to(expr->cond_false, ctype);
+ goto out;
+ }
+ if (rtype == &nullptr_ctype && (lclass & TYPE_PTR)) {
+ ctype = (ltype == &null_ctype) ? &ptr_ctype : ltype;
+ if (ltype == &null_ctype)
+ *cond = cast_to(*cond, ctype);
+ expr->cond_false = cast_to(expr->cond_false, ctype);
+ goto out;
+ }
+ typediff = "different types";
+ goto Err;
+ }
+
if ((lclass | rclass) & TYPE_PTR) {
int is_null1 = is_null_pointer_constant(*cond);
int is_null2 = is_null_pointer_constant(expr->cond_false);
@@ -1486,6 +1596,20 @@ static int check_assignment_types(struct symbol *target, struct expression **rp,
int tclass = classify_type(target, &t);
int sclass = classify_type(source, &s);
+ if (t == &nullptr_ctype) {
+ int is_null;
+ if (s == &nullptr_ctype)
+ return 1;
+ is_null = is_null_pointer_constant(*rp);
+ if (is_null) {
+ if (is_null == NULL_ZERO)
+ bad_null(*rp);
+ goto Cast;
+ }
+ *typediff = "different base types";
+ return 0;
+ }
+
if (tclass & sclass & TYPE_NUM) {
if (tclass & TYPE_RESTRICT) {
/* allowed assignments unfoul */
@@ -1518,10 +1642,21 @@ static int check_assignment_types(struct symbol *target, struct expression **rp,
bad_null(*rp);
goto Cast;
}
+ if (s == &nullptr_ctype) {
+ if (t == &null_ctype) {
+ *typediff = "different base types";
+ return 0;
+ }
+ goto Cast;
+ }
if (!(sclass & TYPE_PTR)) {
*typediff = "different base types";
return 0;
}
+ if (t == &null_ctype && s != &null_ctype) {
+ *typediff = "different base types";
+ return 0;
+ }
b1 = examine_pointer_target(t);
b2 = examine_pointer_target(s);
mod1 = t->ctype.modifiers & (MOD_IGN | MOD_NODEREF);
@@ -1566,7 +1701,7 @@ static int check_assignment_types(struct symbol *target, struct expression **rp,
if (tclass & TYPE_NUM) {
/* XXX: need to turn into comparison with NULL */
- if (is_bool_type(t) && (sclass & TYPE_PTR))
+ if (is_bool_type(t) && ((sclass & TYPE_PTR) || s == &nullptr_ctype))
goto Cast;
*typediff = "different base types";
return 0;
@@ -2067,9 +2202,13 @@ static struct symbol *evaluate_preop(struct expression *expr)
expr->flags = expr->unop->flags & ~CEF_CONST_MASK;
/*
* A logical negation never yields an address constant
- * [6.6(9)].
+ * [6.6(9)], though negating an address constant yields
+ * an arithmetic constant expression.
*/
- expr->flags &= ~CEF_ADDR;
+ if (expr->unop->flags & CEF_ADDR) {
+ expr->flags &= ~CEF_ADDR;
+ expr->flags |= CEF_ACE;
+ }
if (is_safe_type(ctype))
warning(expr->pos, "testing a 'safe expression'");
@@ -2474,6 +2613,8 @@ int evaluate_arguments(struct symbol_list *argtypes, struct expression_list *hea
*p = cast_to(expr, &ptr_ctype);
else
degenerate(expr);
+ } else if (type == &nullptr_ctype) {
+ degenerate(expr);
}
} else if (!target->forced_arg){
static char where[30];
@@ -2984,6 +3125,14 @@ static struct symbol *cast_to_bool(struct expression *expr)
return NULL;
zero = alloc_const_expression(expr->pos, 0);
+ if (otype == &nullptr_ctype) {
+ zero->ctype = otype;
+ expr->op = SPECIAL_NOTEQUAL;
+ expr->type = EXPR_COMPARE;
+ expr->left = old;
+ expr->right = zero;
+ return expr->ctype;
+ }
if (oclass & TYPE_PTR)
zero->ctype = otype;
expr->op = SPECIAL_NOTEQUAL;
@@ -3030,7 +3179,7 @@ static int cast_flags(struct expression *expr, struct expression *old)
*/
else if (old->flags & CEF_FLOAT)
flags = CEF_SET_ICE;
- } else if (class & TYPE_PTR) {
+ } else if ((class & TYPE_PTR) || t == &nullptr_ctype) {
/*
* Casts of integer literals to pointer type yield
* address constants [6.6(9)].
@@ -3145,6 +3294,19 @@ static struct symbol *evaluate_cast(struct expression *expr)
if (expr->type == EXPR_FORCE_CAST)
goto out;
+ if (ttype == &nullptr_ctype) {
+ if (stype != &nullptr_ctype && !is_null_pointer_constant(source))
+ expression_error(expr, "conversion from '%s' to 'nullptr_t'",
+ show_typename(source->ctype));
+ goto out;
+ }
+ if (stype == &nullptr_ctype) {
+ if (!is_bool_type(ttype) && tclass != TYPE_PTR)
+ expression_error(expr, "conversion from 'nullptr_t' to '%s'",
+ show_typename(ctype));
+ goto out;
+ }
+
if (tclass & (TYPE_COMPOUND | TYPE_FN)) {
/*
* Special case: cast to union type (GCC extension)
@@ -3471,8 +3633,13 @@ static struct symbol *evaluate_generic_selection(struct expression *expr)
return NULL;
}
- source = *ctrl;
- source.ctype.modifiers &= ~(MOD_QUALIFIER|MOD_ATOMIC);
+ struct symbol *sel_ctrl = ctrl;
+
+ if (ctrl->ctype.modifiers & (MOD_QUALIFIER|MOD_ATOMIC)) {
+ source = *ctrl;
+ source.ctype.modifiers &= ~(MOD_QUALIFIER|MOD_ATOMIC);
+ sel_ctrl = &source;
+ }
for (map = expr->map; map; map = map->next) {
struct symbol *stype = map->type;
struct symbol *base;
@@ -3509,7 +3676,7 @@ static struct symbol *evaluate_generic_selection(struct expression *expr)
sparse_error(stype->pos, "incomplete type in generic selection");
continue;
}
- if (!type_selection(&source, stype))
+ if (!type_selection(sel_ctrl, stype))
continue;
res = map->expr;
@@ -4009,6 +4176,9 @@ static void evaluate_switch_statement(struct statement *stmt)
evaluate_statement(stmt->switch_statement);
if (!sel)
return;
+ if (valid_type(sel->ctype) && !is_int_type(sel->ctype) &&
+ !is_restricted_type(sel->ctype) && !is_fouled_type(sel->ctype))
+ expression_error(sel, "switch quantity is not an integer");
if (sel->ctype && is_enum_type(sel->ctype))
enumcase_holder = NULL; /* Only check cases against switch */
diff --git a/expression.c b/expression.c
index 9b07963c914a..da2c14b458db 100644
--- a/expression.c
+++ b/expression.c
@@ -480,6 +480,16 @@ struct token *primary_expression(struct token *token, struct expression **tree)
break;
}
+ if (standard >= STANDARD_C23 &&
+ token->ident == &nullptr_ident) {
+ expr = alloc_expression(token->pos, EXPR_VALUE);
+ expr->flags = CEF_ADDR;
+ expr->ctype = &nullptr_ctype;
+ expr->value = 0;
+ token = next;
+ break;
+ }
+
sym = lookup_symbol(token->ident, NS_SYMBOL | NS_TYPEDEF);
if (!sym) {
diff --git a/ident-list.h b/ident-list.h
index 31a37bede4ed..18dabf5193b2 100644
--- a/ident-list.h
+++ b/ident-list.h
@@ -48,6 +48,7 @@ IDENT(alignas);
IDENT(alignof);
IDENT(bool);
IDENT(false);
+IDENT(nullptr);
IDENT(static_assert);
IDENT(thread_local);
IDENT(true);
diff --git a/linearize.c b/linearize.c
index bb3a5a9ecf16..422cee820ddb 100644
--- a/linearize.c
+++ b/linearize.c
@@ -1276,6 +1276,8 @@ retry: switch (s->type) {
case_int:
return sign ? MTYPE_SINT : MTYPE_UINT;
case SYM_BASETYPE:
+ if (s == &nullptr_ctype)
+ return MTYPE_VPTR;
if (s->ctype.base_type == &fp_type)
return MTYPE_FLOAT;
if (s->ctype.base_type == &int_type)
diff --git a/parse.c b/parse.c
index 4edfed1cdf97..5e526b0cc840 100644
--- a/parse.c
+++ b/parse.c
@@ -658,6 +658,7 @@ void init_parser_finalize(void)
alignof_ident.reserved = 1;
true_ident.reserved = 1;
false_ident.reserved = 1;
+ nullptr_ident.reserved = 1;
}
}
@@ -1006,6 +1007,12 @@ static struct token *parse_enum_declaration(struct token *token, struct symbol *
ctype = &void_ctype;
if (expr && expr->ctype)
ctype = expr->ctype;
+ if (is_nullptr_type(ctype)) {
+ sparse_error(expr->pos,
+ "enumerator value for '%s' is not an integer constant",
+ show_ident(token->ident));
+ ctype = &int_ctype;
+ }
} else if (!ctype) {
ctype = &int_ctype;
} else if (is_int_type(ctype)) {
diff --git a/show-parse.c b/show-parse.c
index 850d6ce5f937..78d86e20e93a 100644
--- a/show-parse.c
+++ b/show-parse.c
@@ -278,6 +278,7 @@ static struct ctype_name {
{ &void_ctype, "void", "" },
{ &bool_ctype, "bool", "" },
+ { &nullptr_ctype, "nullptr_t", "" },
{ &float_ctype, "float", "F" },
{ &double_ctype, "double", "" },
diff --git a/sparse-llvm.c b/sparse-llvm.c
index 8f37bde1b98d..c7055238ff88 100644
--- a/sparse-llvm.c
+++ b/sparse-llvm.c
@@ -154,6 +154,9 @@ static LLVMTypeRef sym_basetype_type(struct symbol *sym)
{
LLVMTypeRef ret = NULL;
+ if (sym == &nullptr_ctype)
+ return LLVMPointerType(LLVMInt8TypeInContext(context), 0);
+
if (is_float_type(sym)) {
switch (sym->bit_size) {
case 32:
@@ -458,12 +461,17 @@ static LLVMValueRef value_to_ivalue(struct function *fn, struct symbol *ctype, L
return val;
}
+static inline bool is_llvm_ptr_type(struct symbol *type)
+{
+ return is_ptr_type(type) || is_nullptr_type(type);
+}
+
static LLVMValueRef value_to_pvalue(struct function *fn, struct symbol *ctype, LLVMValueRef val)
{
const char *name = LLVMGetValueName(val);
LLVMTypeRef dtype = symbol_type(ctype);
- assert(is_ptr_type(ctype));
+ assert(is_llvm_ptr_type(ctype));
switch (LLVMGetTypeKind(LLVMTypeOf(val))) {
case LLVMIntegerTypeKind:
val = LLVMBuildIntToPtr(fn->builder, val, dtype, name);
@@ -481,7 +489,7 @@ static LLVMValueRef adjust_type(struct function *fn, struct symbol *ctype, LLVMV
{
if (is_int_type(ctype))
return value_to_ivalue(fn, ctype, val);
- if (is_ptr_type(ctype))
+ if (is_llvm_ptr_type(ctype))
return value_to_pvalue(fn, ctype, val);
return val;
}
@@ -901,18 +909,18 @@ static void output_op_ptrcast(struct function *fn, struct instruction *insn)
case OP_UTPTR:
case OP_SEXT: // FIXME
assert(is_int_type(otype));
- assert(is_ptr_type(insn->type));
+ assert(is_llvm_ptr_type(insn->type));
op = LLVMIntToPtr;
break;
case OP_PTRTU:
- assert(is_ptr_type(otype));
+ assert(is_llvm_ptr_type(otype));
assert(is_int_type(insn->type));
op = LLVMPtrToInt;
break;
case OP_PTRCAST:
case OP_ZEXT: // FIXME
- assert(is_ptr_type(otype));
- assert(is_ptr_type(insn->type));
+ assert(is_llvm_ptr_type(otype));
+ assert(is_llvm_ptr_type(insn->type));
op = LLVMBitCast;
break;
default:
@@ -930,7 +938,7 @@ static void output_op_cast(struct function *fn, struct instruction *insn, LLVMOp
struct symbol *otype = insn->orig_type;
char target_name[64];
- if (is_ptr_type(insn->type)) // cast to void* is OP_CAST ...
+ if (is_llvm_ptr_type(insn->type)) // cast to void* is OP_CAST ...
return output_op_ptrcast(fn, insn);
assert(is_int_type(insn->type));
@@ -939,7 +947,7 @@ static void output_op_cast(struct function *fn, struct instruction *insn, LLVMOp
pseudo_name(insn->target, target_name);
dtype = symbol_type(insn->type);
- if (is_ptr_type(otype)) {
+ if (is_llvm_ptr_type(otype)) {
op = LLVMPtrToInt;
} else if (is_float_type(otype)) {
assert(op == LLVMFPToUI || op == LLVMFPToSI);
@@ -1252,7 +1260,7 @@ static LLVMValueRef output_data(LLVMModuleRef module, struct symbol *sym)
if (initializer) {
switch (initializer->type) {
case EXPR_VALUE:
- initial_value = LLVMConstInt(symbol_type(sym), initializer->value, 1);
+ initial_value = constant_value(initializer->value, symbol_type(sym));
break;
case EXPR_FVALUE:
initial_value = LLVMConstReal(symbol_type(sym), initializer->fvalue);
diff --git a/symbol.c b/symbol.c
index 06cd8ca2ede7..a05e240cdfaa 100644
--- a/symbol.c
+++ b/symbol.c
@@ -307,7 +307,14 @@ static struct symbol * examine_array_type(struct symbol *sym)
unsigned long count = get_expression_value_silent(array_size);
if (base_type->bit_size >= 0)
bit_size = array_element_offset(base_type->bit_size, count);
- if (array_size->type != EXPR_VALUE) {
+ if (valid_type(array_size->ctype) &&
+ !is_int_type(array_size->ctype) &&
+ !is_restricted_type(array_size->ctype) &&
+ !is_fouled_type(array_size->ctype)) {
+ sparse_error(array_size->pos,
+ "size of array has non-integer type");
+ bit_size = 0;
+ } else if (array_size->type != EXPR_VALUE) {
if (Wvla)
warning(array_size->pos, "Variable length array is used.");
bit_size = -1;
@@ -875,7 +882,7 @@ struct symbol bool_ctype, void_ctype, type_ctype,
float_ctype, double_ctype, ldouble_ctype,
string_ctype, ptr_ctype, lazy_ptr_ctype,
incomplete_ctype, label_ctype, bad_ctype,
- null_ctype;
+ null_ctype, nullptr_ctype;
struct symbol autotype_ctype;
struct symbol schar_ptr_ctype, short_ptr_ctype;
struct symbol int_ptr_ctype, uint_ptr_ctype;
@@ -942,6 +949,7 @@ static const struct ctype_declare {
{ &incomplete_ctype, T_BASETYPE },
{ &autotype_ctype, T_BASETYPE },
{ &bad_ctype, T_BASETYPE },
+ { &nullptr_ctype, SYM_BASETYPE, 0, 0, &bits_in_pointer, &pointer_alignment, NULL },
{ &char_ctype, T__INT(-2, char) },
{ &schar_ctype, T_SINT(-2, char) },
diff --git a/symbol.h b/symbol.h
index 2c6731f88399..acd381fe64fd 100644
--- a/symbol.h
+++ b/symbol.h
@@ -306,7 +306,7 @@ extern struct symbol bool_ctype, void_ctype, type_ctype,
float_ctype, double_ctype, ldouble_ctype,
string_ctype, ptr_ctype, lazy_ptr_ctype,
incomplete_ctype, label_ctype, bad_ctype,
- null_ctype;
+ null_ctype, nullptr_ctype;
extern struct symbol autotype_ctype;
extern struct symbol schar_ptr_ctype, short_ptr_ctype;
extern struct symbol int_ptr_ctype, uint_ptr_ctype;
@@ -487,6 +487,13 @@ static inline int is_bool_type(struct symbol *type)
return type == &bool_ctype;
}
+static inline int is_nullptr_type(const struct symbol *type)
+{
+ if (type && type->type == SYM_NODE)
+ type = type->ctype.base_type;
+ return type == &nullptr_ctype;
+}
+
static inline int is_scalar_type(struct symbol *type)
{
if (type->type == SYM_NODE)
@@ -505,6 +512,8 @@ static inline int is_scalar_type(struct symbol *type)
return 1;
if (type->ctype.base_type == &fp_type)
return 1;
+ if (type == &nullptr_ctype)
+ return 1;
return 0;
}
@@ -524,6 +533,8 @@ static inline bool is_integral_type(struct symbol *type)
}
if (type->ctype.base_type == &int_type)
return 1;
+ if (type == &nullptr_ctype)
+ return 1;
return 0;
}
diff --git a/validation/backend/c23-nullptr.c b/validation/backend/c23-nullptr.c
new file mode 100644
index 000000000000..2cfb27b19f47
--- /dev/null
+++ b/validation/backend/c23-nullptr.c
@@ -0,0 +1,21 @@
+typedef typeof(nullptr) nullptr_t;
+
+extern int sink(const char *fmt, ...);
+
+static nullptr_t g_np = nullptr;
+
+int test_nullptr_llvm(nullptr_t arg, int **out);
+int test_nullptr_llvm(nullptr_t arg, int **out)
+{
+ *out = arg;
+ g_np = nullptr;
+ return sink("test", nullptr, arg, g_np);
+}
+
+/*
+ * check-name: backend-c23-nullptr
+ * check-command: sparse-llvm-dis -m64 -std=c23 $file
+ * check-output-ignore
+ * check-output-contains: define i32 @test_nullptr_llvm(
+ * check-output-contains: @sink(
+ */
diff --git a/validation/c23-nullptr-assign.c b/validation/c23-nullptr-assign.c
new file mode 100644
index 000000000000..e863075f2103
--- /dev/null
+++ b/validation/c23-nullptr-assign.c
@@ -0,0 +1,35 @@
+typedef typeof(nullptr) nullptr_t;
+
+void test(int *p, void *v)
+{
+ nullptr_t np;
+ np = p;
+ np = v;
+ np = 0;
+
+ int i = nullptr;
+ double d = np;
+ (void)i;
+ (void)d;
+}
+
+/*
+ * check-name: c23-nullptr-assign
+ * check-command: sparse -std=c23 -Wno-decl $file
+ *
+ * check-error-start
+c23-nullptr-assign.c:6:12: warning: incorrect type in assignment (different base types)
+c23-nullptr-assign.c:6:12: expected nullptr_t [usertype] np
+c23-nullptr-assign.c:6:12: got int *p
+c23-nullptr-assign.c:7:12: warning: incorrect type in assignment (different base types)
+c23-nullptr-assign.c:7:12: expected nullptr_t [usertype] np
+c23-nullptr-assign.c:7:12: got void *v
+c23-nullptr-assign.c:8:14: warning: Using plain integer as NULL pointer
+c23-nullptr-assign.c:10:17: warning: incorrect type in initializer (different base types)
+c23-nullptr-assign.c:10:17: expected int i
+c23-nullptr-assign.c:10:17: got nullptr_t
+c23-nullptr-assign.c:11:20: warning: incorrect type in initializer (different base types)
+c23-nullptr-assign.c:11:20: expected double d
+c23-nullptr-assign.c:11:20: got nullptr_t [assigned] [usertype] np
+ * check-error-end
+ */
diff --git a/validation/c23-nullptr-error.c b/validation/c23-nullptr-error.c
new file mode 100644
index 000000000000..1e26878519a2
--- /dev/null
+++ b/validation/c23-nullptr-error.c
@@ -0,0 +1,107 @@
+typedef typeof(nullptr) nullptr_t;
+
+static int (nullptr);
+
+enum bad_enum {
+ E_BAD = nullptr,
+ E_NEXT,
+};
+
+struct bad_bf {
+ unsigned int x : nullptr;
+};
+
+static int bad_arr[nullptr];
+
+static void test_errors(int *p, int c)
+{
+ nullptr_t np = nullptr;
+ int i2 = (int)nullptr;
+ long l2 = (long)np;
+ double d2 = (double)nullptr;
+ nullptr_t b1 = (nullptr_t)1;
+ nullptr_t b2 = (nullptr_t)(int *)0;
+ bool r1 = nullptr < nullptr;
+ bool r2 = np > p;
+ bool r3 = nullptr == 1;
+ int t1 = c ? nullptr : 1;
+ nullptr_t t0 = c ? nullptr : 0;
+ void *a1 = nullptr + 1;
+ int u1 = *nullptr;
+ int u2 = +nullptr;
+ int u3 = -nullptr;
+ int u4 = ~nullptr;
+ np++;
+
+ switch (np) {
+ default:
+ break;
+ }
+
+ switch (c) {
+ case nullptr:
+ break;
+ }
+
+ (void)i2;
+ (void)l2;
+ (void)d2;
+ (void)b1;
+ (void)b2;
+ (void)r1;
+ (void)r2;
+ (void)r3;
+ (void)t1;
+ (void)t0;
+ (void)a1;
+ (void)u1;
+ (void)u2;
+ (void)u3;
+ (void)u4;
+}
+
+/*
+ * check-name: c23-nullptr-error
+ * check-command: sparse -std=c23 -Wno-decl $file
+ *
+ * check-error-start
+c23-nullptr-error.c:3:12: error: Trying to use reserved word 'nullptr' as identifier
+c23-nullptr-error.c:6:17: error: enumerator value for 'E_BAD' is not an integer constant
+c23-nullptr-error.c:11:26: error: bad integer constant expression
+c23-nullptr-error.c:11:33: error: bitfield 'x' has invalid width (0)
+c23-nullptr-error.c:14:20: error: size of array has non-integer type
+c23-nullptr-error.c:19:19: error: conversion from 'nullptr_t' to 'int'
+c23-nullptr-error.c:20:20: error: conversion from 'nullptr_t' to 'long'
+c23-nullptr-error.c:21:22: error: conversion from 'nullptr_t' to 'double'
+c23-nullptr-error.c:22:25: error: conversion from 'int' to 'nullptr_t'
+c23-nullptr-error.c:23:25: error: conversion from 'int *' to 'nullptr_t'
+c23-nullptr-error.c:24:27: error: incompatible types for operation (<):
+c23-nullptr-error.c:24:27: nullptr_t
+c23-nullptr-error.c:24:27: nullptr_t
+c23-nullptr-error.c:25:22: error: incompatible types for operation (>):
+c23-nullptr-error.c:25:22: nullptr_t [usertype] np
+c23-nullptr-error.c:25:22: int *p
+c23-nullptr-error.c:26:27: error: incompatible types for operation (==):
+c23-nullptr-error.c:26:27: nullptr_t
+c23-nullptr-error.c:26:27: int
+c23-nullptr-error.c:27:20: error: incompatible types in conditional expression (different types):
+c23-nullptr-error.c:27:20: nullptr_t
+c23-nullptr-error.c:27:20: int
+c23-nullptr-error.c:28:26: error: incompatible types in conditional expression (different types):
+c23-nullptr-error.c:28:26: nullptr_t
+c23-nullptr-error.c:28:26: int
+c23-nullptr-error.c:29:28: error: incompatible types for operation (+):
+c23-nullptr-error.c:29:28: nullptr_t
+c23-nullptr-error.c:29:28: int
+c23-nullptr-error.c:30:18: error: cannot dereference this type
+c23-nullptr-error.c:31:18: error: incompatible type for operation (+):
+c23-nullptr-error.c:31:18: nullptr_t
+c23-nullptr-error.c:32:18: error: incompatible type for operation (-):
+c23-nullptr-error.c:32:18: nullptr_t
+c23-nullptr-error.c:33:18: error: incompatible type for operation (~):
+c23-nullptr-error.c:33:18: nullptr_t
+c23-nullptr-error.c:34:11: error: need scalar for ++/--
+c23-nullptr-error.c:36:17: error: switch quantity is not an integer
+c23-nullptr-error.c:42:14: error: incompatible types for 'case' statement
+ * check-error-end
+ */
diff --git a/validation/c23-nullptr.c b/validation/c23-nullptr.c
new file mode 100644
index 000000000000..e7c0d4aa7f45
--- /dev/null
+++ b/validation/c23-nullptr.c
@@ -0,0 +1,107 @@
+typedef typeof(nullptr) nullptr_t;
+
+static_assert(sizeof(nullptr) == sizeof(void *), "sizeof nullptr");
+static_assert(sizeof(nullptr_t) == sizeof(void *), "sizeof nullptr_t");
+static_assert(alignof(nullptr_t) == alignof(void *), "alignof nullptr_t");
+static_assert(_Generic(nullptr, nullptr_t: 1, void *: 0, int: 0, default: 0) == 1,
+ "typeof nullptr is nullptr_t");
+
+void *p = nullptr;
+int *q = nullptr;
+nullptr_t np = nullptr;
+static nullptr_t np_zero = 0;
+static nullptr_t np_void0 = (void *)0;
+static void (*fp_global)(void) = nullptr;
+
+static void test_call(int *arg1, nullptr_t arg2)
+{
+ (void)arg1;
+ (void)arg2;
+}
+
+static void test_varargs(int n, ...)
+{
+ (void)n;
+}
+
+static nullptr_t test_ret_np(void)
+{
+ return nullptr;
+}
+
+static int *test_ret_ptr(void)
+{
+ return nullptr;
+}
+
+static void dummy_fn(void)
+{
+}
+
+static void test_func(const int *cq_const)
+{
+ p = np;
+ q = np;
+ np = 0;
+ np = (void *)0;
+ np = (nullptr_t)nullptr;
+ np = (const nullptr_t)np;
+ np = test_ret_np();
+ q = test_ret_ptr();
+
+ void (*fp)(void) = np;
+ fp = nullptr;
+ if (fp == nullptr || nullptr != dummy_fn) {
+ }
+
+ test_call(nullptr, nullptr);
+ test_call(np, np);
+ test_call(np_zero, np_void0);
+ test_varargs(2, nullptr, np);
+
+ (void)nullptr;
+ (void)np;
+ (void)fp_global;
+
+ bool b1 = nullptr;
+ bool b2 = np;
+ bool b3 = (bool)nullptr;
+ bool b4 = (bool)np;
+ (void)b1;
+ (void)b2;
+ (void)b3;
+ (void)b4;
+
+ if (nullptr) {
+ }
+ if (!nullptr && !np) {
+ }
+ if (p == nullptr && nullptr == q) {
+ }
+ if (np == p && p == np) {
+ }
+ if (np == nullptr && np == 0 && 0 == np && np == (void *)0) {
+ }
+
+ int cond = 1;
+ void *cp = cond ? nullptr : p;
+ int *cq = cond ? q : nullptr;
+ const int *ccq = cond ? np : cq_const;
+ void *cv0 = cond ? nullptr : (void *)0;
+ nullptr_t cnp1 = cond ? np : nullptr;
+
+ static_assert(_Generic(1 ? nullptr : (int *)0, int *: 1, default: 0) == 1);
+ static_assert(_Generic(1 ? nullptr : (void *)0, void *: 1, default: 0) == 1);
+ static_assert(_Generic(1 ? np : nullptr, nullptr_t: 1, default: 0) == 1);
+
+ (void)cp;
+ (void)cq;
+ (void)ccq;
+ (void)cv0;
+ (void)cnp1;
+}
+
+/*
+ * check-name: c23-nullptr
+ * check-command: sparse -std=c23 -pedantic -Wno-decl -Wno-non-pointer-null $file
+ */
diff --git a/validation/linear/c23-nullptr.c b/validation/linear/c23-nullptr.c
new file mode 100644
index 000000000000..bdf4fb36e32a
--- /dev/null
+++ b/validation/linear/c23-nullptr.c
@@ -0,0 +1,20 @@
+typedef typeof(nullptr) nullptr_t;
+
+static void *np_to_ptr(nullptr_t np)
+{
+ return np;
+}
+
+static bool np_is_null(nullptr_t np)
+{
+ return !np;
+}
+
+/*
+ * check-name: linear-c23-nullptr
+ * check-command: test-linearize -m64 -std=c23 $file
+ *
+ * check-output-ignore
+ * check-output-contains: ret\\.64 *%arg1
+ * check-output-contains: seteq\\.1
+ */
next prev parent reply other threads:[~2026-11-09 20:33 UTC|newest]
Thread overview: 74+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-09 20:31 Bart Van Assche
2026-11-09 20:31 ` [PATCH 01/73] Makefile: Pass '--' to rm in clean target to handle dash-prefixed files Bart Van Assche
2026-11-09 20:31 ` [PATCH 02/73] sparse-llvm: Use LLVMCreateBuilderForModule() with LLVM 24 and later Bart Van Assche
2026-11-09 20:31 ` [PATCH 03/73] typeof_unqual: Strip qualifiers and address space from types and arrays Bart Van Assche
2026-11-09 20:31 ` [PATCH 04/73] bitfield: Pack consecutive _Bool bitfields into byte containers Bart Van Assche
2026-11-09 20:31 ` [PATCH 05/73] allocate: Harden slab allocation bounds, zeroing, and mmap checks Bart Van Assche
2026-11-09 20:31 ` [PATCH 06/73] tokenize: Support 4096-byte identifiers and fix escape bounds Bart Van Assche
2026-11-09 20:31 ` [PATCH 07/73] pre-process: Fix macro expansion and include directive handling Bart Van Assche
2026-11-09 20:31 ` [PATCH 08/73] options, lib: Harden warning buffers and command-line option parsing Bart Van Assche
2026-11-09 20:31 ` [PATCH 09/73] builtin: Fix atomic builtin type checking and object_size bounds Bart Van Assche
2026-11-09 20:31 ` [PATCH 10/73] parse, show-parse: Fix attribute propagation, enum overflow, and bounds Bart Van Assche
2026-11-09 20:31 ` [PATCH 11/73] target-openrisc: Set target_openrisc.mach to MACH_OPENRISC Bart Van Assche
2026-11-09 20:31 ` [PATCH 12/73] evaluate: Fix pointer arithmetic, incomplete types, and qualifiers Bart Van Assche
2026-11-09 20:31 ` [PATCH 13/73] expand: Fix static initializer folding and floating-point casts Bart Van Assche
2026-11-09 20:32 ` [PATCH 14/73] inline: Copy union cast types and VLA bounds when inlining Bart Van Assche
2026-11-09 20:32 ` [PATCH 15/73] linearize, flow: Fix asm operands, switch case bounds, and DSE Bart Van Assche
2026-11-09 20:32 ` [PATCH 16/73] memops, ssa: Walk parent insns in reverse and skip mixed-size SSA Bart Van Assche
2026-11-09 20:32 ` [PATCH 17/73] simplify: Prevent overflow on LLONG_MIN negation and fix simplifications Bart Van Assche
2026-11-09 20:32 ` [PATCH 18/73] sparse: Gate context failure warning on Wcontext and catch underflow Bart Van Assche
2026-11-09 20:32 ` [PATCH 19/73] ast-model: Return AST_N_COLUMNS and inspect child nodes lazily Bart Van Assche
2026-11-09 20:32 ` [PATCH 20/73] c2xml: Guard against recursive symbols and free xmlGetProp() string Bart Van Assche
2026-11-09 20:32 ` [PATCH 21/73] compile-i386: Fix buffer bounds, unsigned '<=' codegen, and null checks Bart Van Assche
2026-11-09 20:32 ` [PATCH 22/73] ctags: Fix line comparison order and handle SYM_RESTRICT and SYM_LABEL Bart Van Assche
2026-11-09 20:32 ` [PATCH 23/73] dissect: Unwrap array element type via base_type() Bart Van Assche
2026-11-09 20:32 ` [PATCH 24/73] example: Fix show_op() rotating buffer dimensions Bart Van Assche
2026-11-09 20:32 ` [PATCH 25/73] graph: Check PSEUDO_SYM before dereferencing insn->func->sym Bart Van Assche
2026-11-09 20:32 ` [PATCH 26/73] scheck: Fix slice() upper bit index calculation Bart Van Assche
2026-11-09 20:32 ` [PATCH 27/73] sparse-llvm: Support arbitrary struct member counts and fix operand types Bart Van Assche
2026-11-09 20:32 ` [PATCH 28/73] inline: Duplicate generic expressions instead of evaluating them Bart Van Assche
2026-11-09 20:32 ` [PATCH 29/73] lib: Avoid mutating fmt before va_start() in warning() Bart Van Assche
2026-11-09 20:32 ` [PATCH 30/73] cse, canon: Cast pointers to uintptr_t before relational comparison Bart Van Assche
2026-11-09 20:32 ` [PATCH 31/73] context: Add "conditional context increment" model Bart Van Assche
2026-11-09 20:32 ` [PATCH 32/73] expand: Simplify signed constants early Bart Van Assche
2026-11-09 20:32 ` [PATCH 33/73] bitwise: Allow all-bits-set as a restricted value Bart Van Assche
2026-11-09 20:32 ` [PATCH 34/73] bitwise: Allow restricted ordered compares with unrestricted values Bart Van Assche
2026-11-09 20:32 ` [PATCH 35/73] bitwise: Keep unrestricted values unrestricted after a cast Bart Van Assche
2026-11-09 20:32 ` [PATCH 36/73] bitwise: Don't remove MOD_SPECIFIER from 'bitwise' types Bart Van Assche
2026-11-09 20:32 ` [PATCH 37/73] bitwise: Allow both regular casts and forced casts for restricted values Bart Van Assche
2026-11-09 20:32 ` [PATCH 38/73] clang-format: Add .clang-format configuration file Bart Van Assche
2026-11-09 20:32 ` [PATCH 39/73] options: Add support for -std=c23, -std=c2x and -std=gnu23 Bart Van Assche
2026-11-09 20:32 ` [PATCH 40/73] preprocessor: Add support for #elifdef and #elifndef Bart Van Assche
2026-11-09 20:32 ` [PATCH 41/73] preprocessor: Add support for __has_include and __has_include_next Bart Van Assche
2026-11-09 20:32 ` [PATCH 42/73] c23: Add support for static_assert, alignas, alignof, thread_local Bart Van Assche
2026-11-09 20:32 ` [PATCH 43/73] c23: Add support for bool, true, and false keywords Bart Van Assche
2026-11-09 20:32 ` Bart Van Assche [this message]
2026-11-09 20:32 ` [PATCH 45/73] c23: Add support for digit separators in numeric literals Bart Van Assche
2026-11-09 20:32 ` [PATCH 46/73] c23: Support unnamed parameters in function definitions Bart Van Assche
2026-11-09 20:32 ` [PATCH 47/73] c23: Support variadic functions without named parameters Bart Van Assche
2026-11-09 20:32 ` [PATCH 48/73] c23: Support labels before declarations and at block ends Bart Van Assche
2026-11-09 20:32 ` [PATCH 49/73] c23: Support auto type inference Bart Van Assche
2026-11-09 20:32 ` [PATCH 50/73] c23: Support constexpr object specifier Bart Van Assche
2026-11-09 20:32 ` [PATCH 51/73] c23: Support enumerations with fixed underlying types Bart Van Assche
2026-11-09 20:32 ` [PATCH 52/73] c23: Support standard [[]] attribute syntax Bart Van Assche
2026-11-09 20:32 ` [PATCH 53/73] preprocessor: Add support for __has_c_attribute Bart Van Assche
2026-11-09 20:32 ` [PATCH 54/73] c23: Support empty initializer braces {} Bart Van Assche
2026-11-09 20:32 ` [PATCH 55/73] preprocessor: Add support for __has_embed Bart Van Assche
2026-11-09 20:32 ` [PATCH 56/73] preprocessor: Add support for #embed directive Bart Van Assche
2026-11-09 20:32 ` [PATCH 57/73] c23: Add support for u8 character constants and string literals Bart Van Assche
2026-11-09 20:32 ` [PATCH 58/73] preprocessor: Add support for __VA_OPT__ Bart Van Assche
2026-11-09 20:32 ` [PATCH 59/73] c23: Treat empty parameter list () as (void) and reject K&R functions Bart Van Assche
2026-11-09 20:32 ` [PATCH 60/73] c23: Support storage-class specifiers in compound literals Bart Van Assche
2026-11-09 20:32 ` [PATCH 61/73] c23: Add support for _BitInt(N) Bart Van Assche
2026-11-09 20:32 ` [PATCH 62/73] enum: Support machine mode attributes on enumeration types Bart Van Assche
2026-11-09 20:32 ` [PATCH 63/73] enum: Support __attribute__((packed)) " Bart Van Assche
2026-11-09 20:32 ` [PATCH 64/73] expand: Ignore top-level non-array address space in compare_types() Bart Van Assche
2026-11-09 20:32 ` [PATCH 65/73] builtin: Support __builtin_infer_alloc_token() as a size_t ICE Bart Van Assche
2026-11-09 20:32 ` [PATCH 66/73] builtin: Add support for Linux kernel compiler builtins Bart Van Assche
2026-11-09 20:32 ` [PATCH 67/73] builtin: Add support for __builtin_has_attribute() Bart Van Assche
2026-11-09 20:32 ` [PATCH 68/73] cleanup: Support __attribute__((cleanup(...))) in linearize and -Wcontext Bart Van Assche
2026-11-09 20:32 ` [PATCH 69/73] builtin: Do not evaluate __builtin_clz(0) and __builtin_ctz(0) Bart Van Assche
2026-11-09 20:32 ` [PATCH 70/73] pre-process: Support long token and string expansions dynamically Bart Van Assche
2026-11-09 20:32 ` [PATCH 71/73] x86: Undefine conflicting bitness macros when parsing -m flags Bart Van Assche
2026-11-09 20:32 ` [PATCH 72/73] evaluate: Accept conditional expressions with only one void side Bart Van Assche
2026-11-09 20:32 ` [PATCH 73/73] ci: Add Linux kernel GitHub Actions workflow and build script Bart Van Assche
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=20261109203259.362845-45-bvanassche@acm.org \
--to=bvanassche@acm.org \
--cc=linux-sparse@vger.kernel.org \
--cc=torvalds@linux-foundation.org \
/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