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. |
14 | // |
15 | // One important use case is gathering information for bug reports. |
16 | // Another is reproducing crashes, and checking which setting prevent them. |
17 | // |
18 | // It simulates opening a file (determining compile command, parsing, indexing) |
19 | // and then running features at many locations. |
20 | // |
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). |
24 | // |
25 | //===----------------------------------------------------------------------===// |
26 | |
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> |
80 | |
81 | namespace clang { |
82 | namespace clangd { |
83 | namespace { |
84 | |
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(Val: "" )}; |
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(Val: "" )}; |
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(Val: 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(Val: false)}; |
108 | llvm::cl::opt<bool> CheckWarnings{ |
109 | "check-warnings" , |
110 | llvm::cl::desc("Print warnings as well as errors" ), |
111 | llvm::cl::init(Val: false)}; |
112 | |
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(Fmt: "[{0}] Line {1}: {2}" , Vals: D.Name, Vals: D.Range.start.line + 1, Vals: D.Message); |
119 | if (D.Severity >= DiagnosticsEngine::Error) |
120 | ++ErrCount; |
121 | } |
122 | return ErrCount; |
123 | } |
124 | |
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(CheckFactories&: CTFactories); |
130 | std::vector<std::string> Result; |
131 | for (const auto &E : CTFactories) |
132 | if (G.contains(S: E.getKey())) |
133 | Result.push_back(x: E.getKey().str()); |
134 | llvm::sort(C&: Result); |
135 | return Result; |
136 | } |
137 | |
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; |
157 | |
158 | public: |
159 | // Number of non-fatal errors seen. |
160 | unsigned ErrCount = 0; |
161 | |
162 | Checker(llvm::StringRef File, const ClangdLSPServer::Options &Opts) |
163 | : File(File), Opts(Opts) {} |
164 | |
165 | // Read compilation database and choose a compile command for the file. |
166 | bool buildCommand(const ThreadsafeFS &TFS) { |
167 | log(Fmt: "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>(args&: CDBOpts); |
173 | auto Mangler = CommandMangler::detect(); |
174 | Mangler.SystemIncludeExtractor = |
175 | getSystemIncludeExtractor(QueryDriverGlobs: llvm::ArrayRef(Opts.QueryDriverGlobs)); |
176 | if (Opts.ResourceDir) |
177 | Mangler.ResourceDir = *Opts.ResourceDir; |
178 | auto CDB = std::make_unique<OverlayCDB>( |
179 | args: BaseCDB.get(), args: std::vector<std::string>{}, args: std::move(Mangler)); |
180 | |
181 | if (auto TrueCmd = CDB->getCompileCommand(File)) { |
182 | Cmd = std::move(*TrueCmd); |
183 | log(Fmt: "Compile command {0} is: [{1}] {2}" , |
184 | Vals: Cmd.Heuristic.empty() ? "from CDB" : Cmd.Heuristic, Vals&: Cmd.Directory, |
185 | Vals: printArgv(Args: Cmd.CommandLine)); |
186 | } else { |
187 | Cmd = CDB->getFallbackCommand(File); |
188 | log(Fmt: "Generic fallback command is: [{0}] {1}" , Vals&: Cmd.Directory, |
189 | Vals: printArgv(Args: Cmd.CommandLine)); |
190 | } |
191 | |
192 | return true; |
193 | } |
194 | |
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(Fmt: "Imaginary source file contents:\n{0}" , Vals&: Inputs.Contents); |
208 | } else { |
209 | if (auto Contents = TFS.view(CWD: std::nullopt)->getBufferForFile(Name: File)) { |
210 | Inputs.Contents = Contents->get()->getBuffer().str(); |
211 | } else { |
212 | elog(Fmt: "Couldn't read {0}: {1}" , Vals&: File, Vals: Contents.getError().message()); |
213 | return false; |
214 | } |
215 | } |
216 | log(Fmt: "Parsing command..." ); |
217 | Invocation = |
218 | buildCompilerInvocation(Inputs, D&: CaptureInvocationDiags, CC1Args: &CC1Args); |
219 | auto InvocationDiags = CaptureInvocationDiags.take(); |
220 | ErrCount += showErrors(Diags: InvocationDiags); |
221 | log(Fmt: "internal (cc1) args are: {0}" , Vals: printArgv(Args: CC1Args)); |
222 | if (!Invocation) { |
223 | elog(Fmt: "Failed to parse command line" ); |
224 | return false; |
225 | } |
226 | |
227 | // FIXME: Check that resource-dir/built-in-headers exist? |
228 | |
229 | Style = getFormatStyleForFile(File, Content: Inputs.Contents, TFS, FormatFile: false); |
230 | |
231 | return true; |
232 | } |
233 | |
234 | // Build preamble and AST, and index them. |
235 | bool buildAST() { |
236 | log(Fmt: "Building preamble..." ); |
237 | Preamble = buildPreamble( |
238 | FileName: File, CI: *Invocation, Inputs, /*StoreInMemory=*/true, |
239 | PreambleCallback: [&](CapturedASTCtx Ctx, |
240 | std::shared_ptr<const include_cleaner::PragmaIncludes> PI) { |
241 | if (!Opts.BuildDynamicSymbolIndex) |
242 | return; |
243 | log(Fmt: "Indexing headers..." ); |
244 | Index.updatePreamble(Path: File, /*Version=*/"null" , AST&: Ctx.getASTContext(), |
245 | PP&: Ctx.getPreprocessor(), PI: *PI); |
246 | }); |
247 | if (!Preamble) { |
248 | elog(Fmt: "Failed to build preamble" ); |
249 | return false; |
250 | } |
251 | ErrCount += showErrors(Diags: Preamble->Diags); |
252 | |
253 | log(Fmt: "Building AST..." ); |
254 | AST = ParsedAST::build(Filename: File, Inputs, CI: std::move(Invocation), |
255 | /*InvocationDiags=*/CompilerInvocationDiags: std::vector<Diag>{}, Preamble); |
256 | if (!AST) { |
257 | elog(Fmt: "Failed to build AST" ); |
258 | return false; |
259 | } |
260 | ErrCount += |
261 | showErrors(Diags: AST->getDiagnostics().drop_front(N: Preamble->Diags.size())); |
262 | |
263 | if (Opts.BuildDynamicSymbolIndex) { |
264 | log(Fmt: "Indexing AST..." ); |
265 | Index.updateMain(Path: File, AST&: *AST); |
266 | } |
267 | |
268 | if (!CheckTidyTime.empty()) { |
269 | if (!CLANGD_TIDY_CHECKS) { |
270 | elog(Fmt: "-{0} requires -DCLANGD_TIDY_CHECKS!" , Vals&: CheckTidyTime.ArgStr); |
271 | return false; |
272 | } |
273 | #ifndef NDEBUG |
274 | elog(Fmt: "Timing clang-tidy checks in asserts-mode is not representative!" ); |
275 | #endif |
276 | checkTidyTimes(); |
277 | } |
278 | |
279 | return true; |
280 | } |
281 | |
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). |
284 | // |
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(Fmt: "Timing AST build with individual clang-tidy checks (target accuracy " |
292 | "{0:P0})" , |
293 | Vals&: Stability); |
294 | |
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&: Elapsed, user_time&: UserBegin, sys_time&: System); |
301 | Run(); |
302 | llvm::sys::Process::GetTimeUsage(elapsed&: Elapsed, user_time&: UserEnd, sys_time&: System); |
303 | return UserEnd - UserBegin; |
304 | }; |
305 | auto Change = [&](Duration Exp, Duration Base) -> double { |
306 | return (double)(Exp.count() - Base.count()) / Base.count(); |
307 | }; |
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(); |
313 | }; |
314 | Inputs.ClangTidyProvider = CTProvider; |
315 | // Sigh, can't reuse the CompilerInvocation. |
316 | IgnoringDiagConsumer IgnoreDiags; |
317 | auto Invocation = buildCompilerInvocation(Inputs, D&: IgnoreDiags); |
318 | Duration Val = Time([&] { |
319 | ParsedAST::build(Filename: File, Inputs, CI: std::move(Invocation), CompilerInvocationDiags: {}, Preamble); |
320 | }); |
321 | vlog(Fmt: " Measured {0} ==> {1}" , Vals&: Checks, Vals&: Val); |
322 | return Val; |
323 | }; |
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(C&: Measurements); |
330 | return Measurements[Measurements.size() / 2]; |
331 | }; |
332 | Duration Baseline = MedianTime("-*" ); |
333 | log(Fmt: " Baseline = {0}" , Vals&: 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("-*" ); |
339 | |
340 | // Value only usable if baseline is fairly consistent before/after. |
341 | double DeltaFraction = Change(NewBase, Baseline); |
342 | Baseline = NewBase; |
343 | vlog(Fmt: " Baseline = {0}" , Vals&: Baseline); |
344 | if (DeltaFraction < -Stability || DeltaFraction > Stability) { |
345 | elog(Fmt: " Speed unstable, discarding measurement." ); |
346 | continue; |
347 | } |
348 | return Change(Median, Baseline); |
349 | } |
350 | }; |
351 | |
352 | for (const auto& Check : listTidyChecks(Glob: CheckTidyTime)) { |
353 | // vlog the check name in case we crash! |
354 | vlog(Fmt: " Timing {0}" , Vals: Check); |
355 | double Fraction = Measure(Check); |
356 | log(Fmt: " {0} = {1:P0}" , Vals: Check, Vals&: Fraction); |
357 | } |
358 | log(Fmt: "Finished individual clang-tidy checks" ); |
359 | |
360 | // Restore old options. |
361 | Inputs.ClangTidyProvider = Opts.ClangTidyProvider; |
362 | } |
363 | |
364 | // Build Inlay Hints for the entire AST or the specified range |
365 | void buildInlayHints(std::optional<Range> LineRange) { |
366 | log(Fmt: "Building inlay hints" ); |
367 | auto Hints = inlayHints(AST&: *AST, RestrictRange: LineRange); |
368 | |
369 | for (const auto &Hint : Hints) { |
370 | vlog(Fmt: " {0} {1} [{2}]" , Vals: Hint.kind, Vals: Hint.position, Vals: [&] { |
371 | return llvm::join(R: llvm::map_range(C: Hint.label, |
372 | F: [&](auto &L) { |
373 | return llvm::formatv("{{{0}}" , L); |
374 | }), |
375 | Separator: ", " ); |
376 | }()); |
377 | } |
378 | } |
379 | |
380 | void buildSemanticHighlighting(std::optional<Range> LineRange) { |
381 | log(Fmt: "Building semantic highlighting" ); |
382 | auto Highlights = |
383 | getSemanticHighlightings(AST&: *AST, /*IncludeInactiveRegionTokens=*/true); |
384 | for (const auto HL : Highlights) |
385 | if (!LineRange || LineRange->contains(Rng: HL.R)) |
386 | vlog(Fmt: " {0} {1} {2}" , Vals: HL.R, Vals: HL.Kind, Vals: HL.Modifiers); |
387 | } |
388 | |
389 | // Run AST-based features at each token in the file. |
390 | void testLocationFeatures(std::optional<Range> LineRange) { |
391 | trace::Span Trace("testLocationFeatures" ); |
392 | log(Fmt: "Testing features at each token (may be slow in large files)" ); |
393 | auto &SM = AST->getSourceManager(); |
394 | auto SpelledTokens = AST->getTokens().spelledTokens(FID: SM.getMainFileID()); |
395 | |
396 | CodeCompleteOptions CCOpts = Opts.CodeComplete; |
397 | CCOpts.Index = &Index; |
398 | |
399 | for (const auto &Tok : SpelledTokens) { |
400 | unsigned Start = AST->getSourceManager().getFileOffset(SpellingLoc: Tok.location()); |
401 | unsigned End = Start + Tok.length(); |
402 | Position Pos = offsetToPosition(Code: Inputs.Contents, Offset: Start); |
403 | |
404 | if (LineRange && !LineRange->contains(Pos)) |
405 | continue; |
406 | |
407 | trace::Span Trace("Token" ); |
408 | SPAN_ATTACH(Trace, "pos" , Pos); |
409 | SPAN_ATTACH(Trace, "text" , Tok.text(AST->getSourceManager())); |
410 | |
411 | // FIXME: dumping the tokens may leak sensitive code into bug reports. |
412 | // Add an option to turn this off, once we decide how options work. |
413 | vlog(Fmt: " {0} {1}" , Vals&: Pos, Vals: Tok.text(SM: AST->getSourceManager())); |
414 | auto Tree = SelectionTree::createRight(AST&: AST->getASTContext(), |
415 | Tokens: AST->getTokens(), Begin: Start, End); |
416 | Tweak::Selection Selection(&Index, *AST, Start, End, std::move(Tree), |
417 | nullptr); |
418 | // FS is only populated when applying a tweak, not during prepare as |
419 | // prepare should not do any I/O to be fast. |
420 | auto Tweaks = |
421 | prepareTweaks(S: Selection, Filter: Opts.TweakFilter, Modules: Opts.FeatureModules); |
422 | Selection.FS = |
423 | &AST->getSourceManager().getFileManager().getVirtualFileSystem(); |
424 | for (const auto &T : Tweaks) { |
425 | auto Result = T->apply(Sel: Selection); |
426 | if (!Result) { |
427 | elog(Fmt: " tweak: {0} ==> FAIL: {1}" , Vals: T->id(), Vals: Result.takeError()); |
428 | ++ErrCount; |
429 | } else { |
430 | vlog(Fmt: " tweak: {0}" , Vals: T->id()); |
431 | } |
432 | } |
433 | unsigned Definitions = locateSymbolAt(AST&: *AST, Pos, Index: &Index).size(); |
434 | vlog(Fmt: " definition: {0}" , Vals&: Definitions); |
435 | |
436 | auto Hover = getHover(AST&: *AST, Pos, Style, Index: &Index); |
437 | vlog(Fmt: " hover: {0}" , Vals: Hover.has_value()); |
438 | |
439 | unsigned DocHighlights = findDocumentHighlights(AST&: *AST, Pos).size(); |
440 | vlog(Fmt: " documentHighlight: {0}" , Vals&: DocHighlights); |
441 | |
442 | if (CheckCompletion) { |
443 | Position EndPos = offsetToPosition(Code: Inputs.Contents, Offset: End); |
444 | auto CC = codeComplete(FileName: File, Pos: EndPos, Preamble: Preamble.get(), ParseInput: Inputs, Opts: CCOpts); |
445 | vlog(Fmt: " code completion: {0}" , |
446 | Vals: CC.Completions.empty() ? "<empty>" : CC.Completions[0].Name); |
447 | } |
448 | } |
449 | } |
450 | }; |
451 | |
452 | } // namespace |
453 | |
454 | bool check(llvm::StringRef File, const ThreadsafeFS &TFS, |
455 | const ClangdLSPServer::Options &Opts) { |
456 | std::optional<Range> LineRange; |
457 | if (!CheckFileLines.empty()) { |
458 | uint32_t Begin = 0, End = std::numeric_limits<uint32_t>::max(); |
459 | StringRef RangeStr(CheckFileLines); |
460 | bool ParseError = RangeStr.consumeInteger(Radix: 0, Result&: Begin); |
461 | if (RangeStr.empty()) { |
462 | End = Begin; |
463 | } else { |
464 | ParseError |= !RangeStr.consume_front(Prefix: "-" ); |
465 | ParseError |= RangeStr.consumeInteger(Radix: 0, Result&: End); |
466 | } |
467 | if (ParseError || !RangeStr.empty() || Begin <= 0 || End < Begin) { |
468 | elog(Fmt: "Invalid --check-lines specified. Use Begin-End format, e.g. 3-17" ); |
469 | return false; |
470 | } |
471 | LineRange = Range{.start: Position{.line: static_cast<int>(Begin - 1), .character: 0}, |
472 | .end: Position{.line: static_cast<int>(End), .character: 0}}; |
473 | } |
474 | |
475 | llvm::SmallString<0> FakeFile; |
476 | std::optional<std::string> Contents; |
477 | if (File.empty()) { |
478 | llvm::sys::path::system_temp_directory(erasedOnReboot: false, result&: FakeFile); |
479 | llvm::sys::path::append(path&: FakeFile, a: "test.cc" ); |
480 | File = FakeFile; |
481 | Contents = R"cpp( |
482 | #include <stddef.h> |
483 | #include <string> |
484 | |
485 | size_t N = 50; |
486 | auto xxx = std::string(N, 'x'); |
487 | )cpp" ; |
488 | } |
489 | log(Fmt: "Testing on source file {0}" , Vals&: File); |
490 | |
491 | class OverrideConfigProvider : public config::Provider { |
492 | std::vector<config::CompiledFragment> |
493 | getFragments(const config::Params &, |
494 | config::DiagnosticCallback Diag) const override { |
495 | config::Fragment F; |
496 | // If we're timing clang-tidy checks, implicitly disabling the slow ones |
497 | // is counterproductive! |
498 | if (CheckTidyTime.getNumOccurrences()) |
499 | F.Diagnostics.ClangTidy.FastCheckFilter.emplace(args: "None" ); |
500 | return {std::move(F).compile(Diag)}; |
501 | } |
502 | } OverrideConfig; |
503 | auto ConfigProvider = |
504 | config::Provider::combine({Opts.ConfigProvider, &OverrideConfig}); |
505 | |
506 | auto ContextProvider = ClangdServer::createConfiguredContextProvider( |
507 | Provider: ConfigProvider.get(), nullptr); |
508 | WithContext Ctx(ContextProvider( |
509 | FakeFile.empty() |
510 | ? File |
511 | : /*Don't turn on local configs for an arbitrary temp path.*/ "" )); |
512 | Checker C(File, Opts); |
513 | if (!C.buildCommand(TFS) || !C.buildInvocation(TFS, Contents) || |
514 | !C.buildAST()) |
515 | return false; |
516 | C.buildInlayHints(LineRange); |
517 | C.buildSemanticHighlighting(LineRange); |
518 | if (CheckLocations) |
519 | C.testLocationFeatures(LineRange); |
520 | |
521 | log(Fmt: "All checks completed, {0} errors" , Vals&: C.ErrCount); |
522 | return C.ErrCount == 0; |
523 | } |
524 | |
525 | } // namespace clangd |
526 | } // namespace clang |
527 | |