
## Purpose This patch makes a minor changes to LLVM and Clang so that LLVM can be built as a Windows DLL with `clang-cl`. These changes were not required for building a Windows DLL with MSVC. ## Background The Windows DLL effort is tracked in #109483. Additional context is provided in [this discourse](https://discourse.llvm.org/t/psa-annotating-llvm-public-interface/85307), and documentation for `LLVM_ABI` and related annotations is found in the LLVM repo [here](https://github.com/llvm/llvm-project/blob/main/llvm/docs/InterfaceExportAnnotations.rst). ## Overview Specific changes made in this patch: - Remove `constexpr` fields that reference DLL exported symbols. These symbols cannot be resolved at compile time when building a Windows DLL using `clang-cl`, so they cannot be `constexpr`. Instead, they are made `const` and initialized in the implementation file rather than at declaration in the header. - Annotate symbols now defined out-of-line with `LLVM_ABI` so they are exported when building as a shared library. - Explicitly add default copy assignment operator for `ELFFile` to resolve a compiler warning. ## Validation Local builds and tests to validate cross-platform compatibility. This included llvm, clang, and lldb on the following configurations: - Windows with MSVC - Windows with Clang - Linux with GCC - Linux with Clang - Darwin with Clang
189 lines
7.3 KiB
C++
189 lines
7.3 KiB
C++
//===- Z3CrosscheckVisitor.cpp - 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/AnalyzerOptions.h"
|
|
#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
|
|
#include "clang/StaticAnalyzer/Core/PathSensitive/EntryPointStats.h"
|
|
#include "clang/StaticAnalyzer/Core/PathSensitive/SMTConv.h"
|
|
#include "llvm/Support/SMTAPI.h"
|
|
#include "llvm/Support/Timer.h"
|
|
|
|
#define DEBUG_TYPE "Z3CrosscheckOracle"
|
|
|
|
// Queries attempted at most `Z3CrosscheckMaxAttemptsPerQuery` number of times.
|
|
// Multiple `check()` calls might be called on the same query if previous
|
|
// attempts of the same query resulted in UNSAT for any reason. Each query is
|
|
// only counted once for these statistics, the retries are not accounted for.
|
|
STAT_COUNTER(NumZ3QueriesDone, "Number of Z3 queries done");
|
|
STAT_COUNTER(NumTimesZ3TimedOut, "Number of times Z3 query timed out");
|
|
STAT_COUNTER(NumTimesZ3ExhaustedRLimit,
|
|
"Number of times Z3 query exhausted the rlimit");
|
|
STAT_COUNTER(
|
|
NumTimesZ3SpendsTooMuchTimeOnASingleEQClass,
|
|
"Number of times report equivalenece class was cut because it spent "
|
|
"too much time in Z3");
|
|
|
|
STAT_COUNTER(NumTimesZ3QueryAcceptsReport,
|
|
"Number of Z3 queries accepting a report");
|
|
STAT_COUNTER(NumTimesZ3QueryRejectReport,
|
|
"Number of Z3 queries rejecting a report");
|
|
STAT_COUNTER(NumTimesZ3QueryRejectEQClass,
|
|
"Number of times rejecting an report equivalenece class");
|
|
|
|
STAT_COUNTER(TimeSpentSolvingZ3Queries,
|
|
"Total time spent solving Z3 queries excluding retries");
|
|
STAT_MAX(MaxTimeSpentSolvingZ3Queries,
|
|
"Max time spent solving a Z3 query excluding retries");
|
|
|
|
using namespace clang;
|
|
using namespace ento;
|
|
|
|
Z3CrosscheckVisitor::Z3CrosscheckVisitor(Z3CrosscheckVisitor::Z3Result &Result,
|
|
const AnalyzerOptions &Opts)
|
|
: Constraints(ConstraintMap::Factory().getEmptyMap()), Result(Result),
|
|
Opts(Opts) {}
|
|
|
|
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();
|
|
if (Opts.Z3CrosscheckRLimitThreshold)
|
|
RefutationSolver->setUnsignedParam("rlimit",
|
|
Opts.Z3CrosscheckRLimitThreshold);
|
|
if (Opts.Z3CrosscheckTimeoutThreshold)
|
|
RefutationSolver->setUnsignedParam("timeout",
|
|
Opts.Z3CrosscheckTimeoutThreshold); // 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);
|
|
}
|
|
|
|
auto GetUsedRLimit = [](const llvm::SMTSolverRef &Solver) {
|
|
return Solver->getStatistics()->getUnsigned("rlimit count");
|
|
};
|
|
|
|
auto AttemptOnce = [&](const llvm::SMTSolverRef &Solver) -> Z3Result {
|
|
auto getCurrentTime = llvm::TimeRecord::getCurrentTime;
|
|
unsigned InitialRLimit = GetUsedRLimit(Solver);
|
|
double Start = getCurrentTime(/*Start=*/true).getWallTime();
|
|
std::optional<bool> IsSAT = Solver->check();
|
|
double End = getCurrentTime(/*Start=*/false).getWallTime();
|
|
return {
|
|
IsSAT,
|
|
static_cast<unsigned>((End - Start) * 1000),
|
|
GetUsedRLimit(Solver) - InitialRLimit,
|
|
};
|
|
};
|
|
|
|
// And check for satisfiability
|
|
unsigned MinQueryTimeAcrossAttempts = std::numeric_limits<unsigned>::max();
|
|
for (unsigned I = 0; I < Opts.Z3CrosscheckMaxAttemptsPerQuery; ++I) {
|
|
Result = AttemptOnce(RefutationSolver);
|
|
Result.Z3QueryTimeMilliseconds =
|
|
std::min(MinQueryTimeAcrossAttempts, Result.Z3QueryTimeMilliseconds);
|
|
if (Result.IsSAT.has_value())
|
|
return;
|
|
}
|
|
}
|
|
|
|
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;
|
|
AccumulatedZ3QueryTimeInEqClass += Query.Z3QueryTimeMilliseconds;
|
|
TimeSpentSolvingZ3Queries += Query.Z3QueryTimeMilliseconds;
|
|
MaxTimeSpentSolvingZ3Queries.updateMax(Query.Z3QueryTimeMilliseconds);
|
|
|
|
if (Query.IsSAT && Query.IsSAT.value()) {
|
|
++NumTimesZ3QueryAcceptsReport;
|
|
return AcceptReport;
|
|
}
|
|
|
|
// Suggest cutting the EQClass if certain heuristics trigger.
|
|
if (Opts.Z3CrosscheckTimeoutThreshold &&
|
|
Query.Z3QueryTimeMilliseconds >= Opts.Z3CrosscheckTimeoutThreshold) {
|
|
++NumTimesZ3TimedOut;
|
|
++NumTimesZ3QueryRejectEQClass;
|
|
return RejectEQClass;
|
|
}
|
|
|
|
if (Opts.Z3CrosscheckRLimitThreshold &&
|
|
Query.UsedRLimit >= Opts.Z3CrosscheckRLimitThreshold) {
|
|
++NumTimesZ3ExhaustedRLimit;
|
|
++NumTimesZ3QueryRejectEQClass;
|
|
return RejectEQClass;
|
|
}
|
|
|
|
if (Opts.Z3CrosscheckEQClassTimeoutThreshold &&
|
|
AccumulatedZ3QueryTimeInEqClass >
|
|
Opts.Z3CrosscheckEQClassTimeoutThreshold) {
|
|
++NumTimesZ3SpendsTooMuchTimeOnASingleEQClass;
|
|
++NumTimesZ3QueryRejectEQClass;
|
|
return RejectEQClass;
|
|
}
|
|
|
|
// If no cutoff heuristics trigger, and the report is "unsat" or "undef",
|
|
// then reject the report.
|
|
++NumTimesZ3QueryRejectReport;
|
|
return RejectReport;
|
|
}
|