Skip to content

Commit aef1dbe

Browse files
author
git apple-llvm automerger
committed
Merge commit '55391f85acc7' from llvm.org/main into next
2 parents 5b241ed + 55391f8 commit aef1dbe

File tree

10 files changed

+208
-50
lines changed

10 files changed

+208
-50
lines changed

clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.def

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13-
#ifndef LLVM_ADT_STRINGREF_H
13+
#ifndef LLVM_CLANG_STATICANALYZER_CORE_ANALYZEROPTIONS_H
1414
#error This .def file is expected to be included in translation units where \
15-
"llvm/ADT/StringRef.h" is already included!
15+
"clang/StaticAnalyzer/Core/AnalyzerOptions.h" is already included!
1616
#endif
1717

1818
#ifdef ANALYZER_OPTION
@@ -193,6 +193,8 @@ ANALYZER_OPTION(
193193
"with \"crosscheck-with-z3-timeout-threshold\" of 300 ms, would nicely "
194194
"guarantee that no bug report equivalence class can take longer than "
195195
"1 second, effectively mitigating Z3 hangs during refutation. "
196+
"If there were Z3 retries, only the minimum query time is considered "
197+
"when accumulating query times within a report equivalence class. "
196198
"Set 0 for no timeout.", 0)
197199

