Skip to content

[analyzer][NFC] Reorganize Z3 report refutation #95128

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged

Conversation

steakhal
Copy link
Contributor

This change keeps existing behavior, namely that if we hit a Z3 timeout
we will accept the report as "satisfiable".

This prepares for the commit "Harden safeguards for Z3 query times".
https://discourse.llvm.org/t/analyzer-rfc-taming-z3-query-times/79520

Created using spr 1.3.4
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:static analyzer llvm:support labels Jun 11, 2024
@llvmbot
Copy link
Member

llvmbot commented Jun 11, 2024

@llvm/pr-subscribers-llvm-support
@llvm/pr-subscribers-clang-static-analyzer-1

@llvm/pr-subscribers-clang

Author: Balazs Benics (steakhal)

Changes

This change keeps existing behavior, namely that if we hit a Z3 timeout
we will accept the report as "satisfiable".

This prepares for the commit "Harden safeguards for Z3 query times".
https://discourse.llvm.org/t/analyzer-rfc-taming-z3-query-times/79520


Patch is 27.25 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/95128.diff

12 Files Affected:

  • (modified) clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h (-23)
  • (added) clang/include/clang/StaticAnalyzer/Core/BugReporter/Z3CrosscheckVisitor.h (+66)
  • (modified) clang/include/clang/StaticAnalyzer/Core/PathSensitive/SMTConstraintManager.h (+4-1)
  • (modified) clang/lib/StaticAnalyzer/Core/BugReporter.cpp (+22-6)
  • (modified) clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp (-76)
  • (modified) clang/lib/StaticAnalyzer/Core/CMakeLists.txt (+1)
  • (added) clang/lib/StaticAnalyzer/Core/Z3CrosscheckVisitor.cpp (+118)
  • (added) clang/test/Analysis/z3/crosscheck-statistics.c (+33)
  • (modified) clang/unittests/StaticAnalyzer/CMakeLists.txt (+1)
  • (added) clang/unittests/StaticAnalyzer/Z3CrosscheckOracleTest.cpp (+59)
  • (modified) llvm/include/llvm/Support/SMTAPI.h (+19)
  • (modified) llvm/lib/Support/Z3Solver.cpp (+85-18)
diff --git a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h
index cc3d93aabafda..f97514955a591 100644
--- a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h
+++ b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h
@@ -597,29 +597,6 @@ class SuppressInlineDefensiveChecksVisitor final : public BugReporterVisitor {
                                    PathSensitiveBugReport &BR) override;
 };
 
