Repository navigation
Expand file tree
/
Copy pathmain.cpp
More file actions
268 lines (246 loc) · 11.7 KB
/
Copy pathmain.cpp
File metadata and controls
268 lines (246 loc) · 11.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
// SPDX-License-Identifier: Apache-2.0
#include "app/AnalyzerApp.hpp"
#include "cli/ArgParser.hpp"
#include "analysis/smt/SolverOrchestrator.hpp"
#include "analysis/smt/TextUtil.hpp"
#include <algorithm>
#include <sstream>
#include <string>
#include <utility>
#include <vector>
#include <llvm/Support/raw_ostream.h>
#include <coretrace/logger.hpp>
using namespace ctrace::stack;
static void logText(coretrace::Level level, const std::string& text)
{
if (text.empty())
return;
if (text.back() == '\n')
{
coretrace::log(level, "{}", text);
}
else
{
coretrace::log(level, "{}\n", text);
}
}
static void printHelp()
{
llvm::outs()
<< "Stack Usage Analyzer - static stack usage analysis for LLVM IR/bitcode\n\n"
<< "Usage:\n"
<< " stack_usage_analyzer <file.ll> [file2.ll ...] [options]\n\n"
<< "Options:\n"
<< " --mode=ir|abi Analysis mode (default: ir)\n"
<< " --analysis-profile=fast|full Analysis precision/speed profile (default: full)\n"
<< " --format=json Output JSON report\n"
<< " --format=sarif Output SARIF report\n"
<< " -I<dir> Add include directory for C/C++ inputs\n"
<< " -I <dir> Add include directory for C/C++ inputs\n"
<< " -D<name>[=value] Define macro for C/C++ inputs\n"
<< " -D <name>[=value] Define macro for C/C++ inputs\n"
<< " --compile-arg=<arg> Pass extra compile argument (repeatable)\n"
<< " --compile-commands=<path> Use compile_commands.json (file or directory)\n"
<< " --compdb=<path> Alias for --compile-commands\n"
<< " --compdb-fast Speed up compdb builds (drops heavy flags)\n"
<< " --include-compdb-deps Include _deps entries when auto-discovering files from "
"compile_commands.json\n"
<< " --jobs=<N|auto> Parallel jobs for multi-file loading/analysis and cross-TU "
"summary build (default: 1)\n"
<< " If no input files are provided, supported files are loaded\n"
<< " from compile_commands.json automatically.\n"
<< " --timing Print compilation/analysis timing to stderr\n"
<< " --config=<path> Load optional key=value config file (CLI flags override)\n"
<< " --print-effective-config Print resolved runtime config to stderr\n"
<< " --smt=on|off Enable or disable SMT-assisted reasoning (default: off)\n"
<< " --smt-backend=<name> Primary SMT backend (interval|z3|cvc5)\n"
<< " --smt-secondary-backend=<name> Secondary backend for coupled modes\n"
<< " --smt-mode=<mode> Solver mode: single|portfolio|cross-check|dual-consensus\n"
<< " --smt-timeout-ms=<N> Per-query timeout budget in milliseconds\n"
<< " --smt-budget-nodes=<N> Per-query complexity budget\n"
<< " --smt-rules=<csv> Restrict SMT to selected rules (example: recursion)\n"
<< " --escape-model=<path> Stack escape model file "
"(noescape_arg rules)\n"
<< " --buffer-model=<path> Buffer write model file "
"(bounded_write/unbounded_write rules)\n"
<< " --resource-model=<path> Resource lifetime model file "
"(acquire_out/acquire_ret/release_arg)\n"
<< " --resource-cross-tu Enable cross-TU resource summaries (default: on)\n"
<< " --no-resource-cross-tu Disable cross-TU resource summaries\n"
<< " --resource-summary-cache-dir=<path> Cache directory for cross-TU summaries\n"
<< " --compile-ir-cache-dir=<path> Cache directory for compiled LLVM IR per source "
"file\n"
<< " --compile-ir-format=bc|ll Compilation IR format for source inputs (default: bc)\n"
<< " --resource-summary-cache-memory-only Use in-memory cache only for cross-TU "
"summaries\n"
<< " --uninitialized-cross-tu Enable cross-TU uninitialized summaries (default: on)\n"
<< " --no-uninitialized-cross-tu Disable cross-TU uninitialized summaries\n"
<< " --only-file=<path> Only report functions from this source file\n"
<< " --only-dir=<path> Only report functions under this directory\n"
<< " --exclude-dir=<path> Exclude input files under this directory (comma-separated)\n"
<< " --only-func=<name> Only report functions with this name (comma-separated)\n"
<< " --STL Include STL/system/third-party library functions in analysis\n"
<< " --stack-limit=<value> Override stack size limit (bytes, or KiB/MiB/GiB)\n"
<< " --assume-external-frame=<value>\n"
<< " Charge <value> per unresolved call (indirect, or to an\n"
<< " external declaration) instead of reporting max stack\n"
<< " as unknown; 0 ignores such calls\n"
<< " --base-dir=<path> Strip base directory from SARIF URIs (relative paths)\n"
<< " --dump-filter Print filter decisions to stderr\n"
<< " --dump-ir=<path> Write LLVM IR to file (or directory for multiple inputs)\n"
<< " --quiet Suppress per-function diagnostics\n"
<< " --warnings-only Show warnings and errors only\n"
<< " -h, --help Show this help message and exit\n\n"
<< "Examples:\n"
<< " stack_usage_analyzer input.ll\n"
<< " stack_usage_analyzer input1.ll input2.ll --format=json\n"
<< " stack_usage_analyzer main.cpp -I../include --format=json\n"
<< " stack_usage_analyzer main.cpp -I../include --only-dir=../src\n"
<< " stack_usage_analyzer main.cpp --compile-commands=build/compile_commands.json\n"
<< " stack_usage_analyzer --config=.ctrace-analyzer.cfg --jobs=auto\n"
<< " stack_usage_analyzer --config=.ctrace-analyzer.cfg --print-effective-config\n"
<< " stack_usage_analyzer input.ll --mode=abi --format=json\n"
<< " stack_usage_analyzer input.ll --warnings-only\n";
}
static const char* solverModeName(ctrace::stack::analysis::smt::SolverMode mode)
{
using Mode = ctrace::stack::analysis::smt::SolverMode;
switch (mode)
{
case Mode::Single:
return "single";
case Mode::Portfolio:
return "portfolio";
case Mode::CrossCheck:
return "cross-check";
case Mode::DualConsensus:
return "dual-consensus";
}
return "single";
}
static const char* compileIRFormatName(CompileIRFormat format)
{
switch (format)
{
case CompileIRFormat::BC:
return "bc";
case CompileIRFormat::LL:
return "ll";
}
return "bc";
}
static std::string joinCsv(const std::vector<std::string>& values)
{
if (values.empty())
return {};
std::ostringstream oss;
for (std::size_t i = 0; i < values.size(); ++i)
{
if (i != 0)
oss << ",";
oss << values[i];
}
return oss.str();
}
static void printEffectiveConfig(const ctrace::stack::cli::ParsedArguments& parsed)
{
const AnalysisConfig& cfg = parsed.config;
llvm::errs() << "=== Effective Analyzer Configuration ===\n";
llvm::errs() << "config-file: " << (parsed.configPath.empty() ? "<none>" : parsed.configPath)
<< "\n";
llvm::errs() << "compile-commands: "
<< (parsed.compileCommandsPath.empty() ? "<none>" : parsed.compileCommandsPath)
<< "\n";
llvm::errs() << "analysis-profile: " << (cfg.profile == AnalysisProfile::Fast ? "fast" : "full")
<< "\n";
if (cfg.jobsAuto)
llvm::errs() << "jobs: auto\n";
else
llvm::errs() << "jobs: " << cfg.jobs << "\n";
llvm::errs() << "warnings-only: " << (cfg.warningsOnly ? "true" : "false") << "\n";
llvm::errs() << "quiet: " << (cfg.quiet ? "true" : "false") << "\n";
llvm::errs() << "demangle: " << (cfg.demangle ? "true" : "false") << "\n";
llvm::errs() << "resource-model: "
<< (cfg.resourceModelPath.empty() ? "<none>" : cfg.resourceModelPath) << "\n";
llvm::errs() << "escape-model: "
<< (cfg.escapeModelPath.empty() ? "<none>" : cfg.escapeModelPath) << "\n";
llvm::errs() << "buffer-model: "
<< (cfg.bufferModelPath.empty() ? "<none>" : cfg.bufferModelPath) << "\n";
llvm::errs() << "compile-ir-cache-dir: "
<< (cfg.compileIRCacheDir.empty() ? "<none>" : cfg.compileIRCacheDir) << "\n";
llvm::errs() << "compile-ir-format: " << compileIRFormatName(cfg.compileIRFormat) << "\n";
llvm::errs() << "smt-enabled: " << (cfg.smtEnabled ? "true" : "false") << "\n";
llvm::errs() << "smt-backend: " << cfg.smtBackend << "\n";
llvm::errs() << "smt-secondary-backend: "
<< (cfg.smtSecondaryBackend.empty() ? "<none>" : cfg.smtSecondaryBackend) << "\n";
llvm::errs() << "smt-mode: " << solverModeName(cfg.smtMode) << "\n";
llvm::errs() << "smt-timeout-ms: " << cfg.smtTimeoutMs << "\n";
llvm::errs() << "smt-budget-nodes: " << cfg.smtBudgetNodes << "\n";
llvm::errs() << "smt-rules: " << (cfg.smtRules.empty() ? "<all>" : joinCsv(cfg.smtRules))
<< "\n";
llvm::errs() << "========================================\n";
}
// A backend missing from this build answers every query Unknown, which silently turns
// --smt=on into a no-op; say so once, before any analysis.
static void warnAboutUnavailableSmtBackends(const AnalysisConfig& cfg)
{
using ctrace::stack::analysis::smt::SolverMode;
using ctrace::stack::analysis::smt::toLowerAscii;
if (!cfg.smtEnabled)
return;
// Backend names are case-insensitive, so "CVC5" and "cvc5" are one backend.
std::vector<std::string> used{cfg.smtBackend};
if (cfg.smtMode != SolverMode::Single && !cfg.smtSecondaryBackend.empty() &&
toLowerAscii(cfg.smtSecondaryBackend) != toLowerAscii(cfg.smtBackend))
{
used.push_back(cfg.smtSecondaryBackend);
}
for (const std::string& backend : used)
{
if (!ctrace::stack::analysis::smt::isSmtBackendAvailable(backend))
{
coretrace::log(coretrace::Level::Warn,
"SMT backend '{}' is not available in this build; its queries are "
"inconclusive\n",
backend);
}
}
}
int main(int argc, char** argv)
{
coretrace::enable_logging();
coretrace::set_prefix("==stack-analyzer==");
coretrace::set_min_level(coretrace::Level::Info);
coretrace::set_source_location(true);
coretrace::log(coretrace::Level::Debug, coretrace::Module("cli"),
"Starting analysis for {} input(s)\n", argc - 1);
if (argc < 2)
{
printHelp();
return 1;
}
ctrace::stack::cli::ParseResult parseResult = ctrace::stack::cli::parseArguments(argc, argv);
if (parseResult.status == ctrace::stack::cli::ParseStatus::Help)
{
printHelp();
return 0;
}
if (parseResult.status == ctrace::stack::cli::ParseStatus::Error)
{
logText(coretrace::Level::Error, parseResult.error);
return 1;
}
if (parseResult.parsed.verbose)
coretrace::set_min_level(coretrace::Level::Debug);
if (parseResult.parsed.printEffectiveConfig)
printEffectiveConfig(parseResult.parsed);
warnAboutUnavailableSmtBackends(parseResult.parsed.config);
ctrace::stack::app::RunResult runResult =
ctrace::stack::app::runAnalyzerApp(std::move(parseResult.parsed));
if (!runResult.isOk())
{
logText(coretrace::Level::Error, runResult.error);
return 1;
}
return runResult.exitCode;
}