[clang][Analyzer] Add symbol uninterestingness to bug report.

`PathSensitiveBughReport` has a function to mark a symbol as interesting but
it was not possible to clear this flag. This can be useful in some cases,
so the functionality is added.

Reviewed By: NoQ

Differential Revision: https://reviews.llvm.org/D105637
This commit is contained in:
Balázs Kéri 2021-07-15 08:34:59 +02:00
parent fa2daaeff8
commit b0d38ad0bc
5 changed files with 274 additions and 1 deletions

View File

@ -432,6 +432,8 @@ public:
void markInteresting(SymbolRef sym, bugreporter::TrackingKind TKind =
bugreporter::TrackingKind::Thorough);
void markNotInteresting(SymbolRef sym);
/// Marks a region as interesting. Different kinds of interestingness will
/// be processed differently by visitors (e.g. if the tracking kind is
/// condition, will append "will be used as a condition" to the message).
@ -439,6 +441,8 @@ public:
const MemRegion *R,
bugreporter::TrackingKind TKind = bugreporter::TrackingKind::Thorough);
void markNotInteresting(const MemRegion *R);
/// Marks a symbolic value as interesting. Different kinds of interestingness
/// will be processed differently by visitors (e.g. if the tracking kind is
/// condition, will append "will be used as a condition" to the message).

View File

