27 #include "../clang-tidy/ClangTidyModuleRegistry.h"
28 #include "../clang-tidy/GlobList.h"
48 #include "clang/AST/ASTContext.h"
49 #include "clang/Basic/Diagnostic.h"
50 #include "clang/Format/Format.h"
51 #include "clang/Frontend/CompilerInvocation.h"
52 #include "clang/Tooling/CompilationDatabase.h"
53 #include "llvm/ADT/ArrayRef.h"
54 #include "llvm/Support/Path.h"
55 #include "llvm/Support/Process.h"
63 llvm::cl::opt<std::string> CheckTidyTime{
65 llvm::cl::desc(
"Print the overhead of checks matching this glob"),
67 llvm::cl::opt<std::string> CheckFileLines{
70 "Limits the range of tokens in -check file on which "
71 "various features are tested. Example --check-lines=3-7 restricts "
72 "testing to lines 3 to 7 (inclusive) or --check-lines=5 to restrict "
73 "to one line. Default is testing entire file."),
75 llvm::cl::opt<bool> CheckLocations{
78 "Runs certain features (e.g. hover) at each point in the file. "
80 llvm::cl::init(
true)};
81 llvm::cl::opt<bool> CheckCompletion{
83 llvm::cl::desc(
"Run code-completion at each point (slow)"),
84 llvm::cl::init(
false)};
87 unsigned showErrors(llvm::ArrayRef<Diag>
Diags) {
89 for (
const auto &
D :
Diags) {
91 elog(
"[{0}] Line {1}: {2}",
D.Name,
D.Range.start.line + 1,
D.Message);
98 std::vector<std::string> listTidyChecks(llvm::StringRef Glob) {
99 tidy::GlobList G(Glob);
100 tidy::ClangTidyCheckFactories CTFactories;
101 for (
const auto &
E : tidy::ClangTidyModuleRegistry::entries())
102 E.instantiate()->addCheckFactories(CTFactories);
103 std::vector<std::string> Result;
104 for (
const auto &
E : CTFactories)
105 if (G.contains(
E.getKey()))
106 Result.push_back(
E.getKey().str());
119 ClangdLSPServer::Options Opts;
121 tooling::CompileCommand Cmd;
124 std::unique_ptr<CompilerInvocation> Invocation;
127 std::shared_ptr<const PreambleData>
Preamble;
128 std::optional<ParsedAST> AST;
135 Checker(llvm::StringRef File,
const ClangdLSPServer::Options &Opts)
136 : File(File), Opts(Opts) {}
139 bool buildCommand(
const ThreadsafeFS &TFS) {
140 log(
"Loading compilation database...");
141 DirectoryBasedGlobalCompilationDatabase::Options CDBOpts(TFS);
142 CDBOpts.CompileCommandsDir =
144 std::unique_ptr<GlobalCompilationDatabase> BaseCDB =
145 std::make_unique<DirectoryBasedGlobalCompilationDatabase>(CDBOpts);
147 Mangler.SystemIncludeExtractor =
149 if (Opts.ResourceDir)
150 Mangler.ResourceDir = *Opts.ResourceDir;
151 auto CDB = std::make_unique<OverlayCDB>(
152 BaseCDB.get(), std::vector<std::string>{}, std::move(Mangler));
154 if (
auto TrueCmd = CDB->getCompileCommand(File)) {
155 Cmd = std::move(*TrueCmd);
156 log(
"Compile command {0} is: {1}",
157 Cmd.Heuristic.empty() ?
"from CDB" : Cmd.Heuristic,
160 Cmd = CDB->getFallbackCommand(File);
161 log(
"Generic fallback command is: {0}",
printArgv(Cmd.CommandLine));
168 bool buildInvocation(
const ThreadsafeFS &TFS,
169 std::optional<std::string> Contents) {
170 StoreDiags CaptureInvocationDiags;
171 std::vector<std::string> CC1Args;
172 Inputs.CompileCommand = Cmd;
174 Inputs.ClangTidyProvider = Opts.ClangTidyProvider;
175 Inputs.Opts.PreambleParseForwardingFunctions =
176 Opts.PreambleParseForwardingFunctions;
178 Inputs.Contents = *Contents;
179 log(
"Imaginary source file contents:\n{0}",
Inputs.Contents);
181 if (
auto Contents = TFS.view(std::nullopt)->getBufferForFile(File)) {
182 Inputs.Contents = Contents->get()->getBuffer().str();
184 elog(
"Couldn't read {0}: {1}", File, Contents.getError().message());
188 log(
"Parsing command...");
191 auto InvocationDiags = CaptureInvocationDiags.take();
192 ErrCount += showErrors(InvocationDiags);
193 log(
"internal (cc1) args are: {0}",
printArgv(CC1Args));
195 elog(
"Failed to parse command line");
208 log(
"Building preamble...");
210 [&](ASTContext &
Ctx, Preprocessor &
PP,
211 const CanonicalIncludes &Includes) {
212 if (!Opts.BuildDynamicSymbolIndex)
214 log(
"Indexing headers...");
215 Index.updatePreamble(File,
"null",
219 elog(
"Failed to build preamble");
224 log(
"Building AST...");
228 elog(
"Failed to build AST");
231 ErrCount += showErrors(llvm::ArrayRef(*AST->getDiagnostics())
232 .drop_front(
Preamble->Diags.size()));
234 if (Opts.BuildDynamicSymbolIndex) {
235 log(
"Indexing AST...");
236 Index.updateMain(File, *AST);
239 if (!CheckTidyTime.empty()) {
240 if (!CLANGD_TIDY_CHECKS) {
241 elog(
"-{0} requires -DCLANGD_TIDY_CHECKS!", CheckTidyTime.ArgStr);
245 elog(
"Timing clang-tidy checks in asserts-mode is not representative!");
260 void checkTidyTimes() {
261 double Stability = 0.03;
262 log(
"Timing AST build with individual clang-tidy checks (target accuracy "
266 using Duration = std::chrono::nanoseconds;
268 auto Time = [&](
auto &&Run) -> Duration {
269 llvm::sys::TimePoint<> Elapsed;
270 std::chrono::nanoseconds UserBegin, UserEnd, System;
271 llvm::sys::Process::GetTimeUsage(Elapsed, UserBegin, System);
273 llvm::sys::Process::GetTimeUsage(Elapsed, UserEnd, System);
274 return UserEnd - UserBegin;
276 auto Change = [&](Duration Exp, Duration
Base) ->
double {
277 return (
double)(Exp.count() -
Base.count()) /
Base.count();
280 auto Build = [&](llvm::StringRef
Checks) -> Duration {
281 TidyProvider CTProvider = [&](tidy::ClangTidyOptions &Opts,
283 Opts.Checks =
Checks.str();
285 Inputs.ClangTidyProvider = CTProvider;
289 Duration Val = Time([&] {
296 auto MedianTime = [&](llvm::StringRef
Checks) -> Duration {
297 std::array<Duration, 5> Measurements;
298 for (
auto &
M : Measurements)
300 llvm::sort(Measurements);
301 return Measurements[Measurements.size() / 2];
303 Duration Baseline = MedianTime(
"-*");
304 log(
" Baseline = {0}", Baseline);
306 auto Measure = [&](llvm::StringRef Check) ->
double {
308 Duration Median = MedianTime((
"-*," + Check).str());
309 Duration NewBase = MedianTime(
"-*");
312 double DeltaFraction = Change(NewBase, Baseline);
314 vlog(
" Baseline = {0}", Baseline);
315 if (DeltaFraction < -Stability || DeltaFraction > Stability) {
316 elog(
" Speed unstable, discarding measurement.");
319 return Change(Median, Baseline);
323 for (
const auto& Check : listTidyChecks(CheckTidyTime)) {
325 vlog(
" Timing {0}", Check);
326 double Fraction = Measure(Check);
327 log(
" {0} = {1:P0}", Check, Fraction);
329 log(
"Finished individual clang-tidy checks");
332 Inputs.ClangTidyProvider = Opts.ClangTidyProvider;
336 void buildInlayHints(std::optional<Range> LineRange) {
337 log(
"Building inlay hints");
340 for (
const auto &Hint :
Hints) {
341 vlog(
" {0} {1} {2}", Hint.kind, Hint.position, Hint.label);
345 void buildSemanticHighlighting(std::optional<Range> LineRange) {
346 log(
"Building semantic highlighting");
348 for (
const auto HL : Highlights)
349 if (!LineRange || LineRange->contains(HL.R))
350 vlog(
" {0} {1} {2}", HL.R, HL.Kind, HL.Modifiers);
354 void testLocationFeatures(std::optional<Range> LineRange) {
355 trace::Span Trace(
"testLocationFeatures");
356 log(
"Testing features at each token (may be slow in large files)");
357 auto &SM = AST->getSourceManager();
358 auto SpelledTokens = AST->getTokens().spelledTokens(SM.getMainFileID());
360 CodeCompleteOptions CCOpts = Opts.CodeComplete;
361 CCOpts.Index = &
Index;
363 for (
const auto &Tok : SpelledTokens) {
364 unsigned Start = AST->getSourceManager().getFileOffset(Tok.location());
365 unsigned End = Start + Tok.length();
368 if (LineRange && !LineRange->contains(
Pos))
371 trace::Span Trace(
"Token");
373 SPAN_ATTACH(Trace,
"text", Tok.text(AST->getSourceManager()));
377 vlog(
" {0} {1}",
Pos, Tok.text(AST->getSourceManager()));
379 AST->getTokens(), Start, End);
380 Tweak::Selection Selection(&
Index, *AST, Start, End, std::move(Tree),
385 prepareTweaks(Selection, Opts.TweakFilter, Opts.FeatureModules);
387 &AST->getSourceManager().getFileManager().getVirtualFileSystem();
388 for (
const auto &T : Tweaks) {
389 auto Result = T->apply(Selection);
391 elog(
" tweak: {0} ==> FAIL: {1}", T->id(), Result.takeError());
394 vlog(
" tweak: {0}", T->id());
398 vlog(
" definition: {0}", Definitions);
401 vlog(
" hover: {0}", Hover.has_value());
404 vlog(
" documentHighlight: {0}", DocHighlights);
406 if (CheckCompletion) {
409 vlog(
" code completion: {0}",
410 CC.Completions.empty() ?
"<empty>" :
CC.Completions[0].Name);
420 std::optional<Range> LineRange;
421 if (!CheckFileLines.empty()) {
422 uint32_t Begin = 0, End = std::numeric_limits<uint32_t>::max();
423 StringRef RangeStr(CheckFileLines);
424 bool ParseError = RangeStr.consumeInteger(0, Begin);
425 if (RangeStr.empty()) {
429 ParseError |= RangeStr.consumeInteger(0, End);
431 if (
ParseError || !RangeStr.empty() || Begin <= 0 || End < Begin) {
432 elog(
"Invalid --check-lines specified. Use Begin-End format, e.g. 3-17");
436 Position{
static_cast<int>(End), 0}};
439 llvm::SmallString<0> FakeFile;
440 std::optional<std::string> Contents;
442 llvm::sys::path::system_temp_directory(
false, FakeFile);
443 llvm::sys::path::append(FakeFile,
"test.cc");
450 auto xxx = std::string(N, 'x');
453 log("Testing on source file {0}", File);
456 Opts.ConfigProvider,
nullptr);
461 Checker
C(File, Opts);
462 if (!
C.buildCommand(TFS) || !
C.buildInvocation(TFS, Contents) ||
465 C.buildInlayHints(LineRange);
466 C.buildSemanticHighlighting(LineRange);
468 C.testLocationFeatures(LineRange);
470 log(
"All checks completed, {0} errors",
C.ErrCount);
471 return C.ErrCount == 0;