1 /*
2 * Copyright (C) 2024 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 #include "include_scanner.h"
17
18 #include <memory>
19 #include <string>
20 #include <unordered_map>
21 #include <utility>
22 #include <vector>
23
24 #include "builtin_headers.h"
25 #include "clang/Basic/FileEntry.h"
26 #include "clang/Basic/FileManager.h"
27 #include "clang/Basic/Module.h"
28 #include "clang/Basic/SourceLocation.h"
29 #include "clang/Basic/SourceManager.h"
30 #include "clang/Frontend/ASTUnit.h"
31 #include "clang/Frontend/CompilerInstance.h"
32 #include "clang/Frontend/FrontendActions.h"
33 #include "clang/Lex/PPCallbacks.h"
34 #include "clang/Tooling/ArgumentsAdjusters.h"
35 #include "clang/Tooling/CompilationDatabase.h"
36 #include "clang/Tooling/Tooling.h"
37 #include "llvm/ADT/ArrayRef.h"
38 #include "llvm/ADT/IntrusiveRefCntPtr.h"
39 #include "llvm/ADT/STLExtras.h"
40 #include "llvm/ADT/SmallString.h"
41 #include "llvm/ADT/StringRef.h"
42 #include "llvm/ADT/Twine.h"
43 #include "llvm/Support/Error.h"
44 #include "llvm/Support/MemoryBuffer.h"
45 #include "llvm/Support/Path.h"
46 #include "llvm/Support/VirtualFileSystem.h"
47
48 namespace tools::ide_query::cc_analyzer {
49 namespace {
CleanPath(llvm::StringRef path)50 std::string CleanPath(llvm::StringRef path) {
51 // both ./ and ../ has `./` in them.
52 if (!path.contains("./")) return path.str();
53 llvm::SmallString<256> clean_path(path);
54 llvm::sys::path::remove_dots(clean_path, /*remove_dot_dot=*/true);
55 return clean_path.str().str();
56 }
57
58 // Returns the absolute path to file_name, treating it as relative to cwd if it
59 // isn't already absolute.
GetAbsolutePath(llvm::StringRef cwd,llvm::StringRef file_name)60 std::string GetAbsolutePath(llvm::StringRef cwd, llvm::StringRef file_name) {
61 if (llvm::sys::path::is_absolute(file_name)) return CleanPath(file_name);
62 llvm::SmallString<256> abs_path(cwd);
63 llvm::sys::path::append(abs_path, file_name);
64 llvm::sys::path::remove_dots(abs_path, /*remove_dot_dot=*/true);
65 return abs_path.str().str();
66 }
67
68 class IncludeRecordingPP : public clang::PPCallbacks {
69 public:
IncludeRecordingPP(std::unordered_map<std::string,std::string> & abs_paths,std::string cwd,const clang::SourceManager & sm)70 explicit IncludeRecordingPP(
71 std::unordered_map<std::string, std::string> &abs_paths, std::string cwd,
72 const clang::SourceManager &sm)
73 : abs_paths_(abs_paths), cwd_(std::move(cwd)), sm_(sm) {}
74
LexedFileChanged(clang::FileID FID,LexedFileChangeReason Reason,clang::SrcMgr::CharacteristicKind FileType,clang::FileID PrevFID,clang::SourceLocation Loc)75 void LexedFileChanged(clang::FileID FID, LexedFileChangeReason Reason,
76 clang::SrcMgr::CharacteristicKind FileType,
77 clang::FileID PrevFID,
78 clang::SourceLocation Loc) override {
79 auto file_entry = sm_.getFileEntryRefForID(FID);
80 if (!file_entry) return;
81 auto abs_path = GetAbsolutePath(cwd_, file_entry->getName());
82 auto [it, inserted] = abs_paths_.try_emplace(abs_path);
83 if (inserted) it->second = sm_.getBufferData(FID);
84 }
85
86 std::unordered_map<std::string, std::string> &abs_paths_;
87 const std::string cwd_;
88 const clang::SourceManager &sm_;
89 };
90
91 class IncludeScanningAction final : public clang::PreprocessOnlyAction {
92 public:
IncludeScanningAction(std::unordered_map<std::string,std::string> & abs_paths)93 explicit IncludeScanningAction(
94 std::unordered_map<std::string, std::string> &abs_paths)
95 : abs_paths_(abs_paths) {}
BeginSourceFileAction(clang::CompilerInstance & ci)96 bool BeginSourceFileAction(clang::CompilerInstance &ci) override {
97 // Be more resilient against all warnings/errors, as we want
98 // include-scanning to work even on incomplete sources.
99 ci.getDiagnostics().setEnableAllWarnings(false);
100 ci.getDiagnostics().setSeverityForAll(clang::diag::Flavor::WarningOrError,
101 clang::diag::Severity::Ignored);
102 std::string cwd;
103 auto cwd_or_err = ci.getVirtualFileSystem().getCurrentWorkingDirectory();
104 if (!cwd_or_err || cwd_or_err.get().empty()) return false;
105 cwd = cwd_or_err.get();
106 ci.getPreprocessor().addPPCallbacks(std::make_unique<IncludeRecordingPP>(
107 abs_paths_, std::move(cwd), ci.getSourceManager()));
108 return true;
109 }
110
111 private:
112 std::unordered_map<std::string, std::string> &abs_paths_;
113 };
114
OverlayBuiltinHeaders(std::vector<std::string> & argv,llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> base)115 llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> OverlayBuiltinHeaders(
116 std::vector<std::string> &argv,
117 llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> base) {
118 static constexpr llvm::StringLiteral kResourceDir = "/resources";
119 llvm::IntrusiveRefCntPtr<llvm::vfs::OverlayFileSystem> overlay(
120 new llvm::vfs::OverlayFileSystem(std::move(base)));
121 llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> builtin_headers(
122 new llvm::vfs::InMemoryFileSystem);
123
124 llvm::SmallString<256> file_path;
125 for (const auto &builtin_header :
126 llvm::ArrayRef(builtin_headers_create(), builtin_headers_size())) {
127 file_path.clear();
128 llvm::sys::path::append(file_path, kResourceDir, "include",
129 builtin_header.name);
130 builtin_headers->addFile(
131 file_path,
132 /*ModificationTime=*/0,
133 llvm::MemoryBuffer::getMemBuffer(builtin_header.data));
134 }
135 overlay->pushOverlay(std::move(builtin_headers));
136 argv.insert(llvm::find(argv, "--"),
137 llvm::Twine("-resource-dir=", kResourceDir).str());
138 return overlay;
139 }
140
141 } // namespace
142
ScanIncludes(const clang::tooling::CompileCommand & cmd,llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs)143 llvm::Expected<std::vector<std::pair<std::string, std::string>>> ScanIncludes(
144 const clang::tooling::CompileCommand &cmd,
145 llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs) {
146 if (fs->setCurrentWorkingDirectory(cmd.Directory)) {
147 return llvm::createStringError(
148 llvm::inconvertibleErrorCode(),
149 "Failed to set working directory to: " + cmd.Directory);
150 }
151
152 auto main_file = fs->getBufferForFile(cmd.Filename);
153 if (!main_file) {
154 return llvm::createStringError(llvm::inconvertibleErrorCode(),
155 "Main file doesn't exist: " + cmd.Filename);
156 }
157 std::unordered_map<std::string, std::string> abs_paths;
158 abs_paths.try_emplace(GetAbsolutePath(cmd.Directory, cmd.Filename),
159 main_file.get()->getBuffer().str());
160
161 std::vector<std::string> argv = cmd.CommandLine;
162 // Disable all warnings to be more robust in analysis.
163 argv.insert(llvm::find(argv, "--"), {"-Wno-error", "-w"});
164 fs = OverlayBuiltinHeaders(argv, std::move(fs));
165
166 llvm::IntrusiveRefCntPtr<clang::FileManager> files(
167 new clang::FileManager(/*FileSystemOpts=*/{}, std::move(fs)));
168 clang::tooling::ToolInvocation tool(
169 argv, std::make_unique<IncludeScanningAction>(abs_paths), files.get());
170 if (!tool.run()) {
171 return llvm::createStringError(
172 llvm::inconvertibleErrorCode(),
173 "Failed to scan includes for: " + cmd.Filename);
174 }
175
176 std::vector<std::pair<std::string, std::string>> result;
177 result.reserve(abs_paths.size());
178 for (auto &entry : abs_paths) {
179 result.emplace_back(entry.first, std::move(entry.second));
180 }
181 return result;
182 }
183 } // namespace tools::ide_query::cc_analyzer
184