Skip to content

Commit 733e71b

Browse files
committed
[analyzer] Fix symbolic element index lifetime.
SymbolReaper was destroying the symbol too early when it was referenced only from an index SVal of a live ElementRegion. In order to test certain aspects of this patch, extend the debug.ExprInspection checker to allow testing SymbolReaper in a direct manner. Differential Revision: http://reviews.llvm.org/D12726 llvm-svn: 255236
1 parent a5fbebc commit 733e71b

File tree

8 files changed

+189
-7
lines changed

8 files changed

+189
-7
lines changed

clang/docs/analyzer/DebugChecks.rst

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,29 @@ ExprInspection checks
138138
clang_analyzer_warnIfReached(); // no-warning
139139
}
140140

141+
- void clang_analyzer_warnOnDeadSymbol(int);
142+
143+
Subscribe for a delayed warning when the symbol that represents the value of
144+
the argument is garbage-collected by the analyzer.
145+
146+
When calling 'clang_analyzer_warnOnDeadSymbol(x)', if value of 'x' is a
147+
symbol, then this symbol is marked by the ExprInspection checker. Then,
148+
during each garbage collection run, the checker sees if the marked symbol is
149+
being collected and issues the 'SYMBOL DEAD' warning if it does.
150+
This way you know where exactly, up to the line of code, the symbol dies.
151+
152+
It is unlikely that you call this function after the symbol is already dead,
153+
because the very reference to it as the function argument prevents it from
154+
dying. However, if the argument is not a symbol but a concrete value,
155+
no warning would be issued.
156+
157+
Example usage::
158+
159+
do {
160+
int x = generate_some_integer();
161+
clang_analyzer_warnOnDeadSymbol(x);
162+
} while(0); // expected-warning{{SYMBOL DEAD}}
163+
141164

142165
Statistics
143166
==========

clang/include/clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -639,6 +639,7 @@ class SymbolReaper {
639639
}
640640

641641
void markLive(const MemRegion *region);
642+
void markElementIndicesLive(const MemRegion *region);
642643

643644
/// \brief Set to the value of the symbolic store after
644645
/// StoreManager::removeDeadBindings has been called.

clang/lib/StaticAnalyzer/Checkers/ExprInspectionChecker.cpp

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,22 +17,26 @@ using namespace clang;
1717
using namespace ento;
1818