@ -2249,10 +2249,22 @@ void PathSensitiveBugReport::markInteresting(SymbolRef sym,
insertToInterestingnessMap(InterestingSymbols, sym, TKind);
// FIXME: No tests exist for this code and it is questionable:
// How to handle multiple metadata for the same region?
if (const auto *meta = dyn_cast<SymbolMetadata>(sym))
markInteresting(meta->getRegion(), TKind);
}
void PathSensitiveBugReport::markNotInteresting(SymbolRef sym) {
if (!sym)
return;
InterestingSymbols.erase(sym);
// The metadata part of markInteresting is not reversed here.
// Just making the same region not interesting is incorrect
// in specific cases.
}
void PathSensitiveBugReport::markInteresting(const MemRegion *R,
bugreporter::TrackingKind TKind) {
if (!R)
@ -2265,6 +2277,17 @@ void PathSensitiveBugReport::markInteresting(const MemRegion *R,
markInteresting(SR->getSymbol(), TKind);
}
void PathSensitiveBugReport::markNotInteresting(const MemRegion *R) {
if (!R)
return;
R = R->getBaseRegion();
InterestingRegions.erase(R);
if (const auto *SR = dyn_cast<SymbolicRegion>(R))
markNotInteresting(SR->getSymbol());
}
void PathSensitiveBugReport::markInteresting(SVal V,
bugreporter::TrackingKind TKind) {
markInteresting(V.getAsRegion(), TKind);

View File

@ -0,0 +1,162 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "Reusables.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
#include "clang/StaticAnalyzer/Frontend/AnalysisConsumer.h"
#include "clang/StaticAnalyzer/Frontend/CheckerRegistry.h"
#include "clang/Tooling/Tooling.h"
#include "gtest/gtest.h"
using namespace clang;
using namespace ento;
using namespace llvm;
namespace {
class InterestingnessTestChecker : public Checker<check::PreCall> {
BugType BT_TestBug;
using HandlerFn = std::function<void(const InterestingnessTestChecker *,
const CallEvent &, CheckerContext &)>;
CallDescriptionMap<HandlerFn> Handlers = {
{{"setInteresting", 1}, &InterestingnessTestChecker::handleInteresting},
{{"setNotInteresting", 1},
&InterestingnessTestChecker::handleNotInteresting},
{{"check", 1}, &InterestingnessTestChecker::handleCheck},
{{"bug", 1}, &InterestingnessTestChecker::handleBug},
};
void handleInteresting(const CallEvent &Call, CheckerContext &C) const;
void handleNotInteresting(const CallEvent &Call, CheckerContext &C) const;
void handleCheck(const CallEvent &Call, CheckerContext &C) const;
void handleBug(const CallEvent &Call, CheckerContext &C) const;
public:
InterestingnessTestChecker()
: BT_TestBug(this, "InterestingnessTestBug", "Test") {}
void checkPreCall(const CallEvent &Call, CheckerContext &C) const {
const HandlerFn *Handler = Handlers.lookup(Call);
if (!Handler)
return;
(*Handler)(this, Call, C);
}
};
} // namespace
void InterestingnessTestChecker::handleInteresting(const CallEvent &Call,
CheckerContext &C) const {
SymbolRef Sym = Call.getArgSVal(0).getAsSymbol();
assert(Sym);
C.addTransition(nullptr, C.getNoteTag([Sym](PathSensitiveBugReport &BR) {
BR.markInteresting(Sym);
return "";
}));
}
void InterestingnessTestChecker::handleNotInteresting(const CallEvent &Call,
CheckerContext &C) const {
SymbolRef Sym = Call.getArgSVal(0).getAsSymbol();
assert(Sym);
C.addTransition(nullptr, C.getNoteTag([Sym](PathSensitiveBugReport &BR) {
BR.markNotInteresting(Sym);
return "";
}));
}
void InterestingnessTestChecker::handleCheck(const CallEvent &Call,
CheckerContext &C) const {
SymbolRef Sym = Call.getArgSVal(0).getAsSymbol();
assert(Sym);
C.addTransition(nullptr, C.getNoteTag([Sym](PathSensitiveBugReport &BR) {
if (BR.isInteresting(Sym))
return "Interesting";
else
return "NotInteresting";
}));
}
void InterestingnessTestChecker::handleBug(const CallEvent &Call,
CheckerContext &C) const {
ExplodedNode *N = C.generateErrorNode();
C.emitReport(
std::make_unique<PathSensitiveBugReport>(BT_TestBug, "test bug", N));
}
namespace {
class TestAction : public ASTFrontendAction {
ExpectedDiagsTy ExpectedDiags;
public:
TestAction(ExpectedDiagsTy &&ExpectedDiags)
: ExpectedDiags(std::move(ExpectedDiags)) {}
std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &Compiler,
StringRef File) override {
std::unique_ptr<AnalysisASTConsumer> AnalysisConsumer =
CreateAnalysisConsumer(Compiler);
AnalysisConsumer->AddDiagnosticConsumer(new VerifyPathDiagnosticConsumer(
std::move(ExpectedDiags), Compiler.getSourceManager()));
AnalysisConsumer->AddCheckerRegistrationFn([](CheckerRegistry &Registry) {
Registry.addChecker<InterestingnessTestChecker>("test.Interestingness",
"Description", "");
});
Compiler.getAnalyzerOpts()->CheckersAndPackages = {
{"test.Interestingness", true}};
return std::move(AnalysisConsumer);
}
};
} // namespace
TEST(BugReportInterestingness, Symbols) {
EXPECT_TRUE(tooling::runToolOnCode(
std::make_unique<TestAction>(ExpectedDiagsTy{
{{15, 7},
"test bug",
"test bug",
"test.Interestingness",
"InterestingnessTestBug",
"Test",
{
{{8, 7}, "Interesting", {{{8, 7}, {8, 14}}}},
{{10, 7}, "NotInteresting", {{{10, 7}, {10, 14}}}},
{{12, 7}, "Interesting", {{{12, 7}, {12, 14}}}},
{{14, 7}, "NotInteresting", {{{14, 7}, {14, 14}}}},
{{15, 7}, "test bug", {{{15, 7}, {15, 12}}}},
}}}),
R"(
void setInteresting(int);
void setNotInteresting(int);
void check(int);
void bug(int);
void f(int A) {
check(A);
setInteresting(A);
check(A);
setNotInteresting(A);
check(A);
setInteresting(A);
check(A);
bug(A);
}
)",
"input.cpp"));
}

View File

