Skip to content

Commit 0370beb

Browse files
authored
[Clang] Perform derived-to-base conversion on explicit object parameter in lambda (#89828)
Consider this code: ```c++ template <typename... Ts> struct Overloaded : Ts... { using Ts::operator()...; }; template <typename... Ts> Overloaded(Ts...) -> Overloaded<Ts...>; void f() { int x; Overloaded o { [&](this auto& self) { return &x; } }; o(); } ``` To access `x` in the lambda, we need to perform derived-to-base conversion on `self` (since the type of `self` is not the lambda type, but rather `Overloaded<(lambda type)>`). We were previously missing this step, causing us to attempt to load the entire lambda (as the base class, it would end up being the ‘field’ with index `0` here), which would then assert later on in codegen. Moreover, this is only valid in the first place if there is a unique and publicly accessible cast path from the derived class to the lambda’s type, so this also adds a check in Sema to diagnose problematic cases. This fixes #87210 and fixes #89541.
1 parent 29456e9 commit 0370beb

File tree

10 files changed

+231
-23
lines changed

10 files changed

+231
-23
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -766,6 +766,9 @@ Bug Fixes to C++ Support
766766
- Clang now correctly diagnoses when the current instantiation is used as an incomplete base class.
767767
- Clang no longer treats ``constexpr`` class scope function template specializations of non-static members
768768
as implicitly ``const`` in language modes after C++11.
769+
- Fixed a crash when trying to emit captures in a lambda call operator with an explicit object
770+
parameter that is called on a derived type of the lambda.
771+
Fixes (#GH87210), (GH89541).
769772

770773
Bug Fixes to AST Handling
771774
^^^^^^^^^^^^^^^^^^^^^^^^^

clang/include/clang/AST/ASTContext.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,9 @@ class VarTemplateDecl;
110110
class VTableContextBase;
111111
class XRayFunctionFilter;
112112

113+
/// A simple array of base specifiers.
114+
typedef SmallVector<CXXBaseSpecifier *, 4> CXXCastPath;
115+
113116
namespace Builtin {
114117

115118
class Context;
@@ -1170,6 +1173,12 @@ class ASTContext : public RefCountedBase<ASTContext> {
11701173
/// in device compilation.
11711174
llvm::DenseSet<const FunctionDecl *> CUDAImplicitHostDeviceFunUsedByDevice;
11721175

1176+
/// For capturing lambdas with an explicit object parameter whose type is
1177+
/// derived from the lambda type, we need to perform derived-to-base
1178+
/// conversion so we can access the captures; the cast paths for that
1179+
/// are stored here.
1180+
llvm::DenseMap<const CXXMethodDecl *, CXXCastPath> LambdaCastPaths;
1181+
11731182
ASTContext(LangOptions &LOpts, SourceManager &SM, IdentifierTable &idents,
11741183
SelectorTable &sels, Builtin::Context &builtins,
11751184
TranslationUnitKind TUKind);

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7525,6 +7525,11 @@ def err_explicit_object_parameter_mutable: Error<
75257525
def err_invalid_explicit_object_type_in_lambda: Error<
75267526
"invalid explicit object parameter type %0 in lambda with capture; "
75277527
"the type must be the same as, or derived from, the lambda">;
7528+
def err_explicit_object_lambda_ambiguous_base : Error<
7529+
"lambda %0 is inaccessible due to ambiguity:%1">;
7530+
def err_explicit_object_lambda_inaccessible_base : Error<
7531+
"invalid explicit object parameter type %0 in lambda with capture; "
7532+
"the type must derive publicly from the lambda">;
75287533

75297534
def err_ref_qualifier_overload : Error<
75307535
"cannot overload a member function %select{without a ref-qualifier|with "

clang/include/clang/Sema/Sema.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7071,7 +7071,9 @@ class Sema final : public SemaBase {
70717071
StorageClass SC, ArrayRef<ParmVarDecl *> Params,
70727072
bool HasExplicitResultType);
70737073

7074-
void DiagnoseInvalidExplicitObjectParameterInLambda(CXXMethodDecl *Method);
7074+
/// Returns true if the explicit object parameter was invalid.
7075+
bool DiagnoseInvalidExplicitObjectParameterInLambda(CXXMethodDecl *Method,
7076+
SourceLocation CallLoc);
70757077

70767078
/// Perform initialization analysis of the init-capture and perform
70777079
/// any implicit conversions such as an lvalue-to-rvalue conversion if

clang/lib/CodeGen/CGExpr.cpp

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4676,7 +4676,8 @@ LValue CodeGenFunction::EmitMemberExpr(const MemberExpr *E) {
46764676
LValue CodeGenFunction::EmitLValueForLambdaField(const FieldDecl *Field,
46774677
llvm::Value *ThisValue) {
46784678
bool HasExplicitObjectParameter = false;
4679-
if (const auto *MD = dyn_cast_if_present<CXXMethodDecl>(CurCodeDecl)) {
4679+
const auto *MD = dyn_cast_if_present<CXXMethodDecl>(CurCodeDecl);
4680+
if (MD) {
46804681
HasExplicitObjectParameter = MD->isExplicitObjectMemberFunction();
46814682
assert(MD->getParent()->isLambda());
46824683
assert(MD->getParent() == Field->getParent());
@@ -4693,6 +4694,17 @@ LValue CodeGenFunction::EmitLValueForLambdaField(const FieldDecl *Field,
46934694
else
46944695
LambdaLV = MakeAddrLValue(AddrOfExplicitObject,
46954696
D->getType().getNonReferenceType());
4697+
4698+
// Make sure we have an lvalue to the lambda itself and not a derived class.
4699+
auto *ThisTy = D->getType().getNonReferenceType()->getAsCXXRecordDecl();
4700+
auto *LambdaTy = cast<CXXRecordDecl>(Field->getParent());
4701+
if (ThisTy != LambdaTy) {
4702+
const CXXCastPath &BasePathArray = getContext().LambdaCastPaths.at(MD);
4703+
Address Base = GetAddressOfBaseClass(
4704+
LambdaLV.getAddress(*this), ThisTy, BasePathArray.begin(),
4705+
BasePathArray.end(), /*NullCheckValue=*/false, SourceLocation());
4706+
LambdaLV = MakeAddrLValue(Base, QualType{LambdaTy->getTypeForDecl(), 0});
4707+
}
46964708
} else {
46974709
QualType LambdaTagType = getContext().getTagDeclType(Field->getParent());
46984710
LambdaLV = MakeNaturalAlignAddrLValue(ThisValue, LambdaTagType);

clang/lib/Sema/SemaLambda.cpp

Lines changed: 54 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "clang/Sema/SemaLambda.h"
1313
#include "TypeLocBuilder.h"
1414
#include "clang/AST/ASTLambda.h"
15+
#include "clang/AST/CXXInheritance.h"
1516
#include "clang/AST/ExprCXX.h"
1617
#include "clang/Basic/TargetInfo.h"
1718
#include "clang/Sema/DeclSpec.h"
@@ -386,30 +387,69 @@ buildTypeForLambdaCallOperator(Sema &S, clang::CXXRecordDecl *Class,
386387
// parameter, if any, of the lambda's function call operator (possibly
387388
// instantiated from a function call operator template) shall be either:
388389
// - the closure type,
389-
// - class type derived from the closure type, or
390+
// - class type publicly and unambiguously derived from the closure type, or
390391
// - a reference to a possibly cv-qualified such type.
391-
void Sema::DiagnoseInvalidExplicitObjectParameterInLambda(
392-
CXXMethodDecl *Method) {
392+
bool Sema::DiagnoseInvalidExplicitObjectParameterInLambda(
393+
CXXMethodDecl *Method, SourceLocation CallLoc) {
393394
if (!isLambdaCallWithExplicitObjectParameter(Method))
394-
return;
395+
return false;
395396
CXXRecordDecl *RD = Method->getParent();
396397
if (Method->getType()->isDependentType())
397-
return;
398+
return false;
398399
if (RD->isCapturelessLambda())
399-
return;
400-
QualType ExplicitObjectParameterType = Method->getParamDecl(0)
401-
->getType()
400+
return false;
401+
402+
ParmVarDecl *Param = Method->getParamDecl(0);
403+
QualType ExplicitObjectParameterType = Param->getType()
402404
.getNonReferenceType()
403405
.getUnqualifiedType()
404406
.getDesugaredType(getASTContext());
405407
QualType LambdaType = getASTContext().getRecordType(RD);
406408
if (LambdaType == ExplicitObjectParameterType)
407-
return;
408-
if (IsDerivedFrom(RD->getLocation(), ExplicitObjectParameterType, LambdaType))
409-
return;
410-
Diag(Method->getParamDecl(0)->getLocation(),
411-
diag::err_invalid_explicit_object_type_in_lambda)
412-
<< ExplicitObjectParameterType;
409+
return false;
410+
411+
// Don't check the same instantiation twice.
412+
//
413+
// If this call operator is ill-formed, there is no point in issuing
414+
// a diagnostic every time it is called because the problem is in the
415+
// definition of the derived type, not at the call site.
416+
//
417+
// FIXME: Move this check to where we instantiate the method? This should
418+
// be possible, but the naive approach of just marking the method as invalid
419+
// leads to us emitting more diagnostics than we should have to for this case
420+
// (1 error here *and* 1 error about there being no matching overload at the
421+
// call site). It might be possible to avoid that by also checking if there
422+
// is an empty cast path for the method stored in the context (signalling that
423+
// we've already diagnosed it) and then just not building the call, but that
424+
// doesn't really seem any simpler than diagnosing it at the call site...
425+
if (auto It = Context.LambdaCastPaths.find(Method);
426+
It != Context.LambdaCastPaths.end())
427+
return It->second.empty();
428+
429+
CXXCastPath &Path = Context.LambdaCastPaths[Method];
430+
CXXBasePaths Paths(/*FindAmbiguities=*/true, /*RecordPaths=*/true,
431+
/*DetectVirtual=*/false);
432+
if (!IsDerivedFrom(RD->getLocation(), ExplicitObjectParameterType, LambdaType,
433+
Paths)) {
434+
Diag(Param->getLocation(), diag::err_invalid_explicit_object_type_in_lambda)
435+
<< ExplicitObjectParameterType;
436+
return true;
437+
}
438+
439+
if (Paths.isAmbiguous(LambdaType->getCanonicalTypeUnqualified())) {
440+
std::string PathsDisplay = getAmbiguousPathsDisplayString(Paths);
441+
Diag(CallLoc, diag::err_explicit_object_lambda_ambiguous_base)
442+
<< LambdaType << PathsDisplay;
443+
return true;
444+
}
445+
446+
if (CheckBaseClassAccess(CallLoc, LambdaType, ExplicitObjectParameterType,
447+
Paths.front(),
448+
diag::err_explicit_object_lambda_inaccessible_base))
449+
return true;
450+
451+
BuildBasePathArray(Paths, Path);
452+
return false;
413453
}
414454

415455
void Sema::handleLambdaNumbering(

clang/lib/Sema/SemaOverload.cpp

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6472,17 +6472,20 @@ ExprResult Sema::InitializeExplicitObjectArgument(Sema &S, Expr *Obj,
64726472
Obj->getExprLoc(), Obj);
64736473
}
64746474

6475-
static void PrepareExplicitObjectArgument(Sema &S, CXXMethodDecl *Method,
6475+
static bool PrepareExplicitObjectArgument(Sema &S, CXXMethodDecl *Method,
64766476
Expr *Object, MultiExprArg &Args,
64776477
SmallVectorImpl<Expr *> &NewArgs) {
64786478
assert(Method->isExplicitObjectMemberFunction() &&
64796479
"Method is not an explicit member function");
64806480
assert(NewArgs.empty() && "NewArgs should be empty");
6481+
64816482
NewArgs.reserve(Args.size() + 1);
64826483
Expr *This = GetExplicitObjectExpr(S, Object, Method);
64836484
NewArgs.push_back(This);
64846485
NewArgs.append(Args.begin(), Args.end());
64856486
Args = NewArgs;
6487+
return S.DiagnoseInvalidExplicitObjectParameterInLambda(
6488+
Method, Object->getBeginLoc());
64866489
}
64876490

64886491
/// Determine whether the provided type is an integral type, or an enumeration
@@ -15612,8 +15615,10 @@ ExprResult Sema::BuildCallToMemberFunction(Scope *S, Expr *MemExprE,
1561215615
CallExpr *TheCall = nullptr;
1561315616
llvm::SmallVector<Expr *, 8> NewArgs;
1561415617
if (Method->isExplicitObjectMemberFunction()) {
15615-
PrepareExplicitObjectArgument(*this, Method, MemExpr->getBase(), Args,
15616-
NewArgs);
15618+
if (PrepareExplicitObjectArgument(*this, Method, MemExpr->getBase(), Args,
15619+
NewArgs))
15620+
return ExprError();
15621+
1561715622
// Build the actual expression node.
1561815623
ExprResult FnExpr =
1561915624
CreateFunctionRefExpr(*this, Method, FoundDecl, MemExpr,
@@ -15927,9 +15932,7 @@ Sema::BuildCallToObjectOfClassType(Scope *S, Expr *Obj,
1592715932
// Initialize the object parameter.
1592815933
llvm::SmallVector<Expr *, 8> NewArgs;
1592915934
if (Method->isExplicitObjectMemberFunction()) {
15930-
// FIXME: we should do that during the definition of the lambda when we can.
15931-
DiagnoseInvalidExplicitObjectParameterInLambda(Method);
15932-
PrepareExplicitObjectArgument(*this, Method, Obj, Args, NewArgs);
15935+
IsError |= PrepareExplicitObjectArgument(*this, Method, Obj, Args, NewArgs);
1593315936
} else {
1593415937
ExprResult ObjRes = PerformImplicitObjectArgumentInitialization(
1593515938
Object.get(), /*Qualifier=*/nullptr, Best->FoundDecl, Method);

clang/test/CXX/drs/cwg28xx.cpp

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,3 +109,74 @@ struct A {
109109
#endif
110110

111111
} // namespace cwg2858
112+
113+
namespace cwg2881 { // cwg2881: 19 tentatively ready 2024-04-19
114+
115+
#if __cplusplus >= 202302L
116+
117+
template <typename T> struct A : T {};
118+
template <typename T> struct B : T {};
119+
template <typename T> struct C : virtual T { C(T t) : T(t) {} };
120+
template <typename T> struct D : virtual T { D(T t) : T(t) {} };
121+
122+
template <typename Ts>
123+
struct O1 : A<Ts>, B<Ts> {
124+
using A<Ts>::operator();
125+
using B<Ts>::operator();
126+
};
127+
128+
template <typename Ts> struct O2 : protected Ts { // expected-note {{declared protected here}}
129+
using Ts::operator();
130+
O2(Ts ts) : Ts(ts) {}
131+
};
132+
133+
template <typename Ts> struct O3 : private Ts { // expected-note {{declared private here}}
134+
using Ts::operator();
135+
O3(Ts ts) : Ts(ts) {}
136+
};
137+
138+
// Not ambiguous because of virtual inheritance.
139+
template <typename Ts>
140+
struct O4 : C<Ts>, D<Ts> {
141+
using C<Ts>::operator();
142+
using D<Ts>::operator();
143+
O4(Ts t) : Ts(t), C<Ts>(t), D<Ts>(t) {}
144+
};
145+
146+
// This still has a public path to the lambda, and it's also not
147+
// ambiguous because of virtual inheritance.
148+
template <typename Ts>
149+
struct O5 : private C<Ts>, D<Ts> {
150+
using C<Ts>::operator();
151+
using D<Ts>::operator();
152+
O5(Ts t) : Ts(t), C<Ts>(t), D<Ts>(t) {}
153+
};
154+
155+
// This is only invalid if we call T's call operator.
156+
template <typename T, typename U>
157+
struct O6 : private T, U { // expected-note {{declared private here}}
158+
using T::operator();
159+
using U::operator();
160+
O6(T t, U u) : T(t), U(u) {}
161+
};
162+
163+
void f() {
164+
int x;
165+
auto L1 = [=](this auto&& self) { (void) &x; };
166+
auto L2 = [&](this auto&& self) { (void) &x; };
167+
O1<decltype(L1)>{L1, L1}(); // expected-error {{inaccessible due to ambiguity}}
168+
O1<decltype(L2)>{L2, L2}(); // expected-error {{inaccessible due to ambiguity}}
169+
O2{L1}(); // expected-error {{must derive publicly from the lambda}}
170+
O3{L1}(); // expected-error {{must derive publicly from the lambda}}
171+
O4{L1}();
172+
O5{L1}();
173+
O6 o{L1, L2};
174+
o.decltype(L1)::operator()(); // expected-error {{must derive publicly from the lambda}}
175+
o.decltype(L1)::operator()(); // No error here because we've already diagnosed this method.
176+
o.decltype(L2)::operator()();
177+
}
178+
179+
#endif
180+
181+
} // namespace cwg2881
182+

clang/test/CodeGenCXX/cxx2b-deducing-this.cpp

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,3 +182,66 @@ auto dothing(int num)
182182
fun();
183183
}
184184
}
185+
186+
namespace GH87210 {
187+
template <typename... Ts>
188+
struct Overloaded : Ts... {
189+
using Ts::operator()...;
190+
};
191+
192+
template <typename... Ts>
193+
Overloaded(Ts...) -> Overloaded<Ts...>;
194+
195+
// CHECK-LABEL: define dso_local void @_ZN7GH872101fEv()
196+
// CHECK-NEXT: entry:
197+
// CHECK-NEXT: [[X:%.*]] = alloca i32
198+
// CHECK-NEXT: [[Over:%.*]] = alloca %"{{.*}}Overloaded"
199+
// CHECK: call noundef ptr @"_ZZN7GH872101fEvENH3$_0clINS_10OverloadedIJS0_EEEEEDaRT_"(ptr {{.*}} [[Over]])
200+
void f() {
201+
int x;
202+
Overloaded o {
203+
// CHECK: define internal noundef ptr @"_ZZN7GH872101fEvENH3$_0clINS_10OverloadedIJS0_EEEEEDaRT_"(ptr {{.*}} [[Self:%.*]])
204+
// CHECK-NEXT: entry:
205+
// CHECK-NEXT: [[SelfAddr:%.*]] = alloca ptr
206+
// CHECK-NEXT: store ptr [[Self]], ptr [[SelfAddr]]
207+
// CHECK-NEXT: [[SelfPtr:%.*]] = load ptr, ptr [[SelfAddr]]
208+
// CHECK-NEXT: [[XRef:%.*]] = getelementptr inbounds %{{.*}}, ptr [[SelfPtr]], i32 0, i32 0
209+
// CHECK-NEXT: [[X:%.*]] = load ptr, ptr [[XRef]]
210+
// CHECK-NEXT: ret ptr [[X]]
211+
[&](this auto& self) {
212+
return &x;
213+
}
214+
};
215+
o();
216+
}
217+
218+
void g() {
219+
int x;
220+
Overloaded o {
221+
[=](this auto& self) {
222+
return x;
223+
}
224+
};
225+
o();
226+
}
227+
}
228+
229+
namespace GH89541 {
230+
// Same as above; just check that this doesn't crash.
231+
int one = 1;
232+
auto factory(int& x = one) {
233+
return [&](this auto self) {
234+
x;
235+
};
236+
};
237+
238+
using Base = decltype(factory());
239+
struct Derived : Base {
240+
Derived() : Base(factory()) {}
241+
};
242+
243+
void f() {
244+
Derived d;
245+
d();
246+
}
247+
}

clang/www/cxx_dr_status.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17095,7 +17095,7 @@ <h2 id="cxxdr">C++ defect report implementation status</h2>
1709517095
<td><a href="https://cplusplus.github.io/CWG/issues/2881.html">2881</a></td>
1709617096
<td>tentatively ready</td>
1709717097
<td>Type restrictions for the explicit object parameter of a lambda</td>
17098-
<td align="center">Not resolved</td>
17098+
<td title="Clang 19 implements 2024-04-19 resolution" align="center">Not Resolved*</td>
1709917099
</tr>
1710017100
<tr class="open" id="2882">
1710117101
<td><a href="https://cplusplus.github.io/CWG/issues/2882.html">2882</a></td>

0 commit comments

Comments
 (0)