1 //===- unittests/StaticAnalyzer/FalsePositiveRefutationBRVisitorTest.cpp --===//
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7 //===----------------------------------------------------------------------===//
9 #include "CheckerRegistration.h"
10 #include "Reusables.h"
11 #include "clang/Frontend/CompilerInstance.h"
12 #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
13 #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
14 #include "clang/StaticAnalyzer/Core/Checker.h"
15 #include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
16 #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
17 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
18 #include "clang/StaticAnalyzer/Frontend/AnalysisConsumer.h"
19 #include "clang/StaticAnalyzer/Frontend/CheckerRegistry.h"
20 #include "llvm/Config/llvm-config.h"
21 #include "gtest/gtest.h"
27 class FalsePositiveGenerator
: public Checker
<eval::Call
> {
28 using Self
= FalsePositiveGenerator
;
29 const BugType FalsePositiveGeneratorBug
{this, "FalsePositiveGenerator"};
30 using HandlerFn
= bool (Self::*)(const CallEvent
&Call
,
31 CheckerContext
&) const;
32 CallDescriptionMap
<HandlerFn
> Callbacks
= {
33 {{{"reachedWithContradiction"}, 0}, &Self::reachedWithContradiction
},
34 {{{"reachedWithNoContradiction"}, 0}, &Self::reachedWithNoContradiction
},
35 {{{"reportIfCanBeTrue"}, 1}, &Self::reportIfCanBeTrue
},
38 bool report(CheckerContext
&C
, ProgramStateRef State
,
39 StringRef Description
) const {
40 ExplodedNode
*Node
= C
.generateNonFatalErrorNode(State
);
44 auto Report
= std::make_unique
<PathSensitiveBugReport
>(
45 FalsePositiveGeneratorBug
, Description
, Node
);
46 C
.emitReport(std::move(Report
));
50 bool reachedWithNoContradiction(const CallEvent
&, CheckerContext
&C
) const {
51 return report(C
, C
.getState(), "REACHED_WITH_NO_CONTRADICTION");
54 bool reachedWithContradiction(const CallEvent
&, CheckerContext
&C
) const {
55 return report(C
, C
.getState(), "REACHED_WITH_CONTRADICTION");
58 // Similar to ExprInspectionChecker::analyzerEval except it emits warning only
59 // if the argument can be true. The report emits the report in the state where
60 // the assertion true.
61 bool reportIfCanBeTrue(const CallEvent
&Call
, CheckerContext
&C
) const {
62 // A specific instantiation of an inlined function may have more constrained
63 // values than can generally be assumed. Skip the check.
64 if (C
.getPredecessor()->getLocationContext()->getStackFrame()->getParent())
67 SVal AssertionVal
= Call
.getArgSVal(0);
68 if (AssertionVal
.isUndef())
71 ProgramStateRef State
= C
.getPredecessor()->getState();
72 ProgramStateRef StTrue
;
73 std::tie(StTrue
, std::ignore
) =
74 State
->assume(AssertionVal
.castAs
<DefinedOrUnknownSVal
>());
76 return report(C
, StTrue
, "CAN_BE_TRUE");
81 bool evalCall(const CallEvent
&Call
, CheckerContext
&C
) const {
82 if (const HandlerFn
*Callback
= Callbacks
.lookup(Call
))
83 return (this->*(*Callback
))(Call
, C
);
88 void addFalsePositiveGenerator(AnalysisASTConsumer
&AnalysisConsumer
,
89 AnalyzerOptions
&AnOpts
) {
90 AnOpts
.CheckersAndPackages
= {{"test.FalsePositiveGenerator", true},
91 {"debug.ViewExplodedGraph", false}};
92 AnalysisConsumer
.AddCheckerRegistrationFn([](CheckerRegistry
&Registry
) {
93 Registry
.addChecker
<FalsePositiveGenerator
>(
94 "test.FalsePositiveGenerator", "EmptyDescription", "EmptyDocsUri");
98 class FalsePositiveRefutationBRVisitorTestBase
: public testing::Test
{
100 void SetUp() override
{
102 GTEST_SKIP() << "Requires the LLVM_ENABLE_Z3_SOLVER cmake option.";
107 // C++20 use constexpr below.
108 const std::vector
<std::string
> LazyAssumeArgs
{
109 "-Xclang", "-analyzer-config", "-Xclang", "eagerly-assume=false"};
110 const std::vector
<std::string
> LazyAssumeAndCrossCheckArgs
{
111 "-Xclang", "-analyzer-config", "-Xclang", "eagerly-assume=false",
112 "-Xclang", "-analyzer-config", "-Xclang", "crosscheck-with-z3=true"};
114 TEST_F(FalsePositiveRefutationBRVisitorTestBase
, UnSatInTheMiddleNoReport
) {
115 constexpr auto Code
= R
"(
116 void reachedWithContradiction();
117 void reachedWithNoContradiction();
118 void test(int x, int y) {
121 reachedWithNoContradiction();
123 reachedWithContradiction();
124 // x * y != 0 => x != 0 && y != 0 => contradict with x == 0
129 EXPECT_TRUE(runCheckerOnCodeWithArgs
<addFalsePositiveGenerator
>(
130 Code
, LazyAssumeAndCrossCheckArgs
, Diags
, /*OnlyEmitWarnings=*/ true));
132 "test.FalsePositiveGenerator: REACHED_WITH_NO_CONTRADICTION\n");
133 // Single warning. The second report was invalidated by the visitor.
135 // Without enabling the crosscheck-with-z3 both reports are displayed.
137 EXPECT_TRUE(runCheckerOnCodeWithArgs
<addFalsePositiveGenerator
>(
138 Code
, LazyAssumeArgs
, Diags2
, /*OnlyEmitWarnings=*/ true));
140 "test.FalsePositiveGenerator: REACHED_WITH_NO_CONTRADICTION\n"
141 "test.FalsePositiveGenerator: REACHED_WITH_CONTRADICTION\n");
144 TEST_F(FalsePositiveRefutationBRVisitorTestBase
,
145 UnSatAtErrorNodeWithNewSymbolNoReport
) {
146 constexpr auto Code
= R
"(
147 void reportIfCanBeTrue(bool);
148 void reachedWithNoContradiction();
149 void test(int x, int y) {
152 // We know that 'x * y': {[MIN,-1], [1,MAX]}
153 reachedWithNoContradiction();
154 reportIfCanBeTrue(x == 0); // contradiction
155 // The function introduces the 'x == 0' constraint in the ErrorNode which
156 // leads to contradiction with the constraint of 'x * y'.
157 // Note that the new constraint was bound to a new symbol 'x'.
160 EXPECT_TRUE(runCheckerOnCodeWithArgs
<addFalsePositiveGenerator
>(
161 Code
, LazyAssumeAndCrossCheckArgs
, Diags
, /*OnlyEmitWarnings=*/ true));
163 "test.FalsePositiveGenerator: REACHED_WITH_NO_CONTRADICTION\n");
164 // Single warning. The second report was invalidated by the visitor.
166 // Without enabling the crosscheck-with-z3 both reports are displayed.
168 EXPECT_TRUE(runCheckerOnCodeWithArgs
<addFalsePositiveGenerator
>(
169 Code
, LazyAssumeArgs
, Diags2
, /*OnlyEmitWarnings=*/ true));
171 "test.FalsePositiveGenerator: REACHED_WITH_NO_CONTRADICTION\n"
172 "test.FalsePositiveGenerator: CAN_BE_TRUE\n");
175 TEST_F(FalsePositiveRefutationBRVisitorTestBase
,
176 UnSatAtErrorNodeDueToRefinedConstraintNoReport
) {
177 constexpr auto Code
= R
"(
178 void reportIfCanBeTrue(bool);
179 void reachedWithNoContradiction();
180 void test(unsigned x, unsigned n) {
181 if (n >= 1 && n <= 2) {
184 // x: [0,2] and n: [1,2]
185 int y = x + n; // y: '(x+n)' Which is in approximately between 1 and 4.
187 // Registers the symbol 'y' with the constraint [1, MAX] in the true
190 // Since the x: [0,2] and n: [1,2], the 'y' is indeed greater than
191 // zero. If we emit a warning here, the constraints on the BugPath is
192 // SAT. Therefore that report is NOT invalidated.
193 reachedWithNoContradiction(); // 'y' can be greater than zero. OK
195 // If we ask the analyzer whether the 'y' can be 5. It won't know,
196 // therefore, the state will be created where the 'y' expression is 5.
197 // Although, this assumption is false!
198 // 'y' can not be 5 if the maximal value of both x and n is 2.
199 // The BugPath which become UnSAT in the ErrorNode with a refined
200 // constraint, should be invalidated.
201 reportIfCanBeTrue(y == 5);
207 EXPECT_TRUE(runCheckerOnCodeWithArgs
<addFalsePositiveGenerator
>(
208 Code
, LazyAssumeAndCrossCheckArgs
, Diags
, /*OnlyEmitWarnings=*/ true));
210 "test.FalsePositiveGenerator: REACHED_WITH_NO_CONTRADICTION\n");
211 // Single warning. The second report was invalidated by the visitor.
213 // Without enabling the crosscheck-with-z3 both reports are displayed.
215 EXPECT_TRUE(runCheckerOnCodeWithArgs
<addFalsePositiveGenerator
>(
216 Code
, LazyAssumeArgs
, Diags2
, /*OnlyEmitWarnings=*/ true));
218 "test.FalsePositiveGenerator: REACHED_WITH_NO_CONTRADICTION\n"
219 "test.FalsePositiveGenerator: CAN_BE_TRUE\n");