xref: /llvm-project/clang/unittests/StaticAnalyzer/FalsePositiveRefutationBRVisitorTest.cpp (revision 58bad2862cf136f9483eb005bbfa6915d459b46d)
1 //===- unittests/StaticAnalyzer/FalsePositiveRefutationBRVisitorTest.cpp --===//
2 //
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
6 //
7 //===----------------------------------------------------------------------===//
8 
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"
22 
23 namespace clang {
24 namespace ento {
25 namespace {
26 
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       {{CDM::SimpleFunc, {"reachedWithContradiction"}, 0},
34        &Self::reachedWithContradiction},
35       {{CDM::SimpleFunc, {"reachedWithNoContradiction"}, 0},
36        &Self::reachedWithNoContradiction},
37       {{CDM::SimpleFunc, {"reportIfCanBeTrue"}, 1}, &Self::reportIfCanBeTrue},
38   };
39 
report(CheckerContext & C,ProgramStateRef State,StringRef Description) const40   bool report(CheckerContext &C, ProgramStateRef State,
41               StringRef Description) const {
42     ExplodedNode *Node = C.generateNonFatalErrorNode(State);
43     if (!Node)
44       return false;
45 
46     auto Report = std::make_unique<PathSensitiveBugReport>(
47         FalsePositiveGeneratorBug, Description, Node);
48     C.emitReport(std::move(Report));
49     return true;
50   }
51 
reachedWithNoContradiction(const CallEvent &,CheckerContext & C) const52   bool reachedWithNoContradiction(const CallEvent &, CheckerContext &C) const {
53     return report(C, C.getState(), "REACHED_WITH_NO_CONTRADICTION");
54   }
55 
reachedWithContradiction(const CallEvent &,CheckerContext & C) const56   bool reachedWithContradiction(const CallEvent &, CheckerContext &C) const {
57     return report(C, C.getState(), "REACHED_WITH_CONTRADICTION");
58   }
59 
60   // Similar to ExprInspectionChecker::analyzerEval except it emits warning only
61   // if the argument can be true. The report emits the report in the state where
62   // the assertion true.
reportIfCanBeTrue(const CallEvent & Call,CheckerContext & C) const63   bool reportIfCanBeTrue(const CallEvent &Call, CheckerContext &C) const {
64     // A specific instantiation of an inlined function may have more constrained
65     // values than can generally be assumed. Skip the check.
66     if (C.getPredecessor()->getLocationContext()->getStackFrame()->getParent())
67       return false;
68 
69     SVal AssertionVal = Call.getArgSVal(0);
70     if (AssertionVal.isUndef())
71       return false;
72 
73     ProgramStateRef State = C.getPredecessor()->getState();
74     ProgramStateRef StTrue;
75     std::tie(StTrue, std::ignore) =
76         State->assume(AssertionVal.castAs<DefinedOrUnknownSVal>());
77     if (StTrue)
78       return report(C, StTrue, "CAN_BE_TRUE");
79     return false;
80   }
81 
82 public:
evalCall(const CallEvent & Call,CheckerContext & C) const83   bool evalCall(const CallEvent &Call, CheckerContext &C) const {
84     if (const HandlerFn *Callback = Callbacks.lookup(Call))
85       return (this->*(*Callback))(Call, C);
86     return false;
87   }
88 };
89 
addFalsePositiveGenerator(AnalysisASTConsumer & AnalysisConsumer,AnalyzerOptions & AnOpts)90 void addFalsePositiveGenerator(AnalysisASTConsumer &AnalysisConsumer,
91                                AnalyzerOptions &AnOpts) {
92   AnOpts.CheckersAndPackages = {{"test.FalsePositiveGenerator", true},
93                                 {"debug.ViewExplodedGraph", false}};
94   AnalysisConsumer.AddCheckerRegistrationFn([](CheckerRegistry &Registry) {
95     Registry.addChecker<FalsePositiveGenerator>(
96         "test.FalsePositiveGenerator", "EmptyDescription", "EmptyDocsUri");
97   });
98 }
99 
100 class FalsePositiveRefutationBRVisitorTestBase : public testing::Test {
101 public:
SetUp()102   void SetUp() override {
103 #ifndef LLVM_WITH_Z3
104     GTEST_SKIP() << "Requires the LLVM_ENABLE_Z3_SOLVER cmake option.";
105 #endif
106   }
107 };
108 
109 // C++20 use constexpr below.
110 const std::vector<std::string> LazyAssumeArgs{
111     "-Xclang", "-analyzer-config", "-Xclang", "eagerly-assume=false"};
112 const std::vector<std::string> LazyAssumeAndCrossCheckArgs{
113     "-Xclang", "-analyzer-config", "-Xclang", "eagerly-assume=false",
114     "-Xclang", "-analyzer-config", "-Xclang", "crosscheck-with-z3=true"};
115 
TEST_F(FalsePositiveRefutationBRVisitorTestBase,UnSatInTheMiddleNoReport)116 TEST_F(FalsePositiveRefutationBRVisitorTestBase, UnSatInTheMiddleNoReport) {
117   constexpr auto Code = R"(
118      void reachedWithContradiction();
119      void reachedWithNoContradiction();
120      void test(int x, int y) {
121        if (x * y == 0)
122          return;
123        reachedWithNoContradiction();
124        if (x == 0) {
125          reachedWithContradiction();
126          // x * y != 0  =>  x != 0 && y != 0  => contradict with x == 0
127        }
128      })";
129 
130   std::string Diags;
131   EXPECT_TRUE(runCheckerOnCodeWithArgs<addFalsePositiveGenerator>(
132       Code, LazyAssumeAndCrossCheckArgs, Diags, /*OnlyEmitWarnings=*/ true));
133   EXPECT_EQ(Diags,
134             "test.FalsePositiveGenerator: REACHED_WITH_NO_CONTRADICTION\n");
135   // Single warning. The second report was invalidated by the visitor.
136 
137   // Without enabling the crosscheck-with-z3 both reports are displayed.
138   std::string Diags2;
139   EXPECT_TRUE(runCheckerOnCodeWithArgs<addFalsePositiveGenerator>(
140       Code, LazyAssumeArgs, Diags2, /*OnlyEmitWarnings=*/ true));
141   EXPECT_EQ(Diags2,
142             "test.FalsePositiveGenerator: REACHED_WITH_NO_CONTRADICTION\n"
143             "test.FalsePositiveGenerator: REACHED_WITH_CONTRADICTION\n");
144 }
145 
TEST_F(FalsePositiveRefutationBRVisitorTestBase,UnSatAtErrorNodeWithNewSymbolNoReport)146 TEST_F(FalsePositiveRefutationBRVisitorTestBase,
147        UnSatAtErrorNodeWithNewSymbolNoReport) {
148   constexpr auto Code = R"(
149     void reportIfCanBeTrue(bool);
150     void reachedWithNoContradiction();
151     void test(int x, int y) {
152       if (x * y == 0)
153        return;
154       // We know that 'x * y': {[MIN,-1], [1,MAX]}
155       reachedWithNoContradiction();
156       reportIfCanBeTrue(x == 0); // contradiction
157       // The function introduces the 'x == 0' constraint in the ErrorNode which
158       // leads to contradiction with the constraint of 'x * y'.
159       // Note that the new constraint was bound to a new symbol 'x'.
160     })";
161   std::string Diags;
162   EXPECT_TRUE(runCheckerOnCodeWithArgs<addFalsePositiveGenerator>(
163       Code, LazyAssumeAndCrossCheckArgs, Diags, /*OnlyEmitWarnings=*/ true));
164   EXPECT_EQ(Diags,
165             "test.FalsePositiveGenerator: REACHED_WITH_NO_CONTRADICTION\n");
166   // Single warning. The second report was invalidated by the visitor.
167 
168   // Without enabling the crosscheck-with-z3 both reports are displayed.
169   std::string Diags2;
170   EXPECT_TRUE(runCheckerOnCodeWithArgs<addFalsePositiveGenerator>(
171       Code, LazyAssumeArgs, Diags2, /*OnlyEmitWarnings=*/ true));
172   EXPECT_EQ(Diags2,
173             "test.FalsePositiveGenerator: REACHED_WITH_NO_CONTRADICTION\n"
174             "test.FalsePositiveGenerator: CAN_BE_TRUE\n");
175 }
176 
TEST_F(FalsePositiveRefutationBRVisitorTestBase,UnSatAtErrorNodeDueToRefinedConstraintNoReport)177 TEST_F(FalsePositiveRefutationBRVisitorTestBase,
178        UnSatAtErrorNodeDueToRefinedConstraintNoReport) {
179   constexpr auto Code = R"(
180     void reportIfCanBeTrue(bool);
181     void reachedWithNoContradiction();
182     void test(unsigned x, unsigned n) {
183       if (n >= 1 && n <= 2) {
184         if (x >= 3)
185           return;
186         // x: [0,2] and n: [1,2]
187         int y = x + n; // y: '(x+n)' Which is in approximately between 1 and 4.
188 
189         // Registers the symbol 'y' with the constraint [1, MAX] in the true
190         // branch.
191         if (y > 0) {
192           // Since the x: [0,2] and n: [1,2], the 'y' is indeed greater than
193           // zero. If we emit a warning here, the constraints on the BugPath is
194           // SAT. Therefore that report is NOT invalidated.
195           reachedWithNoContradiction(); // 'y' can be greater than zero. OK
196 
197           // If we ask the analyzer whether the 'y' can be 5. It won't know,
198           // therefore, the state will be created where the 'y' expression is 5.
199           // Although, this assumption is false!
200           // 'y' can not be 5 if the maximal value of both x and n is 2.
201           // The BugPath which become UnSAT in the ErrorNode with a refined
202           // constraint, should be invalidated.
203           reportIfCanBeTrue(y == 5);
204         }
205       }
206     })";
207 
208   std::string Diags;
209   EXPECT_TRUE(runCheckerOnCodeWithArgs<addFalsePositiveGenerator>(
210       Code, LazyAssumeAndCrossCheckArgs, Diags, /*OnlyEmitWarnings=*/ true));
211   EXPECT_EQ(Diags,
212             "test.FalsePositiveGenerator: REACHED_WITH_NO_CONTRADICTION\n");
213   // Single warning. The second report was invalidated by the visitor.
214 
215   // Without enabling the crosscheck-with-z3 both reports are displayed.
216   std::string Diags2;
217   EXPECT_TRUE(runCheckerOnCodeWithArgs<addFalsePositiveGenerator>(
218       Code, LazyAssumeArgs, Diags2, /*OnlyEmitWarnings=*/ true));
219   EXPECT_EQ(Diags2,
220             "test.FalsePositiveGenerator: REACHED_WITH_NO_CONTRADICTION\n"
221             "test.FalsePositiveGenerator: CAN_BE_TRUE\n");
222 }
223 
224 } // namespace
225 } // namespace ento
226 } // namespace clang
227