198200
ANALYZER_OPTION(
@@ -213,6 +215,15 @@ ANALYZER_OPTION(
213215
"400'000 should on average make Z3 queries run for up to 100ms on modern "
214216
"hardware. Set 0 for unlimited.", 0)
215217

218+
ANALYZER_OPTION(
219+
PositiveAnalyzerOption, Z3CrosscheckMaxAttemptsPerQuery,
220+
"crosscheck-with-z3-max-attempts-per-query",
221+
"Set how many times the oracle is allowed to run a Z3 query. "
222+
"This must be a positive value. Set 1 to not allow any retry attempts. "
223+
"Increasing the number of attempts is often more effective at reducing "
224+
"the number of nondeterministic diagnostics than "
225+
"\"crosscheck-with-z3-timeout-threshold\" in practice.", 3)
226+
216227
ANALYZER_OPTION(bool, ShouldReportIssuesInMainSourceFile,
217228
"report-in-main-source-file",
218229
"Whether or not the diagnostic report should be always "

clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,31 @@ enum UserModeKind {
124124

125125
enum class CTUPhase1InliningKind { None, Small, All };
126126

127+
class PositiveAnalyzerOption {
128+
public:
129+
PositiveAnalyzerOption() = default;
130+
PositiveAnalyzerOption(const PositiveAnalyzerOption &) = default;
131+
PositiveAnalyzerOption &operator=(const PositiveAnalyzerOption &) = default;
132+
133+
static std::optional<PositiveAnalyzerOption> create(unsigned Val) {
134+
if (Val == 0)
135+
return std::nullopt;
136+
return PositiveAnalyzerOption{Val};
137+
}
138+
static std::optional<PositiveAnalyzerOption> create(StringRef Str) {
139+
unsigned Parsed = 0;
140+
if (Str.getAsInteger(0, Parsed))
141+
return std::nullopt;
142+
return PositiveAnalyzerOption::create(Parsed);
143+
}
144+
operator unsigned() const { return Value; }
145+
146+
private:
147+
explicit constexpr PositiveAnalyzerOption(unsigned Value) : Value(Value) {}
148+
149+
unsigned Value = 1;
150+
};
151+
127152
/// Stores options for the analyzer from the command line.
128153
///
129154
/// Some options are frontend flags (e.g.: -analyzer-output), but some are

clang/lib/Frontend/CompilerInvocation.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1317,6 +1317,25 @@ static void initOption(AnalyzerOptions::ConfigTable &Config,
13171317
<< Name << "an unsigned";
13181318
}
13191319

1320+
static void initOption(AnalyzerOptions::ConfigTable &Config,
1321+
DiagnosticsEngine *Diags,
1322+
PositiveAnalyzerOption &OptionField, StringRef Name,
1323+
unsigned DefaultVal) {
1324+
auto Parsed = PositiveAnalyzerOption::create(
1325+
getStringOption(Config, Name, std::to_string(DefaultVal)));
1326+
if (Parsed.has_value()) {
1327+
OptionField = Parsed.value();
1328+
return;
1329+
}
1330+
if (Diags && !Parsed.has_value())
1331+
Diags->Report(diag::err_analyzer_config_invalid_input)
1332+
<< Name << "a positive";
1333+
1334+
auto Default = PositiveAnalyzerOption::create(DefaultVal);
1335+
assert(Default.has_value());
1336+
OptionField = Default.value();
1337+
}
1338+
13201339
static void parseAnalyzerConfigs(AnalyzerOptions &AnOpts,
13211340
DiagnosticsEngine *Diags) {
13221341
// TODO: There's no need to store the entire configtable, it'd be plenty

clang/lib/StaticAnalyzer/Core/Z3CrosscheckVisitor.cpp

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@
2121

2222
#define DEBUG_TYPE "Z3CrosscheckOracle"
2323

24+
// Queries attempted at most `Z3CrosscheckMaxAttemptsPerQuery` number of times.
25+
// Multiple `check()` calls might be called on the same query if previous
26+
// attempts of the same query resulted in UNSAT for any reason. Each query is
27+
// only counted once for these statistics, the retries are not accounted for.
2428
STATISTIC(NumZ3QueriesDone, "Number of Z3 queries done");
2529
STATISTIC(NumTimesZ3TimedOut, "Number of times Z3 query timed out");
2630
STATISTIC(NumTimesZ3ExhaustedRLimit,
@@ -77,16 +81,32 @@ void Z3CrosscheckVisitor::finalizeVisitor(BugReporterContext &BRC,
7781
RefutationSolver->addConstraint(SMTConstraints);
7882
}
7983

80-
// And check for satisfiability
81-
llvm::TimeRecord Start = llvm::TimeRecord::getCurrentTime(/*Start=*/true);
82-
std::optional<bool> IsSAT = RefutationSolver->check();
83-
llvm::TimeRecord Diff = llvm::TimeRecord::getCurrentTime(/*Start=*/false);
84-
Diff -= Start;
85-
Result = Z3Result{
86-
IsSAT,
87-
static_cast<unsigned>(Diff.getWallTime() * 1000),
88-
RefutationSolver->getStatistics()->getUnsigned("rlimit count"),
84+
auto GetUsedRLimit = [](const llvm::SMTSolverRef &Solver) {
85+
return Solver->getStatistics()->getUnsigned("rlimit count");
86+
};
87+
88+
auto AttemptOnce = [&](const llvm::SMTSolverRef &Solver) -> Z3Result {
89+
constexpr auto getCurrentTime = llvm::TimeRecord::getCurrentTime;
90+
unsigned InitialRLimit = GetUsedRLimit(Solver);
91+
double Start = getCurrentTime(/*Start=*/true).getWallTime();
92+
std::optional<bool> IsSAT = Solver->check();
93+
double End = getCurrentTime(/*Start=*/false).getWallTime();
94+
return {
95+
IsSAT,
96+
static_cast<unsigned>((End - Start) * 1000),
97+
GetUsedRLimit(Solver) - InitialRLimit,
98+
};
8999
};
100+
101+
// And check for satisfiability
102+
unsigned MinQueryTimeAcrossAttempts = std::numeric_limits<unsigned>::max();
103+
for (unsigned I = 0; I < Opts.Z3CrosscheckMaxAttemptsPerQuery; ++I) {
104+
Result = AttemptOnce(RefutationSolver);
105+
Result.Z3QueryTimeMilliseconds =
106+
std::min(MinQueryTimeAcrossAttempts, Result.Z3QueryTimeMilliseconds);
107+
if (Result.IsSAT.has_value())
108+
return;
109+
}
90110
}
91111

92112
void Z3CrosscheckVisitor::addConstraints(

clang/test/Analysis/analyzer-config.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
// CHECK-NEXT: cplusplus.SmartPtrModeling:ModelSmartPtrDereference = false
4242
// CHECK-NEXT: crosscheck-with-z3 = false
4343
// CHECK-NEXT: crosscheck-with-z3-eqclass-timeout-threshold = 0
44+
// CHECK-NEXT: crosscheck-with-z3-max-attempts-per-query = 3
4445
// CHECK-NEXT: crosscheck-with-z3-rlimit-threshold = 0
4546
// CHECK-NEXT: crosscheck-with-z3-timeout-threshold = 15000
4647
// CHECK-NEXT: ctu-dir = ""
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Check the default config.
2+
// RUN: %clang_analyze_cc1 -analyzer-checker=debug.ConfigDumper 2>&1 \
3+
// RUN: | FileCheck %s --match-full-lines
4+
// CHECK: crosscheck-with-z3-max-attempts-per-query = 3
5+
6+
// RUN: rm -rf %t && mkdir %t
7+
// RUN: %host_cxx -shared -fPIC \
8+
// RUN: %S/z3/Inputs/MockZ3_solver_check.cpp \
9+
// RUN: -o %t/MockZ3_solver_check.so
10+
11+
// DEFINE: %{mocked_clang} = \
12+
// DEFINE: LD_PRELOAD="%t/MockZ3_solver_check.so" \
13+
// DEFINE: %clang_cc1 %s -analyze -setup-static-analyzer \
14+
// DEFINE: -analyzer-config crosscheck-with-z3=true \
15+
// DEFINE: -analyzer-checker=core
16+
17+
// DEFINE: %{attempts} = -analyzer-config crosscheck-with-z3-max-attempts-per-query
18+
19+
// RUN: not %clang_analyze_cc1 %{attempts}=0 2>&1 | FileCheck %s --check-prefix=VERIFY-INVALID
20+
// VERIFY-INVALID: invalid input for analyzer-config option 'crosscheck-with-z3-max-attempts-per-query', that expects a positive value
21+
22+
// RUN: Z3_SOLVER_RESULTS="UNDEF" %{mocked_clang} %{attempts}=1 -verify=refuted
23+
// RUN: Z3_SOLVER_RESULTS="UNSAT" %{mocked_clang} %{attempts}=1 -verify=refuted
24+
// RUN: Z3_SOLVER_RESULTS="SAT" %{mocked_clang} %{attempts}=1 -verify=accepted
25+
26+
// RUN: Z3_SOLVER_RESULTS="UNDEF,UNDEF" %{mocked_clang} %{attempts}=2 -verify=refuted
27+
// RUN: Z3_SOLVER_RESULTS="UNDEF,UNSAT" %{mocked_clang} %{attempts}=2 -verify=refuted
28+
// RUN: Z3_SOLVER_RESULTS="UNDEF,SAT" %{mocked_clang} %{attempts}=2 -verify=accepted
29+
30+
// RUN: Z3_SOLVER_RESULTS="UNDEF,UNDEF,UNDEF" %{mocked_clang} %{attempts}=3 -verify=refuted
31+
// RUN: Z3_SOLVER_RESULTS="UNDEF,UNDEF,UNSAT" %{mocked_clang} %{attempts}=3 -verify=refuted
32+
// RUN: Z3_SOLVER_RESULTS="UNDEF,UNDEF,SAT" %{mocked_clang} %{attempts}=3 -verify=accepted
33+
34+
35+
// REQUIRES: z3, asserts, shell, system-linux
36+
37+
// refuted-no-diagnostics
38+
39+
int div_by_zero_test(int b) {
40+
if (b) {}
41+
return 100 / b; // accepted-warning {{Division by zero}}
42+
}

clang/test/Analysis/z3/D83660.c

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,18 @@
11
// RUN: rm -rf %t && mkdir %t
2-
// RUN: %host_cxx -shared -fPIC %S/Inputs/MockZ3_solver_check.c -o %t/MockZ3_solver_check.so
2+
// RUN: %host_cxx -shared -fPIC \
3+
// RUN: %S/Inputs/MockZ3_solver_check.cpp \
4+
// RUN: -o %t/MockZ3_solver_check.so
35
//
4-
// RUN: LD_PRELOAD="%t/MockZ3_solver_check.so" \
5-
// RUN: %clang_cc1 -analyze -analyzer-constraints=z3 -setup-static-analyzer \
6-
// RUN: -analyzer-checker=core,debug.ExprInspection %s -verify 2>&1 | FileCheck %s
6+
// RUN: Z3_SOLVER_RESULTS="SAT,SAT,SAT,SAT,UNDEF" \
7+
// RUN: LD_PRELOAD="%t/MockZ3_solver_check.so" \
8+
// RUN: %clang_cc1 -analyze -analyzer-constraints=z3 -setup-static-analyzer \
9+
// RUN: -analyzer-checker=core %s -verify
710
//
811
// REQUIRES: z3, asserts, shell, system-linux
912
//
1013
// Works only with the z3 constraint manager.
1114
// expected-no-diagnostics
1215

13-
// CHECK: Z3_solver_check returns the real value: TRUE
14-
// CHECK-NEXT: Z3_solver_check returns the real value: TRUE
15-
// CHECK-NEXT: Z3_solver_check returns the real value: TRUE
16-
// CHECK-NEXT: Z3_solver_check returns the real value: TRUE
17-
// CHECK-NEXT: Z3_solver_check returns a mocked value: UNDEF
18-
1916
void D83660(int b) {
2017
if (b) {
2118
}

clang/test/Analysis/z3/Inputs/MockZ3_solver_check.c

Lines changed: 0 additions & 28 deletions
This file was deleted.
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#include <cassert>
2+
#include <dlfcn.h>
3+
#include <cstdio>
4+
#include <cstdlib>
5+
#include <cstring>
6+
7+
#include <z3.h>
8+
9+
static char *Z3ResultsBegin;
10+
static char *Z3ResultsCursor;
11+
12+
static __attribute__((constructor)) void init() {
13+
const char *Env = getenv("Z3_SOLVER_RESULTS");
14+
if (!Env) {
15+
fprintf(stderr, "Z3_SOLVER_RESULTS envvar must be defined; abort\n");
16+
abort();
17+
}
18+
Z3ResultsBegin = strdup(Env);
19+
Z3ResultsCursor = Z3ResultsBegin;
20+
if (!Z3ResultsBegin) {
21+
fprintf(stderr, "strdup failed; abort\n");
22+
abort();
23+
}
24+
}
25+
26+
static __attribute__((destructor)) void finit() {
27+
if (strlen(Z3ResultsCursor) > 0) {
28+
fprintf(stderr, "Z3_SOLVER_RESULTS should have been completely consumed "
29+
"by the end of the test; abort\n");
30+
abort();
31+
}
32+
free(Z3ResultsBegin);
33+
}
34+
35+
static bool consume_token(char **pointer_to_cursor, const char *token) {
36+
assert(pointer_to_cursor);
37+
int len = strlen(token);
38+
if (*pointer_to_cursor && strncmp(*pointer_to_cursor, token, len) == 0) {
39+
*pointer_to_cursor += len;
40+
return true;
41+
}
42+
return false;
43+
}
44+
45+
Z3_lbool Z3_API Z3_solver_check(Z3_context c, Z3_solver s) {
46+
consume_token(&Z3ResultsCursor, ",");
47+
48+
if (consume_token(&Z3ResultsCursor, "UNDEF")) {
49+
printf("Z3_solver_check returns UNDEF\n");
50+
return Z3_L_UNDEF;
51+
}
52+
if (consume_token(&Z3ResultsCursor, "SAT")) {
53+
printf("Z3_solver_check returns SAT\n");
54+
return Z3_L_TRUE;
55+
}
56+
if (consume_token(&Z3ResultsCursor, "UNSAT")) {
57+
printf("Z3_solver_check returns UNSAT\n");
58+
return Z3_L_FALSE;
59+
}
60+
fprintf(stderr, "Z3_SOLVER_RESULTS was exhausted; abort\n");
61+
abort();
62+
}

clang/unittests/StaticAnalyzer/Z3CrosscheckOracleTest.cpp

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,22 @@ static constexpr std::optional<bool> UNDEF = std::nullopt;
2727
static unsigned operator""_ms(unsigned long long ms) { return ms; }
2828
static unsigned operator""_step(unsigned long long rlimit) { return rlimit; }
2929

30+
template <class Ret, class Arg> static Ret makeDefaultOption(Arg Value) {
31+
return Value;
32+
}
33+
template <> PositiveAnalyzerOption makeDefaultOption(int Value) {
34+
auto DefaultVal = PositiveAnalyzerOption::create(Value);
35+
assert(DefaultVal.has_value());
36+
return DefaultVal.value();
37+
}
38+
3039
static const AnalyzerOptions DefaultOpts = [] {
3140
AnalyzerOptions Config;
3241
#define ANALYZER_OPTION_DEPENDS_ON_USER_MODE(TYPE, NAME, CMDFLAG, DESC, \
3342
SHALLOW_VAL, DEEP_VAL) \
3443
ANALYZER_OPTION(TYPE, NAME, CMDFLAG, DESC, DEEP_VAL)
3544
#define ANALYZER_OPTION(TYPE, NAME, CMDFLAG, DESC, DEFAULT_VAL) \
36-
Config.NAME = DEFAULT_VAL;
45+
Config.NAME = makeDefaultOption<TYPE>(DEFAULT_VAL);
3746
#include "clang/StaticAnalyzer/Core/AnalyzerOptions.def"
3847

3948
// Remember to update the tests in this file when these values change.

0 commit comments

Comments
 (0)