| File: | build/source/clang-tools-extra/clangd/tool/Check.cpp |
| Warning: | line 293, column 7 Address of stack memory associated with local variable 'CTProvider' is still referred to by the stack variable 'C' upon returning to the caller. This will be a dangling reference |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
| 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/ClangTidyModuleRegistry.h" | |||
| 28 | #include "../clang-tidy/GlobList.h" | |||
| 29 | #include "ClangdLSPServer.h" | |||
| 30 | #include "CodeComplete.h" | |||
| 31 | #include "CompileCommands.h" | |||
| 32 | #include "Config.h" | |||
| 33 | #include "Feature.h" | |||
| 34 | #include "GlobalCompilationDatabase.h" | |||
| 35 | #include "Hover.h" | |||
| 36 | #include "InlayHints.h" | |||
| 37 | #include "ParsedAST.h" | |||
| 38 | #include "Preamble.h" | |||
| 39 | #include "Protocol.h" | |||
| 40 | #include "SemanticHighlighting.h" | |||
| 41 | #include "SourceCode.h" | |||
| 42 | #include "XRefs.h" | |||
| 43 | #include "index/CanonicalIncludes.h" | |||
| 44 | #include "index/FileIndex.h" | |||
| 45 | #include "refactor/Tweak.h" | |||
| 46 | #include "support/ThreadsafeFS.h" | |||
| 47 | #include "support/Trace.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" | |||
| 56 | #include <optional> | |||
| 57 | ||||
| 58 | namespace clang { | |||
| 59 | namespace clangd { | |||
| 60 | namespace { | |||
| 61 | ||||
| 62 | // These will never be shown in --help, ClangdMain doesn't list the category. | |||
| 63 | llvm::cl::opt<std::string> CheckTidyTime{ | |||
| 64 | "check-tidy-time", | |||
| 65 | llvm::cl::desc("Print the overhead of checks matching this glob"), | |||
| 66 | llvm::cl::init("")}; | |||
| 67 | llvm::cl::opt<std::string> CheckFileLines{ | |||
| 68 | "check-lines", | |||
| 69 | llvm::cl::desc( | |||
| 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."), | |||
| 74 | llvm::cl::init("")}; | |||
| 75 | llvm::cl::opt<bool> CheckLocations{ | |||
| 76 | "check-locations", | |||
| 77 | llvm::cl::desc( | |||
| 78 | "Runs certain features (e.g. hover) at each point in the file. " | |||
| 79 | "Somewhat slow."), | |||
| 80 | llvm::cl::init(true)}; | |||
| 81 | llvm::cl::opt<bool> CheckCompletion{ | |||
| 82 | "check-completion", | |||
| 83 | llvm::cl::desc("Run code-completion at each point (slow)"), | |||
| 84 | llvm::cl::init(false)}; | |||
| 85 | ||||
| 86 | // Print (and count) the error-level diagnostics (warnings are ignored). | |||
| 87 | unsigned showErrors(llvm::ArrayRef<Diag> Diags) { | |||
| 88 | unsigned ErrCount = 0; | |||
| 89 | for (const auto &D : Diags) { | |||
| 90 | if (D.Severity >= DiagnosticsEngine::Error) { | |||
| 91 | elog("[{0}] Line {1}: {2}", D.Name, D.Range.start.line + 1, D.Message); | |||
| 92 | ++ErrCount; | |||
| 93 | } | |||
| 94 | } | |||
| 95 | return ErrCount; | |||
| 96 | } | |||
| 97 | ||||
| 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()); | |||
| 107 | llvm::sort(Result); | |||
| 108 | return Result; | |||
| 109 | } | |||
| 110 | ||||
| 111 | // This class is just a linear pipeline whose functions get called in sequence. | |||
| 112 | // Each exercises part of clangd's logic on our test file and logs results. | |||
| 113 | // Later steps depend on state built in earlier ones (such as the AST). | |||
| 114 | // Many steps can fatally fail (return false), then subsequent ones cannot run. | |||
| 115 | // Nonfatal failures are logged and tracked in ErrCount. | |||
| 116 | class Checker { | |||
| 117 | // from constructor | |||
| 118 | std::string File; | |||
| 119 | ClangdLSPServer::Options Opts; | |||
| 120 | // from buildCommand | |||
| 121 | tooling::CompileCommand Cmd; | |||
| 122 | // from buildInvocation | |||
| 123 | ParseInputs Inputs; | |||
| 124 | std::unique_ptr<CompilerInvocation> Invocation; | |||
| 125 | format::FormatStyle Style; | |||
| 126 | // from buildAST | |||
| 127 | std::shared_ptr<const PreambleData> Preamble; | |||
| 128 | std::optional<ParsedAST> AST; | |||
| 129 | FileIndex Index; | |||
| 130 | ||||
| 131 | public: | |||
| 132 | // Number of non-fatal errors seen. | |||
| 133 | unsigned ErrCount = 0; | |||
| 134 | ||||
| 135 | Checker(llvm::StringRef File, const ClangdLSPServer::Options &Opts) | |||
| 136 | : File(File), Opts(Opts) {} | |||
| 137 | ||||
| 138 | // Read compilation database and choose a compile command for the file. | |||
| 139 | bool buildCommand(const ThreadsafeFS &TFS) { | |||
| 140 | log("Loading compilation database..."); | |||
| 141 | DirectoryBasedGlobalCompilationDatabase::Options CDBOpts(TFS); | |||
| 142 | CDBOpts.CompileCommandsDir = | |||
| 143 | Config::current().CompileFlags.CDBSearch.FixedCDBPath; | |||
| 144 | std::unique_ptr<GlobalCompilationDatabase> BaseCDB = | |||
| 145 | std::make_unique<DirectoryBasedGlobalCompilationDatabase>(CDBOpts); | |||
| 146 | auto Mangler = CommandMangler::detect(); | |||
| 147 | Mangler.SystemIncludeExtractor = | |||
| 148 | getSystemIncludeExtractor(llvm::ArrayRef(Opts.QueryDriverGlobs)); | |||
| 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)); | |||
| 153 | ||||
| 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, | |||
| 158 | printArgv(Cmd.CommandLine)); | |||
| 159 | } else { | |||
| 160 | Cmd = CDB->getFallbackCommand(File); | |||
| 161 | log("Generic fallback command is: {0}", printArgv(Cmd.CommandLine)); | |||
| 162 | } | |||
| 163 | ||||
| 164 | return true; | |||
| 165 | } | |||
| 166 | ||||
| 167 | // Prepare inputs and build CompilerInvocation (parsed compile command). | |||
| 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; | |||
| 173 | Inputs.TFS = &TFS; | |||
| 174 | Inputs.ClangTidyProvider = Opts.ClangTidyProvider; | |||
| 175 | Inputs.Opts.PreambleParseForwardingFunctions = | |||
| 176 | Opts.PreambleParseForwardingFunctions; | |||
| 177 | if (Contents) { | |||
| 178 | Inputs.Contents = *Contents; | |||
| 179 | log("Imaginary source file contents:\n{0}", Inputs.Contents); | |||
| 180 | } else { | |||
| 181 | if (auto Contents = TFS.view(std::nullopt)->getBufferForFile(File)) { | |||
| 182 | Inputs.Contents = Contents->get()->getBuffer().str(); | |||
| 183 | } else { | |||
| 184 | elog("Couldn't read {0}: {1}", File, Contents.getError().message()); | |||
| 185 | return false; | |||
| 186 | } | |||
| 187 | } | |||
| 188 | log("Parsing command..."); | |||
| 189 | Invocation = | |||
| 190 | buildCompilerInvocation(Inputs, CaptureInvocationDiags, &CC1Args); | |||
| 191 | auto InvocationDiags = CaptureInvocationDiags.take(); | |||
| 192 | ErrCount += showErrors(InvocationDiags); | |||
| 193 | log("internal (cc1) args are: {0}", printArgv(CC1Args)); | |||
| 194 | if (!Invocation) { | |||
| 195 | elog("Failed to parse command line"); | |||
| 196 | return false; | |||
| 197 | } | |||
| 198 | ||||
| 199 | // FIXME: Check that resource-dir/built-in-headers exist? | |||
| 200 | ||||
| 201 | Style = getFormatStyleForFile(File, Inputs.Contents, TFS); | |||
| 202 | ||||
| 203 | return true; | |||
| 204 | } | |||
| 205 | ||||
| 206 | // Build preamble and AST, and index them. | |||
| 207 | bool buildAST() { | |||
| 208 | log("Building preamble..."); | |||
| 209 | Preamble = buildPreamble(File, *Invocation, Inputs, /*StoreInMemory=*/true, | |||
| 210 | [&](ASTContext &Ctx, Preprocessor &PP, | |||
| 211 | const CanonicalIncludes &Includes) { | |||
| 212 | if (!Opts.BuildDynamicSymbolIndex) | |||
| 213 | return; | |||
| 214 | log("Indexing headers..."); | |||
| 215 | Index.updatePreamble(File, /*Version=*/"null", | |||
| 216 | Ctx, PP, Includes); | |||
| 217 | }); | |||
| 218 | if (!Preamble) { | |||
| 219 | elog("Failed to build preamble"); | |||
| 220 | return false; | |||
| 221 | } | |||
| 222 | ErrCount += showErrors(Preamble->Diags); | |||
| 223 | ||||
| 224 | log("Building AST..."); | |||
| 225 | AST = ParsedAST::build(File, Inputs, std::move(Invocation), | |||
| 226 | /*InvocationDiags=*/std::vector<Diag>{}, Preamble); | |||
| 227 | if (!AST) { | |||
| 228 | elog("Failed to build AST"); | |||
| 229 | return false; | |||
| 230 | } | |||
| 231 | ErrCount += showErrors(llvm::ArrayRef(*AST->getDiagnostics()) | |||
| 232 | .drop_front(Preamble->Diags.size())); | |||
| 233 | ||||
| 234 | if (Opts.BuildDynamicSymbolIndex) { | |||
| 235 | log("Indexing AST..."); | |||
| 236 | Index.updateMain(File, *AST); | |||
| 237 | } | |||
| 238 | ||||
| 239 | if (!CheckTidyTime.empty()) { | |||
| 240 | if (!CLANGD_TIDY_CHECKS1) { | |||
| 241 | elog("-{0} requires -DCLANGD_TIDY_CHECKS!", CheckTidyTime.ArgStr); | |||
| 242 | return false; | |||
| 243 | } | |||
| 244 | #ifndef NDEBUG | |||
| 245 | elog("Timing clang-tidy checks in asserts-mode is not representative!"); | |||
| 246 | #endif | |||
| 247 | checkTidyTimes(); | |||
| 248 | } | |||
| 249 | ||||
| 250 | return true; | |||
| 251 | } | |||
| 252 | ||||
| 253 | // For each check foo, we want to build with checks=-* and checks=-*,foo. | |||
| 254 | // (We do a full build rather than just AST matchers to meausre PPCallbacks). | |||
| 255 | // | |||
| 256 | // However, performance has both random noise and systematic changes, such as | |||
| 257 | // step-function slowdowns due to CPU scaling. | |||
| 258 | // We take the median of 5 measurements, and after every check discard the | |||
| 259 | // measurement if the baseline changed by >3%. | |||
| 260 | void checkTidyTimes() { | |||
| 261 | double Stability = 0.03; | |||
| 262 | log("Timing AST build with individual clang-tidy checks (target accuracy " | |||
| 263 | "{0:P0})", | |||
| 264 | Stability); | |||
| 265 | ||||
| 266 | using Duration = std::chrono::nanoseconds; | |||
| 267 | // Measure time elapsed by a block of code. Currently: user CPU time. | |||
| 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); | |||
| 272 | Run(); | |||
| 273 | llvm::sys::Process::GetTimeUsage(Elapsed, UserEnd, System); | |||
| 274 | return UserEnd - UserBegin; | |||
| 275 | }; | |||
| 276 | auto Change = [&](Duration Exp, Duration Base) -> double { | |||
| 277 | return (double)(Exp.count() - Base.count()) / Base.count(); | |||
| 278 | }; | |||
| 279 | // Build ParsedAST with a fixed check glob, and return the time taken. | |||
| 280 | auto Build = [&](llvm::StringRef Checks) -> Duration { | |||
| 281 | TidyProvider CTProvider = [&](tidy::ClangTidyOptions &Opts, | |||
| 282 | llvm::StringRef) { | |||
| 283 | Opts.Checks = Checks.str(); | |||
| 284 | }; | |||
| 285 | Inputs.ClangTidyProvider = CTProvider; | |||
| 286 | // Sigh, can't reuse the CompilerInvocation. | |||
| 287 | IgnoringDiagConsumer IgnoreDiags; | |||
| 288 | auto Invocation = buildCompilerInvocation(Inputs, IgnoreDiags); | |||
| 289 | Duration Val = Time([&] { | |||
| 290 | ParsedAST::build(File, Inputs, std::move(Invocation), {}, Preamble); | |||
| 291 | }); | |||
| 292 | vlog(" Measured {0} ==> {1}", Checks, Val); | |||
| 293 | return Val; | |||
| ||||
| 294 | }; | |||
| 295 | // Measure several times, return the median. | |||
| 296 | auto MedianTime = [&](llvm::StringRef Checks) -> Duration { | |||
| 297 | std::array<Duration, 5> Measurements; | |||
| 298 | for (auto &M : Measurements) | |||
| 299 | M = Build(Checks); | |||
| 300 | llvm::sort(Measurements); | |||
| 301 | return Measurements[Measurements.size() / 2]; | |||
| 302 | }; | |||
| 303 | Duration Baseline = MedianTime("-*"); | |||
| 304 | log(" Baseline = {0}", Baseline); | |||
| 305 | // Attempt to time a check, may update Baseline if it is unstable. | |||
| 306 | auto Measure = [&](llvm::StringRef Check) -> double { | |||
| 307 | for (;;) { | |||
| 308 | Duration Median = MedianTime(("-*," + Check).str()); | |||
| 309 | Duration NewBase = MedianTime("-*"); | |||
| 310 | ||||
| 311 | // Value only usable if baseline is fairly consistent before/after. | |||
| 312 | double DeltaFraction = Change(NewBase, Baseline); | |||
| 313 | Baseline = NewBase; | |||
| 314 | vlog(" Baseline = {0}", Baseline); | |||
| 315 | if (DeltaFraction < -Stability || DeltaFraction > Stability) { | |||
| 316 | elog(" Speed unstable, discarding measurement."); | |||
| 317 | continue; | |||
| 318 | } | |||
| 319 | return Change(Median, Baseline); | |||
| 320 | } | |||
| 321 | }; | |||
| 322 | ||||
| 323 | for (const auto& Check : listTidyChecks(CheckTidyTime)) { | |||
| 324 | // vlog the check name in case we crash! | |||
| 325 | vlog(" Timing {0}", Check); | |||
| 326 | double Fraction = Measure(Check); | |||
| 327 | log(" {0} = {1:P0}", Check, Fraction); | |||
| 328 | } | |||
| 329 | log("Finished individual clang-tidy checks"); | |||
| 330 | ||||
| 331 | // Restore old options. | |||
| 332 | Inputs.ClangTidyProvider = Opts.ClangTidyProvider; | |||
| 333 | } | |||
| 334 | ||||
| 335 | // Build Inlay Hints for the entire AST or the specified range | |||
| 336 | void buildInlayHints(std::optional<Range> LineRange) { | |||
| 337 | log("Building inlay hints"); | |||
| 338 | auto Hints = inlayHints(*AST, LineRange); | |||
| 339 | ||||
| 340 | for (const auto &Hint : Hints) { | |||
| 341 | vlog(" {0} {1} {2}", Hint.kind, Hint.position, Hint.label); | |||
| 342 | } | |||
| 343 | } | |||
| 344 | ||||
| 345 | void buildSemanticHighlighting(std::optional<Range> LineRange) { | |||
| 346 | log("Building semantic highlighting"); | |||
| 347 | auto Highlights = | |||
| 348 | getSemanticHighlightings(*AST, /*IncludeInactiveRegionTokens=*/true); | |||
| 349 | for (const auto HL : Highlights) | |||
| 350 | if (!LineRange || LineRange->contains(HL.R)) | |||
| 351 | vlog(" {0} {1} {2}", HL.R, HL.Kind, HL.Modifiers); | |||
| 352 | } | |||
| 353 | ||||
| 354 | // Run AST-based features at each token in the file. | |||
| 355 | void testLocationFeatures(std::optional<Range> LineRange) { | |||
| 356 | trace::Span Trace("testLocationFeatures"); | |||
| 357 | log("Testing features at each token (may be slow in large files)"); | |||
| 358 | auto &SM = AST->getSourceManager(); | |||
| 359 | auto SpelledTokens = AST->getTokens().spelledTokens(SM.getMainFileID()); | |||
| 360 | ||||
| 361 | CodeCompleteOptions CCOpts = Opts.CodeComplete; | |||
| 362 | CCOpts.Index = &Index; | |||
| 363 | ||||
| 364 | for (const auto &Tok : SpelledTokens) { | |||
| 365 | unsigned Start = AST->getSourceManager().getFileOffset(Tok.location()); | |||
| 366 | unsigned End = Start + Tok.length(); | |||
| 367 | Position Pos = offsetToPosition(Inputs.Contents, Start); | |||
| 368 | ||||
| 369 | if (LineRange && !LineRange->contains(Pos)) | |||
| 370 | continue; | |||
| 371 | ||||
| 372 | trace::Span Trace("Token"); | |||
| 373 | SPAN_ATTACH(Trace, "pos", Pos)do { if (auto *Args = (Trace).Args) (*Args)["pos"] = Pos; } while (0); | |||
| 374 | SPAN_ATTACH(Trace, "text", Tok.text(AST->getSourceManager()))do { if (auto *Args = (Trace).Args) (*Args)["text"] = Tok.text (AST->getSourceManager()); } while (0); | |||
| 375 | ||||
| 376 | // FIXME: dumping the tokens may leak sensitive code into bug reports. | |||
| 377 | // Add an option to turn this off, once we decide how options work. | |||
| 378 | vlog(" {0} {1}", Pos, Tok.text(AST->getSourceManager())); | |||
| 379 | auto Tree = SelectionTree::createRight(AST->getASTContext(), | |||
| 380 | AST->getTokens(), Start, End); | |||
| 381 | Tweak::Selection Selection(&Index, *AST, Start, End, std::move(Tree), | |||
| 382 | nullptr); | |||
| 383 | // FS is only populated when applying a tweak, not during prepare as | |||
| 384 | // prepare should not do any I/O to be fast. | |||
| 385 | auto Tweaks = | |||
| 386 | prepareTweaks(Selection, Opts.TweakFilter, Opts.FeatureModules); | |||
| 387 | Selection.FS = | |||
| 388 | &AST->getSourceManager().getFileManager().getVirtualFileSystem(); | |||
| 389 | for (const auto &T : Tweaks) { | |||
| 390 | auto Result = T->apply(Selection); | |||
| 391 | if (!Result) { | |||
| 392 | elog(" tweak: {0} ==> FAIL: {1}", T->id(), Result.takeError()); | |||
| 393 | ++ErrCount; | |||
| 394 | } else { | |||
| 395 | vlog(" tweak: {0}", T->id()); | |||
| 396 | } | |||
| 397 | } | |||
| 398 | unsigned Definitions = locateSymbolAt(*AST, Pos, &Index).size(); | |||
| 399 | vlog(" definition: {0}", Definitions); | |||
| 400 | ||||
| 401 | auto Hover = getHover(*AST, Pos, Style, &Index); | |||
| 402 | vlog(" hover: {0}", Hover.has_value()); | |||
| 403 | ||||
| 404 | unsigned DocHighlights = findDocumentHighlights(*AST, Pos).size(); | |||
| 405 | vlog(" documentHighlight: {0}", DocHighlights); | |||
| 406 | ||||
| 407 | if (CheckCompletion) { | |||
| 408 | Position EndPos = offsetToPosition(Inputs.Contents, End); | |||
| 409 | auto CC = codeComplete(File, EndPos, Preamble.get(), Inputs, CCOpts); | |||
| 410 | vlog(" code completion: {0}", | |||
| 411 | CC.Completions.empty() ? "<empty>" : CC.Completions[0].Name); | |||
| 412 | } | |||
| 413 | } | |||
| 414 | } | |||
| 415 | }; | |||
| 416 | ||||
| 417 | } // namespace | |||
| 418 | ||||
| 419 | bool check(llvm::StringRef File, const ThreadsafeFS &TFS, | |||
| 420 | const ClangdLSPServer::Options &Opts) { | |||
| 421 | std::optional<Range> LineRange; | |||
| 422 | if (!CheckFileLines.empty()) { | |||
| ||||
| 423 | uint32_t Begin = 0, End = std::numeric_limits<uint32_t>::max(); | |||
| 424 | StringRef RangeStr(CheckFileLines); | |||
| 425 | bool ParseError = RangeStr.consumeInteger(0, Begin); | |||
| 426 | if (RangeStr.empty()) { | |||
| 427 | End = Begin; | |||
| 428 | } else { | |||
| 429 | ParseError |= !RangeStr.consume_front("-"); | |||
| 430 | ParseError |= RangeStr.consumeInteger(0, End); | |||
| 431 | } | |||
| 432 | if (ParseError || !RangeStr.empty() || Begin <= 0 || End < Begin) { | |||
| 433 | elog("Invalid --check-lines specified. Use Begin-End format, e.g. 3-17"); | |||
| 434 | return false; | |||
| 435 | } | |||
| 436 | LineRange = Range{Position{static_cast<int>(Begin - 1), 0}, | |||
| 437 | Position{static_cast<int>(End), 0}}; | |||
| 438 | } | |||
| 439 | ||||
| 440 | llvm::SmallString<0> FakeFile; | |||
| 441 | std::optional<std::string> Contents; | |||
| 442 | if (File.empty()) { | |||
| 443 | llvm::sys::path::system_temp_directory(false, FakeFile); | |||
| 444 | llvm::sys::path::append(FakeFile, "test.cc"); | |||
| 445 | File = FakeFile; | |||
| 446 | Contents = R"cpp( | |||
| 447 | #include <stddef.h> | |||
| 448 | #include <string> | |||
| 449 | ||||
| 450 | size_t N = 50; | |||
| 451 | auto xxx = std::string(N, 'x'); | |||
| 452 | )cpp"; | |||
| 453 | } | |||
| 454 | log("Testing on source file {0}", File); | |||
| 455 | ||||
| 456 | auto ContextProvider = ClangdServer::createConfiguredContextProvider( | |||
| 457 | Opts.ConfigProvider, nullptr); | |||
| 458 | WithContext Ctx(ContextProvider( | |||
| 459 | FakeFile.empty() | |||
| 460 | ? File | |||
| 461 | : /*Don't turn on local configs for an arbitrary temp path.*/ "")); | |||
| 462 | Checker C(File, Opts); | |||
| 463 | if (!C.buildCommand(TFS) || !C.buildInvocation(TFS, Contents) || | |||
| 464 | !C.buildAST()) | |||
| 465 | return false; | |||
| 466 | C.buildInlayHints(LineRange); | |||
| 467 | C.buildSemanticHighlighting(LineRange); | |||
| 468 | if (CheckLocations) | |||
| 469 | C.testLocationFeatures(LineRange); | |||
| 470 | ||||
| 471 | log("All checks completed, {0} errors", C.ErrCount); | |||
| 472 | return C.ErrCount == 0; | |||
| 473 | } | |||
| 474 | ||||
| 475 | } // namespace clangd | |||
| 476 | } // namespace clang |