|  | //===- unittests/StaticAnalyzer/Z3CrosscheckOracleTest.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 "clang/StaticAnalyzer/Core/AnalyzerOptions.h" | 
|  | #include "clang/StaticAnalyzer/Core/BugReporter/Z3CrosscheckVisitor.h" | 
|  | #include "gtest/gtest.h" | 
|  |  | 
|  | using namespace clang; | 
|  | using namespace ento; | 
|  |  | 
|  | using Z3Result = Z3CrosscheckVisitor::Z3Result; | 
|  | using Z3Decision = Z3CrosscheckOracle::Z3Decision; | 
|  |  | 
|  | static constexpr Z3Decision AcceptReport = Z3Decision::AcceptReport; | 
|  | static constexpr Z3Decision RejectReport = Z3Decision::RejectReport; | 
|  | static constexpr Z3Decision RejectEQClass = Z3Decision::RejectEQClass; | 
|  |  | 
|  | static constexpr std::optional<bool> SAT = true; | 
|  | static constexpr std::optional<bool> UNSAT = false; | 
|  | static constexpr std::optional<bool> UNDEF = std::nullopt; | 
|  |  | 
|  | static unsigned operator""_ms(unsigned long long ms) { return ms; } | 
|  | static unsigned operator""_step(unsigned long long rlimit) { return rlimit; } | 
|  |  | 
|  | static const AnalyzerOptions DefaultOpts = [] { | 
|  | AnalyzerOptions Config; | 
|  | #define ANALYZER_OPTION_DEPENDS_ON_USER_MODE(TYPE, NAME, CMDFLAG, DESC,        \ | 
|  | SHALLOW_VAL, DEEP_VAL)            \ | 
|  | ANALYZER_OPTION(TYPE, NAME, CMDFLAG, DESC, DEEP_VAL) | 
|  | #define ANALYZER_OPTION(TYPE, NAME, CMDFLAG, DESC, DEFAULT_VAL)                \ | 
|  | Config.NAME = DEFAULT_VAL; | 
|  | #include "clang/StaticAnalyzer/Core/AnalyzerOptions.def" | 
|  |  | 
|  | // Remember to update the tests in this file when these values change. | 
|  | // Also update the doc comment of `interpretQueryResult`. | 
|  | assert(Config.Z3CrosscheckRLimitThreshold == 0); | 
|  | assert(Config.Z3CrosscheckTimeoutThreshold == 15'000_ms); | 
|  | // Usually, when the timeout/rlimit threshold is reached, Z3 only slightly | 
|  | // overshoots until it realizes that it overshoot and needs to back off. | 
|  | // Consequently, the measured timeout should be fairly close to the threshold. | 
|  | // Same reasoning applies to the rlimit too. | 
|  | return Config; | 
|  | }(); | 
|  |  | 
|  | static const AnalyzerOptions LimitedOpts = [] { | 
|  | AnalyzerOptions Config = DefaultOpts; | 
|  | Config.Z3CrosscheckEQClassTimeoutThreshold = 700_ms; | 
|  | Config.Z3CrosscheckTimeoutThreshold = 300_step; | 
|  | Config.Z3CrosscheckRLimitThreshold = 400'000_step; | 
|  | return Config; | 
|  | }(); | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | template <const AnalyzerOptions &Opts> | 
|  | class Z3CrosscheckOracleTest : public testing::Test { | 
|  | public: | 
|  | Z3Decision interpretQueryResult(const Z3Result &Result) { | 
|  | return Oracle.interpretQueryResult(Result); | 
|  | } | 
|  |  | 
|  | private: | 
|  | Z3CrosscheckOracle Oracle = Z3CrosscheckOracle(Opts); | 
|  | }; | 
|  |  | 
|  | using DefaultZ3CrosscheckOracleTest = Z3CrosscheckOracleTest<DefaultOpts>; | 
|  | using LimitedZ3CrosscheckOracleTest = Z3CrosscheckOracleTest<LimitedOpts>; | 
|  |  | 
|  | TEST_F(DefaultZ3CrosscheckOracleTest, AcceptsFirstSAT) { | 
|  | ASSERT_EQ(AcceptReport, interpretQueryResult({SAT, 25_ms, 1000_step})); | 
|  | } | 
|  | TEST_F(LimitedZ3CrosscheckOracleTest, AcceptsFirstSAT) { | 
|  | ASSERT_EQ(AcceptReport, interpretQueryResult({SAT, 25_ms, 1000_step})); | 
|  | } | 
|  |  | 
|  | TEST_F(DefaultZ3CrosscheckOracleTest, AcceptsSAT) { | 
|  | ASSERT_EQ(RejectReport, interpretQueryResult({UNSAT, 25_ms, 1000_step})); | 
|  | ASSERT_EQ(AcceptReport, interpretQueryResult({SAT, 25_ms, 1000_step})); | 
|  | } | 
|  | TEST_F(LimitedZ3CrosscheckOracleTest, AcceptsSAT) { | 
|  | ASSERT_EQ(RejectReport, interpretQueryResult({UNSAT, 25_ms, 1000_step})); | 
|  | ASSERT_EQ(AcceptReport, interpretQueryResult({SAT, 25_ms, 1000_step})); | 
|  | } | 
|  |  | 
|  | TEST_F(DefaultZ3CrosscheckOracleTest, SATWhenItGoesOverTime) { | 
|  | // Even if it times out, if it is SAT, we should accept it. | 
|  | ASSERT_EQ(AcceptReport, interpretQueryResult({SAT, 15'010_ms, 1000_step})); | 
|  | } | 
|  | TEST_F(LimitedZ3CrosscheckOracleTest, SATWhenItGoesOverTime) { | 
|  | // Even if it times out, if it is SAT, we should accept it. | 
|  | ASSERT_EQ(AcceptReport, interpretQueryResult({SAT, 310_ms, 1000_step})); | 
|  | } | 
|  |  | 
|  | TEST_F(DefaultZ3CrosscheckOracleTest, UNSATWhenItGoesOverTime) { | 
|  | ASSERT_EQ(RejectEQClass, interpretQueryResult({UNSAT, 15'010_ms, 1000_step})); | 
|  | } | 
|  | TEST_F(LimitedZ3CrosscheckOracleTest, UNSATWhenItGoesOverTime) { | 
|  | ASSERT_EQ(RejectEQClass, interpretQueryResult({UNSAT, 310_ms, 1000_step})); | 
|  | } | 
|  |  | 
|  | TEST_F(DefaultZ3CrosscheckOracleTest, RejectsTimeout) { | 
|  | ASSERT_EQ(RejectReport, interpretQueryResult({UNSAT, 25_ms, 1000_step})); | 
|  | ASSERT_EQ(RejectReport, interpretQueryResult({UNSAT, 25_ms, 1000_step})); | 
|  | ASSERT_EQ(RejectEQClass, interpretQueryResult({UNDEF, 15'010_ms, 1000_step})); | 
|  | } | 
|  | TEST_F(LimitedZ3CrosscheckOracleTest, RejectsTimeout) { | 
|  | ASSERT_EQ(RejectReport, interpretQueryResult({UNSAT, 25_ms, 1000_step})); | 
|  | ASSERT_EQ(RejectReport, interpretQueryResult({UNSAT, 25_ms, 1000_step})); | 
|  | ASSERT_EQ(RejectEQClass, interpretQueryResult({UNDEF, 310_ms, 1000_step})); | 
|  | } | 
|  |  | 
|  | TEST_F(DefaultZ3CrosscheckOracleTest, RejectsUNSATs) { | 
|  | ASSERT_EQ(RejectReport, interpretQueryResult({UNSAT, 25_ms, 1000_step})); | 
|  | ASSERT_EQ(RejectReport, interpretQueryResult({UNSAT, 25_ms, 1000_step})); | 
|  | ASSERT_EQ(RejectReport, interpretQueryResult({UNSAT, 25_ms, 1000_step})); | 
|  | ASSERT_EQ(RejectReport, interpretQueryResult({UNSAT, 25_ms, 1000_step})); | 
|  | } | 
|  | TEST_F(LimitedZ3CrosscheckOracleTest, RejectsUNSATs) { | 
|  | ASSERT_EQ(RejectReport, interpretQueryResult({UNSAT, 25_ms, 1000_step})); | 
|  | ASSERT_EQ(RejectReport, interpretQueryResult({UNSAT, 25_ms, 1000_step})); | 
|  | ASSERT_EQ(RejectReport, interpretQueryResult({UNSAT, 25_ms, 1000_step})); | 
|  | ASSERT_EQ(RejectReport, interpretQueryResult({UNSAT, 25_ms, 1000_step})); | 
|  | } | 
|  |  | 
|  | // Testing cut heuristics of the two configurations: | 
|  | // ================================================= | 
|  |  | 
|  | TEST_F(DefaultZ3CrosscheckOracleTest, RejectEQClassIfSpendsTooMuchTotalTime) { | 
|  | // Simulate long queries, that barely doesn't trigger the timeout. | 
|  | ASSERT_EQ(RejectReport, interpretQueryResult({UNSAT, 14'990_ms, 1000_step})); | 
|  | ASSERT_EQ(RejectReport, interpretQueryResult({UNSAT, 14'990_ms, 1000_step})); | 
|  | ASSERT_EQ(RejectReport, interpretQueryResult({UNSAT, 14'990_ms, 1000_step})); | 
|  | } | 
|  | TEST_F(LimitedZ3CrosscheckOracleTest, RejectEQClassIfSpendsTooMuchTotalTime) { | 
|  | // Simulate long queries, that barely doesn't trigger the timeout. | 
|  | ASSERT_EQ(RejectReport, interpretQueryResult({UNSAT, 290_ms, 1000_step})); | 
|  | ASSERT_EQ(RejectReport, interpretQueryResult({UNSAT, 290_ms, 1000_step})); | 
|  | ASSERT_EQ(RejectEQClass, interpretQueryResult({UNSAT, 290_ms, 1000_step})); | 
|  | } | 
|  |  | 
|  | TEST_F(DefaultZ3CrosscheckOracleTest, SATWhenItSpendsTooMuchTotalTime) { | 
|  | // Simulate long queries, that barely doesn't trigger the timeout. | 
|  | ASSERT_EQ(RejectReport, interpretQueryResult({UNSAT, 14'990_ms, 1000_step})); | 
|  | ASSERT_EQ(RejectReport, interpretQueryResult({UNSAT, 14'990_ms, 1000_step})); | 
|  | ASSERT_EQ(AcceptReport, interpretQueryResult({SAT, 14'990_ms, 1000_step})); | 
|  | } | 
|  | TEST_F(LimitedZ3CrosscheckOracleTest, SATWhenItSpendsTooMuchTotalTime) { | 
|  | // Simulate long queries, that barely doesn't trigger the timeout. | 
|  | ASSERT_EQ(RejectReport, interpretQueryResult({UNSAT, 290_ms, 1000_step})); | 
|  | ASSERT_EQ(RejectReport, interpretQueryResult({UNSAT, 290_ms, 1000_step})); | 
|  | ASSERT_EQ(AcceptReport, interpretQueryResult({SAT, 290_ms, 1000_step})); | 
|  | } | 
|  |  | 
|  | // Z3CrosscheckEQClassTimeoutThreshold is disabled in default configuration, so | 
|  | // it doesn't make sense to test that. | 
|  | TEST_F(LimitedZ3CrosscheckOracleTest, RejectEQClassIfAttemptsManySmallQueries) { | 
|  | // Simulate quick, but many queries: 35 quick UNSAT queries. | 
|  | // 35*20ms = 700ms, which is equal to the 700ms threshold. | 
|  | for (int i = 0; i < 35; ++i) { | 
|  | ASSERT_EQ(RejectReport, interpretQueryResult({UNSAT, 20_ms, 1000_step})); | 
|  | } | 
|  | // Do one more to trigger the heuristic. | 
|  | ASSERT_EQ(RejectEQClass, interpretQueryResult({UNSAT, 1_ms, 1000_step})); | 
|  | } | 
|  |  | 
|  | // Z3CrosscheckEQClassTimeoutThreshold is disabled in default configuration, so | 
|  | // it doesn't make sense to test that. | 
|  | TEST_F(LimitedZ3CrosscheckOracleTest, SATWhenItAttemptsManySmallQueries) { | 
|  | // Simulate quick, but many queries: 35 quick UNSAT queries. | 
|  | // 35*20ms = 700ms, which is equal to the 700ms threshold. | 
|  | for (int i = 0; i < 35; ++i) { | 
|  | ASSERT_EQ(RejectReport, interpretQueryResult({UNSAT, 20_ms, 1000_step})); | 
|  | } | 
|  | // Do one more to trigger the heuristic, but given this was SAT, we still | 
|  | // accept the query. | 
|  | ASSERT_EQ(AcceptReport, interpretQueryResult({SAT, 200_ms, 1000_step})); | 
|  | } | 
|  |  | 
|  | // Z3CrosscheckRLimitThreshold is disabled in default configuration, so it | 
|  | // doesn't make sense to test that. | 
|  | TEST_F(LimitedZ3CrosscheckOracleTest, RejectEQClassIfExhaustsRLimit) { | 
|  | ASSERT_EQ(RejectReport, interpretQueryResult({UNSAT, 25_ms, 1000_step})); | 
|  | ASSERT_EQ(RejectReport, interpretQueryResult({UNSAT, 25_ms, 1000_step})); | 
|  | ASSERT_EQ(RejectEQClass, interpretQueryResult({UNDEF, 25_ms, 405'000_step})); | 
|  | } | 
|  |  | 
|  | // Z3CrosscheckRLimitThreshold is disabled in default configuration, so it | 
|  | // doesn't make sense to test that. | 
|  | TEST_F(LimitedZ3CrosscheckOracleTest, SATWhenItExhaustsRLimit) { | 
|  | ASSERT_EQ(RejectReport, interpretQueryResult({UNSAT, 25_ms, 1000_step})); | 
|  | ASSERT_EQ(RejectReport, interpretQueryResult({UNSAT, 25_ms, 1000_step})); | 
|  | ASSERT_EQ(AcceptReport, interpretQueryResult({SAT, 25_ms, 405'000_step})); | 
|  | } | 
|  |  | 
|  | // Demonstrate the weaknesses of the default configuration: | 
|  | // ======================================================== | 
|  |  | 
|  | TEST_F(DefaultZ3CrosscheckOracleTest, ManySlowQueriesHangTheAnalyzer) { | 
|  | // Simulate many slow queries: 250 slow UNSAT queries. | 
|  | // 250*14000ms = 3500s, ~1 hour. Since we disabled the total time limitation, | 
|  | // this eqclass would take roughly 1 hour to process. | 
|  | // It doesn't matter what rlimit the queries consume. | 
|  | for (int i = 0; i < 250; ++i) { | 
|  | ASSERT_EQ(RejectReport, | 
|  | interpretQueryResult({UNSAT, 14'000_ms, 1'000'000_step})); | 
|  | } | 
|  | } | 
|  |  | 
|  | } // namespace |