-/// The bug visitor will walk all the nodes in a path and collect all the
-/// constraints. When it reaches the root node, will create a refutation
-/// manager and check if the constraints are satisfiable
-class FalsePositiveRefutationBRVisitor final : public BugReporterVisitor {
-private:
-  /// Holds the constraints in a given path
-  ConstraintMap Constraints;
-
-public:
-  FalsePositiveRefutationBRVisitor();
-
-  void Profile(llvm::FoldingSetNodeID &ID) const override;
-
-  PathDiagnosticPieceRef VisitNode(const ExplodedNode *N,
-                                   BugReporterContext &BRC,
-                                   PathSensitiveBugReport &BR) override;
-
-  void finalizeVisitor(BugReporterContext &BRC, const ExplodedNode *EndPathNode,
-                       PathSensitiveBugReport &BR) override;
-  void addConstraints(const ExplodedNode *N,
-                      bool OverwriteConstraintsOnExistingSyms);
-};
-
 /// The visitor detects NoteTags and displays the event notes they contain.
 class TagVisitor : public BugReporterVisitor {
 public:
diff --git a/clang/include/clang/StaticAnalyzer/Core/BugReporter/Z3CrosscheckVisitor.h b/clang/include/clang/StaticAnalyzer/Core/BugReporter/Z3CrosscheckVisitor.h
new file mode 100644
index 0000000000000..3ec59e3037363
--- /dev/null
+++ b/clang/include/clang/StaticAnalyzer/Core/BugReporter/Z3CrosscheckVisitor.h
@@ -0,0 +1,66 @@
+//===- Z3CrosscheckVisitor.h - Crosscheck reports with Z3 -------*- C++ -*-===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+//  This file defines the visitor and utilities around it for Z3 report
+//  refutation.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_STATICANALYZER_CORE_BUGREPORTER_Z3CROSSCHECKVISITOR_H
+#define LLVM_CLANG_STATICANALYZER_CORE_BUGREPORTER_Z3CROSSCHECKVISITOR_H
+
+#include "clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h"
+
+namespace clang::ento {
+
+/// The bug visitor will walk all the nodes in a path and collect all the
+/// constraints. When it reaches the root node, will create a refutation
+/// manager and check if the constraints are satisfiable.
+class Z3CrosscheckVisitor final : public BugReporterVisitor {
+public:
+  struct Z3Result {
+    std::optional<bool> IsSAT = std::nullopt;
+  };
+  explicit Z3CrosscheckVisitor(Z3CrosscheckVisitor::Z3Result &Result);
+
+  void Profile(llvm::FoldingSetNodeID &ID) const override;
+
+  PathDiagnosticPieceRef VisitNode(const ExplodedNode *N,
+                                   BugReporterContext &BRC,
+                                   PathSensitiveBugReport &BR) override;
+
+  void finalizeVisitor(BugReporterContext &BRC, const ExplodedNode *EndPathNode,
+                       PathSensitiveBugReport &BR) override;
+
+private:
+  void addConstraints(const ExplodedNode *N,
+                      bool OverwriteConstraintsOnExistingSyms);
+
+  /// Holds the constraints in a given path.
+  ConstraintMap Constraints;
+  Z3Result &Result;
+};
+
+/// The oracle will decide if a report should be accepted or rejected based on
+/// the results of the Z3 solver.
+class Z3CrosscheckOracle {
+public:
+  enum Z3Decision {
+    AcceptReport, // The report was SAT.
+    RejectReport, // The report was UNSAT or UNDEF.
+  };
+
+  /// Makes a decision for accepting or rejecting the report based on the
+  /// result of the corresponding Z3 query.
+  static Z3Decision
+  interpretQueryResult(const Z3CrosscheckVisitor::Z3Result &Query);
+};
+
+} // namespace clang::ento
+
+#endif // LLVM_CLANG_STATICANALYZER_CORE_BUGREPORTER_Z3CROSSCHECKVISITOR_H
\ No newline at end of file
diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SMTConstraintManager.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SMTConstraintManager.h
index 5116a4c06850d..bf18c353b8508 100644
--- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SMTConstraintManager.h
+++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SMTConstraintManager.h
@@ -34,7 +34,10 @@ class SMTConstraintManager : public clang::ento::SimpleConstraintManager {
 public:
   SMTConstraintManager(clang::ento::ExprEngine *EE,
                        clang::ento::SValBuilder &SB)
-      : SimpleConstraintManager(EE, SB) {}
+      : SimpleConstraintManager(EE, SB) {
+    Solver->setBoolParam("model", true); // Enable model finding
+    Solver->setUnsignedParam("timeout", 15000 /*milliseconds*/);
+  }
   virtual ~SMTConstraintManager() = default;
 
   //===------------------------------------------------------------------===//
diff --git a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp
index 14ca507a16d55..c9a7fd0e035c2 100644
--- a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp
+++ b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp
@@ -35,6 +35,7 @@
 #include "clang/StaticAnalyzer/Core/AnalyzerOptions.h"
 #include "clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h"
 #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
+#include "clang/StaticAnalyzer/Core/BugReporter/Z3CrosscheckVisitor.h"
 #include "clang/StaticAnalyzer/Core/Checker.h"
 #include "clang/StaticAnalyzer/Core/CheckerManager.h"
 #include "clang/StaticAnalyzer/Core/CheckerRegistryData.h"
@@ -86,6 +87,11 @@ STATISTIC(MaxValidBugClassSize,
           "The maximum number of bug reports in the same equivalence class "
           "where at least one report is valid (not suppressed)");
 
+STATISTIC(NumTimesReportPassesZ3, "Number of reports passed Z3");
+STATISTIC(NumTimesReportRefuted, "Number of reports refuted by Z3");
+STATISTIC(NumTimesReportEQClassWasExhausted,
+          "Number of times all reports of an equivalence class was refuted");
+
 BugReporterVisitor::~BugReporterVisitor() = default;
 
 void BugReporterContext::anchor() {}
@@ -2864,21 +2870,31 @@ std::optional<PathDiagnosticBuilder> PathDiagnosticBuilder::findValidReport(
         // If crosscheck is enabled, remove all visitors, add the refutation
         // visitor and check again
         R->clearVisitors();
-        R->addVisitor<FalsePositiveRefutationBRVisitor>();
+        Z3CrosscheckVisitor::Z3Result CrosscheckResult;
+        R->addVisitor<Z3CrosscheckVisitor>(CrosscheckResult);
 
         // We don't overwrite the notes inserted by other visitors because the
         // refutation manager does not add any new note to the path
         generateVisitorsDiagnostics(R, BugPath->ErrorNode, BRC);
+        switch (Z3CrosscheckOracle::interpretQueryResult(CrosscheckResult)) {
+        case Z3CrosscheckOracle::RejectReport:
+          ++NumTimesReportRefuted;
+          R->markInvalid("Infeasible constraints", /*Data=*/nullptr);
+          continue;
+        case Z3CrosscheckOracle::AcceptReport:
+          ++NumTimesReportPassesZ3;
+          break;
+        }
       }
 
-      // Check if the bug is still valid
-      if (R->isValid())
-        return PathDiagnosticBuilder(
-            std::move(BRC), std::move(BugPath->BugPath), BugPath->Report,
-            BugPath->ErrorNode, std::move(visitorNotes));
+      assert(R->isValid());
+      return PathDiagnosticBuilder(std::move(BRC), std::move(BugPath->BugPath),
+                                   BugPath->Report, BugPath->ErrorNode,
+                                   std::move(visitorNotes));
     }
   }
 
