Skip to content

Commit 9bb28a1

Browse files
committed
[C2x] Update 'nullptr' implementation based on CD comments
We filed some CD ballot comments which WG14 considered during the ballot comment resolution meetings in Jan and Feb 2023, and this updates our implementation based on the decisions reached. Those decisions were (paraphrased for brevity): US 9-034 (REJECTED) allow (void *)nullptr to be a null pointer constant US 10-035 (ACCEPTED) accept the following code, as in C++: void func(nullptr_t); func(0); US 22-058 (REJECTED) accept the following code, as in C++: nullptr_t val; (void)(1 ? val : 0); (void)(1 ? nullptr : 0); US 23-062 (REJECTED) reject the following code, as in C++: nullptr_t val; bool b1 = val; bool b2 = nullptr; US 24-061 (ACCEPTED) accept the following code, as in C++: nullptr_t val; val = 0; US 21-068 (ACCEPTED) accept the following code, as in C++: (nullptr_t)nullptr; GB-071 (ACCEPTED) accept the following code, as in C++: nullptr_t val; (void)(val == nullptr); This patch updates the implementation as appropriate, but is primarily focused around US 10-035, US 24-061, and US 23-062 in terms of functional changes. Differential Revision: https://reviews.llvm.org/D148800
1 parent 791b0fd commit 9bb28a1

File tree

6 files changed

+146
-39
lines changed

6 files changed

+146
-39
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,25 @@ C2x Feature Support
139139
removed, as this is no longer a GNU extension but a C2x extension. You can
140140
use ``-Wno-c2x-extensions`` to silence the extension warning instead.
141141

142+
- Updated the implementation of
143+
`WG14 N3042 <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3042.htm>`_
144+
based on decisions reached during the WG14 CD Ballot Resolution meetings held
145+
in Jan and Feb 2023. This should complete the implementation of ``nullptr``
146+
and ``nullptr_t`` in C. The specific changes are:
147+
148+
.. code-block:: c
149+
150+
void func(nullptr_t);
151+
func(0); // Previously required to be rejected, is now accepted.
152+
func((void *)0); // Previously required to be rejected, is now accepted.
153+
154+
nullptr_t val;
155+
val = 0; // Previously required to be rejected, is now accepted.
156+
val = (void *)0; // Previously required to be rejected, is now accepted.
157+
158+
bool b = nullptr; // Was incorrectly rejected by Clang, is now accepted.
159+
160+
142161
Non-comprehensive list of changes in this release
143162
-------------------------------------------------
144163
- Clang now saves the address of ABI-indirect function parameters on the stack,

clang/lib/Sema/SemaExpr.cpp

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10118,6 +10118,15 @@ Sema::CheckAssignmentConstraints(QualType LHSType, ExprResult &RHS,
1011810118
return Incompatible;
1011910119
}
1012010120

