Skip to content

Commit 7d0e62b

Browse files
committed
[analyzer][NFCI] Allow clients of NoStateChangeFuncVisitor to check entire function calls, rather than each ExplodedNode in it
D105553 added NoStateChangeFuncVisitor, an abstract class to aid in creating notes such as "Returning without writing to 'x'", or "Returning without changing the ownership status of allocated memory". Its clients need to define, among other things, what a change of state is. For code like this: f() { g(); } foo() { f(); h(); } We'd have a path in the ExplodedGraph that looks like this: -- <g> --> / \ --- <f> --------> --- <h> ---> / \ / \ -------- <foo> ------ <foo> --> When we're interested in whether f neglected to change some property, NoStateChangeFuncVisitor asks these questions: ÷×~ -- <g> --> ß / \$ @&#* --- <f> --------> --- <h> ---> / \ / \ -------- <foo> ------ <foo> --> Has anything changed in between # and *? Has anything changed in between & and *? Has anything changed in between @ and *? ... Has anything changed in between $ and *? Has anything changed in between × and ~? Has anything changed in between ÷ and ~? ... Has anything changed in between ß and *? ... This is a rather thorough line of questioning, which is why in D105819, I was only interested in whether state *right before* and *right after* a function call changed, and early returned to the CallEnter location: if (!CurrN->getLocationAs<CallEnter>()) return; Except that I made a typo, and forgot to negate the condition. So, in this patch, I'm fixing that, and under the same hood allow all clients to decide to do this whole-function check instead of the thorough one. Differential Revision: https://reviews.llvm.org/D108695
1 parent b5fd6b4 commit 7d0e62b

File tree

9 files changed

+477
-77
lines changed

9 files changed

+477
-77
lines changed

clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h

