Skip to content

[Clang][Sema] Properly get captured 'this' pointer in lambdas with an explicit object parameter in constant evaluator #81102

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 11 commits into from
Mar 13, 2024
3 changes: 3 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,9 @@ Bug Fixes to C++ Support
and (`#74494 <https://github.com/llvm/llvm-project/issues/74494>`_)
- Allow access to a public template alias declaration that refers to friend's
private nested type. (#GH25708).
- Fixed a crash in constant evaluation when trying to access a
captured ``this`` pointer in a lambda with an explicit object parameter.
Fixes (#GH80997)

Bug Fixes to AST Handling
^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down
129 changes: 74 additions & 55 deletions clang/lib/AST/ExprConstant.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8517,6 +8517,53 @@ class LValueExprEvaluator
};
} // end anonymous namespace

/// Get an lvalue to a field of a lambda's closure type.
static bool HandleLambdaCapture(EvalInfo &Info, const Expr *E, LValue &Result,
const CXXMethodDecl *MD, const FieldDecl *FD,
bool LValueToRValueConversion) {
// Static lambda function call operators can't have captures. We already
// diagnosed this, so bail out here.
if (MD->isStatic()) {
assert(Info.CurrentCall->This == nullptr &&
"This should not be set for a static call operator");
return false;
}

// Start with 'Result' referring to the complete closure object...
if (MD->isExplicitObjectMemberFunction()) {
// Self may be passed by reference or by value.
const ParmVarDecl *Self = MD->getParamDecl(0);
if (Self->getType()->isReferenceType()) {
APValue *RefValue = Info.getParamSlot(Info.CurrentCall->Arguments, Self);
Result.setFrom(Info.Ctx, *RefValue);
} else {
const ParmVarDecl *VD = Info.CurrentCall->Arguments.getOrigParam(Self);
CallStackFrame *Frame =
Info.getCallFrameAndDepth(Info.CurrentCall->Arguments.CallIndex)
.first;
unsigned Version = Info.CurrentCall->Arguments.Version;
Result.set({VD, Frame->Index, Version});
}
} else
Result = *Info.CurrentCall->This;

// ... then update it to refer to the field of the closure object
// that represents the capture.
if (!HandleLValueMember(Info, E, Result, FD))
return false;

// And if the field is of reference type (or if we captured '*this' by
// reference), update 'Result' to refer to what
// the field refers to.
if (LValueToRValueConversion) {
APValue RVal;
if (!handleLValueToRValueConversion(Info, E, FD->getType(), Result, RVal))
return false;
Result.setFrom(Info.Ctx, RVal);
}
return true;
}

/// Evaluate an expression as an lvalue. This can be legitimately called on
/// expressions which are not glvalues, in three cases:
/// * function designators in C, and
Expand Down Expand Up @@ -8561,37 +8608,8 @@ bool LValueExprEvaluator::VisitVarDecl(const Expr *E, const VarDecl *VD) {

if (auto *FD = Info.CurrentCall->LambdaCaptureFields.lookup(VD)) {
const auto *MD = cast<CXXMethodDecl>(Info.CurrentCall->Callee);

// Static lambda function call operators can't have captures. We already
// diagnosed this, so bail out here.
if (MD->isStatic()) {
assert(Info.CurrentCall->This == nullptr &&
"This should not be set for a static call operator");
return false;
}

// Start with 'Result' referring to the complete closure object...
if (MD->isExplicitObjectMemberFunction()) {
APValue *RefValue =
Info.getParamSlot(Info.CurrentCall->Arguments, MD->getParamDecl(0));
Result.setFrom(Info.Ctx, *RefValue);
} else
Result = *Info.CurrentCall->This;

// ... then update it to refer to the field of the closure object
// that represents the capture.
if (!HandleLValueMember(Info, E, Result, FD))
return false;
// And if the field is of reference type, update 'Result' to refer to what
// the field refers to.
if (FD->getType()->isReferenceType()) {
APValue RVal;
if (!handleLValueToRValueConversion(Info, E, FD->getType(), Result,
RVal))
return false;
Result.setFrom(Info.Ctx, RVal);
}
return true;
return HandleLambdaCapture(Info, E, Result, MD, FD,
FD->getType()->isReferenceType());
}
}

Expand Down Expand Up @@ -9069,45 +9087,46 @@ class PointerExprEvaluator
return Error(E);
}
bool VisitCXXThisExpr(const CXXThisExpr *E) {
// Can't look at 'this' when checking a potential constant expression.
if (Info.checkingPotentialConstantExpression())
return false;
if (!Info.CurrentCall->This) {
auto DiagnoseInvalidUseOfThis = [&] {
if (Info.getLangOpts().CPlusPlus11)
Info.FFDiag(E, diag::note_constexpr_this) << E->isImplicit();
else
Info.FFDiag(E);
};

// Can't look at 'this' when checking a potential constant expression.
if (Info.checkingPotentialConstantExpression())
return false;

bool IsExplicitLambda =
isLambdaCallWithExplicitObjectParameter(Info.CurrentCall->Callee);
if (!IsExplicitLambda) {
if (!Info.CurrentCall->This) {
DiagnoseInvalidUseOfThis();
return false;
}

Result = *Info.CurrentCall->This;
}
Result = *Info.CurrentCall->This;

if (isLambdaCallOperator(Info.CurrentCall->Callee)) {
// Ensure we actually have captured 'this'. If something was wrong with
// 'this' capture, the error would have been previously reported.
// Otherwise we can be inside of a default initialization of an object
// declared by lambda's body, so no need to return false.
if (!Info.CurrentCall->LambdaThisCaptureField)
return true;

// If we have captured 'this', the 'this' expression refers
// to the enclosing '*this' object (either by value or reference) which is
// either copied into the closure object's field that represents the
// '*this' or refers to '*this'.
// Update 'Result' to refer to the data member/field of the closure object
// that represents the '*this' capture.
if (!HandleLValueMember(Info, E, Result,
Info.CurrentCall->LambdaThisCaptureField))
return false;
// If we captured '*this' by reference, replace the field with its referent.
if (Info.CurrentCall->LambdaThisCaptureField->getType()
->isPointerType()) {
APValue RVal;
if (!handleLValueToRValueConversion(Info, E, E->getType(), Result,
RVal))
if (!Info.CurrentCall->LambdaThisCaptureField) {
if (IsExplicitLambda && !Info.CurrentCall->This) {
DiagnoseInvalidUseOfThis();
return false;
}

Result.setFrom(Info.Ctx, RVal);
return true;
}

const auto *MD = cast<CXXMethodDecl>(Info.CurrentCall->Callee);
return HandleLambdaCapture(
Info, E, Result, MD, Info.CurrentCall->LambdaThisCaptureField,
Info.CurrentCall->LambdaThisCaptureField->getType()->isPointerType());
}
return true;
}
Expand Down
34 changes: 34 additions & 0 deletions clang/test/SemaCXX/constexpr-explicit-object-lambda.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// RUN: %clang_cc1 -std=c++23 -verify %s
// expected-no-diagnostics

struct S {
int i = 42;
constexpr auto f1() {
return [this](this auto) {
return this->i;
}();
};

constexpr auto f2() {
return [this](this auto&&) {
return this->i;
}();
};

constexpr auto f3() {
return [i = this->i](this auto) {
return i;
}();
};

constexpr auto f4() {
return [i = this->i](this auto&&) {
return i;
}();
};
};

static_assert(S().f1() == 42);
static_assert(S().f2() == 42);
static_assert(S().f3() == 42);
static_assert(S().f4() == 42);