Eric Liu | c5105f9 | 2018-02-16 14:15:55 +0000 | [diff] [blame] | 1 | //===--- Headers.cpp - Include headers ---------------------------*- C++-*-===// |
| 2 | // |
| 3 | // The LLVM Compiler Infrastructure |
| 4 | // |
| 5 | // This file is distributed under the University of Illinois Open Source |
| 6 | // License. See LICENSE.TXT for details. |
| 7 | // |
| 8 | //===----------------------------------------------------------------------===// |
| 9 | |
| 10 | #include "Headers.h" |
| 11 | #include "Compiler.h" |
| 12 | #include "Logger.h" |
Eric Liu | 155f5a4 | 2018-05-14 12:19:16 +0000 | [diff] [blame^] | 13 | #include "SourceCode.h" |
Eric Liu | c5105f9 | 2018-02-16 14:15:55 +0000 | [diff] [blame] | 14 | #include "clang/Frontend/CompilerInstance.h" |
| 15 | #include "clang/Frontend/CompilerInvocation.h" |
| 16 | #include "clang/Frontend/FrontendActions.h" |
| 17 | #include "clang/Lex/HeaderSearch.h" |
| 18 | #include "clang/Lex/PreprocessorOptions.h" |
| 19 | #include "clang/Tooling/CompilationDatabase.h" |
Eric Liu | 709bde8 | 2018-02-19 18:48:44 +0000 | [diff] [blame] | 20 | #include "llvm/Support/Path.h" |
Eric Liu | c5105f9 | 2018-02-16 14:15:55 +0000 | [diff] [blame] | 21 | |
| 22 | namespace clang { |
| 23 | namespace clangd { |
| 24 | namespace { |
| 25 | |
| 26 | class RecordHeaders : public PPCallbacks { |
| 27 | public: |
Eric Liu | 155f5a4 | 2018-05-14 12:19:16 +0000 | [diff] [blame^] | 28 | RecordHeaders(const SourceManager &SM, |
| 29 | std::function<void(Inclusion)> Callback) |
| 30 | : SM(SM), Callback(std::move(Callback)) {} |
Eric Liu | c5105f9 | 2018-02-16 14:15:55 +0000 | [diff] [blame] | 31 | |
Eric Liu | 155f5a4 | 2018-05-14 12:19:16 +0000 | [diff] [blame^] | 32 | // Record existing #includes - both written and resolved paths. Only #includes |
| 33 | // in the main file are collected. |
| 34 | void InclusionDirective(SourceLocation HashLoc, const Token & /*IncludeTok*/, |
Eric Liu | 6c8e858 | 2018-02-26 08:32:13 +0000 | [diff] [blame] | 35 | llvm::StringRef FileName, bool IsAngled, |
Eric Liu | 155f5a4 | 2018-05-14 12:19:16 +0000 | [diff] [blame^] | 36 | CharSourceRange FilenameRange, const FileEntry *File, |
| 37 | llvm::StringRef /*SearchPath*/, |
Eric Liu | c5105f9 | 2018-02-16 14:15:55 +0000 | [diff] [blame] | 38 | llvm::StringRef /*RelativePath*/, |
Julie Hockett | 546943f | 2018-05-10 19:13:14 +0000 | [diff] [blame] | 39 | const Module * /*Imported*/, |
| 40 | SrcMgr::CharacteristicKind /*FileType*/) override { |
Eric Liu | 155f5a4 | 2018-05-14 12:19:16 +0000 | [diff] [blame^] | 41 | // Only inclusion directives in the main file make sense. The user cannot |
| 42 | // select directives not in the main file. |
| 43 | if (HashLoc.isInvalid() || !SM.isInMainFile(HashLoc)) |
| 44 | return; |
| 45 | std::string Written = |
| 46 | (IsAngled ? "<" + FileName + ">" : "\"" + FileName + "\"").str(); |
| 47 | std::string Resolved = (!File || File->tryGetRealPathName().empty()) |
| 48 | ? "" |
| 49 | : File->tryGetRealPathName(); |
| 50 | Callback({halfOpenToRange(SM, FilenameRange), Written, Resolved}); |
Eric Liu | c5105f9 | 2018-02-16 14:15:55 +0000 | [diff] [blame] | 51 | } |
| 52 | |
| 53 | private: |
Eric Liu | 155f5a4 | 2018-05-14 12:19:16 +0000 | [diff] [blame^] | 54 | const SourceManager &SM; |
| 55 | std::function<void(Inclusion)> Callback; |
Eric Liu | c5105f9 | 2018-02-16 14:15:55 +0000 | [diff] [blame] | 56 | }; |
| 57 | |
| 58 | } // namespace |
| 59 | |
Eric Liu | 6c8e858 | 2018-02-26 08:32:13 +0000 | [diff] [blame] | 60 | bool isLiteralInclude(llvm::StringRef Include) { |
| 61 | return Include.startswith("<") || Include.startswith("\""); |
| 62 | } |
| 63 | |
| 64 | bool HeaderFile::valid() const { |
| 65 | return (Verbatim && isLiteralInclude(File)) || |
| 66 | (!Verbatim && llvm::sys::path::is_absolute(File)); |
| 67 | } |
| 68 | |
Eric Liu | 155f5a4 | 2018-05-14 12:19:16 +0000 | [diff] [blame^] | 69 | std::unique_ptr<PPCallbacks> |
| 70 | collectInclusionsInMainFileCallback(const SourceManager &SM, |
| 71 | std::function<void(Inclusion)> Callback) { |
| 72 | return llvm::make_unique<RecordHeaders>(SM, std::move(Callback)); |
| 73 | } |
| 74 | |
Eric Liu | c5105f9 | 2018-02-16 14:15:55 +0000 | [diff] [blame] | 75 | /// FIXME(ioeric): we might not want to insert an absolute include path if the |
| 76 | /// path is not shortened. |
| 77 | llvm::Expected<std::string> |
Eric Liu | 709bde8 | 2018-02-19 18:48:44 +0000 | [diff] [blame] | 78 | calculateIncludePath(llvm::StringRef File, llvm::StringRef Code, |
Eric Liu | 6c8e858 | 2018-02-26 08:32:13 +0000 | [diff] [blame] | 79 | const HeaderFile &DeclaringHeader, |
| 80 | const HeaderFile &InsertedHeader, |
Eric Liu | 709bde8 | 2018-02-19 18:48:44 +0000 | [diff] [blame] | 81 | const tooling::CompileCommand &CompileCommand, |
| 82 | IntrusiveRefCntPtr<vfs::FileSystem> FS) { |
Eric Liu | 6c8e858 | 2018-02-26 08:32:13 +0000 | [diff] [blame] | 83 | assert(llvm::sys::path::is_absolute(File)); |
| 84 | assert(DeclaringHeader.valid() && InsertedHeader.valid()); |
| 85 | if (File == DeclaringHeader.File || File == InsertedHeader.File) |
Eric Liu | 709bde8 | 2018-02-19 18:48:44 +0000 | [diff] [blame] | 86 | return ""; |
| 87 | FS->setCurrentWorkingDirectory(CompileCommand.Directory); |
| 88 | |
Eric Liu | c5105f9 | 2018-02-16 14:15:55 +0000 | [diff] [blame] | 89 | // Set up a CompilerInstance and create a preprocessor to collect existing |
| 90 | // #include headers in \p Code. Preprocesor also provides HeaderSearch with |
| 91 | // which we can calculate the shortest include path for \p Header. |
| 92 | std::vector<const char *> Argv; |
| 93 | for (const auto &S : CompileCommand.CommandLine) |
| 94 | Argv.push_back(S.c_str()); |
| 95 | IgnoringDiagConsumer IgnoreDiags; |
| 96 | auto CI = clang::createInvocationFromCommandLine( |
| 97 | Argv, |
| 98 | CompilerInstance::createDiagnostics(new DiagnosticOptions(), &IgnoreDiags, |
| 99 | false), |
| 100 | FS); |
| 101 | if (!CI) |
| 102 | return llvm::make_error<llvm::StringError>( |
| 103 | "Failed to create a compiler instance for " + File, |
| 104 | llvm::inconvertibleErrorCode()); |
| 105 | CI->getFrontendOpts().DisableFree = false; |
| 106 | // Parse the main file to get all existing #includes in the file, and then we |
| 107 | // can make sure the same header (even with different include path) is not |
| 108 | // added more than once. |
| 109 | CI->getPreprocessorOpts().SingleFileParseMode = true; |
| 110 | |
Haojian Wu | b603a5e | 2018-02-22 13:35:01 +0000 | [diff] [blame] | 111 | // The diagnostic options must be set before creating a CompilerInstance. |
| 112 | CI->getDiagnosticOpts().IgnoreWarnings = true; |
Eric Liu | c5105f9 | 2018-02-16 14:15:55 +0000 | [diff] [blame] | 113 | auto Clang = prepareCompilerInstance( |
| 114 | std::move(CI), /*Preamble=*/nullptr, |
| 115 | llvm::MemoryBuffer::getMemBuffer(Code, File), |
| 116 | std::make_shared<PCHContainerOperations>(), FS, IgnoreDiags); |
Eric Liu | c5105f9 | 2018-02-16 14:15:55 +0000 | [diff] [blame] | 117 | |
| 118 | if (Clang->getFrontendOpts().Inputs.empty()) |
| 119 | return llvm::make_error<llvm::StringError>( |
| 120 | "Empty frontend action inputs empty for file " + File, |
| 121 | llvm::inconvertibleErrorCode()); |
| 122 | PreprocessOnlyAction Action; |
| 123 | if (!Action.BeginSourceFile(*Clang, Clang->getFrontendOpts().Inputs[0])) |
| 124 | return llvm::make_error<llvm::StringError>( |
| 125 | "Failed to begin preprocessor only action for file " + File, |
| 126 | llvm::inconvertibleErrorCode()); |
Eric Liu | 155f5a4 | 2018-05-14 12:19:16 +0000 | [diff] [blame^] | 127 | std::vector<Inclusion> Inclusions; |
| 128 | Clang->getPreprocessor().addPPCallbacks(collectInclusionsInMainFileCallback( |
| 129 | Clang->getSourceManager(), |
| 130 | [&Inclusions](Inclusion Inc) { Inclusions.push_back(std::move(Inc)); })); |
Eric Liu | c5105f9 | 2018-02-16 14:15:55 +0000 | [diff] [blame] | 131 | if (!Action.Execute()) |
| 132 | return llvm::make_error<llvm::StringError>( |
| 133 | "Failed to execute preprocessor only action for file " + File, |
| 134 | llvm::inconvertibleErrorCode()); |
Eric Liu | 155f5a4 | 2018-05-14 12:19:16 +0000 | [diff] [blame^] | 135 | llvm::StringSet<> IncludedHeaders; |
| 136 | for (const auto &Inc : Inclusions) { |
| 137 | IncludedHeaders.insert(Inc.Written); |
| 138 | if (!Inc.Resolved.empty()) |
| 139 | IncludedHeaders.insert(Inc.Resolved); |
| 140 | } |
Eric Liu | 6c8e858 | 2018-02-26 08:32:13 +0000 | [diff] [blame] | 141 | auto Included = [&](llvm::StringRef Header) { |
Eric Liu | 155f5a4 | 2018-05-14 12:19:16 +0000 | [diff] [blame^] | 142 | return IncludedHeaders.find(Header) != IncludedHeaders.end(); |
Eric Liu | 6c8e858 | 2018-02-26 08:32:13 +0000 | [diff] [blame] | 143 | }; |
| 144 | if (Included(DeclaringHeader.File) || Included(InsertedHeader.File)) |
| 145 | return ""; |
Eric Liu | c5105f9 | 2018-02-16 14:15:55 +0000 | [diff] [blame] | 146 | |
| 147 | auto &HeaderSearchInfo = Clang->getPreprocessor().getHeaderSearchInfo(); |
| 148 | bool IsSystem = false; |
Eric Liu | 6c8e858 | 2018-02-26 08:32:13 +0000 | [diff] [blame] | 149 | |
| 150 | if (InsertedHeader.Verbatim) |
| 151 | return InsertedHeader.File; |
| 152 | |
Eric Liu | c5105f9 | 2018-02-16 14:15:55 +0000 | [diff] [blame] | 153 | std::string Suggested = HeaderSearchInfo.suggestPathToFileForDiagnostics( |
Eric Liu | 6c8e858 | 2018-02-26 08:32:13 +0000 | [diff] [blame] | 154 | InsertedHeader.File, CompileCommand.Directory, &IsSystem); |
Eric Liu | c5105f9 | 2018-02-16 14:15:55 +0000 | [diff] [blame] | 155 | if (IsSystem) |
| 156 | Suggested = "<" + Suggested + ">"; |
| 157 | else |
| 158 | Suggested = "\"" + Suggested + "\""; |
| 159 | |
Eric Liu | 6c8e858 | 2018-02-26 08:32:13 +0000 | [diff] [blame] | 160 | log("Suggested #include for " + InsertedHeader.File + " is: " + Suggested); |
Eric Liu | c5105f9 | 2018-02-16 14:15:55 +0000 | [diff] [blame] | 161 | return Suggested; |
| 162 | } |
| 163 | |
| 164 | } // namespace clangd |
| 165 | } // namespace clang |