10121+
// Conversion to nullptr_t (C2x only)
10122+
if (getLangOpts().C2x && LHSType->isNullPtrType() &&
10123+
RHS.get()->isNullPointerConstant(Context,
10124+
Expr::NPC_ValueDependentIsNull)) {
10125+
// null -> nullptr_t
10126+
Kind = CK_NullToPointer;
10127+
return Compatible;
10128+
}
10129+
1012110130
// Conversions from pointers that are not covered by the above.
1012210131
if (isa<PointerType>(RHSType)) {
1012310132
// T* -> _Bool
@@ -10335,12 +10344,13 @@ Sema::CheckSingleAssignmentConstraints(QualType LHSType, ExprResult &CallerRHS,
1033510344
QualType LHSTypeAfterConversion = LHSType.getAtomicUnqualifiedType();
1033610345

1033710346
// C99 6.5.16.1p1: the left operand is a pointer and the right is
10338-
// a null pointer constant.
10347+
// a null pointer constant <C2x>or its type is nullptr_t;</C2x>.
1033910348
if ((LHSTypeAfterConversion->isPointerType() ||
1034010349
LHSTypeAfterConversion->isObjCObjectPointerType() ||
1034110350
LHSTypeAfterConversion->isBlockPointerType()) &&
10342-
RHS.get()->isNullPointerConstant(Context,
10343-
Expr::NPC_ValueDependentIsNull)) {
10351+
((getLangOpts().C2x && RHS.get()->getType()->isNullPtrType()) ||
10352+
RHS.get()->isNullPointerConstant(Context,
10353+
Expr::NPC_ValueDependentIsNull))) {
1034410354
if (Diagnose || ConvertRHS) {
1034510355
CastKind Kind;
1034610356
CXXCastPath Path;
@@ -10351,6 +10361,26 @@ Sema::CheckSingleAssignmentConstraints(QualType LHSType, ExprResult &CallerRHS,
1035110361
}
1035210362
return Compatible;
1035310363
}
10364+
// C2x 6.5.16.1p1: the left operand has type atomic, qualified, or
10365+
// unqualified bool, and the right operand is a pointer or its type is
10366+
// nullptr_t.
10367+
if (getLangOpts().C2x && LHSType->isBooleanType() &&
10368+
RHS.get()->getType()->isNullPtrType()) {
10369+
// NB: T* -> _Bool is handled in CheckAssignmentConstraints, this only
10370+
// only handles nullptr -> _Bool due to needing an extra conversion
10371+
// step.
10372+
// We model this by converting from nullptr -> void * and then let the
10373+
// conversion from void * -> _Bool happen naturally.
10374+
if (Diagnose || ConvertRHS) {
10375+
CastKind Kind;
10376+
CXXCastPath Path;
10377+
CheckPointerConversion(RHS.get(), Context.VoidPtrTy, Kind, Path,
10378+
/*IgnoreBaseAccess=*/false, Diagnose);
10379+
if (ConvertRHS)
10380+
RHS = ImpCastExprToType(RHS.get(), Context.VoidPtrTy, Kind, VK_PRValue,
10381+
&Path);
10382+
}
10383+
}
1035410384

1035510385
// OpenCL queue_t type assignment.
1035610386
if (LHSType->isQueueT() && RHS.get()->isNullPointerConstant(

clang/test/C/C2x/n3042.c

Lines changed: 25 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
// RUN: %clang_cc1 -verify -ffreestanding -Wno-unused -std=c2x %s
22

3-
/* WG14 N3042: partial
3+
/* WG14 N3042: full
44
* Introduce the nullptr constant
5-
*
6-
* Claiming partial support for this feature until the WG14 NB comments can be
7-
* resolved to know what the correct behavior really should be.
85
*/
96

107
#include <stddef.h>
@@ -21,25 +18,17 @@
2118
void questionable_behaviors() {
2219
nullptr_t val;
2320

24-
// FIXME: This code is intended to be rejected by C and is accepted by C++.
25-
// We've filed an NB comment with WG14 about the incompatibility.
21+
// This code is intended to be rejected by C and is accepted by C++. We filed
22+
// an NB comment asking for this to be changed, but WG14 declined.
2623
(void)(1 ? val : 0); // expected-error {{non-pointer operand type 'int' incompatible with nullptr}}
2724
(void)(1 ? nullptr : 0); // expected-error {{non-pointer operand type 'int' incompatible with nullptr}}
2825

29-
// FIXME: This code is intended to be accepted by C and is rejected by C++.
30-
// We're following the C++ semantics until WG14 has resolved the NB comments
31-
// we've filed about the incompatibility.
32-
_Bool another = val; // expected-error {{initializing 'bool' with an expression of incompatible type 'nullptr_t'}}
33-
another = val; // expected-error {{assigning to 'bool' from incompatible type 'nullptr_t'}}
34-
_Bool again = nullptr; // expected-error {{initializing 'bool' with an expression of incompatible type 'nullptr_t'}}
35-
again = nullptr; // expected-error {{assigning to 'bool' from incompatible type 'nullptr_t'}}
36-
37-
// FIXME: This code is intended to be rejected by C and is accepted by C++.
38-
// We've filed an NB comment with WG14 about the incompatibility.
39-
val = 0; // expected-error {{assigning to 'nullptr_t' from incompatible type 'int'}}
40-
41-
// Not accepted in C++ but might want to accept in C as a null pointer constant?
42-
val = (void *)0; // expected-error {{assigning to 'nullptr_t' from incompatible type 'void *'}}
26+
// This code is intended to be accepted by C and is rejected by C++. We filed
27+
// an NB comment asking for this to be changed, but WG14 declined.
28+
_Bool another = val; // expected-warning {{implicit conversion of nullptr constant to 'bool'}}
29+
another = val; // expected-warning {{implicit conversion of nullptr constant to 'bool'}}
30+
_Bool again = nullptr; // expected-warning {{implicit conversion of nullptr constant to 'bool'}}
31+
again = nullptr; // expected-warning {{implicit conversion of nullptr constant to 'bool'}}
4332
}
4433

4534
void test() {
@@ -67,6 +56,14 @@ void test() {
6756
// How about the null pointer named constant?
6857
&nullptr; // expected-error {{cannot take the address of an rvalue of type 'nullptr_t'}}
6958

59+
// Assignment from a null pointer constant to a nullptr_t is valid.
60+
null_val = 0;
61+
null_val = (void *)0;
62+
63+
// Assignment from a nullptr_t to a pointer is also valid.
64+
typed_ptr = null_val;
65+
void *other_ptr = null_val;
66+
7067
// Can it be used in all the places a scalar can be used?
7168
if (null_val) {}
7269
if (!null_val) {}
@@ -162,18 +159,15 @@ void test() {
162159
}
163160

164161
// Can we use it as a function parameter?
165-
void null_param(nullptr_t); // expected-note 2 {{passing argument to parameter here}}
162+
void null_param(nullptr_t);
166163

167164
void other_test() {
168165
// Can we call the function properly?
169166
null_param(nullptr);
170167

171-
// Do we get reasonable diagnostics when we can't call the function?
172-
null_param((void *)0); // expected-error {{passing 'void *' to parameter of incompatible type 'nullptr_t'}}
173-
174-
// FIXME: The paper requires this to be rejected, but it is accepted in C++.
175-
// This should be addressed after WG14 has processed national body comments.
176-
null_param(0); // expected-error {{passing 'int' to parameter of incompatible type 'nullptr_t'}}
168+
// We can pass any kind of null pointer constant.
169+
null_param((void *)0);
170+
null_param(0);
177171
}
178172

179173

@@ -182,3 +176,7 @@ void format_specifiers() {
182176
// Don't warn when using nullptr with %p.
183177
printf("%p", nullptr);
184178
}
179+
180+
// Ensure that conversion from a null pointer constant to nullptr_t is
181+
// valid in a constant expression.
182+
static_assert((nullptr_t){} == 0);

clang/test/CodeGen/nullptr.c

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// RUN: %clang_cc1 -S %s -std=c2x -emit-llvm -o - | FileCheck %s
2+
3+
// Test that null <-> nullptr_t conversions work as expected.
4+
typedef typeof(nullptr) nullptr_t;
5+
6+
nullptr_t nullptr_t_val;
7+
8+
void bool_func(bool);
9+
void nullptr_func(nullptr_t);
10+
11+
void test() {
12+
// Test initialization
13+
bool bool_from_nullptr_t = nullptr_t_val;
14+
nullptr_t nullptr_t_from_nullptr = nullptr;
15+
void *vp_from_nullptr_t = nullptr_t_val;
16+
nullptr_t nullptr_t_from_vp = (void *)0;
17+
nullptr_t nullptr_t_from_int = 0;
18+
19+
// Test assignment
20+
bool_from_nullptr_t = nullptr_t_val;
21+
nullptr_t_from_nullptr = nullptr;
22+
vp_from_nullptr_t = nullptr_t_val;
23+
nullptr_t_from_vp = (void *)0;
24+
nullptr_t_from_int = 0;
25+
26+
// Test calls
27+
bool_func(nullptr_t_from_nullptr);
28+
nullptr_func(nullptr_t_from_nullptr);
29+
nullptr_func(0);
30+
nullptr_func((void *)0);
31+
nullptr_func(nullptr);
32+
nullptr_func(false);
33+
34+
// Allocation of locals
35+
// CHECK: %[[bool_from_nullptr_t:.*]] = alloca i8, align 1
36+
// CHECK: %[[nullptr_t_from_nullptr:.*]] = alloca ptr, align 8
37+
// CHECK: %[[vp_from_nullptr_t:.*]] = alloca ptr, align 8
38+
// CHECK: %[[nullptr_t_from_vp:.*]] = alloca ptr, align 8
39+
// CHECK: %[[nullptr_t_from_int:.*]] = alloca ptr, align 8
40+
41+
// Initialization of locals
42+
// CHECK: store i8 0, ptr %[[bool_from_nullptr_t]], align 1
43+
// CHECK: store ptr null, ptr %[[nullptr_t_from_nullptr]], align 8
44+
// CHECK: store ptr null, ptr %[[vp_from_nullptr_t]], align 8
45+
// CHECK: store ptr null, ptr %[[nullptr_t_from_vp]], align 8
46+
// CHECK: store ptr null, ptr %[[nullptr_t_from_int]], align 8
47+
48+
// Assignment expressions
49+
// CHECK: store i8 0, ptr %[[bool_from_nullptr_t]], align 1
50+
// CHECK: store ptr null, ptr %[[nullptr_t_from_nullptr]], align 8
51+
// CHECK: store ptr null, ptr %[[vp_from_nullptr_t]], align 8
52+
// CHECK: store ptr null, ptr %[[nullptr_t_from_vp]], align 8
53+
// CHECK: store ptr null, ptr %[[nullptr_t_from_int]], align 8
54+
55+
// Calls
56+
// CHECK: call void @bool_func(i1 noundef zeroext false)
57+
// CHECK: call void @nullptr_func(ptr null)
58+
// CHECK: call void @nullptr_func(ptr null)
59+
// CHECK: call void @nullptr_func(ptr null)
60+
// CHECK: call void @nullptr_func(ptr null)
61+
// CHECK: call void @nullptr_func(ptr null)
62+
}
63+

clang/test/Sema/nullptr.c

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ nullptr_t f(nullptr_t null)
1616
p = null;
1717
int *pi = nullptr;
1818
pi = null;
19-
null = 0; // expected-error {{assigning to 'nullptr_t' from incompatible type 'int'}}
20-
bool b = nullptr; // expected-error {{initializing 'bool' with an expression of incompatible type 'nullptr_t'}}
19+
null = 0;
20+
bool b = nullptr;
2121

2222
// Can't convert nullptr to integral implicitly.
2323
uintptr_t i = nullptr; // expected-error-re {{initializing 'uintptr_t' (aka '{{.*}}') with an expression of incompatible type 'nullptr_t'}}
@@ -77,6 +77,9 @@ void h() {
7777

7878
static_assert(sizeof(nullptr_t) == sizeof(void*), "");
7979

80+
static_assert(!nullptr, "");
81+
static_assert(!(bool){nullptr}, "");
82+
8083
static_assert(!(nullptr < nullptr), ""); // expected-error {{invalid operands to binary expression}}
8184
static_assert(!(nullptr > nullptr), ""); // expected-error {{invalid operands to binary expression}}
8285
static_assert( nullptr <= nullptr, ""); // expected-error {{invalid operands to binary expression}}

clang/www/c_status.html

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1215,13 +1215,7 @@ <h2 id="c2x">C2x implementation status</h2>
12151215
<tr>
12161216
<td>Introduce the nullptr constant</td>
12171217
<td><a href="https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3042.htm">N3042</a></td>
1218-
<td class="partial" align="center">
1219-
<details><summary>Partial</summary>
1220-
Parts of the implementation may be incorrect until WG14 has completed NB comment
1221-
resolution for incompatibilities with C++ that were discovered. The major use cases
1222-
and usage patterns should work well, though.
1223-
</details>
1224-
</td>
1218+
<td class="unreleased" align="center">Clang 17</td>
12251219
</tr>
12261220
<tr>
12271221
<td>Memory layout of unions</td>

0 commit comments

Comments
 (0)