0.6.0
C++ to UML diagram generator based on Clang
Loading...
Searching...
No Matches
translation_unit_visitor.cc
Go to the documentation of this file.
1/**
2 * @file src/include_diagram/visitor/translation_unit_visitor.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
20
21#include "common/clang_utils.h"
22
23#include <filesystem>
24
26
28 clang::SourceManager & /*sm*/,
31 : diagram_{diagram}
32 , config_{config}
33{
34}
35
37 clang::SourceManager &sm,
40 : visitor_specialization_t{sm, diagram, config}
41{
42}
43
44#if LLVM_VERSION_MAJOR >= 19
46 clang::SourceLocation hash_loc, const clang::Token & /*include_tok*/,
47 clang::StringRef file_name, bool is_angled,
48 clang::CharSourceRange /*filename_range*/, clang::OptionalFileEntryRef file,
49 clang::StringRef /*search_path*/, clang::StringRef relative_path,
50 const clang::Module * /* suggested_module */, bool /*imported*/,
51 clang::SrcMgr::CharacteristicKind file_type)
52#elif LLVM_VERSION_MAJOR >= 16
54 clang::SourceLocation hash_loc, const clang::Token & /*include_tok*/,
55 clang::StringRef file_name, bool is_angled,
56 clang::CharSourceRange /*filename_range*/, clang::OptionalFileEntryRef file,
57 clang::StringRef /*search_path*/, clang::StringRef relative_path,
58 const clang::Module * /*imported*/,
59 clang::SrcMgr::CharacteristicKind file_type)
60#elif LLVM_VERSION_MAJOR > 14
62 clang::SourceLocation hash_loc, const clang::Token & /*include_tok*/,
63 clang::StringRef file_name, bool is_angled,
64 clang::CharSourceRange /*filename_range*/,
65 clang::Optional<clang::FileEntryRef> file, clang::StringRef /*search_path*/,
66 clang::StringRef relative_path, const clang::Module * /*imported*/,
67 clang::SrcMgr::CharacteristicKind file_type)
68#else
70 clang::SourceLocation hash_loc, const clang::Token & /*include_tok*/,
71 clang::StringRef file_name, bool is_angled,
72 clang::CharSourceRange /*filename_range*/, const clang::FileEntry *file,
73 clang::StringRef /*search_path*/, clang::StringRef relative_path,
74 const clang::Module * /*imported*/,
75 clang::SrcMgr::CharacteristicKind file_type)
76#endif
77{
81
82 // First process the file which contains the include directive
83 auto current_file =
84 std::filesystem::path{source_manager().getFilename(hash_loc).str()};
85
86 std::string file_name_str = file_name.str();
87 current_file = std::filesystem::absolute(current_file);
88 current_file = current_file.lexically_normal();
89 const auto current_dir = current_file.parent_path();
90
91 auto current_file_id = process_source_file(current_file);
92 if (!current_file_id)
93 return;
94
95 assert(diagram().get(current_file_id.value()));
96
97 // Now try to figure out the full path to the included header
98 std::filesystem::path real_include_path{
99#if LLVM_VERSION_MAJOR > 14
100 file->getFileEntry().tryGetRealPathName().str()
101#else
102 file->tryGetRealPathName().str()
103#endif
104 };
105
106 if (real_include_path.empty()) {
107 // Try to figure out the actual path to the include file somehow
108#if LLVM_VERSION_MAJOR > 14
109 if (!file.has_value())
110 return;
111 auto include_path_parent =
112 std::filesystem::path(file->getDir().getName().str());
113 const std::string include_file_dir{file->getDir().getName()};
114#else
115 if (file == nullptr)
116 return;
117 auto include_path_parent =
118 std::filesystem::path(file->getDir()->getName().str());
119 const std::string include_file_dir{file->getDir()->getName()};
120#endif
121
122 const std::string include_name{file->getName().str()};
123 const auto include_path_pre_normalization =
124 std::filesystem::path{include_file_dir} / include_name;
125
126 real_include_path = include_path_pre_normalization.lexically_normal();
127 }
128 else {
129 real_include_path = real_include_path.lexically_normal();
130 }
131
132 const auto root_directory = config().root_directory();
133
134 LOG_DBG("Processing include directive {} [{}] in file {}", file_name_str,
135 real_include_path.string(), current_file.string());
136
137 if (diagram().should_include(source_file{real_include_path})) {
138 LOG_DBG("Processing internal header: {}", real_include_path.string());
139 process_internal_header(real_include_path,
140 file_type != clang::SrcMgr::CharacteristicKind::C_User,
141 current_file_id.value());
142 }
143 else if (config().generate_system_headers() && is_angled) {
144 process_external_system_header(
145 relative_path.str(), current_file_id.value());
146 }
147 else {
148 LOG_DBG("Skipping include directive to file {}",
149 real_include_path.string());
150 }
151}
152
154 const std::filesystem::path &include_path, bool is_system,
155 const eid_t current_file_id)
156{
157 // Make the path relative with respect to relative_to config option
158 auto relative_include_path =
159 std::filesystem::relative(include_path, config().root_directory());
160
161 // Check if this source file is already registered in the diagram,
162 // if not add it
163 auto diagram_path =
164 common::model::source_file{relative_include_path}.full_path();
165 if (!diagram().get_element(diagram_path.to_string()).has_value()) {
166 diagram().add_file(std::make_unique<common::model::source_file>(
167 diagram_path.to_string()));
168 }
169
170 auto &include_file = diagram().get_element(diagram_path).value();
171
172 include_file.set_type(common::model::source_file_t::kHeader);
173 include_file.set_file(
174 std::filesystem::absolute(include_path).lexically_normal().string());
175 include_file.set_line(0);
176 include_file.set_system_header(is_system);
177
178 // Add relationship from the currently parsed source file to this
179 // include file
180 const auto relationship_type = is_system
183
184 if (diagram().get(current_file_id)) {
185 diagram()
186 .get(current_file_id)
187 .value()
188 .add_relationship(common::model::relationship{relationship_type,
189 include_file.id(), common::model::access_t::kNone});
190 }
191}
192
194 const std::filesystem::path &include_path, const eid_t current_file_id)
195{
196 const auto file_name = include_path.filename();
197 const auto file_name_str = file_name.string();
198
199 auto f = std::make_unique<common::model::source_file>();
200 f->set_name(include_path.string());
202 f->set_id(common::to_id(include_path));
203 f->set_system_header(true);
204
205 const auto f_id = f->id();
206
207 diagram().add_file(std::move(f));
208
209 if (diagram().get(current_file_id)) {
210 diagram()
211 .get(current_file_id)
212 .value()
213 .add_relationship(common::model::relationship{
216 }
217}
218
219std::optional<eid_t>
221 const std::filesystem::path &file)
222{
226
227 auto file_path = std::filesystem::path{file};
228
229 // Make sure the file_path is absolute with respect to the
230 // filesystem, and in normal form
231 if (file_path.is_relative()) {
232 file_path = config().base_directory() / file_path;
233 }
234
235 file_path = file_path.lexically_normal();
236
237 if (diagram().should_include(source_file{file_path})) {
238 LOG_DBG("Processing source file {}", file.string());
239
240 // Relativize the path with respect to effective root directory
241 auto relative_file_path =
242 std::filesystem::relative(file_path, config().root_directory());
243
244 [[maybe_unused]] const auto relative_file_path_str =
245 relative_file_path.string();
246
247 // Check if this source file is already registered in the diagram,
248 // if not add it
249 auto diagram_path = source_file{relative_file_path}.full_path();
250 if (!diagram().get_element(diagram_path).has_value()) {
252 std::make_unique<source_file>(relative_file_path));
253 }
254
255 auto &source_file = diagram().get_element(diagram_path).value();
256
257 const std::string implementation_suffix_prefix{".c"};
258 if (file_path.has_extension() &&
259 (util::starts_with(file_path.extension().string(),
260 implementation_suffix_prefix) ||
261 file_path.extension() == ".m")) {
262 source_file.set_type(source_file_t::kImplementation);
263 }
264 else
265 source_file.set_type(source_file_t::kHeader);
266
267 source_file.set_file(std::filesystem::absolute(file.string())
268 .lexically_normal()
269 .string());
270
271 if (util::is_relative_to(file_path, config().root_directory())) {
272 source_file.set_file_relative(util::path_to_url(
273 relative(source_file.file(), config().root_directory())
274 .string()));
275 }
276 else {
277 source_file.set_file_relative("");
278 }
279
280 source_file.set_line(0);
281
282 return source_file.id();
283 }
284
285 return {};
286}
287
289{
290 return diagram_;
291}
292
295{
296 return config_;
297}
298
300
301} // namespace clanguml::include_diagram::visitor