xref: /llvm-project/clang/unittests/StaticAnalyzer/FalsePositiveRefutationBRVisitorTest.cpp (revision b7065a31b5e7e0bbdae1cbb6cd06fcfe05b7758f)
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 BuiltinBug 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},
36   };
37 
38   bool report(CheckerContext &C, ProgramStateRef State,
39               StringRef Description) const {
40     ExplodedNode *Node = C.generateNonFatalErrorNode(State);
41     if (!Node)
42       return false;
43 
44     auto Report = std::make_unique<PathSensitiveBugReport>(
45         FalsePositiveGeneratorBug, Description, Node);
46     C.emitReport(std::move(Report));
47     return true;
48   }
49 
50   bool reachedWithNoContradiction(const CallEvent &, CheckerContext &C) const {
51     return report(C, C.getState(), "REACHED_WITH_NO_CONTRADICTION");
52   }
53 
54   bool reachedWithContradiction(const CallEvent &, CheckerContext &C) const {
55     return report(C, C.getState(), "REACHED_WITH_CONTRADICTION");
56   }
57 
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())
65       return false;
66 
67     SVal AssertionVal = Call.getArgSVal(0);
68     if (AssertionVal.isUndef())
69       return false;
70 
71     ProgramStateRef State = C.getPredecessor()->getState();
72     ProgramStateRef StTrue;
73     std::tie(StTrue, std::ignore) =
74         State->assume(AssertionVal.castAs<DefinedOrUnknownSVal>());
75     if (StTrue)
76       return report(C, StTrue, "CAN_BE_TRUE");
77     return false;
78   }
79 
80 public:
81   bool evalCall(const CallEvent &Call, CheckerContext &C) const {
82     if (const HandlerFn *Callback = Callbacks.lookup(Call))
83       return (this->*(*Callback))(Call, C);
84     return false;
85   }
86 };
87 
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");
95   });
96 }
97 
98 class FalsePositiveRefutationBRVisitorTestBase : public testing::Test {
99 public:
100   void SetUp() override {
101 #ifndef LLVM_WITH_Z3
102     GTEST_SKIP() << "Requires the LLVM_ENABLE_Z3_SOLVER cmake option.";
103 #endif
104   }
105 };
106 
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"};
113 
114 TEST_F(FalsePositiveRefutationBRVisitorTestBase, UnSatInTheMiddleNoReport) {
115   constexpr auto Code = R"(
116      void reachedWithContradiction();
117      void reachedWithNoContradiction();
118      void test(int x, int y) {
119        if (x * y == 0)
120          return;
121        reachedWithNoContradiction();
122        if (x == 0) {
123          reachedWithContradiction();
124          // x * y != 0  =>  x != 0 && y != 0  => contradict with x == 0
125        }
126      })";
127 
128   std::string Diags;
129   EXPECT_TRUE(runCheckerOnCodeWithArgs<addFalsePositiveGenerator>(
130       Code, LazyAssumeAndCrossCheckArgs, Diags, /*OnlyEmitWarnings=*/ true));
131   EXPECT_EQ(Diags,
132             "test.FalsePositiveGenerator: REACHED_WITH_NO_CONTRADICTION\n");
133   // Single warning. The second report was invalidated by the visitor.
134 
135   // Without enabling the crosscheck-with-z3 both reports are displayed.
136   std::string Diags2;
137   EXPECT_TRUE(runCheckerOnCodeWithArgs<addFalsePositiveGenerator>(
138       Code, LazyAssumeArgs, Diags2, /*OnlyEmitWarnings=*/ true));
139   EXPECT_EQ(Diags2,
140             "test.FalsePositiveGenerator: REACHED_WITH_NO_CONTRADICTION\n"
141             "test.FalsePositiveGenerator: REACHED_WITH_CONTRADICTION\n");
142 }
143 
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) {
150       if (x * y == 0)
151        return;
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'.
158     })";
159   std::string Diags;
160   EXPECT_TRUE(runCheckerOnCodeWithArgs<addFalsePositiveGenerator>(
161       Code, LazyAssumeAndCrossCheckArgs, Diags, /*OnlyEmitWarnings=*/ true));
162   EXPECT_EQ(Diags,
163             "test.FalsePositiveGenerator: REACHED_WITH_NO_CONTRADICTION\n");
164   // Single warning. The second report was invalidated by the visitor.
165 
166   // Without enabling the crosscheck-with-z3 both reports are displayed.
167   std::string Diags2;
168   EXPECT_TRUE(runCheckerOnCodeWithArgs<addFalsePositiveGenerator>(
169       Code, LazyAssumeArgs, Diags2, /*OnlyEmitWarnings=*/ true));
170   EXPECT_EQ(Diags2,
171             "test.FalsePositiveGenerator: REACHED_WITH_NO_CONTRADICTION\n"
172             "test.FalsePositiveGenerator: CAN_BE_TRUE\n");
173 }
174 
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) {
182         if (x >= 3)
183           return;
184         // x: [0,2] and n: [1,2]
185         int y = x + n; // y: '(x+n)' Which is in approximately between 1 and 4.
186 
187         // Registers the symbol 'y' with the constraint [1, MAX] in the true
188         // branch.
189         if (y > 0) {
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
194 
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);
202         }
203       }
204     })";
205 
206   std::string Diags;
207   EXPECT_TRUE(runCheckerOnCodeWithArgs<addFalsePositiveGenerator>(
208       Code, LazyAssumeAndCrossCheckArgs, Diags, /*OnlyEmitWarnings=*/ true));
209   EXPECT_EQ(Diags,
210             "test.FalsePositiveGenerator: REACHED_WITH_NO_CONTRADICTION\n");
211   // Single warning. The second report was invalidated by the visitor.
212 
213   // Without enabling the crosscheck-with-z3 both reports are displayed.
214   std::string Diags2;
215   EXPECT_TRUE(runCheckerOnCodeWithArgs<addFalsePositiveGenerator>(
216       Code, LazyAssumeArgs, Diags2, /*OnlyEmitWarnings=*/ true));
217   EXPECT_EQ(Diags2,
218             "test.FalsePositiveGenerator: REACHED_WITH_NO_CONTRADICTION\n"
219             "test.FalsePositiveGenerator: CAN_BE_TRUE\n");
220 }
221 
222 } // namespace
223 } // namespace ento
224 } // namespace clang
225