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


        
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to