-
Notifications
You must be signed in to change notification settings - Fork 11.1k
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
[clang] Fix a crash from nested ArrayInitLoopExpr #67722
Conversation
@llvm/pr-subscribers-clang ChangesFor the following snippets clang performs the same steps:
From the two however the latter crashes. The source of the crash is that we create a temporary variable for the source expression, the
The problem is that we are unable to differentiate between the temporaries that are created in the different iterations, as we only use the pointer to the node as the key, which stays the same. As for why only the second snippet crashes, the reason is that after evaluating the first iteration, we note a failure and would return, however during analyzing the second snippet, the This patch changes the described behaviour, as if we keep going the exact same expression would be evaluated again and it would fail again, so there's no point of doing that. A different solution would be to wrap the temporary in a Fixes #57135 Full diff: https://github.com/llvm/llvm-project/pull/67722.diff 2 Files Affected:
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index fea06b97259fe31..3a4ef81672911ba 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -10967,19 +10967,19 @@ bool ArrayExprEvaluator::VisitArrayInitLoopExpr(const ArrayInitLoopExpr *E) {
LValue Subobject = This;
Subobject.addArray(Info, E, CAT);
- bool Success = true;
for (EvalInfo::ArrayInitLoopIndex Index(Info); Index != Elements; ++Index) {
if (!EvaluateInPlace(Result.getArrayInitializedElt(Index),
Info, Subobject, E->getSubExpr()) ||
!HandleLValueArrayAdjustment(Info, E, Subobject,
CAT->getElementType(), 1)) {
- if (!Info.noteFailure())
+
+ // There's no need to try and evaluate the rest, as those are the exact same expressions.
+ std::ignore = Info.noteFailure();
return false;
- Success = false;
}
}
- return Success;
+ return true;
}
bool ArrayExprEvaluator::VisitCXXConstructExpr(const CXXConstructExpr *E) {
diff --git a/clang/test/AST/nested-array-init-loop-in-lambda-capture.cpp b/clang/test/AST/nested-array-init-loop-in-lambda-capture.cpp
new file mode 100644
index 000000000000000..82d27380b637d03
--- /dev/null
+++ b/clang/test/AST/nested-array-init-loop-in-lambda-capture.cpp
@@ -0,0 +1,13 @@
+// RUN: %clang_cc1 -fexperimental-new-constant-interpreter -std=c++17 -verify %s
+// RUN: %clang_cc1 -std=c++17 -verify=ref %s
+
+// ref-no-diagnostics
+// expected-no-diagnostics
+
+void used_to_crash() {
+ int s[2][2];
+
+ int arr[4];
+
+ arr[0] = [s] { return s[0][0]; }();
+}
|
264c0fa
to
fb0bf2f
Compare
✅ With the latest revision this PR passed the C/C++ code formatter. |
I decided to push the solution where the temporary is put inside a |
Thank you for the fix! I don't think I understand the bug based on your description. You say
You start saying first iteration and then refer to second snippet did you mean second iteration? You say
Can you link to the line? Can you also point to the line we are checking |
I'll try my best to explain it a bit better. So, this is the function that runs and triggers the assertion failure at one point. bool ArrayExprEvaluator::VisitArrayInitLoopExpr(const ArrayInitLoopExpr *E) {
LValue CommonLV;
if (E->getCommonExpr() &&
!Evaluate(Info.CurrentCall->createTemporary( // assertion failure triggered from createTemporary()
E->getCommonExpr(),
getStorageType(Info.Ctx, E->getCommonExpr()),
ScopeKind::FullExpression, CommonLV),
Info, E->getCommonExpr()->getSourceExpr()))
return false;
auto *CAT = cast<ConstantArrayType>(E->getType()->castAsArrayTypeUnsafe());
uint64_t Elements = CAT->getSize().getZExtValue();
Result = APValue(APValue::UninitArray(), Elements, Elements);
LValue Subobject = This;
Subobject.addArray(Info, E, CAT);
bool Success = true;
for (EvalInfo::ArrayInitLoopIndex Index(Info); Index != Elements; ++Index) {
if (!EvaluateInPlace(Result.getArrayInitializedElt(Index),
Info, Subobject, E->getSubExpr()) ||
!HandleLValueArrayAdjustment(Info, E, Subobject,
CAT->getElementType(), 1)) {
if (!Info.noteFailure())
return false;
Success = false;
}
}
return Success;
} Let's see how it runs on the following snippet and how the AST looks like: S s[2][2];
auto [a1,a2] = s; // the ast is only shown for this line
A quick anatomy guide for ArrayInitLoopExpr
|-OpaqueValueExpr // getCommonExpr() returns this
| `-DeclRefExpr <source_array> // always wrapped inside an OpaqueValueExpr
`-<initializer_used_for_each_element> // getSubExpr() returns this
`-ArrayInitIndexExpr // a placeholder node denoting the "current" index of the element being initialized For more details please check the clang documentation of ArrayInitLoopExpr and ArrayInitIndexExpr So, let's see the step by step execution of -ArrayInitLoopExpr... 'S[2][2]' // <-- we are here
|-OpaqueValueExpr ... 'S[2][2]' lvalue
| `-DeclRefExpr ... 'S[2][2]' lvalue Var ... 's' 'S[2][2]'
`-ArrayInitLoopExpr ... 'S[2]'
if (
E->getCommonExpr() &&
!Evaluate(Info.CurrentCall->createTemporary(E->getCommonExpr(), ...), Info, E->getCommonExpr()->getSourceExpr())
)
return false; I say we create it for the // Key is passed from `E->getCommonExpr()`.
template<typename KeyT>
APValue &CallStackFrame::createTemporary(const KeyT *Key, QualType T,
ScopeKind Scope, LValue &LV) {
unsigned Version = getTempVersion();
APValue::LValueBase Base(Key, Index, Version);
LV.set(Base);
return createLocal(Base, Key, T, Scope);
} APValue &CallStackFrame::createLocal(APValue::LValueBase Base, const void *Key, ...) {
...
unsigned Version = Base.getVersion();
APValue &Result = Temporaries[MapKeyTy(Key, Version)];
assert(Result.isAbsent() && "local created multiple times");
...
} So, from what we know, currently the stack frame looks like this:
for (EvalInfo::ArrayInitLoopIndex Index(Info); Index != Elements; ++Index) {
if (!EvaluateInPlace(Result.getArrayInitializedElt(Index),
Info, Subobject, E->getSubExpr()) ||
!HandleLValueArrayAdjustment(Info, E, Subobject,
CAT->getElementType(), 1)) {
if (!Info.noteFailure())
return false;
Success = false;
}
}
-ArrayInitLoopExpr... 'S[2][2]'
|-OpaqueValueExpr ... 'S[2][2]' lvalue
| `-DeclRefExpr ... 'S[2][2]' lvalue Var ... 's' 'S[2][2]'
`-ArrayInitLoopExpr ... 'S[2]' // <-- we are here
|-OpaqueValueExpr ... 'S[2]' lvalue // <-- temporary created for this
| `-ArraySubscriptExpr ... 'S[2]' lvalue
| |-ImplicitCastExpr ... 'S (*)[2]' <ArrayToPointerDecay>
| | `-OpaqueValueExpr ... 'S[2][2]' lvalue
| | `-DeclRefExpr ... 'S[2][2]' lvalue Var ... 's' 'S[2][2]'
| `-ArrayInitIndexExpr 'unsigned long'
`-<initializer used for each array member> After creating the the new temporary, the stack frame looks like this:
Now let's pretend that the loop presented in step 2 finished execution inside
-ArrayInitLoopExpr... 'S[2][2]' // <-- we are here
|-OpaqueValueExpr ... 'S[2][2]' lvalue
| `-DeclRefExpr ... 'S[2][2]' lvalue Var ... 's' 'S[2][2]'
`-ArrayInitLoopExpr ... 'S[2]' // <-- we evaluated this in step 3 for (EvalInfo::ArrayInitLoopIndex Index(Info); Index != Elements; ++Index) {
if (!EvaluateInPlace(Result.getArrayInitializedElt(Index),
Info, Subobject, E->getSubExpr()) ||
!HandleLValueArrayAdjustment(Info, E, Subobject,
CAT->getElementType(), 1)) {
if (!Info.noteFailure()) // <-- we are here
return false;
Success = false;
}
} In the snippet that we're currently analyzing, Note that the stack frame still looks like this:
Now let's see the snippet that hits the assertion failure and why it actually happens, although I think I spoiled it already. S s[2][2];
int res[4];
res[0] = [s] { return s[0][0].i; }(); Everything up until step 4 is exactly the same as it was with the previous snippet. The difference comes in step 4, where Remember that when we enter that APValue &CallStackFrame::createLocal(...) {
...
unsigned Version = Base.getVersion();
APValue &Result = Temporaries[MapKeyTy(Key, Version)];
assert(Result.isAbsent() && "local created multiple times"); // <- it is not absent
...
} And the reason for why [[nodiscard]] bool noteFailure() {
bool KeepGoing = keepEvaluatingAfterFailure();
EvalStatus.HasSideEffects |= KeepGoing;
return KeepGoing;
} bool keepEvaluatingAfterFailure() const override {
if (!StepsLeft)
return false;
switch (EvalMode) {
case EM_ConstantExpression:
case EM_ConstantExpressionUnevaluated:
case EM_ConstantFold:
case EM_IgnoreSideEffects:
return checkingPotentialConstantExpression() ||
checkingForUndefinedBehavior();
}
llvm_unreachable("Missed EvalMode case");
} /// Are we checking an expression for overflow?
// FIXME: We should check for any kind of undefined or suspicious behavior
// in such constructs, not just overflow.
bool checkingForUndefinedBehavior() const override {
return CheckingForUndefinedBehavior;
} I didn't investigate where this gets set to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ExprConstant changes LGTM. I don't have state on the Interp changes; someone else should review that.
Remove the |
31ad08e
to
3fc8296
Compare
Removed |
I've pushed 38018ec since, so you could remove the |
When visiting an ArrayInitLoopExpr, clang creates a temporary variable for the source array, however in a nested AILE this variable is created multiple times, in every iteration as they have the same AST node and clang is unable to differentiate them. Fixes llvm#57135
3fc8296
to
bedfe40
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM except for a small nit in the changelog
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Apologies for the post commit review, just have one question.
@@ -10977,6 +10987,9 @@ bool ArrayExprEvaluator::VisitArrayInitLoopExpr(const ArrayInitLoopExpr *E) { | |||
return false; | |||
Success = false; | |||
} | |||
|
|||
// Make sure we run the destructors too. | |||
Scope.destroy(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So we don't have to check the return value? It looks like every other use does not discard the return value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The result of the initializer expression should be placed inside a temporary array, which is created on line 10967. We only destroy whatever is left on the stack and we shouldn't need anything from there.
For the following snippets clang performs the same steps:
From the two however the latter crashes. The source of the crash is that we create a temporary variable for the source array expression, the
ArrayInitLoop
is performed on.The problem is that we are unable to differentiate between the temporaries that are created in the different iterations, as we only use the pointer to the node as the key, which stays the same.
As for why only the second snippet crashes, the reason is that after evaluating the first iteration, we note a failure and would return, however during analyzing the second snippet, the
CheckingForUndefinedBehavior
flag is set, probably because we assign to an array member and clang wants us to keep going.This patch changes the described behaviour, as if we keep going the exact same expression would be evaluated again and it would fail again, so there's no point of doing that.A different solution would be to wrap the temporary in aFullExpressionRAII
so that the temporary is cleaned up before it is created once again.Fixes #57135