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.
4
5 #include "tools/gn/header_checker.h"
6
7 #include <algorithm>
8
9 #include "base/bind.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"
24
25 namespace {
26
27 struct PublicGeneratedPair {
PublicGeneratedPair__anonf25324f70111::PublicGeneratedPair28 PublicGeneratedPair() : is_public(false), is_generated(false) {}
29 bool is_public;
30 bool is_generated;
31 };
32
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.
RemoveRootGenDirFromFile(const Target * target,const SourceFile & file)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()));
42 return file;
43 }
44
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.
48 //
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.
CreatePersistentRange(const InputFile & input_file,const LocationRange & range)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.
58
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());
62
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) */));
71 }
72
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.
GetDependencyChainPublicError(const HeaderChecker::Chain & chain)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) +
82 "\n";
83
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);
88 if (chain.empty()) {
89 ret += "There is no dependency chain between these targets.";
90 } else {
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"
97 "\n"
98 "Dependency chain (there may also be others):\n";
99
100 for (int i = static_cast<int>(chain.size()) - 1; i >= 0; i--) {
101 ret.append(" " + chain[i].target->label().GetUserVisibleName(false));
102 if (i != 0) {
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)
108 ret.append(" -->");
109 else
110 ret.append(" --[private]-->");
111 }
112 ret.append("\n");
113 }
114 }
115 return ret;
116 }
117
118 } // namespace
119
HeaderChecker(const BuildSettings * build_settings,const std::vector<const Target * > & targets)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 (size_t i = 0; i < targets.size(); i++)
125 AddTargetToFileMap(targets[i], &file_map_);
126 }
127
~HeaderChecker()128 HeaderChecker::~HeaderChecker() {
129 }
130
Run(const std::vector<const Target * > & to_check,bool force_check,std::vector<Err> * errors)131 bool HeaderChecker::Run(const std::vector<const Target*>& to_check,
132 bool force_check,
133 std::vector<Err>* errors) {
134 if (to_check.empty()) {
135 // Check all files.
136 RunCheckOverFiles(file_map_, force_check);
137 } else {
138 // Run only over the files in the given targets.
139 FileMap files_to_check;
140 for (size_t i = 0; i < to_check.size(); i++)
141 AddTargetToFileMap(to_check[i], &files_to_check);
142 RunCheckOverFiles(files_to_check, force_check);
143 }
144
145 if (errors_.empty())
146 return true;
147 *errors = errors_;
148 return false;
149 }
150
RunCheckOverFiles(const FileMap & files,bool force_check)151 void HeaderChecker::RunCheckOverFiles(const FileMap& files, bool force_check) {
152 if (files.empty())
153 return;
154
155 scoped_refptr<base::SequencedWorkerPool> pool(
156 new base::SequencedWorkerPool(16, "HeaderChecker"));
157 for (FileMap::const_iterator file_i = files.begin();
158 file_i != files.end(); ++file_i) {
159 const TargetVector& vect = file_i->second;
160
161 // Only check C-like source files (RC files also have includes).
162 SourceFileType type = GetSourceFileType(file_i->first);
163 if (type != SOURCE_CC && type != SOURCE_H && type != SOURCE_C &&
164 type != SOURCE_M && type != SOURCE_MM && type != SOURCE_RC)
165 continue;
166
167 // Do a first pass to find if this should be skipped. All targets including
168 // this source file must exclude it from checking, or any target
169 // must mark it as generated (for cases where one target generates a file,
170 // and another lists it as a source to compile it).
171 if (!force_check) {
172 bool check_includes = false;
173 bool is_generated = false;
174 for (size_t vect_i = 0; vect_i < vect.size(); ++vect_i) {
175 check_includes |= vect[vect_i].target->check_includes();
176 is_generated |= vect[vect_i].is_generated;
177 }
178 if (!check_includes || is_generated)
179 continue;
180 }
181
182 for (size_t vect_i = 0; vect_i < vect.size(); ++vect_i) {
183 pool->PostWorkerTaskWithShutdownBehavior(
184 FROM_HERE,
185 base::Bind(&HeaderChecker::DoWork, this,
186 vect[vect_i].target, file_i->first),
187 base::SequencedWorkerPool::BLOCK_SHUTDOWN);
188 }
189 }
190
191 // After this call we're single-threaded again.
192 pool->Shutdown();
193 }
194
DoWork(const Target * target,const SourceFile & file)195 void HeaderChecker::DoWork(const Target* target, const SourceFile& file) {
196 Err err;
197 if (!CheckFile(target, file, &err)) {
198 base::AutoLock lock(lock_);
199 errors_.push_back(err);
200 }
201 }
202
203 // static
AddTargetToFileMap(const Target * target,FileMap * dest)204 void HeaderChecker::AddTargetToFileMap(const Target* target, FileMap* dest) {
205 // Files in the sources have this public bit by default.
206 bool default_public = target->all_headers_public();
207
208 std::map<SourceFile, PublicGeneratedPair> files_to_public;
209
210 // First collect the normal files, they get the default visibility. Always
211 // trim the root gen dir if it exists. This will only exist on outputs of an
212 // action, but those are often then wired into the sources of a compiled
213 // target to actually compile generated code. If you depend on the compiled
214 // target, it should be enough to be able to include the header.
215 const Target::FileList& sources = target->sources();
216 for (size_t i = 0; i < sources.size(); i++) {
217 SourceFile file = RemoveRootGenDirFromFile(target, sources[i]);
218 files_to_public[file].is_public = default_public;
219 }
220
221 // Add in the public files, forcing them to public. This may overwrite some
222 // entries, and it may add new ones.
223 const Target::FileList& public_list = target->public_headers();
224 if (default_public)
225 DCHECK(public_list.empty()); // List only used when default is not public.
226 for (size_t i = 0; i < public_list.size(); i++) {
227 SourceFile file = RemoveRootGenDirFromFile(target, public_list[i]);
228 files_to_public[file].is_public = true;
229 }
230
231 // Add in outputs from actions. These are treated as public (since if other
232 // targets can't use them, then there wouldn't be any point in outputting).
233 std::vector<SourceFile> outputs;
234 target->action_values().GetOutputsAsSourceFiles(target, &outputs);
235 for (size_t i = 0; i < outputs.size(); i++) {
236 // For generated files in the "gen" directory, add the filename to the
237 // map assuming "gen" is the source root. This means that when files include
238 // the generated header relative to there (the recommended practice), we'll
239 // find the file.
240 SourceFile output_file = RemoveRootGenDirFromFile(target, outputs[i]);
241 PublicGeneratedPair* pair = &files_to_public[output_file];
242 pair->is_public = true;
243 pair->is_generated = true;
244 }
245
246 // Add the merged list to the master list of all files.
247 for (std::map<SourceFile, PublicGeneratedPair>::const_iterator i =
248 files_to_public.begin();
249 i != files_to_public.end(); ++i) {
250 (*dest)[i->first].push_back(TargetInfo(
251 target, i->second.is_public, i->second.is_generated));
252 }
253 }
254
IsFileInOuputDir(const SourceFile & file) const255 bool HeaderChecker::IsFileInOuputDir(const SourceFile& file) const {
256 const std::string& build_dir = build_settings_->build_dir().value();
257 return file.value().compare(0, build_dir.size(), build_dir) == 0;
258 }
259
260 // This current assumes all include paths are relative to the source root
261 // which is generally the case for Chromium.
262 //
263 // A future enhancement would be to search the include path for the target
264 // containing the source file containing this include and find the file to
265 // handle the cases where people do weird things with the paths.
SourceFileForInclude(const base::StringPiece & input) const266 SourceFile HeaderChecker::SourceFileForInclude(
267 const base::StringPiece& input) const {
268 std::string str("//");
269 input.AppendToString(&str);
270 return SourceFile(str);
271 }
272
CheckFile(const Target * from_target,const SourceFile & file,Err * err) const273 bool HeaderChecker::CheckFile(const Target* from_target,
274 const SourceFile& file,
275 Err* err) const {
276 ScopedTrace trace(TraceItem::TRACE_CHECK_HEADER, file.value());
277
278 // Sometimes you have generated source files included as sources in another
279 // target. These won't exist at checking time. Since we require all generated
280 // files to be somewhere in the output tree, we can just check the name to
281 // see if they should be skipped.
282 if (IsFileInOuputDir(file))
283 return true;
284
285 base::FilePath path = build_settings_->GetFullPath(file);
286 std::string contents;
287 if (!base::ReadFileToString(path, &contents)) {
288 *err = Err(from_target->defined_from(), "Source file not found.",
289 "The target:\n " + from_target->label().GetUserVisibleName(false) +
290 "\nhas a source file:\n " + file.value() +
291 "\nwhich was not found.");
292 return false;
293 }
294
295 InputFile input_file(file);
296 input_file.SetContents(contents);
297
298 CIncludeIterator iter(&input_file);
299 base::StringPiece current_include;
300 LocationRange range;
301 while (iter.GetNextIncludeString(¤t_include, &range)) {
302 SourceFile include = SourceFileForInclude(current_include);
303 if (!CheckInclude(from_target, input_file, include, range, err))
304 return false;
305 }
306
307 return true;
308 }
309
310 // If the file exists:
311 // - It must be in one or more dependencies of the given target.
312 // - Those dependencies must have visibility from the source file.
313 // - The header must be in the public section of those dependeices.
314 // - Those dependencies must either have no direct dependent configs with
315 // flags that affect the compiler, or those direct dependent configs apply
316 // to the "from_target" (it's one "hop" away). This ensures that if the
317 // include file needs needs compiler settings to compile it, that those
318 // settings are applied to the file including it.
CheckInclude(const Target * from_target,const InputFile & source_file,const SourceFile & include_file,const LocationRange & range,Err * err) const319 bool HeaderChecker::CheckInclude(const Target* from_target,
320 const InputFile& source_file,
321 const SourceFile& include_file,
322 const LocationRange& range,
323 Err* err) const {
324 // Assume if the file isn't declared in our sources that we don't need to
325 // check it. It would be nice if we could give an error if this happens, but
326 // our include finder is too primitive and returns all includes, even if
327 // they're in a #if not executed in the current build. In that case, it's
328 // not unusual for the buildfiles to not specify that header at all.
329 FileMap::const_iterator found = file_map_.find(include_file);
330 if (found == file_map_.end())
331 return true;
332
333 const TargetVector& targets = found->second;
334 Chain chain; // Prevent reallocating in the loop.
335
336 // For all targets containing this file, we require that at least one be
337 // a direct or public dependency of the current target, and that the header
338 // is public within the target.
339 //
340 // If there is more than one target containing this header, we may encounter
341 // some error cases before finding a good one. This error stores the previous
342 // one encountered, which we may or may not throw away.
343 Err last_error;
344
345 bool found_dependency = false;
346 for (size_t i = 0; i < targets.size(); i++) {
347 // We always allow source files in a target to include headers also in that
348 // target.
349 const Target* to_target = targets[i].target;
350 if (to_target == from_target)
351 return true;
352
353 bool is_permitted_chain = false;
354 if (IsDependencyOf(to_target, from_target, &chain, &is_permitted_chain)) {
355 DCHECK(chain.size() >= 2);
356 DCHECK(chain[0].target == to_target);
357 DCHECK(chain[chain.size() - 1].target == from_target);
358
359 found_dependency = true;
360
361 if (targets[i].is_public && is_permitted_chain) {
362 // This one is OK, we're done.
363 last_error = Err();
364 break;
365 }
366
367 // Diagnose the error.
368 if (!targets[i].is_public) {
369 // Danger: must call CreatePersistentRange to put in Err.
370 last_error = Err(
371 CreatePersistentRange(source_file, range),
372 "Including a private header.",
373 "This file is private to the target " +
374 targets[i].target->label().GetUserVisibleName(false));
375 } else if (!is_permitted_chain) {
376 last_error = Err(
377 CreatePersistentRange(source_file, range),
378 "Can't include this header from here.",
379 GetDependencyChainPublicError(chain));
380 } else {
381 NOTREACHED();
382 }
383 } else if (
384 to_target->allow_circular_includes_from().find(from_target->label()) !=
385 to_target->allow_circular_includes_from().end()) {
386 // Not a dependency, but this include is whitelisted from the destination.
387 found_dependency = true;
388 last_error = Err();
389 break;
390 }
391 }
392
393 if (!found_dependency) {
394 DCHECK(!last_error.has_error());
395
396 std::string msg = "It is not in any dependency of " +
397 from_target->label().GetUserVisibleName(false);
398 msg += "\nThe include file is in the target(s):\n";
399 for (size_t i = 0; i < targets.size(); i++)
400 msg += " " + targets[i].target->label().GetUserVisibleName(false) + "\n";
401 if (targets.size() > 1)
402 msg += "at least one of ";
403 msg += "which should somehow be reachable from " +
404 from_target->label().GetUserVisibleName(false);
405
406 // Danger: must call CreatePersistentRange to put in Err.
407 *err = Err(CreatePersistentRange(source_file, range),
408 "Include not allowed.", msg);
409 return false;
410 }
411 if (last_error.has_error()) {
412 // Found at least one dependency chain above, but it had an error.
413 *err = last_error;
414 return false;
415 }
416
417 // One thing we didn't check for is targets that expose their dependents
418 // headers in their own public headers.
419 //
420 // Say we have A -> B -> C. If C has public_configs, everybody getting headers
421 // from C should get the configs also or things could be out-of-sync. Above,
422 // we check for A including C's headers directly, but A could also include a
423 // header from B that in turn includes a header from C.
424 //
425 // There are two ways to solve this:
426 // - If a public header in B includes C, force B to publicly depend on C.
427 // This is possible to check, but might be super annoying because most
428 // targets (especially large leaf-node targets) don't declare
429 // public/private headers and you'll get lots of false positives.
430 //
431 // - Save the includes found in each file and actually compute the graph of
432 // includes to detect when A implicitly includes C's header. This will not
433 // have the annoying false positive problem, but is complex to write.
434
435 return true;
436 }
437
IsDependencyOf(const Target * search_for,const Target * search_from,Chain * chain,bool * is_permitted) const438 bool HeaderChecker::IsDependencyOf(const Target* search_for,
439 const Target* search_from,
440 Chain* chain,
441 bool* is_permitted) const {
442 if (search_for == search_from) {
443 // A target is always visible from itself.
444 *is_permitted = true;
445 return false;
446 }
447
448 // Find the shortest public dependency chain.
449 if (IsDependencyOf(search_for, search_from, true, chain)) {
450 *is_permitted = true;
451 return true;
452 }
453
454 // If not, try to find any dependency chain at all.
455 if (IsDependencyOf(search_for, search_from, false, chain)) {
456 *is_permitted = false;
457 return true;
458 }
459
460 *is_permitted = false;
461 return false;
462 }
463
IsDependencyOf(const Target * search_for,const Target * search_from,bool require_permitted,Chain * chain) const464 bool HeaderChecker::IsDependencyOf(const Target* search_for,
465 const Target* search_from,
466 bool require_permitted,
467 Chain* chain) const {
468 // This method conducts a breadth-first search through the dependency graph
469 // to find a shortest chain from search_from to search_for.
470 //
471 // work_queue maintains a queue of targets which need to be considered as
472 // part of this chain, in the order they were first traversed.
473 //
474 // Each time a new transitive dependency of search_from is discovered for
475 // the first time, it is added to work_queue and a "breadcrumb" is added,
476 // indicating which target it was reached from when first discovered.
477 //
478 // Once this search finds search_for, the breadcrumbs are used to reconstruct
479 // a shortest dependency chain (in reverse order) from search_from to
480 // search_for.
481
482 std::map<const Target*, ChainLink> breadcrumbs;
483 std::queue<ChainLink> work_queue;
484 work_queue.push(ChainLink(search_from, true));
485
486 bool first_time = true;
487 while (!work_queue.empty()) {
488 ChainLink cur_link = work_queue.front();
489 const Target* target = cur_link.target;
490 work_queue.pop();
491
492 if (target == search_for) {
493 // Found it! Reconstruct the chain.
494 chain->clear();
495 while (target != search_from) {
496 chain->push_back(cur_link);
497 cur_link = breadcrumbs[target];
498 target = cur_link.target;
499 }
500 chain->push_back(ChainLink(search_from, true));
501 return true;
502 }
503
504 // Always consider public dependencies as possibilities.
505 const LabelTargetVector& public_deps = target->public_deps();
506 for (size_t i = 0; i < public_deps.size(); i++) {
507 if (breadcrumbs.insert(
508 std::make_pair(public_deps[i].ptr, cur_link)).second)
509 work_queue.push(ChainLink(public_deps[i].ptr, true));
510 }
511
512 if (first_time || !require_permitted) {
513 // Consider all dependencies since all target paths are allowed, so add
514 // in private ones. Also do this the first time through the loop, since
515 // a target can include headers from its direct deps regardless of
516 // public/private-ness.
517 first_time = false;
518 const LabelTargetVector& private_deps = target->private_deps();
519 for (size_t i = 0; i < private_deps.size(); i++) {
520 if (breadcrumbs.insert(
521 std::make_pair(private_deps[i].ptr, cur_link)).second)
522 work_queue.push(ChainLink(private_deps[i].ptr, false));
523 }
524 }
525 }
526
527 return false;
528 }
529