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