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
