clang-tools  16.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 {0} is: {1}",
115  Cmd.Heuristic.empty() ? "from CDB" : Cmd.Heuristic,
116  printArgv(Cmd.CommandLine));
117  } else {
118  Cmd = CDB->getFallbackCommand(File);
119  log("Generic fallback command is: {0}", printArgv(Cmd.CommandLine));
120  }
121 
122  return true;
123  }
124 
125  // Prepare inputs and build CompilerInvocation (parsed compile command).
126  bool buildInvocation(const ThreadsafeFS &TFS,
127  llvm::Optional<std::string> Contents) {
128  StoreDiags CaptureInvocationDiags;
129  std::vector<std::string> CC1Args;
130  Inputs.CompileCommand = Cmd;
131  Inputs.TFS = &TFS;
132  Inputs.ClangTidyProvider = Opts.ClangTidyProvider;
133  Inputs.Opts.PreambleParseForwardingFunctions =
134  Opts.PreambleParseForwardingFunctions;
135  if (Contents) {
136  Inputs.Contents = *Contents;
137  log("Imaginary source file contents:\n{0}", Inputs.Contents);
138  } else {
139  if (auto Contents = TFS.view(llvm::None)->getBufferForFile(File)) {
140  Inputs.Contents = Contents->get()->getBuffer().str();
141  } else {
142  elog("Couldn't read {0}: {1}", File, Contents.getError().message());
143  return false;
144  }
145  }
146  log("Parsing command...");
147  Invocation =
148  buildCompilerInvocation(Inputs, CaptureInvocationDiags, &CC1Args);
149  auto InvocationDiags = CaptureInvocationDiags.take();
150  ErrCount += showErrors(InvocationDiags);
151  log("internal (cc1) args are: {0}", printArgv(CC1Args));
152  if (!Invocation) {
153  elog("Failed to parse command line");
154  return false;
155  }
156 
157  // FIXME: Check that resource-dir/built-in-headers exist?
158 
159  Style = getFormatStyleForFile(File, Inputs.Contents, TFS);
160 
161  return true;
162  }
163 
164  // Build preamble and AST, and index them.
165  bool buildAST() {
166  log("Building preamble...");
167  Preamble = buildPreamble(File, *Invocation, Inputs, /*StoreInMemory=*/true,
168  [&](ASTContext &Ctx, Preprocessor &PP,
169  const CanonicalIncludes &Includes) {
170  if (!Opts.BuildDynamicSymbolIndex)
171  return;
172  log("Indexing headers...");
173  Index.updatePreamble(File, /*Version=*/"null",
174  Ctx, PP, Includes);
175  });
176  if (!Preamble) {
177  elog("Failed to build preamble");
178  return false;
179  }
180  ErrCount += showErrors(Preamble->Diags);
181 
182  log("Building AST...");
183  AST = ParsedAST::build(File, Inputs, std::move(Invocation),
184  /*InvocationDiags=*/std::vector<Diag>{}, Preamble);
185  if (!AST) {
186  elog("Failed to build AST");
187  return false;
188  }
189  ErrCount += showErrors(llvm::makeArrayRef(*AST->getDiagnostics())
190  .drop_front(Preamble->Diags.size()));
191 
192  if (Opts.BuildDynamicSymbolIndex) {
193  log("Indexing AST...");
194  Index.updateMain(File, *AST);
195  }
196  return true;
197  }
198 
199  // Build Inlay Hints for the entire AST or the specified range
200  void buildInlayHints(llvm::Optional<Range> LineRange) {
201  log("Building inlay hints");
202  auto Hints = inlayHints(*AST, LineRange);
203 
204  for (const auto &Hint : Hints) {
205  vlog(" {0} {1} {2}", Hint.kind, Hint.position, Hint.label);
206  }
207  }
208 
209  // Run AST-based features at each token in the file.
210  void testLocationFeatures(llvm::Optional<Range> LineRange,
211  const bool EnableCodeCompletion) {
212  trace::Span Trace("testLocationFeatures");
213  log("Testing features at each token (may be slow in large files)");
214  auto &SM = AST->getSourceManager();
215  auto SpelledTokens = AST->getTokens().spelledTokens(SM.getMainFileID());
216 
217  CodeCompleteOptions CCOpts = Opts.CodeComplete;
218  CCOpts.Index = &Index;
219 
220  for (const auto &Tok : SpelledTokens) {
221  unsigned Start = AST->getSourceManager().getFileOffset(Tok.location());
222  unsigned End = Start + Tok.length();
223  Position Pos = offsetToPosition(Inputs.Contents, Start);
224 
225  if (LineRange && !LineRange->contains(Pos))
226  continue;
227 
228  trace::Span Trace("Token");
229  SPAN_ATTACH(Trace, "pos", Pos);
230  SPAN_ATTACH(Trace, "text", Tok.text(AST->getSourceManager()));
231 
232  // FIXME: dumping the tokens may leak sensitive code into bug reports.
233  // Add an option to turn this off, once we decide how options work.
234  vlog(" {0} {1}", Pos, Tok.text(AST->getSourceManager()));
235  auto Tree = SelectionTree::createRight(AST->getASTContext(),
236  AST->getTokens(), Start, End);
237  Tweak::Selection Selection(&Index, *AST, Start, End, std::move(Tree),
238  nullptr);
239  // FS is only populated when applying a tweak, not during prepare as
240  // prepare should not do any I/O to be fast.
241  auto Tweaks =
242  prepareTweaks(Selection, Opts.TweakFilter, Opts.FeatureModules);
243  Selection.FS =
244  &AST->getSourceManager().getFileManager().getVirtualFileSystem();
245  for (const auto &T : Tweaks) {
246  auto Result = T->apply(Selection);
247  if (!Result) {
248  elog(" tweak: {0} ==> FAIL: {1}", T->id(), Result.takeError());
249  ++ErrCount;
250  } else {
251  vlog(" tweak: {0}", T->id());
252  }
253  }
254  unsigned Definitions = locateSymbolAt(*AST, Pos, &Index).size();
255  vlog(" definition: {0}", Definitions);
256 
257  auto Hover = getHover(*AST, Pos, Style, &Index);
258  vlog(" hover: {0}", Hover.has_value());
259 
260  unsigned DocHighlights = findDocumentHighlights(*AST, Pos).size();
261  vlog(" documentHighlight: {0}", DocHighlights);
262 
263  if (EnableCodeCompletion) {
264  Position EndPos = offsetToPosition(Inputs.Contents, End);
265  auto CC = codeComplete(File, EndPos, Preamble.get(), Inputs, CCOpts);
266  vlog(" code completion: {0}",
267  CC.Completions.empty() ? "<empty>" : CC.Completions[0].Name);
268  }
269  }
270  }
271 };
272 
273 } // namespace
274 
275 bool check(llvm::StringRef File, llvm::Optional<Range> LineRange,
276  const ThreadsafeFS &TFS, const ClangdLSPServer::Options &Opts,
277  bool EnableCodeCompletion) {
278  llvm::SmallString<0> FakeFile;
279  llvm::Optional<std::string> Contents;
280  if (File.empty()) {
281  llvm::sys::path::system_temp_directory(false, FakeFile);
282  llvm::sys::path::append(FakeFile, "test.cc");
283  File = FakeFile;
284  Contents = R"cpp(
285  #include <stddef.h>
286  #include <string>
287 
288  size_t N = 50;
289  auto xxx = std::string(N, 'x');
290  )cpp";
291  }
292  log("Testing on source file {0}", File);
293 
294  auto ContextProvider = ClangdServer::createConfiguredContextProvider(
295  Opts.ConfigProvider, nullptr);
296  WithContext Ctx(ContextProvider(
297  FakeFile.empty()
298  ? File
299  : /*Don't turn on local configs for an arbitrary temp path.*/ ""));
300  Checker C(File, Opts);
301  if (!C.buildCommand(TFS) || !C.buildInvocation(TFS, Contents) ||
302  !C.buildAST())
303  return false;
304  C.buildInlayHints(LineRange);
305  C.testLocationFeatures(LineRange, EnableCodeCompletion);
306 
307  log("All checks completed, {0} errors", C.ErrCount);
308  return C.ErrCount == 0;
309 }
310 
311 } // namespace clangd
312 } // 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:633
clang::clangd::check
bool check(llvm::StringRef File, llvm::Optional< Range > LineRange, const ThreadsafeFS &TFS, const ClangdLSPServer::Options &Opts, bool EnableCodeCompletion)
Definition: Check.cpp:275
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:553
Trace.h
Preamble
const PreambleData & Preamble
Definition: CodeComplete.cpp:1212
Pos
size_t Pos
Definition: NoLintDirectiveHandler.cpp:97
InlayHints.h
clang::clangd::ClangdLSPServer::Options
Definition: ClangdLSPServer.h:39
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:550
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:747
clang::clangd::SelectionTree::createRight
static SelectionTree createRight(ASTContext &AST, const syntax::TokenBuffer &Tokens, unsigned Begin, unsigned End)
Definition: Selection.cpp:1047
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:2044
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:1042
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:185
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:374
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:68
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:701
clang::clangd::Config::CDBSearchSpec::FixedCDBPath
llvm::Optional< std::string > FixedCDBPath
Definition: Config.h:59
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:1217
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:290