Skip to content

Commit b55dd8f

Browse files
authored
[clang][analyzer] Correctly handle structured bindings captured by lambda (#132579)
this PR fixes #91835. For `DeclRefExpr` in lambda's function body, it will references to original variable declaration in AST rather than `FieldDecl` for lambda class, so it's needed to find the corresponding `FieldDecl` and bind `DeclRefExpr`'s value to it. This is already implemented for variables that are not in a structured binding structure, so I extracted that part of the code so that it can be used in the structured binding case.
1 parent 6dd14c8 commit b55dd8f

File tree

3 files changed

+175
-12
lines changed

3 files changed

+175
-12
lines changed

clang/lib/StaticAnalyzer/Core/ExprEngine.cpp

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3129,16 +3129,10 @@ void ExprEngine::VisitCommonDeclRefExpr(const Expr *Ex, const NamedDecl *D,
31293129
ProgramStateRef state = Pred->getState();
31303130
const LocationContext *LCtx = Pred->getLocationContext();
31313131

3132-
if (const auto *VD = dyn_cast<VarDecl>(D)) {
3133-
// C permits "extern void v", and if you cast the address to a valid type,
3134-
// you can even do things with it. We simply pretend
3135-
assert(Ex->isGLValue() || VD->getType()->isVoidType());
3136-
const LocationContext *LocCtxt = Pred->getLocationContext();
3137-
const Decl *D = LocCtxt->getDecl();
3138-
const auto *MD = dyn_cast_or_null<CXXMethodDecl>(D);
3132+
auto resolveAsLambdaCapturedVar =
3133+
[&](const ValueDecl *VD) -> std::optional<std::pair<SVal, QualType>> {
3134+
const auto *MD = dyn_cast<CXXMethodDecl>(LCtx->getDecl());
31393135
const auto *DeclRefEx = dyn_cast<DeclRefExpr>(Ex);
3140-
std::optional<std::pair<SVal, QualType>> VInfo;
3141-
31423136
if (AMgr.options.ShouldInlineLambdas && DeclRefEx &&
31433137
DeclRefEx->refersToEnclosingVariableOrCapture() && MD &&
31443138
MD->getParent()->isLambda()) {
@@ -3151,13 +3145,23 @@ void ExprEngine::VisitCommonDeclRefExpr(const Expr *Ex, const NamedDecl *D,
31513145
// Sema follows a sequence of complex rules to determine whether the
31523146
// variable should be captured.
31533147
if (const FieldDecl *FD = LambdaCaptureFields[VD]) {
3154-
Loc CXXThis =
3155-
svalBuilder.getCXXThis(MD, LocCtxt->getStackFrame());
3148+
Loc CXXThis = svalBuilder.getCXXThis(MD, LCtx->getStackFrame());
31563149
SVal CXXThisVal = state->getSVal(CXXThis);
3157-
VInfo = std::make_pair(state->getLValue(FD, CXXThisVal), FD->getType());
3150+
return std::make_pair(state->getLValue(FD, CXXThisVal), FD->getType());
31583151
}
31593152
}
31603153

3154+
return std::nullopt;
3155+
};
3156+
3157+
if (const auto *VD = dyn_cast<VarDecl>(D)) {
3158+
// C permits "extern void v", and if you cast the address to a valid type,
3159+
// you can even do things with it. We simply pretend
3160+
assert(Ex->isGLValue() || VD->getType()->isVoidType());
3161+
const LocationContext *LocCtxt = Pred->getLocationContext();
3162+
std::optional<std::pair<SVal, QualType>> VInfo =
3163+
resolveAsLambdaCapturedVar(VD);
3164+
31613165
if (!VInfo)
31623166
VInfo = std::make_pair(state->getLValue(VD, LocCtxt), VD->getType());
31633167

@@ -3195,6 +3199,23 @@ void ExprEngine::VisitCommonDeclRefExpr(const Expr *Ex, const NamedDecl *D,
31953199
return;
31963200
}
31973201
if (const auto *BD = dyn_cast<BindingDecl>(D)) {
3202+
// Handle structured bindings captured by lambda.
3203+
if (std::optional<std::pair<SVal, QualType>> VInfo =
3204+
resolveAsLambdaCapturedVar(BD)) {
3205+
auto [V, T] = VInfo.value();
3206+
3207+
if (T->isReferenceType()) {
3208+
if (const MemRegion *R = V.getAsRegion())
3209+
V = state->getSVal(R);
3210+
else
3211+
V = UnknownVal();
3212+
}
3213+
3214+
Bldr.generateNode(Ex, Pred, state->BindExpr(Ex, LCtx, V), nullptr,
3215+
ProgramPoint::PostLValueKind);
3216+
return;
3217+
}
3218+
31983219
const auto *DD = cast<DecompositionDecl>(BD->getDecomposedDecl());
31993220

32003221
SVal Base = state->getLValue(DD, LCtx);

clang/test/Analysis/issue-91835.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// RUN: %clang_analyze_cc1 -std=c++20 %s -analyzer-checker=core.NullDereference -analyzer-output=text -verify
2+
3+
// expected-no-diagnostics
4+
5+
struct S { int x; };
6+
7+
void f(int x) { (void)x; }
8+
9+
int main()
10+
{
11+
S s{42};
12+
auto& [x] = s;
13+
auto g = [x](){ f(x); }; // no warning
14+
g();
15+
}
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
// RUN: %clang_analyze_cc1 -std=c++20 -analyzer-checker=core,debug.ExprInspection -analyzer-config inline-lambdas=true -verify %s
2+
3+
#include "Inputs/system-header-simulator-cxx.h"
4+
void clang_analyzer_warnIfReached();
5+
void clang_analyzer_eval(int);
6+
7+
void capture_structured_binding_to_array_byref() {
8+
int arr[] {5};
9+
auto& [i] = arr;
10+
[i]() mutable {
11+
if (i != 5)
12+
clang_analyzer_warnIfReached();
13+
++i;
14+
}();
15+
[&i] {
16+
if (i != 5)
17+
clang_analyzer_warnIfReached();
18+
}();
19+
[&i] {
20+
if (i != 5)
21+
clang_analyzer_warnIfReached();
22+
i++;
23+
}();
24+
clang_analyzer_eval(i == 6); // expected-warning{{TRUE}}
25+
}
26+
27+
void capture_structured_binding_to_array_byvalue() {
28+
int arr[] {5};
29+
auto [i] = arr;
30+
[i]() mutable {
31+
if (i != 5)
32+
clang_analyzer_warnIfReached();
33+
++i;
34+
}();
35+
[&i] {
36+
if (i != 5)
37+
clang_analyzer_warnIfReached();
38+
}();
39+
[&i] {
40+
if (i != 5)
41+
clang_analyzer_warnIfReached();
42+
i++;
43+
}();
44+
clang_analyzer_eval(i == 6); // expected-warning{{TRUE}}
45+
}
46+
47+
void capture_structured_binding_to_tuple_like_byref() {
48+
std::pair<int, int> p {5, 6};
49+
auto& [i, _] = p;
50+
[i]() mutable {
51+
if (i != 5)
52+
clang_analyzer_warnIfReached();
53+
++i;
54+
}();
55+
[&i] {
56+
if (i != 5)
57+
clang_analyzer_warnIfReached();
58+
}();
59+
[&i] {
60+
if (i != 5)
61+
clang_analyzer_warnIfReached();
62+
i++;
63+
}();
64+
clang_analyzer_eval(i == 6); // expected-warning{{TRUE}}
65+
}
66+
67+
void capture_structured_binding_to_tuple_like_byvalue() {
68+
std::pair<int, int> p {5, 6};
69+
auto [i, _] = p;
70+
[i]() mutable {
71+
if (i != 5)
72+
clang_analyzer_warnIfReached();
73+
++i;
74+
}();
75+
[&i] {
76+
if (i != 5)
77+
clang_analyzer_warnIfReached();
78+
}();
79+
[&i] {
80+
if (i != 5)
81+
clang_analyzer_warnIfReached();
82+
i++;
83+
}();
84+
clang_analyzer_eval(i == 6); // expected-warning{{TRUE}}
85+
}
86+
87+
struct S { int x; };
88+
89+
void capture_structured_binding_to_data_member_byref() {
90+
S s{5};
91+
auto& [i] = s;
92+
[i]() mutable {
93+
if (i != 5)
94+
clang_analyzer_warnIfReached();
95+
++i;
96+
}();
97+
[&i] {
98+
if (i != 5)
99+
clang_analyzer_warnIfReached();
100+
}();
101+
[&i] {
102+
if (i != 5)
103+
clang_analyzer_warnIfReached();
104+
i++;
105+
}();
106+
clang_analyzer_eval(i == 6); // expected-warning{{TRUE}}
107+
}
108+
109+
void capture_structured_binding_to_data_member_byvalue() {
110+
S s{5};
111+
auto [i] = s;
112+
[i]() mutable {
113+
if (i != 5)
114+
clang_analyzer_warnIfReached();
115+
++i;
116+
}();
117+
[&i] {
118+
if (i != 5)
119+
clang_analyzer_warnIfReached();
120+
}();
121+
[&i] {
122+
if (i != 5)
123+
clang_analyzer_warnIfReached();
124+
i++;
125+
}();
126+
clang_analyzer_eval(i == 6); // expected-warning{{TRUE}}
127+
}

0 commit comments

Comments
 (0)