1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "tools/gn/header_checker.h"
10 #include "base/files/file_util.h"
11 #include "base/message_loop/message_loop.h"
12 #include "base/strings/string_util.h"
13 #include "base/threading/sequenced_worker_pool.h"
14 #include "tools/gn/build_settings.h"
15 #include "tools/gn/builder.h"
16 #include "tools/gn/c_include_iterator.h"
17 #include "tools/gn/config.h"
18 #include "tools/gn/err.h"
19 #include "tools/gn/filesystem_utils.h"
20 #include "tools/gn/scheduler.h"
21 #include "tools/gn/source_file_type.h"
22 #include "tools/gn/target.h"
23 #include "tools/gn/trace.h"
27 struct PublicGeneratedPair
{
28 PublicGeneratedPair() : is_public(false), is_generated(false) {}
33 // If the given file is in the "gen" folder, trims this so it treats the gen
34 // directory as the source root:
35 // //out/Debug/gen/foo/bar.h -> //foo/bar.h
36 // If the file isn't in the generated root, returns the input unchanged.
37 SourceFile
RemoveRootGenDirFromFile(const Target
* target
,
38 const SourceFile
& file
) {
39 const SourceDir
& gen
= target
->settings()->toolchain_gen_dir();
40 if (!gen
.is_null() && StartsWithASCII(file
.value(), gen
.value(), true))
41 return SourceFile("//" + file
.value().substr(gen
.value().size()));
45 // This class makes InputFiles on the stack as it reads files to check. When
46 // we throw an error, the Err indicates a locatin which has a pointer to
47 // an InputFile that must persist as long as the Err does.
49 // To make this work, this function creates a clone of the InputFile managed
50 // by the InputFileManager so the error can refer to something that
51 // persists. This means that the current file contents will live as long as
52 // the program, but this is OK since we're erroring out anyway.
53 LocationRange
CreatePersistentRange(const InputFile
& input_file
,
54 const LocationRange
& range
) {
55 InputFile
* clone_input_file
;
56 std::vector
<Token
>* tokens
; // Don't care about this.
57 scoped_ptr
<ParseNode
>* parse_root
; // Don't care about this.
59 g_scheduler
->input_file_manager()->AddDynamicInput(
60 input_file
.name(), &clone_input_file
, &tokens
, &parse_root
);
61 clone_input_file
->SetContents(input_file
.contents());
63 return LocationRange(Location(clone_input_file
,
64 range
.begin().line_number(),
65 range
.begin().char_offset(),
66 -1 /* TODO(scottmg) */),
67 Location(clone_input_file
,
68 range
.end().line_number(),
69 range
.end().char_offset(),
70 -1 /* TODO(scottmg) */));
73 // Given a reverse dependency chain where the target chain[0]'s includes are
74 // being used by chain[end] and not all deps are public, returns the string
75 // describing the error.
76 std::string
GetDependencyChainPublicError(
77 const HeaderChecker::Chain
& chain
) {
78 std::string ret
= "The target:\n " +
79 chain
[chain
.size() - 1].target
->label().GetUserVisibleName(false) +
80 "\nis including a file from the target:\n " +
81 chain
[0].target
->label().GetUserVisibleName(false) +
84 // Invalid chains should always be 0 (no chain) or more than two
85 // (intermediate private dependencies). 1 and 2 are impossible because a
86 // target can always include headers from itself and its direct dependents.
87 DCHECK(chain
.size() != 1 && chain
.size() != 2);
89 ret
+= "There is no dependency chain between these targets.";
91 // Indirect dependency chain, print the chain.
92 ret
+= "\nIt's usually best to depend directly on the destination target.\n"
93 "In some cases, the destination target is considered a subcomponent\n"
94 "of an intermediate target. In this case, the intermediate target\n"
95 "should depend publicly on the destination to forward the ability\n"
96 "to include headers.\n"
98 "Dependency chain (there may also be others):\n";
100 for (int i
= static_cast<int>(chain
.size()) - 1; i
>= 0; i
--) {
101 ret
.append(" " + chain
[i
].target
->label().GetUserVisibleName(false));
103 // Identify private dependencies so the user can see where in the
104 // dependency chain things went bad. Don't list this for the first link
105 // in the chain since direct dependencies are OK, and listing that as
106 // "private" may make people feel like they need to fix it.
107 if (i
== static_cast<int>(chain
.size()) - 1 || chain
[i
- 1].is_public
)
110 ret
.append(" --[private]-->");
120 HeaderChecker::HeaderChecker(const BuildSettings
* build_settings
,
121 const std::vector
<const Target
*>& targets
)
122 : main_loop_(base::MessageLoop::current()),
123 build_settings_(build_settings
) {
124 for (const auto& target
: targets
)
125 AddTargetToFileMap(target
, &file_map_
);
128 HeaderChecker::~HeaderChecker() {
131 bool HeaderChecker::Run(const std::vector
<const Target
*>& to_check
,
133 std::vector
<Err
>* errors
) {
134 if (to_check
.empty()) {
136 RunCheckOverFiles(file_map_
, force_check
);
138 // Run only over the files in the given targets.
139 FileMap files_to_check
;
140 for (const auto& check
: to_check
)
141 AddTargetToFileMap(check
, &files_to_check
);
142 RunCheckOverFiles(files_to_check
, force_check
);
151 void HeaderChecker::RunCheckOverFiles(const FileMap
& files
, bool force_check
) {
155 scoped_refptr
<base::SequencedWorkerPool
> pool(
156 new base::SequencedWorkerPool(16, "HeaderChecker"));
157 for (const auto& file
: files
) {
158 // Only check C-like source files (RC files also have includes).
159 SourceFileType type
= GetSourceFileType(file
.first
);
160 if (type
!= SOURCE_CC
&& type
!= SOURCE_H
&& type
!= SOURCE_C
&&
161 type
!= SOURCE_M
&& type
!= SOURCE_MM
&& type
!= SOURCE_RC
)
164 // Do a first pass to find if this should be skipped. All targets including
165 // this source file must exclude it from checking, or any target
166 // must mark it as generated (for cases where one target generates a file,
167 // and another lists it as a source to compile it).
169 bool check_includes
= false;
170 bool is_generated
= false;
171 for (const auto& vect_i
: file
.second
) {
172 check_includes
|= vect_i
.target
->check_includes();
173 is_generated
|= vect_i
.is_generated
;
175 if (!check_includes
|| is_generated
)
179 for (const auto& vect_i
: file
.second
) {
180 pool
->PostWorkerTaskWithShutdownBehavior(
182 base::Bind(&HeaderChecker::DoWork
, this, vect_i
.target
, file
.first
),
183 base::SequencedWorkerPool::BLOCK_SHUTDOWN
);
187 // After this call we're single-threaded again.
191 void HeaderChecker::DoWork(const Target
* target
, const SourceFile
& file
) {
193 if (!CheckFile(target
, file
, &err
)) {
194 base::AutoLock
lock(lock_
);
195 errors_
.push_back(err
);
200 void HeaderChecker::AddTargetToFileMap(const Target
* target
, FileMap
* dest
) {
201 // Files in the sources have this public bit by default.
202 bool default_public
= target
->all_headers_public();
204 std::map
<SourceFile
, PublicGeneratedPair
> files_to_public
;
206 // First collect the normal files, they get the default visibility. Always
207 // trim the root gen dir if it exists. This will only exist on outputs of an
208 // action, but those are often then wired into the sources of a compiled
209 // target to actually compile generated code. If you depend on the compiled
210 // target, it should be enough to be able to include the header.
211 for (const auto& source
: target
->sources()) {
212 SourceFile file
= RemoveRootGenDirFromFile(target
, source
);
213 files_to_public
[file
].is_public
= default_public
;
216 // Add in the public files, forcing them to public. This may overwrite some
217 // entries, and it may add new ones.
218 if (default_public
) // List only used when default is not public.
219 DCHECK(target
->public_headers().empty());
220 for (const auto& source
: target
->public_headers()) {
221 SourceFile file
= RemoveRootGenDirFromFile(target
, source
);
222 files_to_public
[file
].is_public
= true;
225 // Add in outputs from actions. These are treated as public (since if other
226 // targets can't use them, then there wouldn't be any point in outputting).
227 std::vector
<SourceFile
> outputs
;
228 target
->action_values().GetOutputsAsSourceFiles(target
, &outputs
);
229 for (const auto& output
: outputs
) {
230 // For generated files in the "gen" directory, add the filename to the
231 // map assuming "gen" is the source root. This means that when files include
232 // the generated header relative to there (the recommended practice), we'll
234 SourceFile output_file
= RemoveRootGenDirFromFile(target
, output
);
235 PublicGeneratedPair
* pair
= &files_to_public
[output_file
];
236 pair
->is_public
= true;
237 pair
->is_generated
= true;
240 // Add the merged list to the master list of all files.
241 for (const auto& cur
: files_to_public
) {
242 (*dest
)[cur
.first
].push_back(TargetInfo(
243 target
, cur
.second
.is_public
, cur
.second
.is_generated
));
247 bool HeaderChecker::IsFileInOuputDir(const SourceFile
& file
) const {
248 const std::string
& build_dir
= build_settings_
->build_dir().value();
249 return file
.value().compare(0, build_dir
.size(), build_dir
) == 0;
252 // This current assumes all include paths are relative to the source root
253 // which is generally the case for Chromium.
255 // A future enhancement would be to search the include path for the target
256 // containing the source file containing this include and find the file to
257 // handle the cases where people do weird things with the paths.
258 SourceFile
HeaderChecker::SourceFileForInclude(
259 const base::StringPiece
& input
) const {
260 std::string
str("//");
261 input
.AppendToString(&str
);
262 return SourceFile(str
);
265 bool HeaderChecker::CheckFile(const Target
* from_target
,
266 const SourceFile
& file
,
268 ScopedTrace
trace(TraceItem::TRACE_CHECK_HEADER
, file
.value());
270 // Sometimes you have generated source files included as sources in another
271 // target. These won't exist at checking time. Since we require all generated
272 // files to be somewhere in the output tree, we can just check the name to
273 // see if they should be skipped.
274 if (IsFileInOuputDir(file
))
277 base::FilePath path
= build_settings_
->GetFullPath(file
);
278 std::string contents
;
279 if (!base::ReadFileToString(path
, &contents
)) {
280 *err
= Err(from_target
->defined_from(), "Source file not found.",
281 "The target:\n " + from_target
->label().GetUserVisibleName(false) +
282 "\nhas a source file:\n " + file
.value() +
283 "\nwhich was not found.");
287 InputFile
input_file(file
);
288 input_file
.SetContents(contents
);
290 CIncludeIterator
iter(&input_file
);
291 base::StringPiece current_include
;
293 while (iter
.GetNextIncludeString(¤t_include
, &range
)) {
294 SourceFile include
= SourceFileForInclude(current_include
);
295 if (!CheckInclude(from_target
, input_file
, include
, range
, err
))
302 // If the file exists:
303 // - It must be in one or more dependencies of the given target.
304 // - Those dependencies must have visibility from the source file.
305 // - The header must be in the public section of those dependeices.
306 // - Those dependencies must either have no direct dependent configs with
307 // flags that affect the compiler, or those direct dependent configs apply
308 // to the "from_target" (it's one "hop" away). This ensures that if the
309 // include file needs needs compiler settings to compile it, that those
310 // settings are applied to the file including it.
311 bool HeaderChecker::CheckInclude(const Target
* from_target
,
312 const InputFile
& source_file
,
313 const SourceFile
& include_file
,
314 const LocationRange
& range
,
316 // Assume if the file isn't declared in our sources that we don't need to
317 // check it. It would be nice if we could give an error if this happens, but
318 // our include finder is too primitive and returns all includes, even if
319 // they're in a #if not executed in the current build. In that case, it's
320 // not unusual for the buildfiles to not specify that header at all.
321 FileMap::const_iterator found
= file_map_
.find(include_file
);
322 if (found
== file_map_
.end())
325 const TargetVector
& targets
= found
->second
;
326 Chain chain
; // Prevent reallocating in the loop.
328 // For all targets containing this file, we require that at least one be
329 // a direct or public dependency of the current target, and that the header
330 // is public within the target.
332 // If there is more than one target containing this header, we may encounter
333 // some error cases before finding a good one. This error stores the previous
334 // one encountered, which we may or may not throw away.
337 bool found_dependency
= false;
338 for (size_t i
= 0; i
< targets
.size(); i
++) {
339 // We always allow source files in a target to include headers also in that
341 const Target
* to_target
= targets
[i
].target
;
342 if (to_target
== from_target
)
345 bool is_permitted_chain
= false;
346 if (IsDependencyOf(to_target
, from_target
, &chain
, &is_permitted_chain
)) {
347 DCHECK(chain
.size() >= 2);
348 DCHECK(chain
[0].target
== to_target
);
349 DCHECK(chain
[chain
.size() - 1].target
== from_target
);
351 found_dependency
= true;
353 if (targets
[i
].is_public
&& is_permitted_chain
) {
354 // This one is OK, we're done.
359 // Diagnose the error.
360 if (!targets
[i
].is_public
) {
361 // Danger: must call CreatePersistentRange to put in Err.
363 CreatePersistentRange(source_file
, range
),
364 "Including a private header.",
365 "This file is private to the target " +
366 targets
[i
].target
->label().GetUserVisibleName(false));
367 } else if (!is_permitted_chain
) {
369 CreatePersistentRange(source_file
, range
),
370 "Can't include this header from here.",
371 GetDependencyChainPublicError(chain
));
376 to_target
->allow_circular_includes_from().find(from_target
->label()) !=
377 to_target
->allow_circular_includes_from().end()) {
378 // Not a dependency, but this include is whitelisted from the destination.
379 found_dependency
= true;
385 if (!found_dependency
) {
386 DCHECK(!last_error
.has_error());
388 std::string msg
= "It is not in any dependency of " +
389 from_target
->label().GetUserVisibleName(false);
390 msg
+= "\nThe include file is in the target(s):\n";
391 for (const auto& target
: targets
)
392 msg
+= " " + target
.target
->label().GetUserVisibleName(false) + "\n";
393 if (targets
.size() > 1)
394 msg
+= "at least one of ";
395 msg
+= "which should somehow be reachable from " +
396 from_target
->label().GetUserVisibleName(false);
398 // Danger: must call CreatePersistentRange to put in Err.
399 *err
= Err(CreatePersistentRange(source_file
, range
),
400 "Include not allowed.", msg
);
403 if (last_error
.has_error()) {
404 // Found at least one dependency chain above, but it had an error.
409 // One thing we didn't check for is targets that expose their dependents
410 // headers in their own public headers.
412 // Say we have A -> B -> C. If C has public_configs, everybody getting headers
413 // from C should get the configs also or things could be out-of-sync. Above,
414 // we check for A including C's headers directly, but A could also include a
415 // header from B that in turn includes a header from C.
417 // There are two ways to solve this:
418 // - If a public header in B includes C, force B to publicly depend on C.
419 // This is possible to check, but might be super annoying because most
420 // targets (especially large leaf-node targets) don't declare
421 // public/private headers and you'll get lots of false positives.
423 // - Save the includes found in each file and actually compute the graph of
424 // includes to detect when A implicitly includes C's header. This will not
425 // have the annoying false positive problem, but is complex to write.
430 bool HeaderChecker::IsDependencyOf(const Target
* search_for
,
431 const Target
* search_from
,
433 bool* is_permitted
) const {
434 if (search_for
== search_from
) {
435 // A target is always visible from itself.
436 *is_permitted
= true;
440 // Find the shortest public dependency chain.
441 if (IsDependencyOf(search_for
, search_from
, true, chain
)) {
442 *is_permitted
= true;
446 // If not, try to find any dependency chain at all.
447 if (IsDependencyOf(search_for
, search_from
, false, chain
)) {
448 *is_permitted
= false;
452 *is_permitted
= false;
456 bool HeaderChecker::IsDependencyOf(const Target
* search_for
,
457 const Target
* search_from
,
458 bool require_permitted
,
459 Chain
* chain
) const {
460 // This method conducts a breadth-first search through the dependency graph
461 // to find a shortest chain from search_from to search_for.
463 // work_queue maintains a queue of targets which need to be considered as
464 // part of this chain, in the order they were first traversed.
466 // Each time a new transitive dependency of search_from is discovered for
467 // the first time, it is added to work_queue and a "breadcrumb" is added,
468 // indicating which target it was reached from when first discovered.
470 // Once this search finds search_for, the breadcrumbs are used to reconstruct
471 // a shortest dependency chain (in reverse order) from search_from to
474 std::map
<const Target
*, ChainLink
> breadcrumbs
;
475 std::queue
<ChainLink
> work_queue
;
476 work_queue
.push(ChainLink(search_from
, true));
478 bool first_time
= true;
479 while (!work_queue
.empty()) {
480 ChainLink cur_link
= work_queue
.front();
481 const Target
* target
= cur_link
.target
;
484 if (target
== search_for
) {
485 // Found it! Reconstruct the chain.
487 while (target
!= search_from
) {
488 chain
->push_back(cur_link
);
489 cur_link
= breadcrumbs
[target
];
490 target
= cur_link
.target
;
492 chain
->push_back(ChainLink(search_from
, true));
496 // Always consider public dependencies as possibilities.
497 for (const auto& dep
: target
->public_deps()) {
498 if (breadcrumbs
.insert(std::make_pair(dep
.ptr
, cur_link
)).second
)
499 work_queue
.push(ChainLink(dep
.ptr
, true));
502 if (first_time
|| !require_permitted
) {
503 // Consider all dependencies since all target paths are allowed, so add
504 // in private ones. Also do this the first time through the loop, since
505 // a target can include headers from its direct deps regardless of
506 // public/private-ness.
508 for (const auto& dep
: target
->private_deps()) {
509 if (breadcrumbs
.insert(std::make_pair(dep
.ptr
, cur_link
)).second
)
510 work_queue
.push(ChainLink(dep
.ptr
, false));