+38-6
Original file line numberDiff line numberDiff line change
@@ -633,6 +633,9 @@ class CXXConstructorCall;
633633
/// Descendants can define what a "state change is", like a change of value
634634
/// to a memory region, liveness, etc. For function calls where the state did
635635
/// not change as defined, a custom note may be constructed.
636+
///
637+
/// For a minimal example, check out
638+
/// clang/unittests/StaticAnalyzer/NoStateChangeFuncVisitorTest.cpp.
636639
class NoStateChangeFuncVisitor : public BugReporterVisitor {
637640
private:
638641
/// Frames modifying the state as defined in \c wasModifiedBeforeCallExit.
@@ -643,6 +646,8 @@ class NoStateChangeFuncVisitor : public BugReporterVisitor {
643646
/// many times (going up the path for each node and checking whether the
644647
/// region was written into) we instead lazily compute the stack frames
645648
/// along the path.
649+
// TODO: Can't we just use a map instead? This is likely not as cheap as it
650+
// makes the code difficult to read.
646651
llvm::SmallPtrSet<const StackFrameContext *, 32> FramesModifying;
647652
llvm::SmallPtrSet<const StackFrameContext *, 32> FramesModifyingCalculated;
648653

@@ -651,18 +656,46 @@ class NoStateChangeFuncVisitor : public BugReporterVisitor {
651656
/// The calculation is cached in FramesModifying.
652657
bool isModifiedInFrame(const ExplodedNode *CallExitBeginN);
653658

659+
void markFrameAsModifying(const StackFrameContext *SCtx);
660+
654661
/// Write to \c FramesModifying all stack frames along the path in the current
655662
/// stack frame which modifies the state.
656663
void findModifyingFrames(const ExplodedNode *const CallExitBeginN);
657664

658665
protected:
659666
bugreporter::TrackingKind TKind;
660667

661-
/// \return Whether the state was modified from the current node, \CurrN, to
662-
/// the end of the stack fram, at \p CallExitBeginN.
663-
virtual bool
664-
wasModifiedBeforeCallExit(const ExplodedNode *CurrN,
665-
const ExplodedNode *CallExitBeginN) = 0;
668+
/// \return Whether the state was modified from the current node, \p CurrN, to
669+
/// the end of the stack frame, at \p CallExitBeginN. \p CurrN and
670+
/// \p CallExitBeginN are always in the same stack frame.
671+
/// Clients should override this callback when a state change is important
672+
/// not only on the entire function call, but inside of it as well.
673+
/// Example: we may want to leave a note about the lack of locking/unlocking
674+
/// on a particular mutex, but not if inside the function its state was
675+
/// changed, but also restored. wasModifiedInFunction() wouldn't know of this
676+
/// change.
677+
virtual bool wasModifiedBeforeCallExit(const ExplodedNode *CurrN,
678+
const ExplodedNode *CallExitBeginN) {
679+
return false;
680+
}
681+
682+
/// \return Whether the state was modified in the inlined function call in
683+
/// between \p CallEnterN and \p CallExitEndN. Mind that the stack frame
684+
/// retrieved from a CallEnterN and CallExitEndN is the *caller's* stack
685+
/// frame! The inlined function's stack should be retrieved from either the
686+
/// immediate successor to \p CallEnterN or immediate predecessor to
687+
/// \p CallExitEndN.
688+
/// Clients should override this function if a state changes local to the
689+
/// inlined function are not interesting, only the change occuring as a
690+
/// result of it.
691+
/// Example: we want to leave a not about a leaked resource object not being
692+
/// deallocated / its ownership changed inside a function, and we don't care
693+
/// if it was assigned to a local variable (its change in ownership is
694+
/// inconsequential).
695+
virtual bool wasModifiedInFunction(const ExplodedNode *CallEnterN,
696+
const ExplodedNode *CallExitEndN) {
697+
return false;
698+
}
666699

667700
/// Consume the information on the non-modifying stack frame in order to
668701
/// either emit a note or not. May suppress the report entirely.
@@ -702,7 +735,6 @@ class NoStateChangeFuncVisitor : public BugReporterVisitor {
702735
};
703736

704737
} // namespace ento
705-
706738
} // namespace clang
707739

708740
#endif // LLVM_CLANG_STATICANALYZER_CORE_BUGREPORTER_BUGREPORTERVISITORS_H

clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp

+27-20
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
#include "clang/AST/Expr.h"
5353
#include "clang/AST/ExprCXX.h"
5454
#include "clang/AST/ParentMap.h"
55+
#include "clang/Analysis/ProgramPoint.h"
5556
#include "clang/Basic/LLVM.h"
5657
#include "clang/Basic/SourceManager.h"
5758
#include "clang/Basic/TargetInfo.h"
@@ -76,8 +77,10 @@
7677
#include "llvm/ADT/SetOperations.h"
7778
#include "llvm/ADT/SmallString.h"
7879
#include "llvm/ADT/StringExtras.h"
80+
#include "llvm/Support/Casting.h"
7981
#include "llvm/Support/Compiler.h"
8082
#include "llvm/Support/ErrorHandling.h"
83+
#include "llvm/Support/raw_ostream.h"
8184
#include <climits>
8285
#include <functional>
8386
#include <utility>
@@ -755,6 +758,17 @@ class NoOwnershipChangeVisitor final : public NoStateChangeFuncVisitor {
755758
Owners.insert(Region);
756759
return true;
757760
}
761+
762+
LLVM_DUMP_METHOD void dump() const { dumpToStream(llvm::errs()); }
763+
LLVM_DUMP_METHOD void dumpToStream(llvm::raw_ostream &out) const {
764+
out << "Owners: {\n";
765+
for (const MemRegion *Owner : Owners) {
766+
out << " ";
767+
Owner->dumpToStream(out);
768+
out << ",\n";
769+
}
770+
out << "}\n";
771+
}
758772
};
759773

760774
protected:
@@ -768,31 +782,24 @@ class NoOwnershipChangeVisitor final : public NoStateChangeFuncVisitor {
768782
return Ret;
769783
}
770784

