Linux SPARSE checker discussions
 help / color / mirror / Atom feed
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
+ */

  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