clang-tools  14.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 "Config.h"
31 #include "Hover.h"
32 #include "ParsedAST.h"
33 #include "Preamble.h"
34 #include "SourceCode.h"
35 #include "XRefs.h"
37 #include "index/FileIndex.h"
38 #include "refactor/Tweak.h"
39 #include "support/ThreadsafeFS.h"
40 #include "clang/AST/ASTContext.h"
41 #include "clang/Basic/DiagnosticIDs.h"
42 #include "clang/Format/Format.h"
43 #include "clang/Frontend/CompilerInvocation.h"
44 #include "clang/Tooling/CompilationDatabase.h"
45 #include "llvm/ADT/ArrayRef.h"
46 #include "llvm/ADT/Optional.h"
47 #include "llvm/ADT/StringExtras.h"
48 #include "llvm/Support/Path.h"
49 
50 namespace clang {
51 namespace clangd {
52 namespace {
53 
54 // Print (and count) the error-level diagnostics (warnings are ignored).
55 unsigned showErrors(llvm::ArrayRef<Diag> Diags) {
56  unsigned ErrCount = 0;
57  for (const auto &D : Diags) {
58  if (D.Severity >= DiagnosticsEngine::Error) {
59  elog("[{0}] Line {1}: {2}", D.Name, D.Range.start.line + 1, D.Message);
60  ++ErrCount;
61  }
62  }
63  return ErrCount;
64 }
65 
66 // This class is just a linear pipeline whose functions get called in sequence.
67 // Each exercises part of clangd's logic on our test file and logs results.
68 // Later steps depend on state built in earlier ones (such as the AST).
69 // Many steps can fatally fail (return false), then subsequent ones cannot run.
70 // Nonfatal failures are logged and tracked in ErrCount.
71 class Checker {
72  // from constructor
73  std::string File;
74  ClangdLSPServer::Options Opts;
75  // from buildCommand
76  tooling::CompileCommand Cmd;
77  // from buildInvocation
78  ParseInputs Inputs;
79  std::unique_ptr<CompilerInvocation> Invocation;
80  format::FormatStyle Style;
81  // from buildAST
82  std::shared_ptr<const PreambleData> Preamble;
83  llvm::Optional<ParsedAST> AST;
84  FileIndex Index;
85 
86 public:
87  // Number of non-fatal errors seen.
88  unsigned ErrCount = 0;
89 
90  Checker(llvm::StringRef File, const ClangdLSPServer::Options &Opts)
91  : File(File), Opts(Opts) {}
92 
93  // Read compilation database and choose a compile command for the file.
94  bool buildCommand(const ThreadsafeFS &TFS) {
95  log("Loading compilation database...");
96  DirectoryBasedGlobalCompilationDatabase::Options CDBOpts(TFS);
97  CDBOpts.CompileCommandsDir =
99  std::unique_ptr<GlobalCompilationDatabase> BaseCDB =
100  std::make_unique<DirectoryBasedGlobalCompilationDatabase>(CDBOpts);
101  BaseCDB = getQueryDriverDatabase(llvm::makeArrayRef(Opts.QueryDriverGlobs),
102  std::move(BaseCDB));
103  auto Mangler = CommandMangler::detect();
104  if (Opts.ResourceDir)
105  Mangler.ResourceDir = *Opts.ResourceDir;
106  auto CDB = std::make_unique<OverlayCDB>(
107  BaseCDB.get(), std::vector<std::string>{},
108  tooling::ArgumentsAdjuster(std::move(Mangler)));
109 
110  if (auto TrueCmd = CDB->getCompileCommand(File)) {
111  Cmd = std::move(*TrueCmd);
112  log("Compile command from CDB is: {0}", printArgv(Cmd.CommandLine));
113  } else {
114  Cmd = CDB->getFallbackCommand(File);
115  log("Generic fallback command is: {0}", printArgv(Cmd.CommandLine));
116  }
117 
118  return true;
119  }
120 
121  // Prepare inputs and build CompilerInvocation (parsed compile command).
122  bool buildInvocation(const ThreadsafeFS &TFS,
123  llvm::Optional<std::string> Contents) {
124  StoreDiags CaptureInvocationDiags;
125  std::vector<std::string> CC1Args;
126  Inputs.CompileCommand = Cmd;
127  Inputs.TFS = &TFS;
128  Inputs.ClangTidyProvider = Opts.ClangTidyProvider;
129  if (Contents.hasValue()) {
130  Inputs.Contents = *Contents;
131  log("Imaginary source file contents:\n{0}", Inputs.Contents);
132  } else {
133  if (auto Contents = TFS.view(llvm::None)->getBufferForFile(File)) {
134  Inputs.Contents = Contents->get()->getBuffer().str();
135  } else {
136  elog("Couldn't read {0}: {1}", File, Contents.getError().message());
137  return false;
138  }
139  }
140  log("Parsing command...");
141  Invocation =
142  buildCompilerInvocation(Inputs, CaptureInvocationDiags, &CC1Args);
143  auto InvocationDiags = CaptureInvocationDiags.take();
144  ErrCount += showErrors(InvocationDiags);
145  log("internal (cc1) args are: {0}", printArgv(CC1Args));
146  if (!Invocation) {
147  elog("Failed to parse command line");
148  return false;
149  }
150 
151  // FIXME: Check that resource-dir/built-in-headers exist?
152 
153  Style = getFormatStyleForFile(File, Inputs.Contents, TFS);
154 
155  return true;
156  }
157 
158  // Build preamble and AST, and index them.
159  bool buildAST() {
160  log("Building preamble...");
161  Preamble =
162  buildPreamble(File, *Invocation, Inputs, /*StoreInMemory=*/true,
163  [&](ASTContext &Ctx, std::shared_ptr<Preprocessor> PP,
164  const CanonicalIncludes &Includes) {
165  if (!Opts.BuildDynamicSymbolIndex)
166  return;
167  log("Indexing headers...");
168  Index.updatePreamble(File, /*Version=*/"null", Ctx,
169  std::move(PP), Includes);
170  });
171  if (!Preamble) {
172  elog("Failed to build preamble");
173  return false;
174  }
175  ErrCount += showErrors(Preamble->Diags);
176 
177  log("Building AST...");
178  AST = ParsedAST::build(File, Inputs, std::move(Invocation),
179  /*InvocationDiags=*/std::vector<Diag>{}, Preamble);
180  if (!AST) {
181  elog("Failed to build AST");
182  return false;
183  }
184  ErrCount += showErrors(llvm::makeArrayRef(*AST->getDiagnostics())
185  .drop_front(Preamble->Diags.size()));
186 
187  if (Opts.BuildDynamicSymbolIndex) {
188  log("Indexing AST...");
189  Index.updateMain(File, *AST);
190  }
191  return true;
192  }
193 
194  // Run AST-based features at each token in the file.
195  void testLocationFeatures(
196  llvm::function_ref<bool(const Position &)> ShouldCheckLine,
197  const bool EnableCodeCompletion) {
198  log("Testing features at each token (may be slow in large files)");
199  auto &SM = AST->getSourceManager();
200  auto SpelledTokens = AST->getTokens().spelledTokens(SM.getMainFileID());
201 
202  CodeCompleteOptions CCOpts = Opts.CodeComplete;
203  CCOpts.Index = &Index;
204 
205  for (const auto &Tok : SpelledTokens) {
206  unsigned Start = AST->getSourceManager().getFileOffset(Tok.location());
207  unsigned End = Start + Tok.length();
208  Position Pos = offsetToPosition(Inputs.Contents, Start);
209 
210  if (!ShouldCheckLine(Pos))
211  continue;
212 
213  // FIXME: dumping the tokens may leak sensitive code into bug reports.
214  // Add an option to turn this off, once we decide how options work.
215  vlog(" {0} {1}", Pos, Tok.text(AST->getSourceManager()));
216  auto Tree = SelectionTree::createRight(AST->getASTContext(),
217  AST->getTokens(), Start, End);
218  Tweak::Selection Selection(&Index, *AST, Start, End, std::move(Tree),
219  nullptr);
220  // FS is only populated when applying a tweak, not during prepare as
221  // prepare should not do any I/O to be fast.
222  auto Tweaks =
223  prepareTweaks(Selection, Opts.TweakFilter, Opts.FeatureModules);
224  Selection.FS =
225  &AST->getSourceManager().getFileManager().getVirtualFileSystem();
226  for (const auto &T : Tweaks) {
227  auto Result = T->apply(Selection);
228  if (!Result) {
229  elog(" tweak: {0} ==> FAIL: {1}", T->id(), Result.takeError());
230  ++ErrCount;
231  } else {
232  vlog(" tweak: {0}", T->id());
233  }
234  }
235  unsigned Definitions = locateSymbolAt(*AST, Pos, &Index).size();
236  vlog(" definition: {0}", Definitions);
237 
238  auto Hover = getHover(*AST, Pos, Style, &Index);
239  vlog(" hover: {0}", Hover.hasValue());
240 
241  if (EnableCodeCompletion) {
242  Position EndPos = offsetToPosition(Inputs.Contents, End);
243  auto CC = codeComplete(File, EndPos, Preamble.get(), Inputs, CCOpts);
244  vlog(" code completion: {0}",
245  CC.Completions.empty() ? "<empty>" : CC.Completions[0].Name);
246  }
247  }
248  }
249 };
250 
251 } // namespace
252 
253 bool check(llvm::StringRef File,
254  llvm::function_ref<bool(const Position &)> ShouldCheckLine,
255  const ThreadsafeFS &TFS, const ClangdLSPServer::Options &Opts,
256  bool EnableCodeCompletion) {
257  llvm::SmallString<0> FakeFile;
258  llvm::Optional<std::string> Contents;
259  if (File.empty()) {
260  llvm::sys::path::system_temp_directory(false, FakeFile);
261  llvm::sys::path::append(FakeFile, "test.cc");
262  File = FakeFile;
263  Contents = R"cpp(
264  #include <stddef.h>
265  #include <string>
266 
267  size_t N = 50;
268  auto xxx = std::string(N, 'x');
269  )cpp";
270  }
271  log("Testing on source file {0}", File);
272 
273  auto ContextProvider = ClangdServer::createConfiguredContextProvider(
274  Opts.ConfigProvider, nullptr);
275  WithContext Ctx(ContextProvider(
276  FakeFile.empty()
277  ? File
278  : /*Don't turn on local configs for an arbitrary temp path.*/ ""));
279  Checker C(File, Opts);
280  if (!C.buildCommand(TFS) || !C.buildInvocation(TFS, Contents) ||
281  !C.buildAST())
282  return false;
283  C.testLocationFeatures(ShouldCheckLine, EnableCodeCompletion);
284 
285  log("All checks completed, {0} errors", C.ErrCount);
286  return C.ErrCount == 0;
287 }
288 
289 } // namespace clangd
290 } // namespace clang
XRefs.h
CodeComplete.h
clang::clangd::printArgv
std::string printArgv(llvm::ArrayRef< llvm::StringRef > Args)
Definition: CompileCommands.cpp:582
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:454
clang::clangd::getHover
llvm::Optional< HoverInfo > getHover(ParsedAST &AST, Position Pos, format::FormatStyle Style, const SymbolIndex *Index)
Get the hover information when hovering at Pos.
Definition: Hover.cpp:913
Preamble
const PreambleData & Preamble
Definition: CodeComplete.cpp:1104
clang::clangd::ClangdLSPServer::Options
Definition: ClangdLSPServer.h:44
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:248
Hover.h
Error
constexpr static llvm::SourceMgr::DiagKind Error
Definition: ConfigCompile.cpp:500
Inputs
ParseInputs Inputs
Definition: TUScheduler.cpp:451
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:74
clang::clangd::Position
Definition: Protocol.h:148
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:756
clang::clangd::SelectionTree::createRight
static SelectionTree createRight(ASTContext &AST, const syntax::TokenBuffer &Tokens, unsigned Begin, unsigned End)
Definition: Selection.cpp:853
Tweak.h
ErrCount
unsigned ErrCount
Definition: Check.cpp:88
GlobalCompilationDatabase.h
clang::clangd::CommandMangler::detect
static CommandMangler detect()
Definition: CompileCommands.cpp:188
clang::clangd::buildPreamble
std::shared_ptr< const PreambleData > buildPreamble(PathRef FileName, CompilerInvocation CI, const ParseInputs &Inputs, bool StoreInMemory, PreambleParsedCallback PreambleCallback)
Build a preamble for the new inputs unless an old one can be reused.
Definition: Preamble.cpp:323
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:1863
CC
CognitiveComplexity CC
Definition: FunctionCognitiveComplexityCheck.cpp:496
clang::clangd::vlog
void vlog(const char *Fmt, Ts &&... Vals)
Definition: Logger.h:73
clang::clangd::offsetToPosition
Position offsetToPosition(llvm::StringRef Code, size_t Offset)
Turn an offset in Code into a [line, column] pair.
Definition: SourceCode.cpp:204
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:580
clang::tidy::bugprone::PP
static Preprocessor * PP
Definition: BadSignalToKillThreadCheck.cpp:29
clang::clangd::check
bool check(llvm::StringRef File, llvm::function_ref< bool(const Position &)> ShouldCheckLine, const ThreadsafeFS &TFS, const ClangdLSPServer::Options &Opts, bool EnableCodeCompletion)
Definition: Check.cpp:253
clang::clangd::log
void log(const char *Fmt, Ts &&... Vals)
Definition: Logger.h:68
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:84
SourceCode.h
Index
const SymbolIndex * Index
Definition: Dexp.cpp:99
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:361
Config.h
clang::clangd::ThreadsafeFS
Wrapper for vfs::FileSystem for use in multithreaded programs like clangd.
Definition: ThreadsafeFS.h:28
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:67
clang
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Definition: ApplyReplacements.h:27
Diags
CapturedDiags Diags
Definition: ConfigCompileTests.cpp:38
clang::clangd::Config::current
static const Config & current()
Returns the Config of the current Context, or an empty configuration.
Definition: Config.cpp:17
SM
const SourceManager & SM
Definition: IncludeCleaner.cpp:108
Pos
Position Pos
Definition: SourceCode.cpp:657
clang::clangd::Config::CDBSearchSpec::FixedCDBPath
llvm::Optional< std::string > FixedCDBPath
Definition: Config.h:58
clang::clangd::elog
void elog(const char *Fmt, Ts &&... Vals)
Definition: Logger.h:62
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::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:263