@ -5,6 +5,7 @@ set(LLVM_LINK_COMPONENTS
add_clang_unittest(StaticAnalysisTests
AnalyzerOptionsTest.cpp
BugReportInterestingnessTest.cpp
CallDescriptionTest.cpp
CallEventTest.cpp
FalsePositiveRefutationBRVisitorTest.cpp

View File

@ -10,9 +10,10 @@
#define LLVM_CLANG_UNITTESTS_STATICANALYZER_REUSABLES_H
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/CrossTU/CrossTranslationUnit.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h"
#include "gtest/gtest.h"
namespace clang {
namespace ento {
@ -66,6 +67,88 @@ public:
Eng(CTU, AMgr, &VisitedCallees, &FS, ExprEngine::Inline_Regular) {}
};
struct ExpectedLocationTy {
unsigned Line;
unsigned Column;
void testEquality(SourceLocation L, SourceManager &SM) const {
EXPECT_EQ(SM.getSpellingLineNumber(L), Line);
EXPECT_EQ(SM.getSpellingColumnNumber(L), Column);
}
};
struct ExpectedRangeTy {
ExpectedLocationTy Begin;
ExpectedLocationTy End;
void testEquality(SourceRange R, SourceManager &SM) const {
Begin.testEquality(R.getBegin(), SM);
End.testEquality(R.getEnd(), SM);
}
};
struct ExpectedPieceTy {
ExpectedLocationTy Loc;
std::string Text;
std::vector<ExpectedRangeTy> Ranges;
void testEquality(const PathDiagnosticPiece &Piece, SourceManager &SM) {
Loc.testEquality(Piece.getLocation().asLocation(), SM);
EXPECT_EQ(Piece.getString(), Text);
EXPECT_EQ(Ranges.size(), Piece.getRanges().size());
for (const auto &RangeItem : llvm::enumerate(Piece.getRanges()))
Ranges[RangeItem.index()].testEquality(RangeItem.value(), SM);
}
};
struct ExpectedDiagTy {
ExpectedLocationTy Loc;
std::string VerboseDescription;
std::string ShortDescription;
std::string CheckerName;
std::string BugType;
std::string Category;
std::vector<ExpectedPieceTy> Path;
void testEquality(const PathDiagnostic &Diag, SourceManager &SM) {
Loc.testEquality(Diag.getLocation().asLocation(), SM);
EXPECT_EQ(Diag.getVerboseDescription(), VerboseDescription);
EXPECT_EQ(Diag.getShortDescription(), ShortDescription);
EXPECT_EQ(Diag.getCheckerName(), CheckerName);
EXPECT_EQ(Diag.getBugType(), BugType);
EXPECT_EQ(Diag.getCategory(), Category);
EXPECT_EQ(Path.size(), Diag.path.size());
for (const auto &PieceItem : llvm::enumerate(Diag.path)) {
if (PieceItem.index() < Path.size())
Path[PieceItem.index()].testEquality(*PieceItem.value(), SM);
}
}
};
using ExpectedDiagsTy = std::vector<ExpectedDiagTy>;
// A consumer to verify the generated diagnostics.
class VerifyPathDiagnosticConsumer : public PathDiagnosticConsumer {
ExpectedDiagsTy ExpectedDiags;
SourceManager &SM;
public:
VerifyPathDiagnosticConsumer(ExpectedDiagsTy &&ExpectedDiags,
SourceManager &SM)
: ExpectedDiags(ExpectedDiags), SM(SM) {}
StringRef getName() const override { return "verify test diagnostics"; }
void FlushDiagnosticsImpl(std::vector<const PathDiagnostic *> &Diags,
FilesMade *filesMade) override {
EXPECT_EQ(Diags.size(), ExpectedDiags.size());
for (const auto &Item : llvm::enumerate(Diags))
if (Item.index() < ExpectedDiags.size())
ExpectedDiags[Item.index()].testEquality(*Item.value(), SM);
}
};
} // namespace ento
} // namespace clang