Skip to content

Commit deede2b

Browse files
authored
[analyzer] Eliminate unique release point assertion (#150240)
MallocChecker.cpp has a complex heuristic that supresses reports where the memory release happens during the release of a reference-counted object (to suppress a significant amount of false positives). Previously this logic asserted that there is at most one release point corresponding to a symbol, but it turns out that there is a rare corner case where the symbol can be released, forgotten and then released again. This commit removes that assertion to avoid the crash. (As this issue just affects a bug suppression heuristic, I didn't want to dig deeper and modify the way the state of the symbol is changed.) Fixes #149754
1 parent 0c4d56a commit deede2b

File tree

2 files changed

+24
-8
lines changed

2 files changed

+24
-8
lines changed

clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3730,13 +3730,15 @@ PathDiagnosticPieceRef MallocBugVisitor::VisitNode(const ExplodedNode *N,
37303730
return nullptr;
37313731
}
37323732

3733-
// Save the first destructor/function as release point.
3734-
assert(!ReleaseFunctionLC && "There should be only one release point");
3733+
// Record the stack frame that is _responsible_ for this memory release
3734+
// event. This will be used by the false positive suppression heuristics
3735+
// that recognize the release points of reference-counted objects.
3736+
//
3737+
// Usually (e.g. in C) we say that the _responsible_ stack frame is the
3738+
// current innermost stack frame:
37353739
ReleaseFunctionLC = CurrentLC->getStackFrame();
3736-
3737-
// See if we're releasing memory while inlining a destructor that
3738-
// decrement reference counters (or one of its callees).
3739-
// This turns on various common false positive suppressions.
3740+
// ...but if the stack contains a destructor call, then we say that the
3741+
// outermost destructor stack frame is the _responsible_ one:
37403742
for (const LocationContext *LC = CurrentLC; LC; LC = LC->getParent()) {
37413743
if (const auto *DD = dyn_cast<CXXDestructorDecl>(LC->getDecl())) {
37423744
if (isReferenceCountingPointerDestructor(DD)) {

clang/test/Analysis/malloc.c

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1954,9 +1954,23 @@ int conjure(void);
19541954
void testExtent(void) {
19551955
int x = conjure();
19561956
clang_analyzer_dump(x);
1957-
// expected-warning-re@-1 {{{{^conj_\$[[:digit:]]+{int, LC1, S[[:digit:]]+, #1}}}}}}
1957+
// expected-warning-re@-1 {{{{^conj_\$[[:digit:]]+{int, LC[[:digit:]]+, S[[:digit:]]+, #1}}}}}}
19581958
int *p = (int *)malloc(x);
19591959
clang_analyzer_dumpExtent(p);
1960-
// expected-warning-re@-1 {{{{^conj_\$[[:digit:]]+{int, LC1, S[[:digit:]]+, #1}}}}}}
1960+
// expected-warning-re@-1 {{{{^conj_\$[[:digit:]]+{int, LC[[:digit:]]+, S[[:digit:]]+, #1}}}}}}
19611961
free(p);
19621962
}
1963+
1964+
void gh149754(void *p) {
1965+
// This testcase demonstrates an unusual situation where a certain symbol
1966+
// (the value of `p`) is released (more precisely, transitions from
1967+
// untracked state to Released state) twice within the same bug path because
1968+
// the `EvalAssume` callback resets it to untracked state after the first
1969+
// time when it is released. This caused the failure of an assertion, which
1970+
// was since then removed for the codebase.
1971+
if (!realloc(p, 8)) {
1972+
realloc(p, 8);
1973+
free(p); // expected-warning {{Attempt to free released memory}}
1974+
}
1975+
// expected-warning@+1 {{Potential memory leak}}
1976+
}

0 commit comments

Comments
 (0)