[analyzer][NFC] Add unittest for FalsePositiveRefutationBRVisitor

Adds the test infrastructure for testing the FalsePositiveRefutationBRVisitor.
It will be extended in the D78457 patch, which demonstrates and fixes a bug in
the visitor.

Differential Revision: https://reviews.llvm.org/D78704
This commit is contained in:
Balazs Benics 2020-04-21 15:44:34 +02:00
parent 333aa690f4
commit e22cae32c5
4 changed files with 217 additions and 2 deletions

View File

@ -52,6 +52,7 @@
#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/Statistic.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Casting.h"
@ -2812,12 +2813,19 @@ UndefOrNullArgVisitor::VisitNode(const ExplodedNode *N, BugReporterContext &BRC,
// Implementation of FalsePositiveRefutationBRVisitor.
//===----------------------------------------------------------------------===//
#define DEBUG_TYPE "FalsePositiveRefutationBRVisitor"
STATISTIC(CrosscheckedBugReports,
"The # of bug reports which were checked for infeasible constraints");
STATISTIC(CrosscheckInvalidatedBugReports,
"The # of bug reports invalidated due to infeasible constraints");
FalsePositiveRefutationBRVisitor::FalsePositiveRefutationBRVisitor()
: Constraints(ConstraintRangeTy::Factory().getEmptyMap()) {}
void FalsePositiveRefutationBRVisitor::finalizeVisitor(
BugReporterContext &BRC, const ExplodedNode *EndPathNode,
PathSensitiveBugReport &BR) {
++CrosscheckedBugReports;
// Collect new constraints
VisitNode(EndPathNode, BRC, BR);
@ -2848,9 +2856,11 @@ void FalsePositiveRefutationBRVisitor::finalizeVisitor(
if (!isSat.hasValue())
return;
if (!isSat.getValue())
if (!isSat.getValue()) {
++CrosscheckInvalidatedBugReports;
BR.markInvalid("Infeasible constraints", EndPathNode->getLocationContext());
}
}
PathDiagnosticPieceRef FalsePositiveRefutationBRVisitor::VisitNode(
const ExplodedNode *N, BugReporterContext &, PathSensitiveBugReport &) {

View File

@ -7,6 +7,7 @@ add_clang_unittest(StaticAnalysisTests
AnalyzerOptionsTest.cpp
CallDescriptionTest.cpp
CallEventTest.cpp
FalsePositiveRefutationBRVisitorTest.cpp
ParamRegionTest.cpp
RangeSetTest.cpp
RegisterCustomCheckersTest.cpp

View File

@ -14,6 +14,7 @@
#include "clang/StaticAnalyzer/Frontend/AnalysisConsumer.h"
#include "clang/StaticAnalyzer/Frontend/CheckerRegistry.h"
#include "clang/Tooling/Tooling.h"
#include "gtest/gtest.h"
namespace clang {
namespace ento {
@ -65,10 +66,21 @@ public:
}
};
inline SmallString<80> getCurrentTestNameAsFileName() {
const ::testing::TestInfo *Info =
::testing::UnitTest::GetInstance()->current_test_info();
SmallString<80> FileName;
(Twine{Info->name()} + ".cc").toVector(FileName);
return FileName;
}
template <AddCheckerFn... Fns>
bool runCheckerOnCode(const std::string &Code, std::string &Diags) {
const SmallVectorImpl<char> &FileName = getCurrentTestNameAsFileName();
llvm::raw_string_ostream OS(Diags);
return tooling::runToolOnCode(std::make_unique<TestAction<Fns...>>(OS), Code);
return tooling::runToolOnCode(std::make_unique<TestAction<Fns...>>(OS), Code,
FileName);
}
template <AddCheckerFn... Fns>
@ -77,5 +89,22 @@ bool runCheckerOnCode(const std::string &Code) {
return runCheckerOnCode<Fns...>(Code, Diags);
}
template <AddCheckerFn... Fns>
bool runCheckerOnCodeWithArgs(const std::string &Code,
const std::vector<std::string> &Args,
std::string &Diags) {
const SmallVectorImpl<char> &FileName = getCurrentTestNameAsFileName();
llvm::raw_string_ostream OS(Diags);
return tooling::runToolOnCodeWithArgs(
std::make_unique<TestAction<Fns...>>(OS), Code, Args, FileName);
}
template <AddCheckerFn... Fns>
bool runCheckerOnCodeWithArgs(const std::string &Code,
const std::vector<std::string> &Args) {
std::string Diags;
return runCheckerOnCodeWithArgs<Fns...>(Code, Args, Diags);
}
} // namespace ento
} // namespace clang

View File

@ -0,0 +1,175 @@
//===- unittests/StaticAnalyzer/FalsePositiveRefutationBRVisitorTest.cpp --===//
//
// 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 "CheckerRegistration.h"
#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/CallEvent.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
#include "clang/StaticAnalyzer/Frontend/AnalysisConsumer.h"
#include "clang/StaticAnalyzer/Frontend/CheckerRegistry.h"
#include "llvm/Config/config.h"
#include "gtest/gtest.h"
// FIXME: Use GTEST_SKIP() instead if GTest is updated to version 1.10.0
#define SKIP_WITHOUT_Z3 \
do \
if (!LLVM_WITH_Z3) \
return; \
while (0)
namespace clang {
namespace ento {
namespace {
class FalsePositiveGenerator : public Checker<eval::Call> {
using Self = FalsePositiveGenerator;
const BuiltinBug FalsePositiveGeneratorBug{this, "FalsePositiveGenerator"};
using HandlerFn = bool (Self::*)(const CallEvent &Call,
CheckerContext &) const;
CallDescriptionMap<HandlerFn> Callbacks = {
{{"reachedWithContradiction", 0}, &Self::reachedWithContradiction},
{{"reachedWithNoContradiction", 0}, &Self::reachedWithNoContradiction},
{{"reportIfCanBeTrue", 1}, &Self::reportIfCanBeTrue},
};
bool report(CheckerContext &C, ProgramStateRef State,
StringRef Description) const {
ExplodedNode *Node = C.generateNonFatalErrorNode(State);
if (!Node)
return false;
auto Report = std::make_unique<PathSensitiveBugReport>(
FalsePositiveGeneratorBug, Description, Node);
C.emitReport(std::move(Report));
return true;
}
bool reachedWithNoContradiction(const CallEvent &, CheckerContext &C) const {
return report(C, C.getState(), "REACHED_WITH_NO_CONTRADICTION");
}
bool reachedWithContradiction(const CallEvent &, CheckerContext &C) const {
return report(C, C.getState(), "REACHED_WITH_CONTRADICTION");
}
// Similar to ExprInspectionChecker::analyzerEval except it emits warning only
// if the argument can be true. The report emits the report in the state where
// the assertion true.
bool reportIfCanBeTrue(const CallEvent &Call, CheckerContext &C) const {
// A specific instantiation of an inlined function may have more constrained
// values than can generally be assumed. Skip the check.
if (C.getPredecessor()->getLocationContext()->getStackFrame()->getParent())
return false;
SVal AssertionVal = Call.getArgSVal(0);
if (AssertionVal.isUndef())
return false;
ProgramStateRef State = C.getPredecessor()->getState();
ProgramStateRef StTrue;
std::tie(StTrue, std::ignore) =
State->assume(AssertionVal.castAs<DefinedOrUnknownSVal>());
if (StTrue)
return report(C, StTrue, "CAN_BE_TRUE");
return false;
}
public:
bool evalCall(const CallEvent &Call, CheckerContext &C) const {
if (const HandlerFn *Callback = Callbacks.lookup(Call))
return (this->*(*Callback))(Call, C);
return false;
}
};
void addFalsePositiveGenerator(AnalysisASTConsumer &AnalysisConsumer,
AnalyzerOptions &AnOpts) {
AnOpts.CheckersAndPackages = {{"test.FalsePositiveGenerator", true},
{"debug.ViewExplodedGraph", false}};
AnalysisConsumer.AddCheckerRegistrationFn([](CheckerRegistry &Registry) {
Registry.addChecker<FalsePositiveGenerator>(
"test.FalsePositiveGenerator", "EmptyDescription", "EmptyDocsUri");
});
}
// C++20 use constexpr below.
const std::vector<std::string> LazyAssumeArgs{
"-Xclang", "-analyzer-config", "-Xclang", "eagerly-assume=false"};
const std::vector<std::string> LazyAssumeAndCrossCheckArgs{
"-Xclang", "-analyzer-config", "-Xclang", "eagerly-assume=false",
"-Xclang", "-analyzer-config", "-Xclang", "crosscheck-with-z3=true"};
TEST(FalsePositiveRefutationBRVisitor, UnSatInTheMiddleNoReport) {
SKIP_WITHOUT_Z3;
constexpr auto Code = R"(
void reachedWithContradiction();
void reachedWithNoContradiction();
void test(int x, int y) {
if (x * y == 0)
return;
reachedWithNoContradiction();
if (x == 0) {
reachedWithContradiction();
// x * y != 0 => x != 0 && y != 0 => contradict with x == 0
}
})";
std::string Diags;
EXPECT_TRUE(runCheckerOnCodeWithArgs<addFalsePositiveGenerator>(
Code, LazyAssumeAndCrossCheckArgs, Diags));
EXPECT_EQ(Diags,
"test.FalsePositiveGenerator:REACHED_WITH_NO_CONTRADICTION\n");
// Single warning. The second report was invalidated by the visitor.
// Without enabling the crosscheck-with-z3 both reports are displayed.
std::string Diags2;
EXPECT_TRUE(runCheckerOnCodeWithArgs<addFalsePositiveGenerator>(
Code, LazyAssumeArgs, Diags2));
EXPECT_EQ(Diags2,
"test.FalsePositiveGenerator:REACHED_WITH_NO_CONTRADICTION\n"
"test.FalsePositiveGenerator:REACHED_WITH_CONTRADICTION\n");
}
TEST(FalsePositiveRefutationBRVisitor, UnSatAtErrorNodeWithNewSymbolNoReport) {
SKIP_WITHOUT_Z3;
constexpr auto Code = R"(
void reportIfCanBeTrue(bool);
void reachedWithNoContradiction();
void test(int x, int y) {
if (x * y == 0)
return;
// We know that 'x * y': {[MIN,-1], [1,MAX]}
reachedWithNoContradiction();
reportIfCanBeTrue(x == 0); // contradiction
// The function introduces the 'x == 0' constraint in the ErrorNode which
// leads to contradiction with the constraint of 'x * y'.
// Note that the new constraint was bound to a new symbol 'x'.
})";
std::string Diags;
EXPECT_TRUE(runCheckerOnCodeWithArgs<addFalsePositiveGenerator>(
Code, LazyAssumeAndCrossCheckArgs, Diags));
EXPECT_EQ(Diags,
"test.FalsePositiveGenerator:REACHED_WITH_NO_CONTRADICTION\n");
// Single warning. The second report was invalidated by the visitor.
// Without enabling the crosscheck-with-z3 both reports are displayed.
std::string Diags2;
EXPECT_TRUE(runCheckerOnCodeWithArgs<addFalsePositiveGenerator>(
Code, LazyAssumeArgs, Diags2));
EXPECT_EQ(Diags2,
"test.FalsePositiveGenerator:REACHED_WITH_NO_CONTRADICTION\n"
"test.FalsePositiveGenerator:CAN_BE_TRUE\n");
}
} // namespace
} // namespace ento
} // namespace clang