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 std::string cwd;
98 auto cwd_or_err = ci.getVirtualFileSystem().getCurrentWorkingDirectory();
99 if (!cwd_or_err || cwd_or_err.get().empty()) return false;
100 cwd = cwd_or_err.get();
101 ci.getPreprocessor().addPPCallbacks(std::make_unique<IncludeRecordingPP>(
102 abs_paths_, std::move(cwd), ci.getSourceManager()));
103 return true;
104 }
105
106 private:
107 std::unordered_map<std::string, std::string> &abs_paths_;
108 };
109
OverlayBuiltinHeaders(std::vector<std::string> & argv,llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> base)110 llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> OverlayBuiltinHeaders(
111 std::vector<std::string> &argv,
112 llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> base) {
113 static constexpr llvm::StringLiteral kResourceDir = "/resources";
114 llvm::IntrusiveRefCntPtr<llvm::vfs::OverlayFileSystem> overlay(
115 new llvm::vfs::OverlayFileSystem(std::move(base)));
116 llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> builtin_headers(
117 new llvm::vfs::InMemoryFileSystem);
118
119 llvm::SmallString<256> file_path;
120 for (const auto &builtin_header :
121 llvm::ArrayRef(builtin_headers_create(), builtin_headers_size())) {
122 file_path.clear();
123 llvm::sys::path::append(file_path, kResourceDir, "include",
124 builtin_header.name);
125 builtin_headers->addFile(
126 file_path,
127 /*ModificationTime=*/0,
128 llvm::MemoryBuffer::getMemBuffer(builtin_header.data));
129 }
130 overlay->pushOverlay(std::move(builtin_headers));
131 argv.insert(llvm::find(argv, "--"),
132 llvm::Twine("-resource-dir=", kResourceDir).str());
133 return overlay;
134 }
135
136 } // namespace
137
ScanIncludes(const clang::tooling::CompileCommand & cmd,llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs)138 llvm::Expected<std::vector<std::pair<std::string, std::string>>> ScanIncludes(
139 const clang::tooling::CompileCommand &cmd,
140 llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs) {
141 if (fs->setCurrentWorkingDirectory(cmd.Directory)) {
142 return llvm::createStringError(
143 llvm::inconvertibleErrorCode(),
144 "Failed to set working directory to: " + cmd.Directory);
145 }
146
147 auto main_file = fs->getBufferForFile(cmd.Filename);
148 if (!main_file) {
149 return llvm::createStringError(llvm::inconvertibleErrorCode(),
150 "Main file doesn't exist: " + cmd.Filename);
151 }
152 std::unordered_map<std::string, std::string> abs_paths;
153 abs_paths.try_emplace(GetAbsolutePath(cmd.Directory, cmd.Filename),
154 main_file.get()->getBuffer().str());
155
156 std::vector<std::string> argv = cmd.CommandLine;
157 fs = OverlayBuiltinHeaders(argv, std::move(fs));
158
159 llvm::IntrusiveRefCntPtr<clang::FileManager> files(
160 new clang::FileManager(/*FileSystemOpts=*/{}, std::move(fs)));
161 clang::tooling::ToolInvocation tool(
162 argv, std::make_unique<IncludeScanningAction>(abs_paths), files.get());
163 if (!tool.run()) {
164 return llvm::createStringError(
165 llvm::inconvertibleErrorCode(),
166 "Failed to scan includes for: " + cmd.Filename);
167 }
168
169 std::vector<std::pair<std::string, std::string>> result;
170 result.reserve(abs_paths.size());
171 for (auto &entry : abs_paths) {
172 result.emplace_back(entry.first, std::move(entry.second));
173 }
174 return result;
175 }
176 } // namespace tools::ide_query::cc_analyzer
177