11#include " sentinel/core/AnalysisDriver.h"
22#include " sentinel/core/RuleFrontendAction.h"
3+ #include " sentinel/core/RuleRegistry.h"
34#include " sentinel/core/ToolchainDefaults.h"
45
56#include < clang/Tooling/Tooling.h>
67
8+ #include < llvm/Support/ThreadPool.h>
9+ #include < llvm/Support/Threading.h>
10+
11+ #include < algorithm>
12+ #include < memory>
713#include < utility>
814
915#ifndef SENTINEL_CLANG_RESOURCE_DIR
@@ -31,6 +37,49 @@ class RuleActionFactory : public clang::tooling::FrontendActionFactory {
3137 HeaderFilter headerFilter_;
3238};
3339
40+ // Result of processing exactly one translation unit in its own parallel
41+ // task: the rule instances that did the processing (merged back into the
42+ // driver's own "template" instances once every task finishes -- see
43+ // Rule::mergeFrom()), the diagnostics they produced, and whether the TU
44+ // failed to compile.
45+ struct TaskResult {
46+ std::vector<std::unique_ptr<Rule>> rules;
47+ std::vector<Diagnostic> diagnostics;
48+ bool hadCompileError = false ;
49+ };
50+
51+ // Returns a shared_ptr rather than TaskResult by value: llvm::ThreadPool's
52+ // async() returns a std::shared_future, whose get() returns a `const T&`
53+ // (it can be called more than once) -- copy-constructing a TaskResult
54+ // from that would require copying the unique_ptr<Rule>s inside it, which
55+ // doesn't compile. A shared_ptr<TaskResult> is itself cheap to copy, so
56+ // shared_future's contract is satisfied without needing TaskResult (or
57+ // Rule ownership) to change shape at all.
58+ std::shared_ptr<TaskResult> analyzeOneTranslationUnit (
59+ clang::tooling::CompilationDatabase &compileDb, const std::string &sourcePath,
60+ const std::vector<std::string> &ruleIds, const HeaderFilter &headerFilter) {
61+ auto result = std::make_shared<TaskResult>();
62+ for (const auto &id : ruleIds)
63+ result->rules .push_back (RuleRegistry::instance ().create (id));
64+
65+ clang::tooling::ClangTool tool (compileDb, {sourcePath});
66+ tool.appendArgumentsAdjuster (
67+ makeDefaultToolchainAdjuster (SENTINEL_CLANG_RESOURCE_DIR , detectMacSysroot ()));
68+ RuleActionFactory factory (result->rules , result->diagnostics , headerFilter);
69+ result->hadCompileError = tool.run (&factory) != 0 ;
70+ return result;
71+ }
72+
73+ bool diagnosticLess (const Diagnostic &a, const Diagnostic &b) {
74+ if (a.location .file != b.location .file )
75+ return a.location .file < b.location .file ;
76+ if (a.location .line != b.location .line )
77+ return a.location .line < b.location .line ;
78+ if (a.location .column != b.location .column )
79+ return a.location .column < b.location .column ;
80+ return a.ruleId < b.ruleId ;
81+ }
82+
3483} // namespace
3584
3685AnalysisDriver::AnalysisDriver (std::vector<std::unique_ptr<Rule>> rules)
@@ -39,19 +88,52 @@ AnalysisDriver::AnalysisDriver(std::vector<std::unique_ptr<Rule>> rules)
3988std::vector<Diagnostic>
4089AnalysisDriver::run (clang::tooling::CompilationDatabase &compileDb,
4190 const std::vector<std::string> &sourcePaths, bool *hadCompileErrors,
42- HeaderFilter headerFilter) {
91+ const HeaderFilter &headerFilter, unsigned jobCount) {
92+ std::vector<std::string> ruleIds;
93+ for (const auto &rule : rules_)
94+ ruleIds.push_back (rule->id ());
95+
96+ // Each task gets its own fresh rule instances (see analyzeOneTranslationUnit)
97+ // rather than sharing rules_ directly: an AST-matcher rule stashes its
98+ // current TU's TranslationUnitContext in a member between
99+ // registerMatchers() and run() (see Rule.h), which would race if two
100+ // threads dispatched the same shared instance for two different TUs at
101+ // once.
102+ llvm::DefaultThreadPool pool (llvm::hardware_concurrency (jobCount));
103+ std::vector<std::shared_future<std::shared_ptr<TaskResult>>> futures;
104+ futures.reserve (sourcePaths.size ());
105+ for (const auto &sourcePath : sourcePaths) {
106+ futures.push_back (pool.async ([&compileDb, sourcePath, &ruleIds, &headerFilter] {
107+ return analyzeOneTranslationUnit (compileDb, sourcePath, ruleIds, headerFilter);
108+ }));
109+ }
110+
43111 std::vector<Diagnostic> diagnostics;
44- clang::tooling::ClangTool tool (compileDb, sourcePaths);
45- tool.appendArgumentsAdjuster (
46- makeDefaultToolchainAdjuster (SENTINEL_CLANG_RESOURCE_DIR , detectMacSysroot ()));
47- RuleActionFactory factory (rules_, diagnostics, std::move (headerFilter));
48- int result = tool.run (&factory);
112+ bool anyCompileError = false ;
113+ for (auto &future : futures) {
114+ std::shared_ptr<TaskResult> result = future.get ();
115+ anyCompileError = anyCompileError || result->hadCompileError ;
116+ diagnostics.insert (diagnostics.end (),
117+ std::make_move_iterator (result->diagnostics .begin ()),
118+ std::make_move_iterator (result->diagnostics .end ()));
119+ // result->rules[i] and rules_[i] are the same rule id, in the same
120+ // order, since analyzeOneTranslationUnit built its rules from ruleIds
121+ // (itself built from rules_) in order.
122+ for (size_t i = 0 ; i < rules_.size (); ++i)
123+ rules_[i]->mergeFrom (*result->rules [i]);
124+ }
125+
49126 if (hadCompileErrors)
50- *hadCompileErrors = result != 0 ;
127+ *hadCompileErrors = anyCompileError ;
51128
52129 for (auto &rule : rules_)
53130 rule->finalize (diagnostics);
54131
132+ // Deterministic regardless of how the thread pool happened to schedule
133+ // and interleave tasks -- otherwise the same project could report its
134+ // findings in a different order from one run to the next.
135+ std::sort (diagnostics.begin (), diagnostics.end (), diagnosticLess);
136+
55137 return diagnostics;
56138}
57139
0 commit comments