Skip to content

[Clang] Only remove lambda scope after computing evaluation context #154106

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

Merged
merged 2 commits into from
Aug 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ Bug Fixes to C++ Support
- Diagnose binding a reference to ``*nullptr`` during constant evaluation. (#GH48665)
- Suppress ``-Wdeprecated-declarations`` in implicitly generated functions. (#GH147293)
- Fix a crash when deleting a pointer to an incomplete array (#GH150359).
- Fixed a mismatched lambda scope bug when propagating up ``consteval`` within nested lambdas. (#GH145776)
- Fix an assertion failure when expression in assumption attribute
(``[[assume(expr)]]``) creates temporary objects.

Expand Down
22 changes: 14 additions & 8 deletions clang/include/clang/Sema/Sema.h
Original file line number Diff line number Diff line change
Expand Up @@ -4179,8 +4179,15 @@ class Sema final : public SemaBase {
/// return statement in the scope of a variable has the same NRVO candidate,
/// that candidate is an NRVO variable.
void computeNRVO(Stmt *Body, sema::FunctionScopeInfo *Scope);
Decl *ActOnFinishFunctionBody(Decl *Decl, Stmt *Body);
Decl *ActOnFinishFunctionBody(Decl *Decl, Stmt *Body, bool IsInstantiation);

/// Performs semantic analysis at the end of a function body.
///
/// \param RetainFunctionScopeInfo If \c true, the client is responsible for
/// releasing the associated \p FunctionScopeInfo. This is useful when
/// building e.g. LambdaExprs.
Decl *ActOnFinishFunctionBody(Decl *Decl, Stmt *Body,
bool IsInstantiation = false,
bool RetainFunctionScopeInfo = false);
Decl *ActOnSkippedFunctionBody(Decl *Decl);
void ActOnFinishInlineFunctionDef(FunctionDecl *D);

Expand Down Expand Up @@ -6873,23 +6880,23 @@ class Sema final : public SemaBase {
assert(!ExprEvalContexts.empty() &&
"Must be in an expression evaluation context");
return ExprEvalContexts.back();
};
}

ExpressionEvaluationContextRecord &currentEvaluationContext() {
assert(!ExprEvalContexts.empty() &&
"Must be in an expression evaluation context");
return ExprEvalContexts.back();
};
}

ExpressionEvaluationContextRecord &parentEvaluationContext() {
assert(ExprEvalContexts.size() >= 2 &&
"Must be in an expression evaluation context");
return ExprEvalContexts[ExprEvalContexts.size() - 2];
};
}

const ExpressionEvaluationContextRecord &parentEvaluationContext() const {
return const_cast<Sema *>(this)->parentEvaluationContext();
};
}

bool isAttrContext() const {
return ExprEvalContexts.back().ExprContext ==
Expand Down Expand Up @@ -9139,8 +9146,7 @@ class Sema final : public SemaBase {

/// Complete a lambda-expression having processed and attached the
/// lambda body.
ExprResult BuildLambdaExpr(SourceLocation StartLoc, SourceLocation EndLoc,
sema::LambdaScopeInfo *LSI);
ExprResult BuildLambdaExpr(SourceLocation StartLoc, SourceLocation EndLoc);

/// Get the return type to use for a lambda's conversion function(s) to
/// function pointer type, given the type of the call operator.
Expand Down
11 changes: 4 additions & 7 deletions clang/lib/Sema/SemaDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16149,10 +16149,6 @@ Decl *Sema::ActOnSkippedFunctionBody(Decl *Decl) {
return Decl;
}

Decl *Sema::ActOnFinishFunctionBody(Decl *D, Stmt *BodyArg) {
return ActOnFinishFunctionBody(D, BodyArg, /*IsInstantiation=*/false);
}

/// RAII object that pops an ExpressionEvaluationContext when exiting a function
/// body.
class ExitFunctionBodyRAII {
Expand Down Expand Up @@ -16223,8 +16219,8 @@ void Sema::CheckCoroutineWrapper(FunctionDecl *FD) {
Diag(FD->getLocation(), diag::err_coroutine_return_type) << RD;
}

Decl *Sema::ActOnFinishFunctionBody(Decl *dcl, Stmt *Body,
bool IsInstantiation) {
Decl *Sema::ActOnFinishFunctionBody(Decl *dcl, Stmt *Body, bool IsInstantiation,
bool RetainFunctionScopeInfo) {
FunctionScopeInfo *FSI = getCurFunction();
FunctionDecl *FD = dcl ? dcl->getAsFunction() : nullptr;

Expand Down Expand Up @@ -16681,7 +16677,8 @@ Decl *Sema::ActOnFinishFunctionBody(Decl *dcl, Stmt *Body,
if (!IsInstantiation)
PopDeclContext();

PopFunctionScopeInfo(ActivePolicy, dcl);
if (!RetainFunctionScopeInfo)
PopFunctionScopeInfo(ActivePolicy, dcl);
// If any errors have occurred, clear out any temporaries that may have
// been leftover. This ensures that these temporaries won't be picked up for
// deletion in some later function.
Expand Down
18 changes: 13 additions & 5 deletions clang/lib/Sema/SemaLambda.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1968,14 +1968,15 @@ ExprResult Sema::BuildCaptureInit(const Capture &Cap,
}

ExprResult Sema::ActOnLambdaExpr(SourceLocation StartLoc, Stmt *Body) {
LambdaScopeInfo LSI = *cast<LambdaScopeInfo>(FunctionScopes.back());
LambdaScopeInfo &LSI = *cast<LambdaScopeInfo>(FunctionScopes.back());

if (LSI.CallOperator->hasAttr<SYCLKernelEntryPointAttr>())
SYCL().CheckSYCLEntryPointFunctionDecl(LSI.CallOperator);

ActOnFinishFunctionBody(LSI.CallOperator, Body);
ActOnFinishFunctionBody(LSI.CallOperator, Body, /*IsInstantiation=*/false,
/*RetainFunctionScopeInfo=*/true);

return BuildLambdaExpr(StartLoc, Body->getEndLoc(), &LSI);
return BuildLambdaExpr(StartLoc, Body->getEndLoc());
}

static LambdaCaptureDefault
Expand Down Expand Up @@ -2132,8 +2133,9 @@ ConstructFixItRangeForUnusedCapture(Sema &S, SourceRange CaptureRange,
return SourceRange(FixItStart, FixItEnd);
}

ExprResult Sema::BuildLambdaExpr(SourceLocation StartLoc, SourceLocation EndLoc,
LambdaScopeInfo *LSI) {
ExprResult Sema::BuildLambdaExpr(SourceLocation StartLoc,
SourceLocation EndLoc) {
LambdaScopeInfo *LSI = cast<LambdaScopeInfo>(FunctionScopes.back());
// Collect information from the lambda scope.
SmallVector<LambdaCapture, 4> Captures;
SmallVector<Expr *, 4> CaptureInits;
Expand Down Expand Up @@ -2170,6 +2172,12 @@ ExprResult Sema::BuildLambdaExpr(SourceLocation StartLoc, SourceLocation EndLoc,

PopExpressionEvaluationContext();

sema::AnalysisBasedWarnings::Policy WP =
AnalysisWarnings.getPolicyInEffectAt(EndLoc);
// We cannot release LSI until we finish computing captures, which
// requires the scope to be popped.
PoppedFunctionScopePtr _ = PopFunctionScopeInfo(&WP, LSI->CallOperator);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is causing a use-after-free that CI is hitting:
#154342
#154347

because LSI is used outside of this block:

DiagnoseShadowingLambdaDecls(LSI);

maybeAddDeclWithEffects(LSI->CallOperator);

Perhaps we should revert the changes until that's fixed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I reverted in #154382 :(


// True if the current capture has a used capture or default before it.
bool CurHasPreviousCapture = CaptureDefault != LCD_None;
SourceLocation PrevCaptureLoc = CurHasPreviousCapture ?
Expand Down
13 changes: 5 additions & 8 deletions clang/lib/Sema/TreeTransform.h
Original file line number Diff line number Diff line change
Expand Up @@ -4073,7 +4073,7 @@ class TreeTransform {
PVD->getUninstantiatedDefaultArg()
->containsUnexpandedParameterPack();
}
return getSema().BuildLambdaExpr(StartLoc, EndLoc, LSI);
return getSema().BuildLambdaExpr(StartLoc, EndLoc);
}

/// Build an empty C++1z fold-expression with the given operator.
Expand Down Expand Up @@ -15791,12 +15791,9 @@ TreeTransform<Derived>::TransformLambdaExpr(LambdaExpr *E) {
return ExprError();
}

// Copy the LSI before ActOnFinishFunctionBody removes it.
// FIXME: This is dumb. Store the lambda information somewhere that outlives
// the call operator.
auto LSICopy = *LSI;
getSema().ActOnFinishFunctionBody(NewCallOperator, Body.get(),
/*IsInstantiation*/ true);
/*IsInstantiation=*/true,
/*RetainFunctionScopeInfo=*/true);
SavedContext.pop();

// Recompute the dependency of the lambda so that we can defer the lambda call
Expand Down Expand Up @@ -15832,15 +15829,15 @@ TreeTransform<Derived>::TransformLambdaExpr(LambdaExpr *E) {
// *after* the substitution in case we can't decide the dependency
// so early, e.g. because we want to see if any of the *substituted*
// parameters are dependent.
DependencyKind = getDerived().ComputeLambdaDependency(&LSICopy);
DependencyKind = getDerived().ComputeLambdaDependency(LSI);
Class->setLambdaDependencyKind(DependencyKind);
// Clean up the type cache created previously. Then, we re-create a type for
// such Decl with the new DependencyKind.
Class->setTypeForDecl(nullptr);
getSema().Context.getTypeDeclType(Class);

return getDerived().RebuildLambdaExpr(E->getBeginLoc(),
Body.get()->getEndLoc(), &LSICopy);
Body.get()->getEndLoc(), LSI);
}

template<typename Derived>
Expand Down
16 changes: 16 additions & 0 deletions clang/test/SemaCXX/cxx2b-consteval-propagate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -610,3 +610,19 @@ namespace GH135281 {
void (*ff)() = f2<B>; // expected-note {{instantiation of function template specialization}}
}
#endif

namespace GH145776 {

void runtime_only() {}
consteval void comptime_only() {}

void fn() {
[]() {
runtime_only();
[]() {
&comptime_only;
}();
}();
}

}