224 lines
8.5 KiB
C++
224 lines
8.5 KiB
C++
|
//===- 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/llvm-config.h"
|
||
|
#include "gtest/gtest.h"
|
||
|
|
||
|
// FIXME: Use GTEST_SKIP() instead if GTest is updated to version 1.10.0
|
||
|
#ifdef LLVM_WITH_Z3
|
||
|
#define SKIP_WITHOUT_Z3
|
||
|
#else
|
||
|
#define SKIP_WITHOUT_Z3 return
|
||
|
#endif
|
||
|
|
||
|
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");
|
||
|
}
|
||
|
|
||
|
TEST(FalsePositiveRefutationBRVisitor,
|
||
|
UnSatAtErrorNodeDueToRefinedConstraintNoReport) {
|
||
|
SKIP_WITHOUT_Z3;
|
||
|
constexpr auto Code = R"(
|
||
|
void reportIfCanBeTrue(bool);
|
||
|
void reachedWithNoContradiction();
|
||
|
void test(unsigned x, unsigned n) {
|
||
|
if (n >= 1 && n <= 2) {
|
||
|
if (x >= 3)
|
||
|
return;
|
||
|
// x: [0,2] and n: [1,2]
|
||
|
int y = x + n; // y: '(x+n)' Which is in approximately between 1 and 4.
|
||
|
|
||
|
// Registers the symbol 'y' with the constraint [1, MAX] in the true
|
||
|
// branch.
|
||
|
if (y > 0) {
|
||
|
// Since the x: [0,2] and n: [1,2], the 'y' is indeed greater than
|
||
|
// zero. If we emit a warning here, the constraints on the BugPath is
|
||
|
// SAT. Therefore that report is NOT invalidated.
|
||
|
reachedWithNoContradiction(); // 'y' can be greater than zero. OK
|
||
|
|
||
|
// If we ask the analyzer whether the 'y' can be 5. It won't know,
|
||
|
// therefore, the state will be created where the 'y' expression is 5.
|
||
|
// Although, this assumption is false!
|
||
|
// 'y' can not be 5 if the maximal value of both x and n is 2.
|
||
|
// The BugPath which become UnSAT in the ErrorNode with a refined
|
||
|
// constraint, should be invalidated.
|
||
|
reportIfCanBeTrue(y == 5);
|
||
|
}
|
||
|
}
|
||
|
})";
|
||
|
|
||
|
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
|