//===- 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 SAT = true; static constexpr std::optional UNSAT = false; static constexpr std::optional 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 == 400'000); assert(Config.Z3CrosscheckTimeoutThreshold == 300_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; }(); namespace { class Z3CrosscheckOracleTest : public testing::Test { public: Z3Decision interpretQueryResult(const Z3Result &Result) { return Oracle.interpretQueryResult(Result); } private: Z3CrosscheckOracle Oracle = Z3CrosscheckOracle(DefaultOpts); }; TEST_F(Z3CrosscheckOracleTest, AcceptsFirstSAT) { ASSERT_EQ(AcceptReport, interpretQueryResult({SAT, 25_ms, 1000_step})); } TEST_F(Z3CrosscheckOracleTest, AcceptsSAT) { ASSERT_EQ(RejectReport, interpretQueryResult({UNSAT, 25_ms, 1000_step})); ASSERT_EQ(AcceptReport, interpretQueryResult({SAT, 25_ms, 1000_step})); } TEST_F(Z3CrosscheckOracleTest, 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(Z3CrosscheckOracleTest, UNSATWhenItGoesOverTime) { ASSERT_EQ(RejectEQClass, interpretQueryResult({UNSAT, 310_ms, 1000_step})); } TEST_F(Z3CrosscheckOracleTest, 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(Z3CrosscheckOracleTest, 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: // ======================= TEST_F(Z3CrosscheckOracleTest, 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(Z3CrosscheckOracleTest, 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})); } TEST_F(Z3CrosscheckOracleTest, 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})); } TEST_F(Z3CrosscheckOracleTest, SATWhenIfAttemptsManySmallQueries) { // 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})); } TEST_F(Z3CrosscheckOracleTest, 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})); } TEST_F(Z3CrosscheckOracleTest, 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})); } } // namespace