-
Notifications
You must be signed in to change notification settings - Fork 13.5k
[clang][CompundLiteralExpr] Don't defer evaluation for CLEs #137163
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -48,6 +48,7 @@ | |
#include "clang/AST/OptionalDiagnostic.h" | ||
#include "clang/AST/RecordLayout.h" | ||
#include "clang/AST/StmtVisitor.h" | ||
#include "clang/AST/Type.h" | ||
#include "clang/AST/TypeLoc.h" | ||
#include "clang/Basic/Builtins.h" | ||
#include "clang/Basic/DiagnosticSema.h" | ||
|
@@ -4522,6 +4523,30 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E, | |
|
||
BaseVal = MTE->getOrCreateValue(false); | ||
assert(BaseVal && "got reference to unevaluated temporary"); | ||
} else if (const CompoundLiteralExpr *CLE = | ||
dyn_cast_or_null<CompoundLiteralExpr>(Base)) { | ||
// According to GCC info page: | ||
// | ||
// 6.28 Compound Literals | ||
// | ||
// 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. | ||
// | ||
// Obey that rule by checking constness for converted array types. | ||
if (QualType CLETy = CLE->getType(); CLETy->isArrayType() && | ||
!LValType->isArrayType() && | ||
!CLETy.isConstant(Info.Ctx)) { | ||
Info.FFDiag(E); | ||
Info.Note(CLE->getExprLoc(), diag::note_declared_at); | ||
return CompleteObject(); | ||
} | ||
|
||
BaseVal = &CLE->getStaticValue(); | ||
} else { | ||
if (!IsAccess) | ||
return CompleteObject(LVal.getLValueBase(), nullptr, BaseType); | ||
|
@@ -4587,44 +4612,7 @@ handleLValueToRValueConversion(EvalInfo &Info, const Expr *Conv, QualType Type, | |
WantObjectRepresentation ? AK_ReadObjectRepresentation : AK_Read; | ||
|
||
if (Base && !LVal.getLValueCallIndex() && !Type.isVolatileQualified()) { | ||
if (const CompoundLiteralExpr *CLE = dyn_cast<CompoundLiteralExpr>(Base)) { | ||
// In C99, a CompoundLiteralExpr is an lvalue, and we defer evaluating the | ||
// initializer until now for such expressions. Such an expression can't be | ||
// an ICE in C, so this only matters for fold. | ||
if (Type.isVolatileQualified()) { | ||
Info.FFDiag(Conv); | ||
return false; | ||
} | ||
|
||
APValue Lit; | ||
if (!Evaluate(Lit, Info, CLE->getInitializer())) | ||
return false; | ||
|
||
// According to GCC info page: | ||
// | ||
// 6.28 Compound Literals | ||
// | ||
// 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. | ||
// | ||
// Obey that rule by checking constness for converted array types. | ||
|
||
QualType CLETy = CLE->getType(); | ||
if (CLETy->isArrayType() && !Type->isArrayType()) { | ||
if (!CLETy.isConstant(Info.Ctx)) { | ||
Info.FFDiag(Conv); | ||
Info.Note(CLE->getExprLoc(), diag::note_declared_at); | ||
return false; | ||
} | ||
} | ||
|
||
CompleteObject LitObj(LVal.Base, &Lit, Base->getType()); | ||
return extractSubobject(Info, Conv, LitObj, LVal.Designator, RVal, AK); | ||
} else if (isa<StringLiteral>(Base) || isa<PredefinedExpr>(Base)) { | ||
if (isa<StringLiteral>(Base) || isa<PredefinedExpr>(Base)) { | ||
// Special-case character extraction so we don't have to construct an | ||
// APValue for the whole string. | ||
assert(LVal.Designator.Entries.size() <= 1 && | ||
|
@@ -9125,9 +9113,26 @@ bool | |
LValueExprEvaluator::VisitCompoundLiteralExpr(const CompoundLiteralExpr *E) { | ||
assert((!Info.getLangOpts().CPlusPlus || E->isFileScope()) && | ||
"lvalue compound literal in c++?"); | ||
// 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. | ||
return Success(E); | ||
APValue *Lit; | ||
// If CompountLiteral has static storage, its value can be used outside | ||
// this expression. So evaluate it once and store it in ASTContext. | ||
if (E->hasStaticStorage()) { | ||
Lit = &E->getOrCreateStaticValue(Info.Ctx); | ||
Result.set(E); | ||
// Reset any previously evaluated state, otherwise evaluation below might | ||
// fail. | ||
// FIXME: Should we just re-use the previously evaluated value instead? | ||
*Lit = APValue(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The evaluation should produce the same result every time, so it doesn't really do any harm to do it twice. But if you do it right, it's also very easy to avoid evaluating it twice. I suspect using EvaluateInPlace will cause state to leak into the evaluation when it shouldn't (like, we accidentally accept a use of a temporary inside the initializer). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since I don't fully understand the implications here, I'd rather keep the evaluation behavior similar to the previous version (e.g. evaluate every-time we need it). Happy to evaluate once if you think that should be safe ~always and feel strongly about this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm more concerned about the fact that you're using EvaluateInPlace, as opposed to using EvaluateAsInitializer. Which... I'm not sure it's actually possible to observe a difference at the moment due to the rule that static compound literals are required to have a constant initializer, but it's fragile. On a sort of related note, I was experimenting with some testcases, and the following crashed:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I actually think of the opposite. The code here mimics what There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also it seems to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was thinking about it as a different thing from MaterializeTemporaryExpr... but I guess it's similar enough to a lifetime-extended temporary that we can just adopt the same semantic rules. Which might not be what a user would expect, but I guess it's at least self-consistent. Do we need the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Considering the previous comments in the code, I think we need to perform folding also for CLEs with static duration. There are also many test failures if we don't fold these in static initializers, https://github.com/llvm/llvm-project/blob/main/clang/test/AST/ByteCode/c.c#L155-L174 is an example (but I am also surprised to see that hold for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ping here There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Dug into the EvalMode thing... I remember why we added it now. See https://reviews.llvm.org/D151587 for context. Roughly, the issue is that, if you constant-evaluate the expression in isolation, you get a different result from what you'd get evaluating the whole expression. So we can't redo the evaluation in ConstantFold mode: if you do, you'll corrupt the precomputed value, for cases like clang/test/CodeGenCXX/const-init-cxx1y.cpp. I think compound literals run into similar issues if you allow ConstantFold mode to overwrite the evaluated value. Maybe we can add some workaround specifically for cases where we immediately do an lvalue-to-rvalue conversion. |
||
} else { | ||
assert(!Info.getLangOpts().CPlusPlus); | ||
Lit = &Info.CurrentCall->createTemporary(E, E->getInitializer()->getType(), | ||
ScopeKind::Block, Result); | ||
} | ||
if (!EvaluateInPlace(*Lit, Info, Result, E->getInitializer())) { | ||
*Lit = APValue(); | ||
return false; | ||
} | ||
return true; | ||
} | ||
|
||
bool LValueExprEvaluator::VisitCXXTypeidExpr(const CXXTypeidExpr *E) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
// Test that we can successfully compile this code, especially under ASAN. | ||
// RUN: %clang_cc1 -verify -std=c++20 -fsyntax-only %s | ||
// expected-no-diagnostics | ||
struct Foo { | ||
Foo* f; | ||
operator bool() const { return true; } | ||
}; | ||
constexpr Foo f((Foo[]){}); | ||
int foo() { | ||
if (Foo(*f.f)) return 1; | ||
return 0; | ||
} |
Uh oh!
There was an error while loading. Please reload this page.