+  ++NumTimesReportEQClassWasExhausted;
   return {};
 }
 
diff --git a/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp b/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp
index 487a3bd16b674..68dac65949117 100644
--- a/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp
+++ b/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp
@@ -3446,82 +3446,6 @@ UndefOrNullArgVisitor::VisitNode(const ExplodedNode *N, BugReporterContext &BRC,
   return nullptr;
 }
 
-//===----------------------------------------------------------------------===//
-// Implementation of FalsePositiveRefutationBRVisitor.
-//===----------------------------------------------------------------------===//
-
-FalsePositiveRefutationBRVisitor::FalsePositiveRefutationBRVisitor()
-    : Constraints(ConstraintMap::Factory().getEmptyMap()) {}
-
-void FalsePositiveRefutationBRVisitor::finalizeVisitor(
-    BugReporterContext &BRC, const ExplodedNode *EndPathNode,
-    PathSensitiveBugReport &BR) {
-  // Collect new constraints
-  addConstraints(EndPathNode, /*OverwriteConstraintsOnExistingSyms=*/true);
-
-  // Create a refutation manager
-  llvm::SMTSolverRef RefutationSolver = llvm::CreateZ3Solver();
-  ASTContext &Ctx = BRC.getASTContext();
-
-  // Add constraints to the solver
-  for (const auto &I : Constraints) {
-    const SymbolRef Sym = I.first;
-    auto RangeIt = I.second.begin();
-
-    llvm::SMTExprRef SMTConstraints = SMTConv::getRangeExpr(
-        RefutationSolver, Ctx, Sym, RangeIt->From(), RangeIt->To(),
-        /*InRange=*/true);
-    while ((++RangeIt) != I.second.end()) {
-      SMTConstraints = RefutationSolver->mkOr(
-          SMTConstraints, SMTConv::getRangeExpr(RefutationSolver, Ctx, Sym,
-                                                RangeIt->From(), RangeIt->To(),
-                                                /*InRange=*/true));
-    }
-
-    RefutationSolver->addConstraint(SMTConstraints);
-  }
-
-  // And check for satisfiability
-  std::optional<bool> IsSAT = RefutationSolver->check();
-  if (!IsSAT)
-    return;
-
-  if (!*IsSAT)
-    BR.markInvalid("Infeasible constraints", EndPathNode->getLocationContext());
-}
-
-void FalsePositiveRefutationBRVisitor::addConstraints(
-    const ExplodedNode *N, bool OverwriteConstraintsOnExistingSyms) {
-  // Collect new constraints
-  ConstraintMap NewCs = getConstraintMap(N->getState());
-  ConstraintMap::Factory &CF = N->getState()->get_context<ConstraintMap>();
-
-  // Add constraints if we don't have them yet
-  for (auto const &C : NewCs) {
-    const SymbolRef &Sym = C.first;
-    if (!Constraints.contains(Sym)) {
-      // This symbol is new, just add the constraint.
-      Constraints = CF.add(Constraints, Sym, C.second);
-    } else if (OverwriteConstraintsOnExistingSyms) {
-      // Overwrite the associated constraint of the Symbol.
-      Constraints = CF.remove(Constraints, Sym);
-      Constraints = CF.add(Constraints, Sym, C.second);
-    }
-  }
-}
-
-PathDiagnosticPieceRef FalsePositiveRefutationBRVisitor::VisitNode(
-    const ExplodedNode *N, BugReporterContext &, PathSensitiveBugReport &) {
-  addConstraints(N, /*OverwriteConstraintsOnExistingSyms=*/false);
-  return nullptr;
-}
-
-void FalsePositiveRefutationBRVisitor::Profile(
-    llvm::FoldingSetNodeID &ID) const {
-  static int Tag = 0;
-  ID.AddPointer(&Tag);
-}
-
 //===----------------------------------------------------------------------===//
 // Implementation of TagVisitor.
 //===----------------------------------------------------------------------===//
