0.6.0
C++ to UML diagram generator based on Clang
Loading...
Searching...
No Matches
clang_tool.cc
Go to the documentation of this file.
1/**
2 * @file src/common/generators/clang_tool.cc
3 *
4 * Copyright (c) 2021-2025 Bartek Kryza <bkryza@gmail.com>
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18
19#include "clang_tool.h"
20
21#include <clang/Frontend/CompilerInstance.h>
22#include <clang/Frontend/CompilerInvocation.h>
23#include <clang/Tooling/CompilationDatabase.h>
24
25#include "util/util.h"
26
28
29namespace {
30void inject_resource_dir(
31 CommandLineArguments &args, const char *argv_0, void *main_addr)
32{
33 using namespace std::string_literals;
34
35 if (std::any_of(std::begin(args), std::end(args), [](const auto &arg) {
36 return util::starts_with(arg, "-resource-dir"s);
37 }))
38 return;
39
40 args = clang::tooling::getInsertArgumentAdjuster(("-resource-dir=" +
41 clang::CompilerInvocation::GetResourcesPath(argv_0, main_addr))
42 .c_str())(args, "");
43}
44} // namespace
45
46std::string to_string(const clanguml::generators::diagnostic &d)
47{
48 if (!d.location) {
49 return fmt::format("[{}] {}", d.level, d.description);
50 }
51
52 std::string filepath = d.location->file_relative().empty()
53 ? d.location->file()
54 : d.location->file_relative();
55 auto line = d.location->line();
56
57 return fmt::format(
58 "[{}] {}:{}: {}", d.level, filepath, line, d.description);
59}
60
61void to_json(nlohmann::json &j, const diagnostic &a)
62{
63 j["level"] = a.level;
64 j["description"] = logging::escape_json(a.description);
65 if (a.location) {
66 j["location"]["file"] = a.location.value().file();
67 j["location"]["line"] = a.location.value().line();
68 j["location"]["column"] = a.location.value().column();
69 }
70}
71
73 std::string dn, std::vector<diagnostic> d, std::string description)
74 : error::diagram_generation_error{dt, dn, description}
75 , diagnostics{std::move(d)}
76{
77}
78
79diagnostic_consumer::diagnostic_consumer(std::filesystem::path relative_to)
80 : relative_to_{std::move(relative_to)}
81{
82}
83
85 DiagnosticsEngine::Level diag_level, const Diagnostic &info)
86{
87 SmallVector<char> buf{};
88 info.FormatDiagnostic(buf);
89
90 diagnostic d;
91 d.level = diag_level;
92 d.description = std::string{buf.data(), buf.size()};
93
94 if (info.hasSourceManager() && info.getLocation().isValid()) {
96 common::set_source_location(info.getSourceManager(), info.getLocation(),
97 *d.location, {}, relative_to_);
98 }
99
100 if (diag_level == clang::DiagnosticsEngine::Level::Error ||
101 diag_level == clang::DiagnosticsEngine::Level::Fatal) {
102 failed = true;
103 }
104
105 diagnostics.emplace_back(std::move(d));
106}
107
109 std::string diagram_name,
110 const clanguml::common::compilation_database &compilation_database,
111 const std::vector<std::string> &source_paths,
112 std::filesystem::path relative_to, bool quiet)
113 : diagram_type_{diagram_type}
114 , diagram_name_{std::move(diagram_name)}
115 , compilations_{compilation_database}
116 , source_paths_{source_paths}
117 , quiet_{quiet}
118 , pch_container_ops_{std::make_shared<PCHContainerOperations>()}
119 , overlay_fs_{new llvm::vfs::OverlayFileSystem(
120 llvm::vfs::getRealFileSystem())}
121 , inmemory_fs_{new llvm::vfs::InMemoryFileSystem}
122 , files_{new FileManager(FileSystemOptions(), overlay_fs_)}
123 , diag_consumer_{std::make_unique<diagnostic_consumer>(relative_to)}
124 , diag_opts_{new clang::DiagnosticOptions}
125{
126 overlay_fs_->pushOverlay(inmemory_fs_);
127
128 append_arguments_adjuster(getClangStripOutputAdjuster());
129 append_arguments_adjuster(getClangSyntaxOnlyAdjuster());
130 append_arguments_adjuster(getClangStripDependencyFileAdjuster());
131}
132
133clang_tool::~clang_tool() = default;
134
135void clang_tool::append_arguments_adjuster(ArgumentsAdjuster Adjuster)
136{
138 combineAdjusters(std::move(args_adjuster_), std::move(Adjuster));
139}
140
141void clang_tool::run(ToolAction *Action)
142{
143 static int static_symbol;
144
145 std::vector<std::string> absolute_tu_paths;
146 absolute_tu_paths.reserve(source_paths_.size());
147 for (const auto &source_path : source_paths_) {
148 auto absolute_tu_path =
149 clang::tooling::getAbsolutePath(*overlay_fs_, source_path);
150 if (!absolute_tu_path) {
151 if (!quiet_) {
152 LOG_WARN("Skipping file {} in diagram {}. Could not resolve "
153 "absolute path for translation unit: {}",
154 source_path, diagram_name_,
155 llvm::toString(absolute_tu_path.takeError()));
156 }
157 continue;
158 }
159 absolute_tu_paths.emplace_back(std::move(*absolute_tu_path));
160 }
161
162 // Remember the working directory in case we need to restore it.
163 std::string initial_workdir;
164 if (auto current_workdir = overlay_fs_->getCurrentWorkingDirectory()) {
165 initial_workdir = std::move(*current_workdir);
166 }
167 else {
168 if (!quiet_)
169 LOG_ERROR("Could not get current working directory when generating "
170 "diagram '{}': {}",
171 diagram_name_, current_workdir.getError().message());
172 }
173
174 for (const auto &file : absolute_tu_paths) {
175 if (!quiet_)
176 LOG_INFO("Processing diagram '{}' translation unit: {}",
177 diagram_name_, file);
178
179 auto compile_commands_for_file = compilations_.getCompileCommands(file);
180
181 if (compile_commands_for_file.empty()) {
182 if (!quiet_)
183 LOG_WARN(
184 "Skipping file {} for diagram '{}'. Compilation command "
185 "not found.",
186 file, diagram_name_);
187 continue;
188 }
189
190 if (compile_commands_for_file.size() > 1 &&
192 LOG_WARN("Multiple compile commands detected for file '{}' in "
193 "diagram '{}' - using only the first one...",
194 file, diagram_name_);
195 }
196
197 for (auto &compile_command : compile_commands_for_file) {
198 if (overlay_fs_->setCurrentWorkingDirectory(
199 compile_command.Directory))
200 llvm::report_fatal_error("Cannot chdir into \"" +
201 Twine(compile_command.Directory) + "\"!");
202
203 // Now fill the in-memory VFS with the relative file mappings so it
204 // will have the correct relative paths. We never remove mappings
205 // but that should be fine.
206 if (visited_working_directories_.insert(compile_command.Directory)
207 .second) {
208 for (const auto &[file_name, file_content] :
210 if (!llvm::sys::path::is_absolute(file_name))
211 inmemory_fs_->addFile(file_name, 0,
212 llvm::MemoryBuffer::getMemBuffer(file_content));
213 }
214
215 auto command_line = compile_command.CommandLine;
216 if (args_adjuster_)
217 command_line =
218 args_adjuster_(command_line, compile_command.Filename);
219
220 assert(!command_line.empty());
221
222 inject_resource_dir(command_line, "clang_tool", &static_symbol);
223
224 ToolInvocation invocation(std::move(command_line), Action,
226 invocation.setDiagnosticConsumer(diag_consumer_.get());
227#if LLVM_VERSION_MAJOR > 13
228 invocation.setDiagnosticOptions(diag_opts_.get());
229#endif
230
231 if (!invocation.run() || diag_consumer_->failed) {
232 if (!initial_workdir.empty()) {
233 if (const auto ec = overlay_fs_->setCurrentWorkingDirectory(
234 initial_workdir);
235 ec)
236 if (!quiet_)
237 LOG_ERROR("Error when trying to restore working "
238 "directory: {}",
239 ec.message());
240 }
241
242 if (diag_consumer_ && diag_consumer_->failed) {
243 if (!(diag_consumer_->diagnostics.empty())) {
245 diag_consumer_->diagnostics,
246 to_string(diag_consumer_->diagnostics.back()));
247 }
248
250 diag_consumer_->diagnostics);
251 }
252
253 throw std::runtime_error(
254 fmt::format("Unknown error while processing {}", file));
255 }
256
258 break;
259 }
260 }
261
262 if (!initial_workdir.empty()) {
263 if (const auto ec =
264 overlay_fs_->setCurrentWorkingDirectory(initial_workdir))
265 if (!quiet_)
266 LOG_ERROR("Error when trying to restore working dir: {}",
267 ec.message());
268 }
269}
270} // namespace clanguml::generators
271
272namespace clang {
273std::string to_string(clang::DiagnosticsEngine::Level level)
274{
275 std::string level_str;
276 switch (level) {
277 case clang::DiagnosticsEngine::Ignored:
278 level_str = "IGNORED";
279 break;
280 case clang::DiagnosticsEngine::Note:
281 level_str = "NOTE";
282 break;
283 case clang::DiagnosticsEngine::Remark:
284 level_str = "REMARK";
285 break;
286 case clang::DiagnosticsEngine::Warning:
287 level_str = "WARNING";
288 break;
289 case clang::DiagnosticsEngine::Error:
290 level_str = "ERROR";
291 break;
292 case clang::DiagnosticsEngine::Fatal:
293 level_str = "FATAL";
294 break;
295 default:
296 level_str = "UNKNOWN";
297 break;
298 }
299
300 return level_str;
301}
302} // namespace clang