Skip to content

Commit 70965ef

Browse files
authored
[Clang] Prevent assignment to captured structured bindings inside immutable lambda (#120849)
For structured bindings, a call to getCapturedDeclRefType(...) was missing. This PR fixes that behavior and adds the related diagnostics too. This fixes #95081.
1 parent ff97daa commit 70965ef

File tree

3 files changed

+50
-17
lines changed

3 files changed

+50
-17
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -885,6 +885,7 @@ Bug Fixes to C++ Support
885885
- Fixed recognition of ``std::initializer_list`` when it's surrounded with ``extern "C++"`` and exported
886886
out of a module (which is the case e.g. in MSVC's implementation of ``std`` module). (#GH118218)
887887
- Fixed a pack expansion issue in checking unexpanded parameter sizes. (#GH17042)
888+
- Fixed a bug where captured structured bindings were modifiable inside non-mutable lambda (#GH95081)
888889

889890
Bug Fixes to AST Handling
890891
^^^^^^^^^^^^^^^^^^^^^^^^^

clang/lib/Sema/SemaExpr.cpp

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3352,6 +3352,7 @@ ExprResult Sema::BuildDeclarationNameExpr(
33523352
case Decl::VarTemplateSpecialization:
33533353
case Decl::VarTemplatePartialSpecialization:
33543354
case Decl::Decomposition:
3355+
case Decl::Binding:
33553356
case Decl::OMPCapturedExpr:
33563357
// In C, "extern void blah;" is valid and is an r-value.
33573358
if (!getLangOpts().CPlusPlus && !type.hasQualifiers() &&
@@ -3371,20 +3372,13 @@ ExprResult Sema::BuildDeclarationNameExpr(
33713372
// potentially-evaluated contexts? Since the variable isn't actually
33723373
// captured in an unevaluated context, it seems that the answer is no.
33733374
if (!isUnevaluatedContext()) {
3374-
QualType CapturedType = getCapturedDeclRefType(cast<VarDecl>(VD), Loc);
3375+
QualType CapturedType = getCapturedDeclRefType(cast<ValueDecl>(VD), Loc);
33753376
if (!CapturedType.isNull())
33763377
type = CapturedType;
33773378
}
3378-
33793379
break;
33803380
}
33813381

3382-
case Decl::Binding:
3383-
// These are always lvalues.
3384-
valueKind = VK_LValue;
3385-
type = type.getNonReferenceType();
3386-
break;
3387-
33883382
case Decl::Function: {
33893383
if (unsigned BID = cast<FunctionDecl>(VD)->getBuiltinID()) {
33903384
if (!Context.BuiltinInfo.isDirectlyAddressable(BID)) {
@@ -13297,11 +13291,24 @@ static NonConstCaptureKind isReferenceToNonConstCapture(Sema &S, Expr *E) {
1329713291
if (!DRE) return NCCK_None;
1329813292
if (!DRE->refersToEnclosingVariableOrCapture()) return NCCK_None;
1329913293

13300-
// The declaration must be a variable which is not declared 'const'.
13301-
VarDecl *var = dyn_cast<VarDecl>(DRE->getDecl());
13302-
if (!var) return NCCK_None;
13303-
if (var->getType().isConstQualified()) return NCCK_None;
13304-
assert(var->hasLocalStorage() && "capture added 'const' to non-local?");
13294+
ValueDecl *Value = dyn_cast<ValueDecl>(DRE->getDecl());
13295+
13296+
// The declaration must be a value which is not declared 'const'.
13297+
if (!Value || Value->getType().isConstQualified())
13298+
return NCCK_None;
13299+
13300+
BindingDecl *Binding = dyn_cast<BindingDecl>(Value);
13301+
if (Binding) {
13302+
assert(S.getLangOpts().CPlusPlus && "BindingDecl outside of C++?");
13303+
assert(!isa<BlockDecl>(Binding->getDeclContext()));
13304+
return NCCK_Lambda;
13305+
}
13306+
13307+
VarDecl *Var = dyn_cast<VarDecl>(Value);
13308+
if (!Var)
13309+
return NCCK_None;
13310+
13311+
assert(Var->hasLocalStorage() && "capture added 'const' to non-local?");
1330513312

1330613313
// Decide whether the first capture was for a block or a lambda.
1330713314
DeclContext *DC = S.CurContext, *Prev = nullptr;
@@ -13310,16 +13317,16 @@ static NonConstCaptureKind isReferenceToNonConstCapture(Sema &S, Expr *E) {
1331013317
// For init-capture, it is possible that the variable belongs to the
1331113318
// template pattern of the current context.
1331213319
if (auto *FD = dyn_cast<FunctionDecl>(DC))
13313-
if (var->isInitCapture() &&
13314-
FD->getTemplateInstantiationPattern() == var->getDeclContext())
13320+
if (Var->isInitCapture() &&
13321+
FD->getTemplateInstantiationPattern() == Var->getDeclContext())
1331513322
break;
13316-
if (DC == var->getDeclContext())
13323+
if (DC == Var->getDeclContext())
1331713324
break;
1331813325
Prev = DC;
1331913326
DC = DC->getParent();
1332013327
}
1332113328
// Unless we have an init-capture, we've gone one step too far.
13322-
if (!var->isInitCapture())
13329+
if (!Var->isInitCapture())
1332313330
DC = Prev;
1332413331
return (isa<BlockDecl>(DC) ? NCCK_Block : NCCK_Lambda);
1332513332
}
@@ -19247,6 +19254,8 @@ bool Sema::NeedToCaptureVariable(ValueDecl *Var, SourceLocation Loc) {
1924719254
}
1924819255

1924919256
QualType Sema::getCapturedDeclRefType(ValueDecl *Var, SourceLocation Loc) {
19257+
assert(Var && "Null value cannot be captured");
19258+
1925019259
QualType CaptureType;
1925119260
QualType DeclRefType;
1925219261

clang/test/SemaCXX/cxx20-decomposition.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,3 +183,26 @@ namespace ODRUseTests {
183183
}(0); }(0); // expected-note 2{{in instantiation}}
184184
}
185185
}
186+
187+
188+
namespace GH95081 {
189+
void prevent_assignment_check() {
190+
int arr[] = {1,2};
191+
auto [e1, e2] = arr;
192+
193+
auto lambda = [e1] {
194+
e1 = 42; // expected-error {{cannot assign to a variable captured by copy in a non-mutable lambda}}
195+
};
196+
}
197+
198+
void f(int&) = delete;
199+
void f(const int&);
200+
201+
int arr[1];
202+
void foo() {
203+
auto [x] = arr;
204+
[x]() {
205+
f(x); // deleted f(int&) used to be picked up erroneously
206+
} ();
207+
}
208+
}

0 commit comments

Comments
 (0)