This is an archive of the discontinued LLVM Phabricator instance.

[clang] Prevent folding of non-const compound expr
ClosedPublic

Authored by serge-sans-paille on Apr 19 2022, 1:25 PM.

Details

Summary

When a non-const compound statement is used to initialize a constexpr pointer,
the pointed value is not const itself and cannot be folded at codegen time.

This matches GCC behavior for compund literal array

Fix issue #39324.

Diff Detail

Event Timeline

Herald added a project: Restricted Project. · View Herald TranscriptApr 19 2022, 1:25 PM
serge-sans-paille requested review of this revision.Apr 19 2022, 1:25 PM
Herald added a project: Restricted Project. · View Herald TranscriptApr 19 2022, 1:25 PM
Herald added a subscriber: cfe-commits. · View Herald Transcript

The fix doesn't look right.

A CompoundLiteralExpr is itself an lvalue; we should catch any issues when we visit it, not a VarDecl that contains a pointer to it.

The fix doesn't look right.

A CompoundLiteralExpr is itself an lvalue; we should catch any issues when we visit it, not a VarDecl that contains a pointer to it.

Well, the visitor for CompoundLiteralExpr explicitly states

// Defer visiting the literal until the lvalue-to-rvalue conversion. We can
// only see this when folding in C, so there's no standard to follow here.

Which is what I was trying to do in this patch.
We only visit CompoundLiteralExpr once, during initialization of the VarDecl, and that initialization is correct. We don't visit it again during checking of expression constness because we stop at the VarDecl level. I'm checking if we could peform an extra check at that point. But maybe I'm totally taking a wrong turn, feel free to advise :-)

Alternatively, the following also works, but it splits the logic into anotherplace, while current patch is at least consistent with existing state

diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 498f0d4..233307f 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -3352,6 +3352,17 @@ static bool evaluateVarDeclInit(EvalInfo &Info, const Expr *E,
     NoteLValueLocation(Info, Base);
   }
 
+  // In the particular case of CompoundLiteralExpr initialization, check that it is itself
+  // constant.
+  if (Info.InConstantContext)
+    if (const CompoundLiteralExpr *CLE = dyn_cast_or_null<CompoundLiteralExpr>(
+            VD->getAnyInitializer()->IgnoreCasts())) {
+      QualType CLET = CLE->getType().getCanonicalType();
+      if (!CLET.isConstant(Info.Ctx)) {
+        Info.FFDiag(E);
+      }
+    }
+

The VD->getAnyInitializer()->IgnoreCasts() is the part that's wrong. We want to allow something like the following:

constexpr int *q = (int *)(int[1]){3};
constexpr int *q2 = q;

To catch the bad cases specifically, we have to wait until we actually reach the compound literal. See line 4262, or something like that.

Match GCC behavior here: some test case were previously accepted while having the opposite behavior. This pacth both fixes the original issue and adopt gcc behavior.

efriedma added inline comments.May 9 2022, 10:21 AM
clang/lib/AST/ExprConstant.cpp
4267

Is the "isArrayType()" check here necessary?

clang/lib/AST/ExprConstant.cpp
4267

Yeah, otherwise we have an issue with

typedef __attribute__(( ext_vector_type(4) )) float float4;
float4 foo = (float4){ 1.0, 2.0, 3.0, 4.0 };

error: cannot compile this static initializer yet

efriedma added inline comments.May 9 2022, 1:38 PM
clang/lib/AST/ExprConstant.cpp
4267

Hmm, I see... in C++, the compound literal rules are weird. C compound literals are lvalues, but C++ ones are not. So normally, we don't allow taking the address of a compound literal or any of its members. But if it's an array, somehow different rules (what rules?) apply.

If you can add a comment explaining what's going on here, maybe it's okay?

I'm tempted to say we should reject the testcase, though. For example, in the following, it doesn't really make sense to reject the first variable, but accept the second.

typedef int arr[2];
constexpr int *x = arr{1};
constexpr int *y = (arr){1};

Can you add a testcase involving an array of structs with a "mutable" member?

serge-sans-paille edited the summary of this revision. (Show Details)

Added reg to GCC info page to explain current behavior, and make the test more explicit with respect to that quote.

clang/test/SemaCXX/constant-expression-cxx11.cpp
1609

@efriedma this is the test you asked for. it's already implemented, and my patch doesn't change its behavior, which matches the GCC behavior.

efriedma added a comment.EditedMay 10 2022, 12:54 PM

I think you're looking at old documentation? Here's what the current page (https://gcc.gnu.org/onlinedocs/gcc/Compound-Literals.html) has to say:

In C, a compound literal designates an unnamed object with static or automatic storage duration. In C++, a compound literal designates a temporary object that only lives until the end of its full-expression. As a result, well-defined C code that takes the address of a subobject of a compound literal can be undefined in C++, so G++ rejects the conversion of a temporary array to a pointer. For instance, if the array compound literal example above appeared inside a function, any subsequent use of foo in C++ would have undefined behavior because the lifetime of the array ends after the declaration of foo.

As an optimization, G++ sometimes gives array compound literals longer lifetimes: when the array either appears outside a function or has a const-qualified type. If foo and its initializer had elements of type char *const rather than char *, or if foo were a global variable, the array would have static storage duration. But it is probably safest just to avoid the use of array compound literals in C++ code.

I think you're looking at old documentation? Here's what the current page (https://gcc.gnu.org/onlinedocs/gcc/Compound-Literals.html) has to say:

Indeed! I was looking at my local Info Page. thanks for the extra pointer.

As an optimization, G++ sometimes gives array compound literals longer lifetimes: when the array either appears outside a function or has a const-qualified type. If foo and its initializer had elements of type char *const rather than char *, or if foo were a global variable, the array would have static storage duration. But it is probably safest just to avoid the use of array compound literals in C++ code.

I can quote that part instead. I don't think this invalidates the actual code, right? At least with that commit the observable behavior gets closer to GCC, and it fixes https://github.com/llvm/llvm-project/issues/39324

I think so, yes.

Update GCC manual quote

Updated messed up formatting?

Otherwise, I guess this is fine.

Update messed up format

This revision is now accepted and ready to land.May 13 2022, 12:10 PM