[clang] 4417a75 - [ExprConstant] Treat `&*p` as not a dereference in C constant initializers (#201483)
via cfe-commits
cfe-commits at lists.llvm.org
Thu Jun 4 19:05:19 PDT 2026
Author: Akira Hatanaka
Date: 2026-06-05T02:05:13Z
New Revision: 4417a7501aae8a9b8d3ffee1d491540a0858cc29
URL: https://github.com/llvm/llvm-project/commit/4417a7501aae8a9b8d3ffee1d491540a0858cc29
DIFF: https://github.com/llvm/llvm-project/commit/4417a7501aae8a9b8d3ffee1d491540a0858cc29.diff
LOG: [ExprConstant] Treat `&*p` as not a dereference in C constant initializers (#201483)
In C, [C11 6.5.3.2p3] specifies that when the operand of unary `&` is
the result of a unary `*` operator, neither operator is evaluated and
the result is as if both were omitted. So `&*p` yields the pointer value
`p` without performing a dereference, and forming it is well-defined
even when `p` is null (e.g. `&*(int *)0`).
The constant evaluator did not honor this: it evaluated the `*` as a
real lvalue access and diagnosed a null dereference as undefined
behavior. This went unnoticed for ordinary scalar initializers, which
use the relaxed `Expr::isConstantInitializer()` check, but a bit-field
initializer is evaluated via `EvaluateAsInt()` with `SE_NoSideEffects`,
so the same expression was rejected there with "initializer element is
not a compile-time constant":
```
struct S { long v : 8; };
const struct S s = { .v = (long)&*(int *)0 }; // error
const long x = (long)&*(int *)0; // accepted
```
Handle `&*p` in `PointerExprEvaluator::VisitUnaryAddrOf` (and the
corresponding `UO_AddrOf` case in the bytecode interpreter) by
evaluating the pointer operand directly in C.
Fixes #197846.
rdar://158774335
Added:
Modified:
clang/lib/AST/ByteCode/Compiler.cpp
clang/lib/AST/ExprConstant.cpp
clang/test/Sema/static-init.c
Removed:
################################################################################
diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp
index 4784627bca3b9..6279b2fb5a38e 100644
--- a/clang/lib/AST/ByteCode/Compiler.cpp
+++ b/clang/lib/AST/ByteCode/Compiler.cpp
@@ -7385,6 +7385,17 @@ bool Compiler<Emitter>::VisitUnaryOperator(const UnaryOperator *E) {
return true;
return this->emitGetMemberPtr(cast<DeclRefExpr>(SubExpr)->getDecl(), E);
}
+ // [C11 6.5.3.2p3]: if the operand of '&' is the result of a unary '*'
+ // operator, neither operator is evaluated and the result is as if both
+ // were omitted. So '&*q' is just 'q' with no dereference; delegate to the
+ // pointer operand directly instead of to the '*' (which would emit a null
+ // check), so that e.g. '&*(int *)0' is not rejected.
+ if (!Ctx.getLangOpts().CPlusPlus) {
+ const Expr *Sub = SubExpr->IgnoreParens();
+ if (const auto *Deref = dyn_cast<UnaryOperator>(Sub);
+ Deref && Deref->getOpcode() == UO_Deref)
+ return this->delegate(Deref->getSubExpr());
+ }
// We should already have a pointer when we get here.
return this->delegate(SubExpr);
case UO_Deref: // *x
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 86c162cc040f9..c774a02440274 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -10036,6 +10036,19 @@ bool PointerExprEvaluator::VisitBinaryOperator(const BinaryOperator *E) {
}
bool PointerExprEvaluator::VisitUnaryAddrOf(const UnaryOperator *E) {
+ // [C11 6.5.3.2p3]: if the operand of '&' is the result of a unary '*'
+ // operator, neither operator is evaluated and the result is as if both were
+ // omitted (except that the operators' constraints, already enforced by Sema,
+ // still apply, and the result is not an lvalue). So '&*p' is just the pointer
+ // value 'p' with no dereference, and forming it is therefore not undefined
+ // behavior even when 'p' is null, e.g. '&*(int *)0'. Evaluate the pointer
+ // operand directly so we don't spuriously diagnose a null dereference.
+ if (!Info.getLangOpts().CPlusPlus) {
+ const Expr *Sub = E->getSubExpr()->IgnoreParens();
+ if (const auto *Deref = dyn_cast<UnaryOperator>(Sub);
+ Deref && Deref->getOpcode() == UO_Deref)
+ return evaluatePointer(Deref->getSubExpr(), Result);
+ }
return evaluateLValue(E->getSubExpr(), Result);
}
diff --git a/clang/test/Sema/static-init.c b/clang/test/Sema/static-init.c
index ecd7b5c78d0d5..d8456846625d8 100644
--- a/clang/test/Sema/static-init.c
+++ b/clang/test/Sema/static-init.c
@@ -23,3 +23,20 @@ union bar u[1];
struct foo x = {(intptr_t) u}; // expected-error {{initializer element is not a compile-time constant}}
struct foo y = {(char) u}; // expected-error {{initializer element is not a compile-time constant}}
intptr_t z = (intptr_t) u; // no-error
+
+// [C11 6.5.3.2p3]: in '&*p' neither operator is evaluated and the result is as
+// if both were omitted, so forming it is not a dereference and is a valid
+// constant initializer even when 'p' is null. This must hold for a bit-field
+// initializer just as it does for a plain scalar (it was previously rejected
+// for bit-fields only, because they take a
diff erent evaluation path).
+intptr_t deref_scalar = (intptr_t) &*(int *)0; // no-error
+struct with_bitfield {
+ long v : 8;
+};
+struct with_bitfield deref_bitfield = {.v = (long) &*(int *)0}; // no-error
+
+// '&a[i]' is evaluated as 'a + i'.
+intptr_t subscript_scalar = (intptr_t) &((int *)0)[0]; // no-error
+intptr_t subscript_scalar_offset = (intptr_t) &((int *)0)[2]; // no-error
+struct with_bitfield subscript_bitfield = {.v = (long) &((int *)0)[0]}; // no-error
+struct with_bitfield subscript_bitfield_offset = {.v = (long) &((int *)0)[2]}; // no-error
More information about the cfe-commits
mailing list