clang-tools 20.0.0git
clang-include-fixer/IncludeFixer.cpp
Go to the documentation of this file.
1//===-- IncludeFixer.cpp - Include inserter based on sema callbacks -------===//
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#include "IncludeFixer.h"
10#include "clang/Format/Format.h"
11#include "clang/Frontend/CompilerInstance.h"
12#include "clang/Lex/HeaderSearch.h"
13#include "clang/Lex/Preprocessor.h"
14#include "clang/Parse/ParseAST.h"
15#include "clang/Sema/Sema.h"
16#include "llvm/Support/Debug.h"
17#include "llvm/Support/raw_ostream.h"
18
19#define DEBUG_TYPE "clang-include-fixer"
20
21using namespace clang;
22
23namespace clang {
24namespace include_fixer {
25namespace {
26/// Manages the parse, gathers include suggestions.
27class Action : public clang::ASTFrontendAction {
28public:
29 explicit Action(SymbolIndexManager &SymbolIndexMgr, bool MinimizeIncludePaths)
30 : SemaSource(new IncludeFixerSemaSource(SymbolIndexMgr,
31 MinimizeIncludePaths,
32 /*GenerateDiagnostics=*/false)) {}
33
34 std::unique_ptr<clang::ASTConsumer>
35 CreateASTConsumer(clang::CompilerInstance &Compiler,
36 StringRef InFile) override {
37 SemaSource->setFilePath(InFile);
38 return std::make_unique<clang::ASTConsumer>();
39 }
40
41 void ExecuteAction() override {
42 clang::CompilerInstance *Compiler = &getCompilerInstance();
43 assert(!Compiler->hasSema() && "CI already has Sema");
44
45 // Set up our hooks into sema and parse the AST.
46 if (hasCodeCompletionSupport() &&
47 !Compiler->getFrontendOpts().CodeCompletionAt.FileName.empty())
48 Compiler->createCodeCompletionConsumer();
49
50 clang::CodeCompleteConsumer *CompletionConsumer = nullptr;
51 if (Compiler->hasCodeCompletionConsumer())
52 CompletionConsumer = &Compiler->getCodeCompletionConsumer();
53
54 Compiler->createSema(getTranslationUnitKind(), CompletionConsumer);
55 SemaSource->setCompilerInstance(Compiler);
56 Compiler->getSema().addExternalSource(SemaSource.get());
57
58 clang::ParseAST(Compiler->getSema(), Compiler->getFrontendOpts().ShowStats,
59 Compiler->getFrontendOpts().SkipFunctionBodies);
60 }
61
62 IncludeFixerContext
63 getIncludeFixerContext(const clang::SourceManager &SourceManager,
64 clang::HeaderSearch &HeaderSearch) const {
65 return SemaSource->getIncludeFixerContext(SourceManager, HeaderSearch,
66 SemaSource->getMatchedSymbols());
67 }
68
69private:
70 IntrusiveRefCntPtr<IncludeFixerSemaSource> SemaSource;
71};
72
73} // namespace
74
76 SymbolIndexManager &SymbolIndexMgr,
77 std::vector<IncludeFixerContext> &Contexts, StringRef StyleName,
78 bool MinimizeIncludePaths)
79 : SymbolIndexMgr(SymbolIndexMgr), Contexts(Contexts),
80 MinimizeIncludePaths(MinimizeIncludePaths) {}
81
83
85 std::shared_ptr<clang::CompilerInvocation> Invocation,
86 clang::FileManager *Files,
87 std::shared_ptr<clang::PCHContainerOperations> PCHContainerOps,
88 clang::DiagnosticConsumer *Diagnostics) {
89 assert(Invocation->getFrontendOpts().Inputs.size() == 1);
90
91 // Set up Clang.
92 clang::CompilerInstance Compiler(PCHContainerOps);
93 Compiler.setInvocation(std::move(Invocation));
94 Compiler.setFileManager(Files);
95
96 // Create the compiler's actual diagnostics engine. We want to drop all
97 // diagnostics here.
98 Compiler.createDiagnostics(Files->getVirtualFileSystem(),
99 new clang::IgnoringDiagConsumer,
100 /*ShouldOwnClient=*/true);
101 Compiler.createSourceManager(*Files);
102
103 // We abort on fatal errors so don't let a large number of errors become
104 // fatal. A missing #include can cause thousands of errors.
105 Compiler.getDiagnostics().setErrorLimit(0);
106
107 // Run the parser, gather missing includes.
108 auto ScopedToolAction =
109 std::make_unique<Action>(SymbolIndexMgr, MinimizeIncludePaths);
110 Compiler.ExecuteAction(*ScopedToolAction);
111
112 Contexts.push_back(ScopedToolAction->getIncludeFixerContext(
113 Compiler.getSourceManager(),
114 Compiler.getPreprocessor().getHeaderSearchInfo()));
115
116 // Technically this should only return true if we're sure that we have a
117 // parseable file. We don't know that though. Only inform users of fatal
118 // errors.
119 return !Compiler.getDiagnostics().hasFatalErrorOccurred();
120}
121
122static bool addDiagnosticsForContext(TypoCorrection &Correction,
123 const IncludeFixerContext &Context,
124 StringRef Code, SourceLocation StartOfFile,
125 ASTContext &Ctx) {
127 Code, Context, format::getLLVMStyle(), /*AddQualifiers=*/false);
128 if (!Reps || Reps->size() != 1)
129 return false;
130
131 unsigned DiagID = Ctx.getDiagnostics().getCustomDiagID(
132 DiagnosticsEngine::Note, "Add '#include %0' to provide the missing "
133 "declaration [clang-include-fixer]");
134
135 // FIXME: Currently we only generate a diagnostic for the first header. Give
136 // the user choices.
137 const tooling::Replacement &Placed = *Reps->begin();
138
139 auto Begin = StartOfFile.getLocWithOffset(Placed.getOffset());
140 auto End = Begin.getLocWithOffset(std::max(0, (int)Placed.getLength() - 1));
141 PartialDiagnostic PD(DiagID, Ctx.getDiagAllocator());
142 PD << Context.getHeaderInfos().front().Header
143 << FixItHint::CreateReplacement(CharSourceRange::getCharRange(Begin, End),
144 Placed.getReplacementText());
145 Correction.addExtraDiagnostic(std::move(PD));
146 return true;
147}
148
149/// Callback for incomplete types. If we encounter a forward declaration we
150/// have the fully qualified name ready. Just query that.
152 clang::SourceLocation Loc, clang::QualType T) {
153 // Ignore spurious callbacks from SFINAE contexts.
154 if (CI->getSema().isSFINAEContext())
155 return false;
156
157 clang::ASTContext &context = CI->getASTContext();
158 std::string QueryString = QualType(T->getUnqualifiedDesugaredType(), 0)
159 .getAsString(context.getPrintingPolicy());
160 LLVM_DEBUG(llvm::dbgs() << "Query missing complete type '" << QueryString
161 << "'");
162 // Pass an empty range here since we don't add qualifier in this case.
163 std::vector<find_all_symbols::SymbolInfo> MatchedSymbols =
164 query(QueryString, "", tooling::Range());
165
166 if (!MatchedSymbols.empty() && GenerateDiagnostics) {
167 TypoCorrection Correction;
168 FileID FID = CI->getSourceManager().getFileID(Loc);
169 StringRef Code = CI->getSourceManager().getBufferData(FID);
170 SourceLocation StartOfFile =
171 CI->getSourceManager().getLocForStartOfFile(FID);
173 Correction,
174 getIncludeFixerContext(CI->getSourceManager(),
175 CI->getPreprocessor().getHeaderSearchInfo(),
176 MatchedSymbols),
177 Code, StartOfFile, CI->getASTContext());
178 for (const PartialDiagnostic &PD : Correction.getExtraDiagnostics())
179 CI->getSema().Diag(Loc, PD);
180 }
181 return true;
182}
183
184/// Callback for unknown identifiers. Try to piece together as much
185/// qualification as we can get and do a query.
187 const DeclarationNameInfo &Typo, int LookupKind, Scope *S, CXXScopeSpec *SS,
188 CorrectionCandidateCallback &CCC, DeclContext *MemberContext,
189 bool EnteringContext, const ObjCObjectPointerType *OPT) {
190 // Ignore spurious callbacks from SFINAE contexts.
191 if (CI->getSema().isSFINAEContext())
192 return clang::TypoCorrection();
193
194 // We currently ignore the unidentified symbol which is not from the
195 // main file.
196 //
197 // However, this is not always true due to templates in a non-self contained
198 // header, consider the case:
199 //
200 // // header.h
201 // template <typename T>
202 // class Foo {
203 // T t;
204 // };
205 //
206 // // test.cc
207 // // We need to add <bar.h> in test.cc instead of header.h.
208 // class Bar;
209 // Foo<Bar> foo;
210 //
211 // FIXME: Add the missing header to the header file where the symbol comes
212 // from.
213 if (!CI->getSourceManager().isWrittenInMainFile(Typo.getLoc()))
214 return clang::TypoCorrection();
215
216 std::string TypoScopeString;
217 if (S) {
218 // FIXME: Currently we only use namespace contexts. Use other context
219 // types for query.
220 for (const auto *Context = S->getEntity(); Context;
221 Context = Context->getParent()) {
222 if (const auto *ND = dyn_cast<NamespaceDecl>(Context)) {
223 if (!ND->getName().empty())
224 TypoScopeString = ND->getNameAsString() + "::" + TypoScopeString;
225 }
226 }
227 }
228
229 auto ExtendNestedNameSpecifier = [this](CharSourceRange Range) {
230 StringRef Source =
231 Lexer::getSourceText(Range, CI->getSourceManager(), CI->getLangOpts());
232
233 // Skip forward until we find a character that's neither identifier nor
234 // colon. This is a bit of a hack around the fact that we will only get a
235 // single callback for a long nested name if a part of the beginning is
236 // unknown. For example:
237 //
238 // llvm::sys::path::parent_path(...)
239 // ^~~~ ^~~
240 // known
241 // ^~~~
242 // unknown, last callback
243 // ^~~~~~~~~~~
244 // no callback
245 //
246 // With the extension we get the full nested name specifier including
247 // parent_path.
248 // FIXME: Don't rely on source text.
249 const char *End = Source.end();
250 while (isAsciiIdentifierContinue(*End) || *End == ':')
251 ++End;
252
253 return std::string(Source.begin(), End);
254 };
255
256 /// If we have a scope specification, use that to get more precise results.
257 std::string QueryString;
258 tooling::Range SymbolRange;
259 const auto &SM = CI->getSourceManager();
260 auto CreateToolingRange = [&QueryString, &SM](SourceLocation BeginLoc) {
261 return tooling::Range(SM.getDecomposedLoc(BeginLoc).second,
262 QueryString.size());
263 };
264 if (SS && SS->getRange().isValid()) {
265 auto Range = CharSourceRange::getTokenRange(SS->getRange().getBegin(),
266 Typo.getLoc());
267
268 QueryString = ExtendNestedNameSpecifier(Range);
269 SymbolRange = CreateToolingRange(Range.getBegin());
270 } else if (Typo.getName().isIdentifier() && !Typo.getLoc().isMacroID()) {
271 auto Range =
272 CharSourceRange::getTokenRange(Typo.getBeginLoc(), Typo.getEndLoc());
273
274 QueryString = ExtendNestedNameSpecifier(Range);
275 SymbolRange = CreateToolingRange(Range.getBegin());
276 } else {
277 QueryString = Typo.getAsString();
278 SymbolRange = CreateToolingRange(Typo.getLoc());
279 }
280
281 LLVM_DEBUG(llvm::dbgs() << "TypoScopeQualifiers: " << TypoScopeString
282 << "\n");
283 std::vector<find_all_symbols::SymbolInfo> MatchedSymbols =
284 query(QueryString, TypoScopeString, SymbolRange);
285
286 if (!MatchedSymbols.empty() && GenerateDiagnostics) {
287 TypoCorrection Correction(Typo.getName());
288 Correction.setCorrectionRange(SS, Typo);
289 FileID FID = SM.getFileID(Typo.getLoc());
290 StringRef Code = SM.getBufferData(FID);
291 SourceLocation StartOfFile = SM.getLocForStartOfFile(FID);
293 Correction, getIncludeFixerContext(
294 SM, CI->getPreprocessor().getHeaderSearchInfo(),
295 MatchedSymbols),
296 Code, StartOfFile, CI->getASTContext()))
297 return Correction;
298 }
299 return TypoCorrection();
300}
301
302/// Get the minimal include for a given path.
304 StringRef Include, const clang::SourceManager &SourceManager,
305 clang::HeaderSearch &HeaderSearch) const {
306 if (!MinimizeIncludePaths)
307 return std::string(Include);
308
309 // Get the FileEntry for the include.
310 StringRef StrippedInclude = Include.trim("\"<>");
311 auto Entry =
312 SourceManager.getFileManager().getOptionalFileRef(StrippedInclude);
313
314 // If the file doesn't exist return the path from the database.
315 // FIXME: This should never happen.
316 if (!Entry)
317 return std::string(Include);
318
319 bool IsAngled = false;
320 std::string Suggestion =
321 HeaderSearch.suggestPathToFileForDiagnostics(*Entry, "", &IsAngled);
322
323 return IsAngled ? '<' + Suggestion + '>' : '"' + Suggestion + '"';
324}
325
326/// Get the include fixer context for the queried symbol.
328 const clang::SourceManager &SourceManager,
329 clang::HeaderSearch &HeaderSearch,
330 ArrayRef<find_all_symbols::SymbolInfo> MatchedSymbols) const {
331 std::vector<find_all_symbols::SymbolInfo> SymbolCandidates;
332 for (const auto &Symbol : MatchedSymbols) {
333 std::string FilePath = Symbol.getFilePath().str();
334 std::string MinimizedFilePath = minimizeInclude(
335 ((FilePath[0] == '"' || FilePath[0] == '<') ? FilePath
336 : "\"" + FilePath + "\""),
337 SourceManager, HeaderSearch);
338 SymbolCandidates.emplace_back(Symbol.getName(), Symbol.getSymbolKind(),
339 MinimizedFilePath, Symbol.getContexts());
340 }
341 return IncludeFixerContext(FilePath, QuerySymbolInfos, SymbolCandidates);
342}
343
344std::vector<find_all_symbols::SymbolInfo>
345IncludeFixerSemaSource::query(StringRef Query, StringRef ScopedQualifiers,
346 tooling::Range Range) {
347 assert(!Query.empty() && "Empty query!");
348
349 // Save all instances of an unidentified symbol.
350 //
351 // We use conservative behavior for detecting the same unidentified symbol
352 // here. The symbols which have the same ScopedQualifier and RawIdentifier
353 // are considered equal. So that clang-include-fixer avoids false positives,
354 // and always adds missing qualifiers to correct symbols.
355 if (!GenerateDiagnostics && !QuerySymbolInfos.empty()) {
356 if (ScopedQualifiers == QuerySymbolInfos.front().ScopedQualifiers &&
357 Query == QuerySymbolInfos.front().RawIdentifier) {
358 QuerySymbolInfos.push_back(
359 {Query.str(), std::string(ScopedQualifiers), Range});
360 }
361 return {};
362 }
363
364 LLVM_DEBUG(llvm::dbgs() << "Looking up '" << Query << "' at ");
365 LLVM_DEBUG(CI->getSourceManager()
366 .getLocForStartOfFile(CI->getSourceManager().getMainFileID())
367 .getLocWithOffset(Range.getOffset())
368 .print(llvm::dbgs(), CI->getSourceManager()));
369 LLVM_DEBUG(llvm::dbgs() << " ...");
370 llvm::StringRef FileName = CI->getSourceManager().getFilename(
371 CI->getSourceManager().getLocForStartOfFile(
372 CI->getSourceManager().getMainFileID()));
373
374 QuerySymbolInfos.push_back(
375 {Query.str(), std::string(ScopedQualifiers), Range});
376
377 // Query the symbol based on C++ name Lookup rules.
378 // Firstly, lookup the identifier with scoped namespace contexts;
379 // If that fails, falls back to look up the identifier directly.
380 //
381 // For example:
382 //
383 // namespace a {
384 // b::foo f;
385 // }
386 //
387 // 1. lookup a::b::foo.
388 // 2. lookup b::foo.
389 std::string QueryString = ScopedQualifiers.str() + Query.str();
390 // It's unsafe to do nested search for the identifier with scoped namespace
391 // context, it might treat the identifier as a nested class of the scoped
392 // namespace.
393 std::vector<find_all_symbols::SymbolInfo> MatchedSymbols =
394 SymbolIndexMgr.search(QueryString, /*IsNestedSearch=*/false, FileName);
395 if (MatchedSymbols.empty())
396 MatchedSymbols =
397 SymbolIndexMgr.search(Query, /*IsNestedSearch=*/true, FileName);
398 LLVM_DEBUG(llvm::dbgs() << "Having found " << MatchedSymbols.size()
399 << " symbols\n");
400 // We store a copy of MatchedSymbols in a place where it's globally reachable.
401 // This is used by the standalone version of the tool.
402 this->MatchedSymbols = MatchedSymbols;
403 return MatchedSymbols;
404}
405
406llvm::Expected<tooling::Replacements> createIncludeFixerReplacements(
407 StringRef Code, const IncludeFixerContext &Context,
408 const clang::format::FormatStyle &Style, bool AddQualifiers) {
409 if (Context.getHeaderInfos().empty())
410 return tooling::Replacements();
411 StringRef FilePath = Context.getFilePath();
412 std::string IncludeName =
413 "#include " + Context.getHeaderInfos().front().Header + "\n";
414 // Create replacements for the new header.
415 clang::tooling::Replacements Insertions;
416 auto Err =
417 Insertions.add(tooling::Replacement(FilePath, UINT_MAX, 0, IncludeName));
418 if (Err)
419 return std::move(Err);
420
421 auto CleanReplaces = cleanupAroundReplacements(Code, Insertions, Style);
422 if (!CleanReplaces)
423 return CleanReplaces;
424
425 auto Replaces = std::move(*CleanReplaces);
426 if (AddQualifiers) {
427 for (const auto &Info : Context.getQuerySymbolInfos()) {
428 // Ignore the empty range.
429 if (Info.Range.getLength() > 0) {
430 auto R = tooling::Replacement(
431 {FilePath, Info.Range.getOffset(), Info.Range.getLength(),
432 Context.getHeaderInfos().front().QualifiedName});
433 auto Err = Replaces.add(R);
434 if (Err) {
435 llvm::consumeError(std::move(Err));
436 R = tooling::Replacement(
437 R.getFilePath(), Replaces.getShiftedCodePosition(R.getOffset()),
438 R.getLength(), R.getReplacementText());
439 Replaces = Replaces.merge(tooling::Replacements(R));
440 }
441 }
442 }
443 }
444 return formatReplacements(Code, Replaces, Style);
445}
446
447} // namespace include_fixer
448} // namespace clang
Replacements Replaces
Definition: ClangTidy.cpp:306
FunctionInfo Info
CharSourceRange Range
SourceRange for the file name.
bool IsAngled
true if this was an include with angle brackets
StringRef FileName
SourceLocation Loc
FieldAction Action
WantDiagnostics Diagnostics
bool runInvocation(std::shared_ptr< clang::CompilerInvocation > Invocation, clang::FileManager *Files, std::shared_ptr< clang::PCHContainerOperations > PCHContainerOps, clang::DiagnosticConsumer *Diagnostics) override
IncludeFixerActionFactory(SymbolIndexManager &SymbolIndexMgr, std::vector< IncludeFixerContext > &Contexts, StringRef StyleName, bool MinimizeIncludePaths=true)
A context for a file being processed.
IncludeFixerContext getIncludeFixerContext(const clang::SourceManager &SourceManager, clang::HeaderSearch &HeaderSearch, ArrayRef< find_all_symbols::SymbolInfo > MatchedSymbols) const
Get the include fixer context for the queried symbol.
std::string minimizeInclude(StringRef Include, const clang::SourceManager &SourceManager, clang::HeaderSearch &HeaderSearch) const
Get the minimal include for a given path.
clang::TypoCorrection CorrectTypo(const DeclarationNameInfo &Typo, int LookupKind, Scope *S, CXXScopeSpec *SS, CorrectionCandidateCallback &CCC, DeclContext *MemberContext, bool EnteringContext, const ObjCObjectPointerType *OPT) override
Callback for unknown identifiers.
bool MaybeDiagnoseMissingCompleteType(clang::SourceLocation Loc, clang::QualType T) override
Callback for incomplete types.
This class provides an interface for finding the header files corresponding to an identifier in the s...
std::vector< find_all_symbols::SymbolInfo > search(llvm::StringRef Identifier, bool IsNestedSearch=true, llvm::StringRef FileName="") const
Search for header files to be included for an identifier.
static bool addDiagnosticsForContext(TypoCorrection &Correction, const IncludeFixerContext &Context, StringRef Code, SourceLocation StartOfFile, ASTContext &Ctx)
llvm::Expected< tooling::Replacements > createIncludeFixerReplacements(StringRef Code, const IncludeFixerContext &Context, const clang::format::FormatStyle &Style, bool AddQualifiers)
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//