diff --git a/clang/lib/StaticAnalyzer/Core/CMakeLists.txt b/clang/lib/StaticAnalyzer/Core/CMakeLists.txt
index 8672876c0608d..fb9394a519eb7 100644
--- a/clang/lib/StaticAnalyzer/Core/CMakeLists.txt
+++ b/clang/lib/StaticAnalyzer/Core/CMakeLists.txt
@@ -51,6 +51,7 @@ add_clang_library(clangStaticAnalyzerCore
   SymbolManager.cpp
   TextDiagnostics.cpp
   WorkList.cpp
+  Z3CrosscheckVisitor.cpp
 
   LINK_LIBS
   clangAST
diff --git a/clang/lib/StaticAnalyzer/Core/Z3CrosscheckVisitor.cpp b/clang/lib/StaticAnalyzer/Core/Z3CrosscheckVisitor.cpp
new file mode 100644
index 0000000000000..20cd8434cbdc6
--- /dev/null
+++ b/clang/lib/StaticAnalyzer/Core/Z3CrosscheckVisitor.cpp
@@ -0,0 +1,118 @@
+//===- Z3CrosscheckVisitor.h - Crosscheck reports with Z3 -------*- C++ -*-===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+//  This file declares the visitor and utilities around it for Z3 report
+//  refutation.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/StaticAnalyzer/Core/BugReporter/Z3CrosscheckVisitor.h"
+#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/SMTConv.h"
+#include "llvm/ADT/Statistic.h"
+#include "llvm/Support/SMTAPI.h"
+
+#define DEBUG_TYPE "Z3CrosscheckOracle"
+
+STATISTIC(NumZ3QueriesDone, "Number of Z3 queries done");
+STATISTIC(NumTimesZ3TimedOut, "Number of times Z3 query timed out");
+
+STATISTIC(NumTimesZ3QueryAcceptsReport,
+          "Number of Z3 queries accepting a report");
+STATISTIC(NumTimesZ3QueryRejectReport,
+          "Number of Z3 queries rejecting a report");
+
+using namespace clang;
+using namespace ento;
+
+Z3CrosscheckVisitor::Z3CrosscheckVisitor(Z3CrosscheckVisitor::Z3Result &Result)
+    : Constraints(ConstraintMap::Factory().getEmptyMap()), Result(Result) {}
+
+void Z3CrosscheckVisitor::finalizeVisitor(BugReporterContext &BRC,
+                                          const ExplodedNode *EndPathNode,
+                                          PathSensitiveBugReport &BR) {
+  // Collect new constraints
+  addConstraints(EndPathNode, /*OverwriteConstraintsOnExistingSyms=*/true);
+
+  // Create a refutation manager
+  llvm::SMTSolverRef RefutationSolver = llvm::CreateZ3Solver();
+  RefutationSolver->setBoolParam("model", true);        // Enable model finding
+  RefutationSolver->setUnsignedParam("timeout", 15000); // ms
+
+  ASTContext &Ctx = BRC.getASTContext();
+
+  // Add constraints to the solver
+  for (const auto &[Sym, Range] : Constraints) {
+    auto RangeIt = Range.begin();
+
+    llvm::SMTExprRef SMTConstraints = SMTConv::getRangeExpr(
+        RefutationSolver, Ctx, Sym, RangeIt->From(), RangeIt->To(),
+        /*InRange=*/true);
+    while ((++RangeIt) != Range.end()) {
+      SMTConstraints = RefutationSolver->mkOr(
+          SMTConstraints, SMTConv::getRangeExpr(RefutationSolver, Ctx, Sym,
+                                                RangeIt->From(), RangeIt->To(),
+                                                /*InRange=*/true));
+    }
+    RefutationSolver->addConstraint(SMTConstraints);
+  }
+
+  // And check for satisfiability
+  std::optional<bool> IsSAT = RefutationSolver->check();
+  Result = Z3Result{IsSAT};
+}
+
+void Z3CrosscheckVisitor::addConstraints(
+    const ExplodedNode *N, bool OverwriteConstraintsOnExistingSyms) {
+  // Collect new constraints
+  ConstraintMap NewCs = getConstraintMap(N->getState());
+  ConstraintMap::Factory &CF = N->getState()->get_context<ConstraintMap>();
+
+  // Add constraints if we don't have them yet
+  for (auto const &[Sym, Range] : NewCs) {
+    if (!Constraints.contains(Sym)) {
+      // This symbol is new, just add the constraint.
+      Constraints = CF.add(Constraints, Sym, Range);
+    } else if (OverwriteConstraintsOnExistingSyms) {
+      // Overwrite the associated constraint of the Symbol.
+      Constraints = CF.remove(Constraints, Sym);
+      Constraints = CF.add(Constraints, Sym, Range);
+    }
+  }
+}
+
+PathDiagnosticPieceRef
+Z3CrosscheckVisitor::VisitNode(const ExplodedNode *N, BugReporterContext &,
+                               PathSensitiveBugReport &) {
+  addConstraints(N, /*OverwriteConstraintsOnExistingSyms=*/false);
+  return nullptr;
+}
+
+void Z3CrosscheckVisitor::Profile(llvm::FoldingSetNodeID &ID) const {
+  static int Tag = 0;
+  ID.AddPointer(&Tag);
+}
+
+Z3CrosscheckOracle::Z3Decision Z3CrosscheckOracle::interpretQueryResult(
+    const Z3CrosscheckVisitor::Z3Result &Query) {
+  ++NumZ3QueriesDone;
+
+  if (!Query.IsSAT.has_value()) {
+    // For backward compatibility, let's accept the first timeout.
+    ++NumTimesZ3TimedOut;
+    return AcceptReport;
+  }
+
+  if (Query.IsSAT.value()) {
+    ++NumTimesZ3QueryAcceptsReport;
+    return AcceptReport; // sat
+  }
+
+  ++NumTimesZ3QueryRejectReport;
+  return RejectReport; // unsat
+}
diff --git a/clang/test/Analysis/z3/crosscheck-statistics.c b/clang/test/Analysis/z3/crosscheck-statistics.c
new file mode 100644
index 0000000000000..7192824c5be31
--- /dev/null
+++ b/clang/test/Analysis/z3/crosscheck-statistics.c
@@ -0,0 +1,33 @@
+// RUN: %clang_analyze_cc1 -analyzer-checker=core -verify %s  \
+// RUN:   -analyzer-config crosscheck-with-z3=true \
+// RUN:   -analyzer-stats 2>&1 | FileCheck %s
+
+// REQUIRES: z3
+
+// expected-error@1 {{Z3 refutation rate:1/2}}
+
+int accepting(int n) {
+  if (n == 4) {
+    n = n / (n-4); // expected-warning {{Division by zero}}
+  }
+  return n;
+}
+
+int rejecting(int n, int x) {
+  // Let's make the path infeasible.
+  if (2 < x && x < 5 && x*x == x*x*x) {
+    // Have the same condition as in 'accepting'.
+    if (n == 4) {
+      n = x / (n-4); // no-warning: refuted
+    }
+  }
+  return n;
+}
+
+// CHECK:       1 BugReporter         - Number of times all reports of an equivalence class was refuted
+// CHECK-NEXT:  1 BugReporter         - Number of reports passed Z3
+// CHECK-NEXT:  1 BugReporter         - Number of reports refuted by Z3
+
+// CHECK:       1 Z3CrosscheckVisitor - Number of Z3 queries accepting a report
+// CHECK-NEXT:  1 Z3CrosscheckVisitor - Number of Z3 queries rejecting a report
+// CHECK-NEXT:  2 Z3CrosscheckVisitor - Number of Z3 queries done
diff --git a/clang/unittests/StaticAnalyzer/CMakeLists.txt b/clang/unittests/StaticAnalyzer/CMakeLists.txt
index ff34d5747cc81..dcc557b44fb31 100644
--- a/clang/unittests/StaticAnalyzer/CMakeLists.txt
+++ b/clang/unittests/StaticAnalyzer/CMakeLists.txt
@@ -21,6 +21,7 @@ add_clang_unittest(StaticAnalysisTests
   SymbolReaperTest.cpp
   SValTest.cpp
   TestReturnValueUnderConstruction.cpp
+  Z3CrosscheckOracleTest.cpp
   )
 
 clang_target_link_libraries(StaticAnalysisTests
diff --git a/clang/unittests/StaticAnalyzer/Z3CrosscheckOracleTest.cpp b/clang/unittests/StaticAnalyzer/Z3CrosscheckOracleTest.cpp
new file mode 100644
index 0000000000000..efad4dd3f03b9
--- /dev/null
+++ b/clang/unittests/StaticAnalyzer/Z3CrosscheckOracleTest.cpp
@@ -0,0 +1,59 @@
+//===- 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/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 std::optional<bool> SAT = true;
+static constexpr std::optional<bool> UNSAT = false;
+static constexpr std::optional<bool> UNDEF = std::nullopt;
+
+namespace {
+
+struct Z3CrosscheckOracleTest : public testing::Test {
+  Z3Decision interpretQueryResult(const Z3Result &Result) const {
+    return Z3CrosscheckOracle::interpretQueryResult(Result);
+  }
+};
+
+TEST_F(Z3CrosscheckOracleTest, AcceptsFirstSAT) {
+  ASSERT_EQ(AcceptReport, interpretQueryResult({SAT}));
+}
+
+TEST_F(Z3CrosscheckOracleTest, AcceptsSAT) {
+  ASSERT_EQ(RejectReport, interpretQueryResult({UNSAT}));
+  ASSERT_EQ(AcceptReport, interpretQueryResult({SAT}));
+}
+
+TEST_F(Z3CrosscheckOracleTest, AcceptsFirstTimeout) {
+  ASSERT_EQ(AcceptReport, interpretQueryResult({UNDEF}));
+}
+
+TEST_F(Z3...
[truncated]

Created using spr 1.3.4
Copy link
Contributor

@NagyDonat NagyDonat left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. I didn't deeply analyze all the small details of the commit, but it is clearly NFC (builds better infrastructure for follow-up commits), the implementation is clear and elegant (slightly better quality than what I can write) and the tests demonstrate that it's working.

IMO feel free to merge this and continue with the next commit.

@steakhal
Copy link
Contributor Author

Thanks for the review!

LGTM. I didn't deeply analyze all the small details of the commit, but it is clearly NFC (builds better infrastructure for follow-up commits), the implementation is clear and elegant (slightly better quality than what I can write) and the tests demonstrate that it's working.

The problem is that currently one can't run tests with Z3 crosschecking. Consequently, no tests cover this functionality.
The unit-tests in the next PR tests the Z3 oracle, which is not tight to the actual Z3 library in an ways.

IMO feel free to merge this and continue with the next commit.

Please have a second look at the llvm-related changes. Z3 has a really dangerous C API. I shot myself in the foot literally 3 times while working on this PR.

And btw notice that I removed the proof=false configuration as that is the default AFAIK, so that's why that is missing.

@steakhal
Copy link
Contributor Author

You can continue the review with #95129 - which is stacked on top of this one.

@NagyDonat
Copy link
Contributor

Please have a second look at the llvm-related changes. Z3 has a really dangerous C API. I shot myself in the foot literally 3 times while working on this PR.

And btw notice that I removed the proof=false configuration as that is the default AFAIK, so that's why that is missing.

OK, I'll re-check the footgun handling either today or tomorrow.

Copy link
Contributor

@NagyDonat NagyDonat left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to review the resource management code thoroughly, but I admit that I'm not skilled in this area -- I'm used to working in modern C++ and Python where these footguns are less relevant.

I found two classed that (if I understand correctly) violate the "rule of three", but I didn't find any acute issues.

Created using spr 1.3.4
@steakhal
Copy link
Contributor Author

Implement move-only rule-of-5 for:

  • Z3Config
  • Z3Context

@steakhal steakhal merged commit 89c26f6 into main Jun 18, 2024
4 of 6 checks passed
@steakhal steakhal deleted the users/steakhal/spr/analyzernfc-reorganize-z3-report-refutation branch June 18, 2024 07:42
@mikaelholmen
Copy link
Collaborator

Hi @steakhal and @NagyDonat ,

Does this patch have requirements on the Z3 version?

I get
LLVM ERROR: Z3 error: unknown parameter 'timeout'
for the following testcases

Failed Tests (3):
  Clang-Unit :: StaticAnalyzer/./StaticAnalysisTests/24/188
  Clang-Unit :: StaticAnalyzer/./StaticAnalysisTests/25/188
  Clang-Unit :: StaticAnalyzer/./StaticAnalysisTests/26/188

@steakhal
Copy link
Contributor Author

steakhal commented Jun 18, 2024

Hi @steakhal and @NagyDonat ,

Does this patch have requirements on the Z3 version?

I get
LLVM ERROR: Z3 error: unknown parameter 'timeout'
for the following testcases

Failed Tests (3):
  Clang-Unit :: StaticAnalyzer/./StaticAnalysisTests/24/188
  Clang-Unit :: StaticAnalyzer/./StaticAnalysisTests/25/188
  Clang-Unit :: StaticAnalyzer/./StaticAnalysisTests/26/188

I'm not aware. I used the latest Z3 when testing. I'll have a look.

Edit: Do you have a build bot link, such that I could checkout the exact environment?

@mikaelholmen
Copy link
Collaborator

Hi @steakhal and @NagyDonat ,
Does this patch have requirements on the Z3 version?
I get
LLVM ERROR: Z3 error: unknown parameter 'timeout'
for the following testcases

Failed Tests (3):
  Clang-Unit :: StaticAnalyzer/./StaticAnalysisTests/24/188
  Clang-Unit :: StaticAnalyzer/./StaticAnalysisTests/25/188
  Clang-Unit :: StaticAnalyzer/./StaticAnalysisTests/26/188

I'm not aware. I used the latest Z3 when testing. I'll have a look.

Edit: Do you have a build bot link, such that I could checkout the exact environment?

Unfortunately not, I'm on our internal servers.
From what I can see we're using Z3 4.8.8-1

@steakhal
Copy link
Contributor Author

From the logs you sent, its not clear to me which statianalyzer test fails. Could you point me to an exact gtest or file linenumber pair?

@mikaelholmen
Copy link
Collaborator

It seems to be the following tests

FalsePositiveRefutationBRVisitorTestBase.UnSatInTheMiddleNoReport
FalsePositiveRefutationBRVisitorTestBase.UnSatAtErrorNodeDueToRefinedConstraintNoReport
FalsePositiveRefutationBRVisitorTestBase.UnSatAtErrorNodeWithNewSymbolNoReport

and they fail like

[ RUN      ] FalsePositiveRefutationBRVisitorTestBase.UnSatAtErrorNodeWithNewSymbolNoReport
LLVM ERROR: Z3 error: unknown parameter 'timeout'
Legal parameters are:
  arith.auto_config_simplex (bool) (default: false)
  arith.bprop_on_pivoted_rows (bool) (default: true)
  arith.branch_cut_ratio (unsigned int) (default: 2)
  arith.dump_lemmas (bool) (default: false)
  arith.eager_eq_axioms (bool) (default: true)
  arith.enable_hnf (bool) (default: true)
  arith.greatest_error_pivot (bool) (default: false)
  arith.ignore_int (bool) (default: false)
  arith.int_eq_branch (bool) (default: false)
  arith.min (bool) (default: false)
  arith.nl (bool) (default: true)
  arith.nl.branching (bool) (default: true)
  arith.nl.gb (bool) (default: true)
  arith.nl.gr_q (unsigned int) (default: 10)
  arith.nl.grobner (bool) (default: true)
  arith.nl.grobner_cnfl_to_report (unsigned int) (default: 1)
  arith.nl.grobner_eqs_growth (unsigned int) (default: 10)
  arith.nl.grobner_expr_degree_growth (unsigned int) (default: 2)
  arith.nl.grobner_expr_size_growth (unsigned int) (default: 2)
  arith.nl.grobner_max_simplified (unsigned int) (default: 10000)
  arith.nl.grobner_subs_fixed (unsigned int) (default: 2)
  arith.nl.horner (bool) (default: true)
  arith.nl.horner_frequency (unsigned int) (default: 4)
  arith.nl.horner_row_length_limit (unsigned int) (default: 10)
  arith.nl.horner_subs_fixed (unsigned int) (default: 2)
  arith.nl.order (bool) (default: true)
  arith.nl.rounds (unsigned int) (default: 1024)
  arith.nl.tangents (bool) (default: true)
  arith.print_ext_var_names (bool) (default: false)
  arith.print_stats (bool) (default: false)
  arith.propagate_eqs (bool) (default: true)
  arith.propagation_mode (unsigned int) (default: 2)
  arith.random_initial_value (bool) (default: false)
  arith.reflect (bool) (default: true)
  arith.rep_freq (unsigned int) (default: 0)
  arith.simplex_strategy (unsigned int) (default: 0)
  arith.solver (unsigned int) (default: 2)
  array.extensional (bool) (default: true)
  array.weak (bool) (default: false)
  auto_config (bool) (default: true)
  bv.enable_int2bv (bool) (default: true)
  bv.reflect (bool) (default: true)
  case_split (unsigned int) (default: 1)
  clause_proof (bool) (default: false)
  core.extend_nonlocal_patterns (bool) (default: false)
  core.extend_patterns (bool) (default: false)
  core.extend_patterns.max_distance (unsigned int) (default: 4294967295)
  core.minimize (bool) (default: false)
  core.validate (bool) (default: false)
  dack (unsigned int) (default: 1)
  dack.eq (bool) (default: false)
  dack.factor (double) (default: 0.1)
  dack.gc (unsigned int) (default: 2000)
  dack.gc_inv_decay (double) (default: 0.8)
  dack.threshold (unsigned int) (default: 10)
  delay_units (bool) (default: false)
  delay_units_threshold (unsigned int) (default: 32)
  dt_lazy_splits (unsigned int) (default: 1)
  ematching (bool) (default: true)
  induction (bool) (default: false)
  lemma_gc_strategy (unsigned int) (default: 0)
  logic (symbol) (default: )
  macro_finder (bool) (default: false)
  max_conflicts (unsigned int) (default: 4294967295)
  mbqi (bool) (default: true)
  mbqi.force_template (unsigned int) (default: 10)
  mbqi.id (string) (default: )
  mbqi.max_cexs (unsigned int) (default: 1)
  mbqi.max_cexs_incr (unsigned int) (default: 0)
  mbqi.max_iterations (unsigned int) (default: 1000)
  mbqi.trace (bool) (default: false)
  model (bool) (default: true)
  pb.conflict_frequency (unsigned int) (default: 1000)
  pb.learn_complements (bool) (default: true)
  phase_selection (unsigned int) (default: 3)
  proof (bool) (default: false)
  pull_nested_quantifiers (bool) (default: false)
  qi.cost (string) (default: (+ weight generation))
  qi.eager_threshold (double) (default: 10.0)
  qi.lazy_threshold (double) (default: 20.0)
  qi.max_instances (unsigned int) (default: 4294967295)
  qi.max_multi_patterns (unsigned int) (default: 0)
  qi.profile (bool) (default: false)
  qi.profile_freq (unsigned int) (default: 4294967295)
  qi.quick_checker (unsigned int) (default: 0)
  quasi_macros (bool) (default: false)
  random_seed (unsigned int) (default: 0)
  refine_inj_axioms (bool) (default: true)
  relevancy (unsigned int) (default: 2)
  restart.max (unsigned int) (default: 4294967295)
  restart_factor (double) (default: 1.1)
  restart_strategy (unsigned int) (default: 1)
  restricted_quasi_macros (bool) (default: false)
  seq.split_w_len (bool) (default: true)
  seq.validate (bool) (default: false)
  str.aggressive_length_testing (bool) (default: false)
  str.aggressive_unroll_testing (bool) (default: true)
  str.aggressive_value_testing (bool) (default: false)
  str.fast_length_tester_cache (bool) (default: false)
  str.fast_value_tester_cache (bool) (default: true)
  str.fixed_length_naive_cex (bool) (default: true)
  str.fixed_length_refinement (bool) (default: false)
  str.overlap_priority (double) (default: -0.1)
  str.regex_automata_difficulty_threshold (unsigned int) (default: 1000)
  str.regex_automata_failed_automaton_threshold (unsigned int) (default: 10)
  str.regex_automata_failed_intersection_threshold (unsigned int) (default: 10)
  str.regex_automata_intersection_difficulty_threshold (unsigned int) (default: 1000)
  str.regex_automata_length_attempt_threshold (unsigned int) (default: 10)
  str.string_constant_cache (bool) (default: true)
  str.strong_arrangements (bool) (default: true)
  string_solver (symbol) (default: seq)
  theory_aware_branching (bool) (default: false)
  theory_case_split (bool) (default: false)
  threads (unsigned int) (default: 1)
  threads.max_conflicts (unsigned int) (default: 400)
  unsat_core (bool) (default: false)
[stack trace]

@steakhal
Copy link
Contributor Author

Alright. I had a look and it's interesting.
For me, it crashes with LLVM ERROR: Z3 error: unknown parameter 'rlimit', but that shouldn't matter.
Actually rlimit should be available from 4.5.0, according to the release notes of Z3.
That release note does not mention rlimit or timeout basically at all. However, by manual checking it seems that the from the very next patch release (4.8.9+) it compiles and passes the test you mentioned failing.
I'd recommend you to upgrade Z3 to unblock your workflow.
Given this, I'll propose an RFC later (in the upcoming weeks) to bump the minimal Z3 version to 4.8.9 (by one patch release), to follow the llvm guidelines.

Thanks for reporting this, and I hope this helps.

@mikaelholmen
Copy link
Collaborator

With the next patch eacc3b3 it fails on rlimit for me, but on this patch it's on timeout.

@steakhal
Copy link
Contributor Author

With the next patch eacc3b3 it fails on rlimit for me, but on this patch it's on timeout.

What do you suggest? Do you know a reliable minimal Z3 version which we should require?

FYI I published this post about raising Z3 version requirement.

@mikaelholmen
Copy link
Collaborator

I don't know really, I just noticed that main suddenly broke for us and bisected to this patch.

@steakhal
Copy link
Contributor Author

steakhal commented Jun 18, 2024

I don't know really, I just noticed that main suddenly broke for us and bisected to this patch.

If after the second patch the tests pass then I'm contemplated to say I'd go for upgrading Z3 requirements to 4.8.9, and accept that with my first commit would have failed the CI/tests with an old(er) Z3.
On my system, all StaticAnalysisTests pass with 4.8.9 on main.
Are you okay with upgrading Z3 to at least 4.8.9 or preferably to latest (4.13.0)?

@mikaelholmen
Copy link
Collaborator

I don't know really, I just noticed that main suddenly broke for us and bisected to this patch.

If after the second patch the tests pass then I'm contemplated to say I'd go for upgrading Z3 requirements to 4.8.9, and accept that with my first commit would have failed the CI/tests with an old(er) Z3. On my system, all StaticAnalysisTests pass with 4.8.9 on main. Are you okay with upgrading Z3 to at least 4.8.9 or preferably to latest (4.13.0)?

The testcases fail botth on this and on the next patch. I just commented here because this is the first patch there are problems.

I have to check with my team why we are on 4.8.8 and what we can upgrade to. In any case that won't happen today.
But I'm surprised that requirements were suddenly raised without warning.

@steakhal
Copy link
Contributor Author

@mikaelholmen Please approve the revert PR then.

steakhal added a commit that referenced this pull request Jun 18, 2024
Requested in:
#95128 (comment)

Revert "[analyzer] Harden safeguards for Z3 query times"
Revert "[analyzer][NFC] Reorganize Z3 report refutation"

This reverts commit eacc3b3.
This reverts commit 89c26f6.
@mikaelholmen
Copy link
Collaborator

@mikaelholmen Please approve the revert PR then.

Done. Thank you!

steakhal added a commit that referenced this pull request Jul 1, 2024
This is exactly as originally landed in #95128,
but now the minimal Z3 version was increased to meet this change in #96682.

https://discourse.llvm.org/t/bump-minimal-z3-requirements-from-4-7-1-to-4-8-9/79664/4

---

This change keeps existing behavior, namely that if we hit a Z3 timeout
we will accept the report as "satisfiable".

This prepares for the commit "Harden safeguards for Z3 query times".
https://discourse.llvm.org/t/analyzer-rfc-taming-z3-query-times/79520

(cherry picked from commit 89c26f6)
lravenclaw pushed a commit to lravenclaw/llvm-project that referenced this pull request Jul 3, 2024
This is exactly as originally landed in llvm#95128,
but now the minimal Z3 version was increased to meet this change in llvm#96682.

https://discourse.llvm.org/t/bump-minimal-z3-requirements-from-4-7-1-to-4-8-9/79664/4

---

This change keeps existing behavior, namely that if we hit a Z3 timeout
we will accept the report as "satisfiable".

This prepares for the commit "Harden safeguards for Z3 query times".
https://discourse.llvm.org/t/analyzer-rfc-taming-z3-query-times/79520

(cherry picked from commit 89c26f6)
kbluck pushed a commit to kbluck/llvm-project that referenced this pull request Jul 6, 2024
This is exactly as originally landed in llvm#95128,
but now the minimal Z3 version was increased to meet this change in llvm#96682.

https://discourse.llvm.org/t/bump-minimal-z3-requirements-from-4-7-1-to-4-8-9/79664/4

---

This change keeps existing behavior, namely that if we hit a Z3 timeout
we will accept the report as "satisfiable".

This prepares for the commit "Harden safeguards for Z3 query times".
https://discourse.llvm.org/t/analyzer-rfc-taming-z3-query-times/79520

(cherry picked from commit 89c26f6)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:static analyzer clang Clang issues not falling into any other category llvm:support
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants