clang-tools  15.0.0git
Check.cpp
Go to the documentation of this file.
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 "ClangdLSPServer.h"
28 #include "CodeComplete.h"
29 #include "CompileCommands.h"
30 #include "Config.h"
32 #include "Hover.h"
33 #include "InlayHints.h"
34 #include "ParsedAST.h"
35 #include "Preamble.h"
36 #include "Protocol.h"
37 #include "SourceCode.h"
38 #include "XRefs.h"
40 #include "index/FileIndex.h"
41 #include "refactor/Tweak.h"
42 #include "support/ThreadsafeFS.h"
43 #include "support/Trace.h"
44 #include "clang/AST/ASTContext.h"
45 #include "clang/Format/Format.h"
46 #include "clang/Frontend/CompilerInvocation.h"
47 #include "clang/Tooling/CompilationDatabase.h"
48 #include "llvm/ADT/ArrayRef.h"
49 #include "llvm/ADT/Optional.h"
50 #include "llvm/Support/Path.h"
51 
52 namespace clang {
53 namespace clangd {
54 namespace {
55 
56 // Print (and count) the error-level diagnostics (warnings are ignored).
57 unsigned showErrors(llvm::ArrayRef<Diag> Diags) {
58  unsigned ErrCount = 0;
59  for (const auto &D : Diags) {
60  if (D.Severity >= DiagnosticsEngine::Error) {
61  elog("[{0}] Line {1}: {2}", D.Name, D.Range.start.line + 1, D.Message);
62  ++ErrCount;
63  }
64  }
65  return ErrCount;
66 }
67 
68 // This class is just a linear pipeline whose functions get called in sequence.
69 // Each exercises part of clangd's logic on our test file and logs results.
70 // Later steps depend on state built in earlier ones (such as the AST).
71 // Many steps can fatally fail (return false), then subsequent ones cannot run.
72 // Nonfatal failures are logged and tracked in ErrCount.
73 class Checker {
74  // from constructor
75  std::string File;
76  ClangdLSPServer::Options Opts;
77  // from buildCommand
78  tooling::CompileCommand Cmd;
79  // from buildInvocation
80  ParseInputs Inputs;
81  std::unique_ptr<CompilerInvocation> Invocation;
82  format::FormatStyle Style;
83  // from buildAST
84  std::shared_ptr<const PreambleData> Preamble;
85  llvm::Optional<ParsedAST> AST;
86  FileIndex Index;
87 
88 public:
89  // Number of non-fatal errors seen.
90  unsigned ErrCount = 0;
91 
92  Checker(llvm::StringRef File, const ClangdLSPServer::Options &Opts)
93  : File(File), Opts(Opts) {}
94 
95  // Read compilation database and choose a compile command for the file.
96  bool buildCommand(const ThreadsafeFS &TFS) {
97  log("Loading compilation database...");
98  DirectoryBasedGlobalCompilationDatabase::Options CDBOpts(TFS);
99  CDBOpts.CompileCommandsDir =
101  std::unique_ptr<GlobalCompilationDatabase> BaseCDB =
102  std::make_unique<DirectoryBasedGlobalCompilationDatabase>(CDBOpts);
103  BaseCDB = getQueryDriverDatabase(llvm::makeArrayRef(Opts.QueryDriverGlobs),
104  std::move(BaseCDB));
105  auto Mangler = CommandMangler::detect();
106  if (Opts.ResourceDir)
107  Mangler.ResourceDir = *Opts.ResourceDir;
108  auto CDB = std::make_unique<OverlayCDB>(
109  BaseCDB.get(), std::vector<std::string>{},
110  tooling::ArgumentsAdjuster(std::move(Mangler)));
111 
112  if (auto TrueCmd = CDB->getCompileCommand(File)) {
113  Cmd = std::move(*TrueCmd);
114  log("Compile command from CDB is: {0}", printArgv(Cmd.CommandLine));
115  } else {
116  Cmd = CDB->getFallbackCommand(File);
117  log("Generic fallback command is: {0}", printArgv(Cmd.CommandLine));
118  }
119 
120  return true;
121  }
122 
123  // Prepare inputs and build CompilerInvocation (parsed compile command).
124  bool buildInvocation(const ThreadsafeFS &TFS,
125  llvm::Optional<std::string> Contents) {
126  StoreDiags CaptureInvocationDiags;
127  std::vector<std::string> CC1Args;
128  Inputs.CompileCommand = Cmd;
129  Inputs.TFS = &TFS;
130  Inputs.ClangTidyProvider = Opts.ClangTidyProvider;
131  Inputs.Opts.PreambleParseForwardingFunctions =
132  Opts.PreambleParseForwardingFunctions;
133  if (Contents) {
134  Inputs.Contents = *Contents;
135  log("Imaginary source file contents:\n{0}", Inputs.Contents);
136  } else {
137  if (auto Contents = TFS.view(llvm::None)->getBufferForFile(File)) {
138  Inputs.Contents = Contents->get()->getBuffer().str();
139  } else {
140  elog("Couldn't read {0}: {1}", File, Contents.getError().message());
141  return false;
142  }
143  }
144  log("Parsing command...");
145  Invocation =
146  buildCompilerInvocation(Inputs, CaptureInvocationDiags, &CC1Args);
147  auto InvocationDiags = CaptureInvocationDiags.take();
148  ErrCount += showErrors(InvocationDiags);
149  log("internal (cc1) args are: {0}", printArgv(CC1Args));
150  if (!Invocation) {
151  elog("Failed to parse command line");
152  return false;
153  }
154 
155  // FIXME: Check that resource-dir/built-in-headers exist?
156 
157  Style = getFormatStyleForFile(File, Inputs.Contents, TFS);
158 
159  return true;
160  }
161 
162  // Build preamble and AST, and index them.
163  bool buildAST() {
164  log("Building preamble...");
165  Preamble = buildPreamble(File, *Invocation, Inputs, /*StoreInMemory=*/true,
166  [&](ASTContext &Ctx, Preprocessor &PP,
167  const CanonicalIncludes &Includes) {
168  if (!Opts.BuildDynamicSymbolIndex)
169  return;
170  log("Indexing headers...");
171  Index.updatePreamble(File, /*Version=*/"null",
172  Ctx, PP, Includes);
173  });
174  if (!Preamble) {
175  elog("Failed to build preamble");
176  return false;
177  }
178  ErrCount += showErrors(Preamble->Diags);
179 
180  log("Building AST...");
181  AST = ParsedAST::build(File, Inputs, std::move(Invocation),
182  /*InvocationDiags=*/std::vector<Diag>{}, Preamble);
183  if (!AST) {
184  elog("Failed to build AST");
185  return false;
186  }
187  ErrCount += showErrors(llvm::makeArrayRef(*AST->getDiagnostics())
188  .drop_front(Preamble->Diags.size()));
189 
190  if (Opts.BuildDynamicSymbolIndex) {
191  log("Indexing AST...");
192  Index.updateMain(File, *AST);
193  }
194  return true;
195  }
196 
197  // Build Inlay Hints for the entire AST or the specified range
198  void buildInlayHints(llvm::Optional<Range> LineRange) {
199  log("Building inlay hints");
200  auto Hints = inlayHints(*AST, LineRange);
201 
202  for (const auto &Hint : Hints) {
203  vlog(" {0} {1} {2}", Hint.kind, Hint.position, Hint.label);
204  }
205  }
206 
207  // Run AST-based features at each token in the file.
208  void testLocationFeatures(llvm::Optional<Range> LineRange,
209  const bool EnableCodeCompletion) {
210  trace::Span Trace("testLocationFeatures");
211  log("Testing features at each token (may be slow in large files)");
212  auto &SM = AST->getSourceManager();
213  auto SpelledTokens = AST->getTokens().spelledTokens(SM.getMainFileID());
214 
215  CodeCompleteOptions CCOpts = Opts.CodeComplete;
216  CCOpts.Index = &Index;
217 
218  for (const auto &Tok : SpelledTokens) {
219  unsigned Start = AST->getSourceManager().getFileOffset(Tok.location());
220  unsigned End = Start + Tok.length();
221  Position Pos = offsetToPosition(Inputs.Contents, Start);
222 
223  if (LineRange && !LineRange->contains(Pos))
224  continue;
225 
226  trace::Span Trace("Token");
227  SPAN_ATTACH(Trace, "pos", Pos);
228  SPAN_ATTACH(Trace, "text", Tok.text(AST->getSourceManager()));
229 
230  // FIXME: dumping the tokens may leak sensitive code into bug reports.
231  // Add an option to turn this off, once we decide how options work.
232  vlog(" {0} {1}", Pos, Tok.text(AST->getSourceManager()));
233  auto Tree = SelectionTree::createRight(AST->getASTContext(),
234  AST->getTokens(), Start, End);
235  Tweak::Selection Selection(&Index, *AST, Start, End, std::move(Tree),
236  nullptr);
237  // FS is only populated when applying a tweak, not during prepare as
238  // prepare should not do any I/O to be fast.
239  auto Tweaks =
240  prepareTweaks(Selection, Opts.TweakFilter, Opts.FeatureModules);
241  Selection.FS =
242  &AST->getSourceManager().getFileManager().getVirtualFileSystem();
243  for (const auto &T : Tweaks) {
244  auto Result = T->apply(Selection);
245  if (!Result) {
246  elog(" tweak: {0} ==> FAIL: {1}", T->id(), Result.takeError());
247  ++ErrCount;
248  } else {
249  vlog(" tweak: {0}", T->id());
250  }
251  }
252  unsigned Definitions = locateSymbolAt(*AST, Pos, &Index).size();
253  vlog(" definition: {0}", Definitions);
254 
255  auto Hover = getHover(*AST, Pos, Style, &Index);
256  vlog(" hover: {0}", Hover.has_value());
257 
258  unsigned DocHighlights = findDocumentHighlights(*AST, Pos).size();
259  vlog(" documentHighlight: {0}", DocHighlights);
260 
261  if (EnableCodeCompletion) {
262  Position EndPos = offsetToPosition(Inputs.Contents, End);
263  auto CC = codeComplete(File, EndPos, Preamble.get(), Inputs, CCOpts);
264  vlog(" code completion: {0}",
265  CC.Completions.empty() ? "<empty>" : CC.Completions[0].Name);
266  }
267  }
268  }
269 };
270 
271 } // namespace
272 
273 bool check(llvm::StringRef File, llvm::Optional<Range> LineRange,
274  const ThreadsafeFS &TFS, const ClangdLSPServer::Options &Opts,
275  bool EnableCodeCompletion) {
276  llvm::SmallString<0> FakeFile;
277  llvm::Optional<std::string> Contents;
278  if (File.empty()) {
279  llvm::sys::path::system_temp_directory(false, FakeFile);
280  llvm::sys::path::append(FakeFile, "test.cc");
281  File = FakeFile;
282  Contents = R"cpp(
283  #include <stddef.h>
284  #include <string>
285 
286  size_t N = 50;
287  auto xxx = std::string(N, 'x');
288  )cpp";
289  }
290  log("Testing on source file {0}", File);
291 
292  auto ContextProvider = ClangdServer::createConfiguredContextProvider(
293  Opts.ConfigProvider, nullptr);
294  WithContext Ctx(ContextProvider(
295  FakeFile.empty()
296  ? File
297  : /*Don't turn on local configs for an arbitrary temp path.*/ ""));
298  Checker C(File, Opts);
299  if (!C.buildCommand(TFS) || !C.buildInvocation(TFS, Contents) ||
300  !C.buildAST())
301  return false;
302  C.buildInlayHints(LineRange);
303  C.testLocationFeatures(LineRange, EnableCodeCompletion);
304 
305  log("All checks completed, {0} errors", C.ErrCount);
306  return C.ErrCount == 0;
307 }
308 
309 } // namespace clangd
310 } // namespace clang
clang::clangd::buildPreamble
std::shared_ptr< const PreambleData > buildPreamble(PathRef FileName, CompilerInvocation CI, const ParseInputs &Inputs, bool StoreInMemory, PreambleParsedCallback PreambleCallback, PreambleBuildStats *Stats)
Build a preamble for the new inputs unless an old one can be reused.
Definition: Preamble.cpp:466
XRefs.h
Hints
std::vector< FixItHint > Hints
Definition: RedundantStrcatCallsCheck.cpp:46
CodeComplete.h
clang::clangd::printArgv
std::string printArgv(llvm::ArrayRef< llvm::StringRef > Args)
Definition: CompileCommands.cpp:628
clang::clangd::check
bool check(llvm::StringRef File, llvm::Optional< Range > LineRange, const ThreadsafeFS &TFS, const ClangdLSPServer::Options &Opts, bool EnableCodeCompletion)
Definition: Check.cpp:273
FormatStyle
static cl::opt< std::string > FormatStyle("format-style", cl::desc(R"( Style for formatting code around applied fixes: - 'none' (default) turns off formatting - 'file' (literally 'file', not a placeholder) uses .clang-format file in the closest parent directory - '{ <json> }' specifies options inline, e.g. -format-style='{BasedOnStyle: llvm, IndentWidth: 8}' - 'llvm', 'google', 'webkit', 'mozilla' See clang-format documentation for the up-to-date information about formatting styles and options. This option overrides the 'FormatStyle` option in .clang-tidy file, if any. )"), cl::init("none"), cl::cat(ClangTidyCategory))
Preamble.h
Ctx
Context Ctx
Definition: TUScheduler.cpp:495
Trace.h
Preamble
const PreambleData & Preamble
Definition: CodeComplete.cpp:1204
Pos
size_t Pos
Definition: NoLintDirectiveHandler.cpp:97
InlayHints.h
clang::clangd::ClangdLSPServer::Options
Definition: ClangdLSPServer.h:38
Protocol.h
ThreadsafeFS.h
clang::clangd::ParsedAST::build
static llvm::Optional< ParsedAST > build(llvm::StringRef Filename, const ParseInputs &Inputs, std::unique_ptr< clang::CompilerInvocation > CI, llvm::ArrayRef< Diag > CompilerInvocationDiags, std::shared_ptr< const PreambleData > Preamble)
Attempts to run Clang and store the parsed AST.
Definition: ParsedAST.cpp:342
Hover.h
Error
constexpr static llvm::SourceMgr::DiagKind Error
Definition: ConfigCompile.cpp:591
Inputs
ParseInputs Inputs
Definition: TUScheduler.cpp:492
clang::clangd::prepareTweaks
std::vector< std::unique_ptr< Tweak > > prepareTweaks(const Tweak::Selection &S, llvm::function_ref< bool(const Tweak &)> Filter, const FeatureModuleSet *Modules)
Calls prepare() on all tweaks that satisfy the filter, returning those that can run on the selection.
Definition: Tweak.cpp:73
CanonicalIncludes.h
ns1::ns2::D
@ D
Definition: CategoricalFeature.h:3
ClangdLSPServer.h
clang::clangd::locateSymbolAt
std::vector< LocatedSymbol > locateSymbolAt(ParsedAST &AST, Position Pos, const SymbolIndex *Index)
Get definition of symbol at a specified Pos.
Definition: XRefs.cpp:760
clang::clangd::SelectionTree::createRight
static SelectionTree createRight(ASTContext &AST, const syntax::TokenBuffer &Tokens, unsigned Begin, unsigned End)
Definition: Selection.cpp:1039
Tweak.h
ErrCount
unsigned ErrCount
Definition: Check.cpp:90
GlobalCompilationDatabase.h
clang::clangd::CommandMangler::detect
static CommandMangler detect()
Definition: CompileCommands.cpp:188
CompileCommands.h
clang::clangd::codeComplete
CodeCompleteResult codeComplete(PathRef FileName, Position Pos, const PreambleData *Preamble, const ParseInputs &ParseInput, CodeCompleteOptions Opts, SpeculativeFuzzyFind *SpecFuzzyFind)
Gets code completions at a specified Pos in FileName.
Definition: CodeComplete.cpp:2026
SPAN_ATTACH
#define SPAN_ATTACH(S, Name, Expr)
Attach a key-value pair to a Span event.
Definition: Trace.h:164
CC
CognitiveComplexity CC
Definition: FunctionCognitiveComplexityCheck.cpp:495
clang::clangd::vlog
void vlog(const char *Fmt, Ts &&... Vals)
Definition: Logger.h:72
clang::clangd::getHover
llvm::Optional< HoverInfo > getHover(ParsedAST &AST, Position Pos, const format::FormatStyle &Style, const SymbolIndex *Index)
Get the hover information when hovering at Pos.
Definition: Hover.cpp:960
clang::clangd::offsetToPosition
Position offsetToPosition(llvm::StringRef Code, size_t Offset)
Turn an offset in Code into a [line, column] pair.
Definition: SourceCode.cpp:201
FileIndex.h
clang::clangd::WithContext
WithContext replaces Context::current() with a provided scope.
Definition: Context.h:187
clang::clangd::getFormatStyleForFile
format::FormatStyle getFormatStyleForFile(llvm::StringRef File, llvm::StringRef Content, const ThreadsafeFS &TFS)
Choose the clang-format style we should apply to a certain file.
Definition: SourceCode.cpp:577
clang::tidy::bugprone::PP
static Preprocessor * PP
Definition: BadSignalToKillThreadCheck.cpp:29
clang::clangd::log
void log(const char *Fmt, Ts &&... Vals)
Definition: Logger.h:67
clang::clangd::buildCompilerInvocation
std::unique_ptr< CompilerInvocation > buildCompilerInvocation(const ParseInputs &Inputs, clang::DiagnosticConsumer &D, std::vector< std::string > *CC1Args)
Builds compiler invocation that could be used to build AST or preamble.
Definition: Compiler.cpp:86
SourceCode.h
Index
const SymbolIndex * Index
Definition: Dexp.cpp:98
clang::clangd::getQueryDriverDatabase
std::unique_ptr< GlobalCompilationDatabase > getQueryDriverDatabase(llvm::ArrayRef< std::string > QueryDriverGlobs, std::unique_ptr< GlobalCompilationDatabase > Base)
Extracts system include search path from drivers matching QueryDriverGlobs and adds them to the compi...
Definition: QueryDriverDatabase.cpp:375
Config.h
clang::clangd::ThreadsafeFS
Wrapper for vfs::FileSystem for use in multithreaded programs like clangd.
Definition: ThreadsafeFS.h:27
C
const Criteria C
Definition: FunctionCognitiveComplexityCheck.cpp:93
clang::clangd::Config::CDBSearch
CDBSearchSpec CDBSearch
Where to search for compilation databases for this file's flags.
Definition: Config.h:69
clang
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Definition: ApplyReplacements.h:27
Diags
CapturedDiags Diags
Definition: ConfigCompileTests.cpp:39
clang::clangd::Config::current
static const Config & current()
Returns the Config of the current Context, or an empty configuration.
Definition: Config.cpp:17
clang::clangd::inlayHints
std::vector< InlayHint > inlayHints(ParsedAST &AST, llvm::Optional< Range > RestrictRange)
Compute and return inlay hints for a file.
Definition: InlayHints.cpp:681
clang::clangd::Config::CDBSearchSpec::FixedCDBPath
llvm::Optional< std::string > FixedCDBPath
Definition: Config.h:60
clang::clangd::elog
void elog(const char *Fmt, Ts &&... Vals)
Definition: Logger.h:61
clang::clangd::Config::CompileFlags
struct clang::clangd::Config::@2 CompileFlags
Controls how the compile command for the current file is determined.
ParsedAST.h
clang::clangd::findDocumentHighlights
std::vector< DocumentHighlight > findDocumentHighlights(ParsedAST &AST, Position Pos)
Returns highlights for all usages of a symbol at Pos.
Definition: XRefs.cpp:1240
clang::clangd::ClangdServer::createConfiguredContextProvider
static std::function< Context(PathRef)> createConfiguredContextProvider(const config::Provider *Provider, ClangdServer::Callbacks *)
Creates a context provider that loads and installs config.
Definition: ClangdServer.cpp:288