[Flang] remove whole-archive option for AIX linker (#76039)
[llvm-project.git] / clang-tools-extra / clangd / tool / Check.cpp
blobb5c4d145619df33a5da26c5704f79776d6d4c3d6
1 //===--- Check.cpp - clangd self-diagnostics ------------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 //
9 // Many basic problems can occur processing a file in clangd, e.g.:
10 // - system includes are not found
11 // - crash when indexing its AST
12 // clangd --check provides a simplified, isolated way to reproduce these,
13 // with no editor, LSP, threads, background indexing etc to contend with.
15 // One important use case is gathering information for bug reports.
16 // Another is reproducing crashes, and checking which setting prevent them.
18 // It simulates opening a file (determining compile command, parsing, indexing)
19 // and then running features at many locations.
21 // Currently it adds some basic logging of progress and results.
22 // We should consider extending it to also recognize common symptoms and
23 // recommend solutions (e.g. standard library installation issues).
25 //===----------------------------------------------------------------------===//
27 #include "../clang-tidy/ClangTidyModule.h"
28 #include "../clang-tidy/ClangTidyModuleRegistry.h"
29 #include "../clang-tidy/ClangTidyOptions.h"
30 #include "../clang-tidy/GlobList.h"
31 #include "ClangdLSPServer.h"
32 #include "ClangdServer.h"
33 #include "CodeComplete.h"
34 #include "CompileCommands.h"
35 #include "Compiler.h"
36 #include "Config.h"
37 #include "ConfigFragment.h"
38 #include "ConfigProvider.h"
39 #include "Diagnostics.h"
40 #include "Feature.h"
41 #include "GlobalCompilationDatabase.h"
42 #include "Hover.h"
43 #include "InlayHints.h"
44 #include "ParsedAST.h"
45 #include "Preamble.h"
46 #include "Protocol.h"
47 #include "Selection.h"
48 #include "SemanticHighlighting.h"
49 #include "SourceCode.h"
50 #include "TidyProvider.h"
51 #include "XRefs.h"
52 #include "clang-include-cleaner/Record.h"
53 #include "index/FileIndex.h"
54 #include "refactor/Tweak.h"
55 #include "support/Context.h"
56 #include "support/Logger.h"
57 #include "support/ThreadsafeFS.h"
58 #include "support/Trace.h"
59 #include "clang/AST/ASTContext.h"
60 #include "clang/Basic/Diagnostic.h"
61 #include "clang/Basic/LLVM.h"
62 #include "clang/Format/Format.h"
63 #include "clang/Frontend/CompilerInvocation.h"
64 #include "clang/Tooling/CompilationDatabase.h"
65 #include "llvm/ADT/ArrayRef.h"
66 #include "llvm/ADT/STLExtras.h"
67 #include "llvm/ADT/SmallString.h"
68 #include "llvm/Support/Chrono.h"
69 #include "llvm/Support/CommandLine.h"
70 #include "llvm/Support/Path.h"
71 #include "llvm/Support/Process.h"
72 #include <array>
73 #include <chrono>
74 #include <cstdint>
75 #include <limits>
76 #include <memory>
77 #include <optional>
78 #include <utility>
79 #include <vector>
81 namespace clang {
82 namespace clangd {
83 namespace {
85 // These will never be shown in --help, ClangdMain doesn't list the category.
86 llvm::cl::opt<std::string> CheckTidyTime{
87 "check-tidy-time",
88 llvm::cl::desc("Print the overhead of checks matching this glob"),
89 llvm::cl::init("")};
90 llvm::cl::opt<std::string> CheckFileLines{
91 "check-lines",
92 llvm::cl::desc(
93 "Limits the range of tokens in -check file on which "
94 "various features are tested. Example --check-lines=3-7 restricts "
95 "testing to lines 3 to 7 (inclusive) or --check-lines=5 to restrict "
96 "to one line. Default is testing entire file."),
97 llvm::cl::init("")};
98 llvm::cl::opt<bool> CheckLocations{
99 "check-locations",
100 llvm::cl::desc(
101 "Runs certain features (e.g. hover) at each point in the file. "
102 "Somewhat slow."),
103 llvm::cl::init(true)};
104 llvm::cl::opt<bool> CheckCompletion{
105 "check-completion",
106 llvm::cl::desc("Run code-completion at each point (slow)"),
107 llvm::cl::init(false)};
108 llvm::cl::opt<bool> CheckWarnings{
109 "check-warnings",
110 llvm::cl::desc("Print warnings as well as errors"),
111 llvm::cl::init(false)};
113 // Print the diagnostics meeting severity threshold, and return count of errors.
114 unsigned showErrors(llvm::ArrayRef<Diag> Diags) {
115 unsigned ErrCount = 0;
116 for (const auto &D : Diags) {
117 if (D.Severity >= DiagnosticsEngine::Error || CheckWarnings)
118 elog("[{0}] Line {1}: {2}", D.Name, D.Range.start.line + 1, D.Message);
119 if (D.Severity >= DiagnosticsEngine::Error)
120 ++ErrCount;
122 return ErrCount;
125 std::vector<std::string> listTidyChecks(llvm::StringRef Glob) {
126 tidy::GlobList G(Glob);
127 tidy::ClangTidyCheckFactories CTFactories;
128 for (const auto &E : tidy::ClangTidyModuleRegistry::entries())
129 E.instantiate()->addCheckFactories(CTFactories);
130 std::vector<std::string> Result;
131 for (const auto &E : CTFactories)
132 if (G.contains(E.getKey()))
133 Result.push_back(E.getKey().str());
134 llvm::sort(Result);
135 return Result;
138 // This class is just a linear pipeline whose functions get called in sequence.
139 // Each exercises part of clangd's logic on our test file and logs results.
140 // Later steps depend on state built in earlier ones (such as the AST).
141 // Many steps can fatally fail (return false), then subsequent ones cannot run.
142 // Nonfatal failures are logged and tracked in ErrCount.
143 class Checker {
144 // from constructor
145 std::string File;
146 ClangdLSPServer::Options Opts;
147 // from buildCommand
148 tooling::CompileCommand Cmd;
149 // from buildInvocation
150 ParseInputs Inputs;
151 std::unique_ptr<CompilerInvocation> Invocation;
152 format::FormatStyle Style;
153 // from buildAST
154 std::shared_ptr<const PreambleData> Preamble;
155 std::optional<ParsedAST> AST;
156 FileIndex Index;
158 public:
159 // Number of non-fatal errors seen.
160 unsigned ErrCount = 0;
162 Checker(llvm::StringRef File, const ClangdLSPServer::Options &Opts)
163 : File(File), Opts(Opts) {}
165 // Read compilation database and choose a compile command for the file.
166 bool buildCommand(const ThreadsafeFS &TFS) {
167 log("Loading compilation database...");
168 DirectoryBasedGlobalCompilationDatabase::Options CDBOpts(TFS);
169 CDBOpts.CompileCommandsDir =
170 Config::current().CompileFlags.CDBSearch.FixedCDBPath;
171 std::unique_ptr<GlobalCompilationDatabase> BaseCDB =
172 std::make_unique<DirectoryBasedGlobalCompilationDatabase>(CDBOpts);
173 auto Mangler = CommandMangler::detect();
174 Mangler.SystemIncludeExtractor =
175 getSystemIncludeExtractor(llvm::ArrayRef(Opts.QueryDriverGlobs));
176 if (Opts.ResourceDir)
177 Mangler.ResourceDir = *Opts.ResourceDir;
178 auto CDB = std::make_unique<OverlayCDB>(
179 BaseCDB.get(), std::vector<std::string>{}, std::move(Mangler));
181 if (auto TrueCmd = CDB->getCompileCommand(File)) {
182 Cmd = std::move(*TrueCmd);
183 log("Compile command {0} is: [{1}] {2}",
184 Cmd.Heuristic.empty() ? "from CDB" : Cmd.Heuristic, Cmd.Directory,
185 printArgv(Cmd.CommandLine));
186 } else {
187 Cmd = CDB->getFallbackCommand(File);
188 log("Generic fallback command is: [{0}] {1}", Cmd.Directory,
189 printArgv(Cmd.CommandLine));
192 return true;
195 // Prepare inputs and build CompilerInvocation (parsed compile command).
196 bool buildInvocation(const ThreadsafeFS &TFS,
197 std::optional<std::string> Contents) {
198 StoreDiags CaptureInvocationDiags;
199 std::vector<std::string> CC1Args;
200 Inputs.CompileCommand = Cmd;
201 Inputs.TFS = &TFS;
202 Inputs.ClangTidyProvider = Opts.ClangTidyProvider;
203 Inputs.Opts.PreambleParseForwardingFunctions =
204 Opts.PreambleParseForwardingFunctions;
205 if (Contents) {
206 Inputs.Contents = *Contents;
207 log("Imaginary source file contents:\n{0}", Inputs.Contents);
208 } else {
209 if (auto Contents = TFS.view(std::nullopt)->getBufferForFile(File)) {
210 Inputs.Contents = Contents->get()->getBuffer().str();
211 } else {
212 elog("Couldn't read {0}: {1}", File, Contents.getError().message());
213 return false;
216 log("Parsing command...");
217 Invocation =
218 buildCompilerInvocation(Inputs, CaptureInvocationDiags, &CC1Args);
219 auto InvocationDiags = CaptureInvocationDiags.take();
220 ErrCount += showErrors(InvocationDiags);
221 log("internal (cc1) args are: {0}", printArgv(CC1Args));
222 if (!Invocation) {
223 elog("Failed to parse command line");
224 return false;
227 // FIXME: Check that resource-dir/built-in-headers exist?
229 Style = getFormatStyleForFile(File, Inputs.Contents, TFS);
231 return true;
234 // Build preamble and AST, and index them.
235 bool buildAST() {
236 log("Building preamble...");
237 Preamble = buildPreamble(
238 File, *Invocation, Inputs, /*StoreInMemory=*/true,
239 [&](CapturedASTCtx Ctx,
240 std::shared_ptr<const include_cleaner::PragmaIncludes> PI) {
241 if (!Opts.BuildDynamicSymbolIndex)
242 return;
243 log("Indexing headers...");
244 Index.updatePreamble(File, /*Version=*/"null", Ctx.getASTContext(),
245 Ctx.getPreprocessor(), *PI);
247 if (!Preamble) {
248 elog("Failed to build preamble");
249 return false;
251 ErrCount += showErrors(Preamble->Diags);
253 log("Building AST...");
254 AST = ParsedAST::build(File, Inputs, std::move(Invocation),
255 /*InvocationDiags=*/std::vector<Diag>{}, Preamble);
256 if (!AST) {
257 elog("Failed to build AST");
258 return false;
260 ErrCount +=
261 showErrors(AST->getDiagnostics().drop_front(Preamble->Diags.size()));
263 if (Opts.BuildDynamicSymbolIndex) {
264 log("Indexing AST...");
265 Index.updateMain(File, *AST);
268 if (!CheckTidyTime.empty()) {
269 if (!CLANGD_TIDY_CHECKS) {
270 elog("-{0} requires -DCLANGD_TIDY_CHECKS!", CheckTidyTime.ArgStr);
271 return false;
273 #ifndef NDEBUG
274 elog("Timing clang-tidy checks in asserts-mode is not representative!");
275 #endif
276 checkTidyTimes();
279 return true;
282 // For each check foo, we want to build with checks=-* and checks=-*,foo.
283 // (We do a full build rather than just AST matchers to meausre PPCallbacks).
285 // However, performance has both random noise and systematic changes, such as
286 // step-function slowdowns due to CPU scaling.
287 // We take the median of 5 measurements, and after every check discard the
288 // measurement if the baseline changed by >3%.
289 void checkTidyTimes() {
290 double Stability = 0.03;
291 log("Timing AST build with individual clang-tidy checks (target accuracy "
292 "{0:P0})",
293 Stability);
295 using Duration = std::chrono::nanoseconds;
296 // Measure time elapsed by a block of code. Currently: user CPU time.
297 auto Time = [&](auto &&Run) -> Duration {
298 llvm::sys::TimePoint<> Elapsed;
299 std::chrono::nanoseconds UserBegin, UserEnd, System;
300 llvm::sys::Process::GetTimeUsage(Elapsed, UserBegin, System);
301 Run();
302 llvm::sys::Process::GetTimeUsage(Elapsed, UserEnd, System);
303 return UserEnd - UserBegin;
305 auto Change = [&](Duration Exp, Duration Base) -> double {
306 return (double)(Exp.count() - Base.count()) / Base.count();
308 // Build ParsedAST with a fixed check glob, and return the time taken.
309 auto Build = [&](llvm::StringRef Checks) -> Duration {
310 TidyProvider CTProvider = [&](tidy::ClangTidyOptions &Opts,
311 llvm::StringRef) {
312 Opts.Checks = Checks.str();
314 Inputs.ClangTidyProvider = CTProvider;
315 // Sigh, can't reuse the CompilerInvocation.
316 IgnoringDiagConsumer IgnoreDiags;
317 auto Invocation = buildCompilerInvocation(Inputs, IgnoreDiags);
318 Duration Val = Time([&] {
319 ParsedAST::build(File, Inputs, std::move(Invocation), {}, Preamble);
321 vlog(" Measured {0} ==> {1}", Checks, Val);
322 return Val;
324 // Measure several times, return the median.
325 auto MedianTime = [&](llvm::StringRef Checks) -> Duration {
326 std::array<Duration, 5> Measurements;
327 for (auto &M : Measurements)
328 M = Build(Checks);
329 llvm::sort(Measurements);
330 return Measurements[Measurements.size() / 2];
332 Duration Baseline = MedianTime("-*");
333 log(" Baseline = {0}", Baseline);
334 // Attempt to time a check, may update Baseline if it is unstable.
335 auto Measure = [&](llvm::StringRef Check) -> double {
336 for (;;) {
337 Duration Median = MedianTime(("-*," + Check).str());
338 Duration NewBase = MedianTime("-*");
340 // Value only usable if baseline is fairly consistent before/after.
341 double DeltaFraction = Change(NewBase, Baseline);
342 Baseline = NewBase;
343 vlog(" Baseline = {0}", Baseline);
344 if (DeltaFraction < -Stability || DeltaFraction > Stability) {
345 elog(" Speed unstable, discarding measurement.");
346 continue;
348 return Change(Median, Baseline);
352 for (const auto& Check : listTidyChecks(CheckTidyTime)) {
353 // vlog the check name in case we crash!
354 vlog(" Timing {0}", Check);
355 double Fraction = Measure(Check);
356 log(" {0} = {1:P0}", Check, Fraction);
358 log("Finished individual clang-tidy checks");
360 // Restore old options.
361 Inputs.ClangTidyProvider = Opts.ClangTidyProvider;
364 // Build Inlay Hints for the entire AST or the specified range
365 void buildInlayHints(std::optional<Range> LineRange) {
366 log("Building inlay hints");
367 auto Hints = inlayHints(*AST, LineRange);
369 for (const auto &Hint : Hints) {
370 vlog(" {0} {1} {2}", Hint.kind, Hint.position, Hint.label);
374 void buildSemanticHighlighting(std::optional<Range> LineRange) {
375 log("Building semantic highlighting");
376 auto Highlights =
377 getSemanticHighlightings(*AST, /*IncludeInactiveRegionTokens=*/true);
378 for (const auto HL : Highlights)
379 if (!LineRange || LineRange->contains(HL.R))
380 vlog(" {0} {1} {2}", HL.R, HL.Kind, HL.Modifiers);
383 // Run AST-based features at each token in the file.
384 void testLocationFeatures(std::optional<Range> LineRange) {
385 trace::Span Trace("testLocationFeatures");
386 log("Testing features at each token (may be slow in large files)");
387 auto &SM = AST->getSourceManager();
388 auto SpelledTokens = AST->getTokens().spelledTokens(SM.getMainFileID());
390 CodeCompleteOptions CCOpts = Opts.CodeComplete;
391 CCOpts.Index = &Index;
393 for (const auto &Tok : SpelledTokens) {
394 unsigned Start = AST->getSourceManager().getFileOffset(Tok.location());
395 unsigned End = Start + Tok.length();
396 Position Pos = offsetToPosition(Inputs.Contents, Start);
398 if (LineRange && !LineRange->contains(Pos))
399 continue;
401 trace::Span Trace("Token");
402 SPAN_ATTACH(Trace, "pos", Pos);
403 SPAN_ATTACH(Trace, "text", Tok.text(AST->getSourceManager()));
405 // FIXME: dumping the tokens may leak sensitive code into bug reports.
406 // Add an option to turn this off, once we decide how options work.
407 vlog(" {0} {1}", Pos, Tok.text(AST->getSourceManager()));
408 auto Tree = SelectionTree::createRight(AST->getASTContext(),
409 AST->getTokens(), Start, End);
410 Tweak::Selection Selection(&Index, *AST, Start, End, std::move(Tree),
411 nullptr);
412 // FS is only populated when applying a tweak, not during prepare as
413 // prepare should not do any I/O to be fast.
414 auto Tweaks =
415 prepareTweaks(Selection, Opts.TweakFilter, Opts.FeatureModules);
416 Selection.FS =
417 &AST->getSourceManager().getFileManager().getVirtualFileSystem();
418 for (const auto &T : Tweaks) {
419 auto Result = T->apply(Selection);
420 if (!Result) {
421 elog(" tweak: {0} ==> FAIL: {1}", T->id(), Result.takeError());
422 ++ErrCount;
423 } else {
424 vlog(" tweak: {0}", T->id());
427 unsigned Definitions = locateSymbolAt(*AST, Pos, &Index).size();
428 vlog(" definition: {0}", Definitions);
430 auto Hover = getHover(*AST, Pos, Style, &Index);
431 vlog(" hover: {0}", Hover.has_value());
433 unsigned DocHighlights = findDocumentHighlights(*AST, Pos).size();
434 vlog(" documentHighlight: {0}", DocHighlights);
436 if (CheckCompletion) {
437 Position EndPos = offsetToPosition(Inputs.Contents, End);
438 auto CC = codeComplete(File, EndPos, Preamble.get(), Inputs, CCOpts);
439 vlog(" code completion: {0}",
440 CC.Completions.empty() ? "<empty>" : CC.Completions[0].Name);
446 } // namespace
448 bool check(llvm::StringRef File, const ThreadsafeFS &TFS,
449 const ClangdLSPServer::Options &Opts) {
450 std::optional<Range> LineRange;
451 if (!CheckFileLines.empty()) {
452 uint32_t Begin = 0, End = std::numeric_limits<uint32_t>::max();
453 StringRef RangeStr(CheckFileLines);
454 bool ParseError = RangeStr.consumeInteger(0, Begin);
455 if (RangeStr.empty()) {
456 End = Begin;
457 } else {
458 ParseError |= !RangeStr.consume_front("-");
459 ParseError |= RangeStr.consumeInteger(0, End);
461 if (ParseError || !RangeStr.empty() || Begin <= 0 || End < Begin) {
462 elog("Invalid --check-lines specified. Use Begin-End format, e.g. 3-17");
463 return false;
465 LineRange = Range{Position{static_cast<int>(Begin - 1), 0},
466 Position{static_cast<int>(End), 0}};
469 llvm::SmallString<0> FakeFile;
470 std::optional<std::string> Contents;
471 if (File.empty()) {
472 llvm::sys::path::system_temp_directory(false, FakeFile);
473 llvm::sys::path::append(FakeFile, "test.cc");
474 File = FakeFile;
475 Contents = R"cpp(
476 #include <stddef.h>
477 #include <string>
479 size_t N = 50;
480 auto xxx = std::string(N, 'x');
481 )cpp";
483 log("Testing on source file {0}", File);
485 class OverrideConfigProvider : public config::Provider {
486 std::vector<config::CompiledFragment>
487 getFragments(const config::Params &,
488 config::DiagnosticCallback Diag) const override {
489 config::Fragment F;
490 // If we're timing clang-tidy checks, implicitly disabling the slow ones
491 // is counterproductive!
492 if (CheckTidyTime.getNumOccurrences())
493 F.Diagnostics.ClangTidy.FastCheckFilter.emplace("None");
494 return {std::move(F).compile(Diag)};
496 } OverrideConfig;
497 auto ConfigProvider =
498 config::Provider::combine({Opts.ConfigProvider, &OverrideConfig});
500 auto ContextProvider = ClangdServer::createConfiguredContextProvider(
501 ConfigProvider.get(), nullptr);
502 WithContext Ctx(ContextProvider(
503 FakeFile.empty()
504 ? File
505 : /*Don't turn on local configs for an arbitrary temp path.*/ ""));
506 Checker C(File, Opts);
507 if (!C.buildCommand(TFS) || !C.buildInvocation(TFS, Contents) ||
508 !C.buildAST())
509 return false;
510 C.buildInlayHints(LineRange);
511 C.buildSemanticHighlighting(LineRange);
512 if (CheckLocations)
513 C.testLocationFeatures(LineRange);
515 log("All checks completed, {0} errors", C.ErrCount);
516 return C.ErrCount == 0;
519 } // namespace clangd
520 } // namespace clang