771-
static const ExplodedNode *getCallExitEnd(const ExplodedNode *N) {
772-
while (N && !N->getLocationAs<CallExitEnd>())
773-
N = N->getFirstSucc();
774-
return N;
785+
LLVM_DUMP_METHOD static std::string
786+
getFunctionName(const ExplodedNode *CallEnterN) {
787+
if (const CallExpr *CE = llvm::dyn_cast_or_null<CallExpr>(
788+
CallEnterN->getLocationAs<CallEnter>()->getCallExpr()))
789+
if (const FunctionDecl *FD = CE->getDirectCallee())
790+
return FD->getQualifiedNameAsString();
791+
return "";
775792
}
776793

777794
virtual bool
778-
wasModifiedBeforeCallExit(const ExplodedNode *CurrN,
779-
const ExplodedNode *CallExitN) override {
780-
if (CurrN->getLocationAs<CallEnter>())
781-
return true;
782-
783-
// Its the state right *after* the call that is interesting. Any pointers
784-
// inside the call that pointed to the allocated memory are of little
785-
// consequence if their lifetime ends within the function.
786-
CallExitN = getCallExitEnd(CallExitN);
787-
if (!CallExitN)
788-
return true;
789-
790-
if (CurrN->getState()->get<RegionState>(Sym) !=
791-
CallExitN->getState()->get<RegionState>(Sym))
795+
wasModifiedInFunction(const ExplodedNode *CallEnterN,
796+
const ExplodedNode *CallExitEndN) override {
797+
if (CallEnterN->getState()->get<RegionState>(Sym) !=
798+
CallExitEndN->getState()->get<RegionState>(Sym))
792799
return true;
793800

794-
OwnerSet CurrOwners = getOwnersAtNode(CurrN);
795-
OwnerSet ExitOwners = getOwnersAtNode(CallExitN);
801+
OwnerSet CurrOwners = getOwnersAtNode(CallEnterN);
802+
OwnerSet ExitOwners = getOwnersAtNode(CallExitEndN);
796803

797804
// Owners in the current set may be purged from the analyzer later on.
798805
// If a variable is dead (is not referenced directly or indirectly after

clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp

+63-25
Original file line numberDiff line numberDiff line change
@@ -355,43 +355,81 @@ bool NoStateChangeFuncVisitor::isModifiedInFrame(const ExplodedNode *N) {
355355
return FramesModifying.count(SCtx);
356356
}
357357

358+
void NoStateChangeFuncVisitor::markFrameAsModifying(
359+
const StackFrameContext *SCtx) {
360+
while (SCtx) {
361+
auto p = FramesModifying.insert(SCtx);
362+
if (!p.second)
363+
break; // Frame and all its parents already inserted.
364+
SCtx = SCtx->getParent()->getStackFrame();
365+
}
366+
}
367+
368+
static const ExplodedNode *getMatchingCallExitEnd(const ExplodedNode *N) {
369+
assert(N->getLocationAs<CallEnter>());
370+
// The stackframe of the callee is only found in the nodes succeeding
371+
// the CallEnter node. CallEnter's stack frame refers to the caller.
372+
const StackFrameContext *OrigSCtx = N->getFirstSucc()->getStackFrame();
373+
374+
// Similarly, the nodes preceding CallExitEnd refer to the callee's stack
375+
// frame.
376+
auto IsMatchingCallExitEnd = [OrigSCtx](const ExplodedNode *N) {
377+
return N->getLocationAs<CallExitEnd>() &&
378+
OrigSCtx == N->getFirstPred()->getStackFrame();
379+
};
380+
while (N && !IsMatchingCallExitEnd(N)) {
381+
assert(N->succ_size() <= 1 &&
382+
"This function is to be used on the trimmed ExplodedGraph!");
383+
N = N->getFirstSucc();
384+
}
385+
return N;
386+
}
387+
358388
void NoStateChangeFuncVisitor::findModifyingFrames(
359389
const ExplodedNode *const CallExitBeginN) {
360390

361391
assert(CallExitBeginN->getLocationAs<CallExitBegin>());
362-
const ExplodedNode *LastReturnN = CallExitBeginN;
392+
363393
const StackFrameContext *const OriginalSCtx =
364394
CallExitBeginN->getLocationContext()->getStackFrame();
365395

366-
const ExplodedNode *CurrN = CallExitBeginN;
367-
368-
do {
369-
ProgramStateRef State = CurrN->getState();
370-
auto CallExitLoc = CurrN->getLocationAs<CallExitBegin>();
371-
if (CallExitLoc) {
372-
LastReturnN = CurrN;
396+
const ExplodedNode *CurrCallExitBeginN = CallExitBeginN;
397+
const StackFrameContext *CurrentSCtx = OriginalSCtx;
398+
399+
for (const ExplodedNode *CurrN = CallExitBeginN; CurrN;
400+
CurrN = CurrN->getFirstPred()) {
401+
// Found a new inlined call.
402+
if (CurrN->getLocationAs<CallExitBegin>()) {
403+
CurrCallExitBeginN = CurrN;
404+
CurrentSCtx = CurrN->getStackFrame();
405+
FramesModifyingCalculated.insert(CurrentSCtx);
406+
// We won't see a change in between two identical exploded nodes: skip.
407+
continue;
373408
}
374409

375-
FramesModifyingCalculated.insert(
376-
CurrN->getLocationContext()->getStackFrame());
377-
378-
if (wasModifiedBeforeCallExit(CurrN, LastReturnN)) {
379-
const StackFrameContext *SCtx = CurrN->getStackFrame();
380-
while (!SCtx->inTopFrame()) {
381-
auto p = FramesModifying.insert(SCtx);
382-
if (!p.second)
383-
break; // Frame and all its parents already inserted.
384-
SCtx = SCtx->getParent()->getStackFrame();
410+
if (auto CE = CurrN->getLocationAs<CallEnter>()) {
411+
if (const ExplodedNode *CallExitEndN = getMatchingCallExitEnd(CurrN))
412+
if (wasModifiedInFunction(CurrN, CallExitEndN))
413+
markFrameAsModifying(CurrentSCtx);
414+
415+
// We exited this inlined call, lets actualize the stack frame.
416+
CurrentSCtx = CurrN->getStackFrame();
417+
418+
// Stop calculating at the current function, but always regard it as
419+
// modifying, so we can avoid notes like this:
420+
// void f(Foo &F) {
421+
// F.field = 0; // note: 0 assigned to 'F.field'
422+
// // note: returning without writing to 'F.field'
423+
// }
424+
if (CE->getCalleeContext() == OriginalSCtx) {
425+
markFrameAsModifying(CurrentSCtx);
426+
break;
385427
}
386428
}
387429

388-
// Stop calculation at the call to the current function.
389-
if (auto CE = CurrN->getLocationAs<CallEnter>())
390-
if (CE->getCalleeContext() == OriginalSCtx)
391-
break;
392-
393-
CurrN = CurrN->getFirstPred();
394-
} while (CurrN);
430+
if (wasModifiedBeforeCallExit(CurrN, CurrCallExitBeginN))
431+
markFrameAsModifying(CurrentSCtx);
432+
}
395433
}
396434

397435
PathDiagnosticPieceRef NoStateChangeFuncVisitor::VisitNode(

clang/unittests/StaticAnalyzer/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ add_clang_unittest(StaticAnalysisTests
99
CallDescriptionTest.cpp
1010
CallEventTest.cpp
1111
FalsePositiveRefutationBRVisitorTest.cpp
12+
NoStateChangeFuncVisitorTest.cpp
1213
ParamRegionTest.cpp
1314
RangeSetTest.cpp
1415
RegisterCustomCheckersTest.cpp

clang/unittests/StaticAnalyzer/CallEventTest.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ TEST(CXXDeallocatorCall, SimpleDestructor) {
8181
}
8282
)",
8383
Diags));
84-
EXPECT_EQ(Diags, "test.CXXDeallocator:NumArgs: 1\n");
84+
EXPECT_EQ(Diags, "test.CXXDeallocator: NumArgs: 1\n");
8585
}
8686

8787
} // namespace

clang/unittests/StaticAnalyzer/CheckerRegistration.h

+18-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
//
77
//===----------------------------------------------------------------------===//
88

9+
#include "clang/Analysis/PathDiagnostic.h"
910
#include "clang/Frontend/CompilerInstance.h"
1011
#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
1112
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
@@ -26,8 +27,23 @@ class DiagConsumer : public PathDiagnosticConsumer {
2627
DiagConsumer(llvm::raw_ostream &Output) : Output(Output) {}
2728
void FlushDiagnosticsImpl(std::vector<const PathDiagnostic *> &Diags,
2829
FilesMade *filesMade) override {
29-
for (const auto *PD : Diags)
30-
Output << PD->getCheckerName() << ":" << PD->getShortDescription() << '\n';
30+
for (const auto *PD : Diags) {
31+
Output << PD->getCheckerName() << ": ";
32+
33+
for (PathDiagnosticPieceRef Piece :
34+
PD->path.flatten(/*ShouldFlattenMacros*/ true)) {
35+
if (Piece->getKind() != PathDiagnosticPiece::Event)
36+
continue;
37+
if (Piece->getString().empty())
38+
continue;
39+
// The last event is usually the same as the warning message, skip.
40+
if (Piece->getString() == PD->getShortDescription())
41+
continue;
42+
43+
Output << Piece->getString() << " | ";
44+
}
45+
Output << PD->getShortDescription() << '\n';
46+
}
3147
}
3248

3349
StringRef getName() const override { return "Test"; }

clang/unittests/StaticAnalyzer/FalsePositiveRefutationBRVisitorTest.cpp

+9-9
Original file line numberDiff line numberDiff line change
@@ -128,16 +128,16 @@ TEST_F(FalsePositiveRefutationBRVisitorTestBase, UnSatInTheMiddleNoReport) {
128128
EXPECT_TRUE(runCheckerOnCodeWithArgs<addFalsePositiveGenerator>(
129129
Code, LazyAssumeAndCrossCheckArgs, Diags));
130130
EXPECT_EQ(Diags,
131-
"test.FalsePositiveGenerator:REACHED_WITH_NO_CONTRADICTION\n");
131+
"test.FalsePositiveGenerator: REACHED_WITH_NO_CONTRADICTION\n");
132132
// Single warning. The second report was invalidated by the visitor.
133133

134134
// Without enabling the crosscheck-with-z3 both reports are displayed.
135135
std::string Diags2;
136136
EXPECT_TRUE(runCheckerOnCodeWithArgs<addFalsePositiveGenerator>(
137137
Code, LazyAssumeArgs, Diags2));
138138
EXPECT_EQ(Diags2,
139-
"test.FalsePositiveGenerator:REACHED_WITH_NO_CONTRADICTION\n"
140-
"test.FalsePositiveGenerator:REACHED_WITH_CONTRADICTION\n");
139+
"test.FalsePositiveGenerator: REACHED_WITH_NO_CONTRADICTION\n"
140+
"test.FalsePositiveGenerator: REACHED_WITH_CONTRADICTION\n");
141141
}
142142

143143
TEST_F(FalsePositiveRefutationBRVisitorTestBase,
@@ -159,16 +159,16 @@ TEST_F(FalsePositiveRefutationBRVisitorTestBase,
159159
EXPECT_TRUE(runCheckerOnCodeWithArgs<addFalsePositiveGenerator>(
160160
Code, LazyAssumeAndCrossCheckArgs, Diags));
161161
EXPECT_EQ(Diags,
162-
"test.FalsePositiveGenerator:REACHED_WITH_NO_CONTRADICTION\n");
162+
"test.FalsePositiveGenerator: REACHED_WITH_NO_CONTRADICTION\n");
163163
// Single warning. The second report was invalidated by the visitor.
164164

165165
// Without enabling the crosscheck-with-z3 both reports are displayed.
166166
std::string Diags2;
167167
EXPECT_TRUE(runCheckerOnCodeWithArgs<addFalsePositiveGenerator>(
168168
Code, LazyAssumeArgs, Diags2));
169169
EXPECT_EQ(Diags2,
170-
"test.FalsePositiveGenerator:REACHED_WITH_NO_CONTRADICTION\n"
171-
"test.FalsePositiveGenerator:CAN_BE_TRUE\n");
170+
"test.FalsePositiveGenerator: REACHED_WITH_NO_CONTRADICTION\n"
171+
"test.FalsePositiveGenerator: CAN_BE_TRUE\n");
172172
}
173173

174174
TEST_F(FalsePositiveRefutationBRVisitorTestBase,
@@ -206,16 +206,16 @@ TEST_F(FalsePositiveRefutationBRVisitorTestBase,
206206
EXPECT_TRUE(runCheckerOnCodeWithArgs<addFalsePositiveGenerator>(
207207
Code, LazyAssumeAndCrossCheckArgs, Diags));
208208
EXPECT_EQ(Diags,
209-
"test.FalsePositiveGenerator:REACHED_WITH_NO_CONTRADICTION\n");
209+
"test.FalsePositiveGenerator: REACHED_WITH_NO_CONTRADICTION\n");
210210
// Single warning. The second report was invalidated by the visitor.
211211

212212
// Without enabling the crosscheck-with-z3 both reports are displayed.
213213
std::string Diags2;
214214
EXPECT_TRUE(runCheckerOnCodeWithArgs<addFalsePositiveGenerator>(
215215
Code, LazyAssumeArgs, Diags2));
216216
EXPECT_EQ(Diags2,
217-
"test.FalsePositiveGenerator:REACHED_WITH_NO_CONTRADICTION\n"
218-
"test.FalsePositiveGenerator:CAN_BE_TRUE\n");
217+
"test.FalsePositiveGenerator: REACHED_WITH_NO_CONTRADICTION\n"
218+
"test.FalsePositiveGenerator: CAN_BE_TRUE\n");
219219
}
220220

221221
} // namespace

0 commit comments

Comments
 (0)