1919
namespace {
20-
class ExprInspectionChecker : public Checker< eval::Call > {
20+
class ExprInspectionChecker : public Checker<eval::Call, check::DeadSymbols> {
2121
mutable std::unique_ptr<BugType> BT;
2222

2323
void analyzerEval(const CallExpr *CE, CheckerContext &C) const;
2424
void analyzerCheckInlined(const CallExpr *CE, CheckerContext &C) const;
2525
void analyzerWarnIfReached(const CallExpr *CE, CheckerContext &C) const;
2626
void analyzerCrash(const CallExpr *CE, CheckerContext &C) const;
27+
void analyzerWarnOnDeadSymbol(const CallExpr *CE, CheckerContext &C) const;
2728

2829
typedef void (ExprInspectionChecker::*FnCheck)(const CallExpr *,
2930
CheckerContext &C) const;
3031

3132
public:
3233
bool evalCall(const CallExpr *CE, CheckerContext &C) const;
34+
void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
3335
};
3436
}
3537

38+
REGISTER_SET_WITH_PROGRAMSTATE(MarkedSymbols, const void *)
39+
3640
bool ExprInspectionChecker::evalCall(const CallExpr *CE,
3741
CheckerContext &C) const {
3842
// These checks should have no effect on the surrounding environment
@@ -42,7 +46,10 @@ bool ExprInspectionChecker::evalCall(const CallExpr *CE,
4246
.Case("clang_analyzer_checkInlined",
4347
&ExprInspectionChecker::analyzerCheckInlined)
4448
.Case("clang_analyzer_crash", &ExprInspectionChecker::analyzerCrash)
45-
.Case("clang_analyzer_warnIfReached", &ExprInspectionChecker::analyzerWarnIfReached)
49+
.Case("clang_analyzer_warnIfReached",
50+
&ExprInspectionChecker::analyzerWarnIfReached)
51+
.Case("clang_analyzer_warnOnDeadSymbol",
52+
&ExprInspectionChecker::analyzerWarnOnDeadSymbol)
4653
.Default(nullptr);
4754

4855
if (!Handler)
@@ -137,6 +144,41 @@ void ExprInspectionChecker::analyzerCheckInlined(const CallExpr *CE,
137144
llvm::make_unique<BugReport>(*BT, getArgumentValueString(CE, C), N));
138145
}
139146

147+
void ExprInspectionChecker::analyzerWarnOnDeadSymbol(const CallExpr *CE,
148+
CheckerContext &C) const {
149+
if (CE->getNumArgs() == 0)
150+
return;
151+
SVal Val = C.getSVal(CE->getArg(0));
152+
SymbolRef Sym = Val.getAsSymbol();
153+
if (!Sym)
154+
return;
155+
156+
ProgramStateRef State = C.getState();
157+
State = State->add<MarkedSymbols>(Sym);
158+
C.addTransition(State);
159+
}
160+
161+
void ExprInspectionChecker::checkDeadSymbols(SymbolReaper &SymReaper,
162+
CheckerContext &C) const {
163+
ProgramStateRef State = C.getState();
164+
const MarkedSymbolsTy &Syms = State->get<MarkedSymbols>();
165+
for (auto I = Syms.begin(), E = Syms.end(); I != E; ++I) {
166+
SymbolRef Sym = static_cast<SymbolRef>(*I);
167+
if (!SymReaper.isDead(Sym))
168+
continue;
169+
170+
if (!BT)
171+
BT.reset(new BugType(this, "Checking analyzer assumptions", "debug"));
172+
173+
ExplodedNode *N = C.generateNonFatalErrorNode();
174+
if (!N)
175+
return;
176+
177+
C.emitReport(llvm::make_unique<BugReport>(*BT, "SYMBOL DEAD", N));
178+
C.addTransition(State->remove<MarkedSymbols>(Sym), N);
179+
}
180+
}
181+
140182
void ExprInspectionChecker::analyzerCrash(const CallExpr *CE,
141183
CheckerContext &C) const {
142184
LLVM_BUILTIN_TRAP;

clang/lib/StaticAnalyzer/Core/Environment.cpp

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -171,10 +171,6 @@ EnvironmentManager::removeDeadBindings(Environment Env,
171171
// Copy the binding to the new map.
172172
EBMapRef = EBMapRef.add(BlkExpr, X);
173173

174-
// If the block expr's value is a memory region, then mark that region.
175-
if (Optional<loc::MemRegionVal> R = X.getAs<loc::MemRegionVal>())
176-
SymReaper.markLive(R->getRegion());
177-
178174
// Mark all symbols in the block expr's value live.
179175
RSScaner.scan(X);
180176
continue;

clang/lib/StaticAnalyzer/Core/RegionStore.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2348,8 +2348,12 @@ void removeDeadBindingsWorker::VisitCluster(const MemRegion *baseR,
23482348
if (const SymbolicRegion *SymR = dyn_cast<SymbolicRegion>(baseR))
23492349
SymReaper.markLive(SymR->getSymbol());
23502350

2351-
for (ClusterBindings::iterator I = C->begin(), E = C->end(); I != E; ++I)
2351+
for (ClusterBindings::iterator I = C->begin(), E = C->end(); I != E; ++I) {
2352+
// Element index of a binding key is live.
2353+
SymReaper.markElementIndicesLive(I.getKey().getRegion());
2354+
23522355
VisitBinding(I.getData());
2356+
}
23532357
}
23542358

23552359
void removeDeadBindingsWorker::VisitBinding(SVal V) {
@@ -2370,6 +2374,7 @@ void removeDeadBindingsWorker::VisitBinding(SVal V) {
23702374
// If V is a region, then add it to the worklist.
23712375
if (const MemRegion *R = V.getAsRegion()) {
23722376
AddToWorkList(R);
2377+
SymReaper.markLive(R);
23732378

23742379
// All regions captured by a block are also live.
23752380
if (const BlockDataRegion *BR = dyn_cast<BlockDataRegion>(R)) {

clang/lib/StaticAnalyzer/Core/SymbolManager.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,18 @@ void SymbolReaper::markLive(SymbolRef sym) {
391391

392392
void SymbolReaper::markLive(const MemRegion *region) {
393393
RegionRoots.insert(region);
394+
markElementIndicesLive(region);
395+
}
396+
397+
void SymbolReaper::markElementIndicesLive(const MemRegion *region) {
398+
for (auto SR = dyn_cast<SubRegion>(region); SR;
399+
SR = dyn_cast<SubRegion>(SR->getSuperRegion())) {
400+
if (auto ER = dyn_cast<ElementRegion>(SR)) {
401+
SVal Idx = ER->getIndex();
402+
for (auto SI = Idx.symbol_begin(), SE = Idx.symbol_end(); SI != SE; ++SI)
403+
markLive(*SI);
404+
}
405+
}
394406
}
395407

396408
void SymbolReaper::markInUse(SymbolRef sym) {
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// RUN: %clang_cc1 -analyze -analyzer-checker=alpha.security.ReturnPtrRange -verify %s
2+
3+
int arr[10];
4+
int *ptr;
5+
6+
int conjure_index();
7+
8+
int *test_element_index_lifetime() {
9+
do {
10+
int x = conjure_index();
11+
ptr = arr + x;
12+
if (x != 20)
13+
return arr; // no-warning
14+
} while (0);
15+
return ptr; // expected-warning{{Returned pointer value points outside the original object (potential buffer overflow)}}
16+
}
17+
18+
int *test_element_index_lifetime_with_local_ptr() {
19+
int *local_ptr;
20+
do {
21+
int x = conjure_index();
22+
local_ptr = arr + x;
23+
if (x != 20)
24+
return arr; // no-warning
25+
} while (0);
26+
return local_ptr; // expected-warning{{Returned pointer value points outside the original object (potential buffer overflow)}}
27+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// RUN: %clang_cc1 -analyze -analyzer-checker=debug.ExprInspection -verify %s
2+
3+
void clang_analyzer_eval(int);
4+
void clang_analyzer_warnOnDeadSymbol(int);
5+
6+
int conjure_index();
7+
8+
void test_that_expr_inspection_works() {
9+
do {
10+
int x = conjure_index();
11+
clang_analyzer_warnOnDeadSymbol(x);
12+
} while(0); // expected-warning{{SYMBOL DEAD}}
13+
}
14+
15+
// These tests verify the reaping of symbols that are only referenced as
16+
// index values in element regions. Most of the time, depending on where
17+
// the element region, as Loc value, is stored, it is possible to
18+
// recover the index symbol in checker code, which is also demonstrated
19+
// in the return_ptr_range.c test file.
20+
21+
int arr[3];
22+
23+
int *test_element_index_lifetime_in_environment_values() {
24+
int *ptr;
25+
do {
26+
int x = conjure_index();
27+
clang_analyzer_warnOnDeadSymbol(x);
28+
ptr = arr + x;
29+
} while (0);
30+
return ptr;
31+
}
32+
33+
void test_element_index_lifetime_in_store_keys() {
34+
do {
35+
int x = conjure_index();
36+
clang_analyzer_warnOnDeadSymbol(x);
37+
arr[x] = 1;
38+
if (x) {}
39+
} while (0); // no-warning
40+
}
41+
42+
int *ptr;
43+
void test_element_index_lifetime_in_store_values() {
44+
do {
45+
int x = conjure_index();
46+
clang_analyzer_warnOnDeadSymbol(x);
47+
ptr = arr + x;
48+
} while (0); // no-warning
49+
}
50+
51+
struct S1 {
52+
int field;
53+
};
54+
struct S2 {
55+
struct S1 array[5];
56+
} s2;
57+
58+
void test_element_index_lifetime_with_complicated_hierarchy_of_regions() {
59+
do {
60+
int x = conjure_index();
61+
clang_analyzer_warnOnDeadSymbol(x);
62+
s2.array[x].field = 1;
63+
if (x) {}
64+
} while (0); // no-warning
65+
}
66+
67+
// Test below checks lifetime of SymbolRegionValue in certain conditions.
68+
69+
int **ptrptr;
70+
void test_region_lifetime_as_store_value(int *x) {
71+
clang_analyzer_warnOnDeadSymbol((int) x);
72+
*x = 1;
73+
ptrptr = &x;
74+
(void)0; // No-op; make sure the environment forgets things and the GC runs.
75+
clang_analyzer_eval(**ptrptr); // expected-warning{{TRUE}}
76+
} // no-warning

0 commit comments

Comments
 (0)