OLD | NEW |
---|---|
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "tools/gn/header_checker.h" | 5 #include "tools/gn/header_checker.h" |
6 | 6 |
7 #include <algorithm> | 7 #include <algorithm> |
8 | 8 |
9 #include "base/bind.h" | 9 #include "base/bind.h" |
10 #include "base/files/file_util.h" | 10 #include "base/files/file_util.h" |
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
60 input_file.name(), &clone_input_file, &tokens, &parse_root); | 60 input_file.name(), &clone_input_file, &tokens, &parse_root); |
61 clone_input_file->SetContents(input_file.contents()); | 61 clone_input_file->SetContents(input_file.contents()); |
62 | 62 |
63 return LocationRange( | 63 return LocationRange( |
64 Location(clone_input_file, range.begin().line_number(), | 64 Location(clone_input_file, range.begin().line_number(), |
65 range.begin().char_offset()), | 65 range.begin().char_offset()), |
66 Location(clone_input_file, range.end().line_number(), | 66 Location(clone_input_file, range.end().line_number(), |
67 range.end().char_offset())); | 67 range.end().char_offset())); |
68 } | 68 } |
69 | 69 |
70 // Returns true if the given config could affect how the compiler runs (rather | 70 // Given a reverse dependency chain where the target chain[0]'s includes are |
71 // than being empty or just affecting linker flags). | 71 // being used by chain[end] and not all deps are public, returns the string |
72 bool ConfigHasCompilerSettings(const Config* config) { | 72 // describing the error. |
73 const ConfigValues& values = config->config_values(); | 73 std::string GetDependencyChainPublicError( |
74 return | 74 const std::vector<const Target*>& chain) { |
75 !values.cflags().empty() || | 75 std::string ret = "The target " + |
76 !values.cflags_c().empty() || | 76 chain[chain.size() - 1]->label().GetUserVisibleName(false) + "\n" + |
77 !values.cflags_cc().empty() || | 77 "is including a file in " + chain[0]->label().GetUserVisibleName(false) + |
78 !values.cflags_objc().empty() || | 78 "\n"; |
79 !values.cflags_objcc().empty() || | 79 if (chain.size() > 2) { |
80 !values.defines().empty() || | 80 // Indirect dependency chain, print the chain. |
81 !values.include_dirs().empty(); | 81 ret += "\nIt's usually best to depend directly on the desination target.\n" |
jamesr
2014/09/16 22:14:24
s/desination/destination/
| |
82 } | 82 "In some cases, the detination target is considered a subcomponent\n" |
jamesr
2014/09/16 22:14:24
s/detination/destination/
| |
83 "of an intermediate target. In this case, the intermediate target\n" | |
84 "should depend publicly on the destination to forward the ability\n" | |
85 "to include headers. Dependency chain (there may be others):\n"; | |
jamesr
2014/09/16 22:14:24
\n before 'Dependency chain' ?
| |
83 | 86 |
84 // Returns true if the given target has any direct dependent configs with | 87 for (int i = static_cast<int>(chain.size()) - 1; i >= 0; i--) { |
85 // compiler settings in it. | 88 ret.append(" " + chain[i]->label().GetUserVisibleName(false)); |
86 bool HasDirectDependentCompilerSettings(const Target* target) { | 89 if (i != 0) |
87 const UniqueVector<LabelConfigPair>& direct = | 90 ret.append(" ->"); |
88 target->direct_dependent_configs(); | 91 ret.append("\n"); |
89 for (size_t i = 0; i < direct.size(); i++) { | 92 } |
90 if (ConfigHasCompilerSettings(direct[i].ptr)) | |
91 return true; | |
92 } | 93 } |
93 return false; | 94 return ret; |
94 } | |
95 | |
96 // Given a reverse dependency chain where the target chain[0]'s dependent | |
97 // configs don't apply to chain[end], returns the string describing the error. | |
98 // The problematic index is the target where the dependent configs were lost. | |
99 std::string GetDependentConfigChainError( | |
100 const std::vector<const Target*>& chain, | |
101 size_t problematic_index) { | |
102 // Erroneous dependent config chains are always at least three long, since | |
103 // dependent configs would apply if it was length two. | |
104 DCHECK(chain.size() >= 3); | |
105 | |
106 std::string from_label = | |
107 chain[chain.size() - 1]->label().GetUserVisibleName(false); | |
108 std::string to_label = | |
109 chain[0]->label().GetUserVisibleName(false); | |
110 std::string problematic_label = | |
111 chain[problematic_index]->label().GetUserVisibleName(false); | |
112 std::string problematic_upstream_label = | |
113 chain[problematic_index - 1]->label().GetUserVisibleName(false); | |
114 | |
115 return | |
116 "You have the dependency tree: SOURCE -> MID -> DEST\n" | |
117 "Where a file from:\n" | |
118 " SOURCE = " + from_label + "\n" | |
119 "is including a header from:\n" | |
120 " DEST = " + to_label + "\n" | |
121 "\n" | |
122 "DEST has direct_dependent_configs, and they don't apply to SOURCE " | |
123 "because\nSOURCE is more than one hop away. This means that DEST's " | |
124 "headers might not\nreceive the expected compiler flags.\n" | |
125 "\n" | |
126 "To fix this, make SOURCE depend directly on DEST.\n" | |
127 "\n" | |
128 "Alternatively, if the target:\n" | |
129 " MID = " + problematic_label + "\n" | |
130 "exposes DEST as part of its public API, you can declare this by " | |
131 "adding:\n" | |
132 " forward_dependent_configs_from = [\n" | |
133 " \"" + problematic_upstream_label + "\"\n" | |
134 " ]\n" | |
135 "to MID. This will apply DEST's direct dependent configs to SOURCE.\n"; | |
136 } | 95 } |
137 | 96 |
138 } // namespace | 97 } // namespace |
139 | 98 |
140 HeaderChecker::HeaderChecker(const BuildSettings* build_settings, | 99 HeaderChecker::HeaderChecker(const BuildSettings* build_settings, |
141 const std::vector<const Target*>& targets) | 100 const std::vector<const Target*>& targets) |
142 : main_loop_(base::MessageLoop::current()), | 101 : main_loop_(base::MessageLoop::current()), |
143 build_settings_(build_settings) { | 102 build_settings_(build_settings) { |
144 for (size_t i = 0; i < targets.size(); i++) | 103 for (size_t i = 0; i < targets.size(); i++) |
145 AddTargetToFileMap(targets[i], &file_map_); | 104 AddTargetToFileMap(targets[i], &file_map_); |
(...skipping 187 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
333 // check it. It would be nice if we could give an error if this happens, but | 292 // check it. It would be nice if we could give an error if this happens, but |
334 // our include finder is too primitive and returns all includes, even if | 293 // our include finder is too primitive and returns all includes, even if |
335 // they're in a #if not executed in the current build. In that case, it's | 294 // they're in a #if not executed in the current build. In that case, it's |
336 // not unusual for the buildfiles to not specify that header at all. | 295 // not unusual for the buildfiles to not specify that header at all. |
337 FileMap::const_iterator found = file_map_.find(include_file); | 296 FileMap::const_iterator found = file_map_.find(include_file); |
338 if (found == file_map_.end()) | 297 if (found == file_map_.end()) |
339 return true; | 298 return true; |
340 | 299 |
341 const TargetVector& targets = found->second; | 300 const TargetVector& targets = found->second; |
342 std::vector<const Target*> chain; // Prevent reallocating in the loop. | 301 std::vector<const Target*> chain; // Prevent reallocating in the loop. |
343 bool direct_dependent_configs_apply = false; | |
344 | 302 |
345 // For all targets containing this file, we require that at least one be | 303 // For all targets containing this file, we require that at least one be |
346 // a dependency of the current target, and all targets that are dependencies | 304 // a dependency of the current target, and all targets that are dependencies |
347 // must have the file listed in the public section. | 305 // must have the file listed in the public section. |
348 bool found_dependency = false; | 306 bool found_dependency = false; |
349 for (size_t i = 0; i < targets.size(); i++) { | 307 for (size_t i = 0; i < targets.size(); i++) { |
350 // We always allow source files in a target to include headers also in that | 308 // We always allow source files in a target to include headers also in that |
351 // target. | 309 // target. |
352 const Target* to_target = targets[i].target; | 310 const Target* to_target = targets[i].target; |
353 if (to_target == from_target) | 311 if (to_target == from_target) |
354 return true; | 312 return true; |
355 | 313 |
356 bool has_direct_dependent_compiler_settings = | 314 bool is_public_dep_chain = false; |
357 HasDirectDependentCompilerSettings(to_target); | 315 if (IsDependencyOf(to_target, from_target, &chain, &is_public_dep_chain)) { |
358 if (IsDependencyOf(to_target, | |
359 from_target, | |
360 has_direct_dependent_compiler_settings, | |
361 &chain, | |
362 &direct_dependent_configs_apply)) { | |
363 DCHECK(chain.size() >= 2); | 316 DCHECK(chain.size() >= 2); |
364 DCHECK(chain[0] == to_target); | 317 DCHECK(chain[0] == to_target); |
365 DCHECK(chain[chain.size() - 1] == from_target); | 318 DCHECK(chain[chain.size() - 1] == from_target); |
366 | 319 |
367 // The include is in a target that's a proper dependency. Verify that | |
368 // the including target has visibility. | |
369 if (!to_target->visibility().CanSeeMe(from_target->label())) { | |
370 std::string msg = "The included file is in " + | |
371 to_target->label().GetUserVisibleName(false) + | |
372 "\nwhich is not visible from " + | |
373 from_target->label().GetUserVisibleName(false) + | |
374 "\n(see \"gn help visibility\")."; | |
375 | |
376 // Danger: must call CreatePersistentRange to put in Err. | |
377 *err = Err(CreatePersistentRange(source_file, range), | |
378 "Including a header from non-visible target.", msg); | |
379 return false; | |
380 } | |
381 | |
382 // The file must be public in the target. | 320 // The file must be public in the target. |
383 if (!targets[i].is_public) { | 321 if (!targets[i].is_public) { |
384 // Danger: must call CreatePersistentRange to put in Err. | 322 // Danger: must call CreatePersistentRange to put in Err. |
385 *err = Err(CreatePersistentRange(source_file, range), | 323 *err = Err(CreatePersistentRange(source_file, range), |
386 "Including a private header.", | 324 "Including a private header.", |
387 "This file is private to the target " + | 325 "This file is private to the target " + |
388 targets[i].target->label().GetUserVisibleName(false)); | 326 targets[i].target->label().GetUserVisibleName(false)); |
389 return false; | 327 return false; |
390 } | 328 } |
391 | 329 |
392 // If the to_target has direct_dependent_configs, they must apply to the | 330 // The dependency chain must be public. |
393 // from_target. | 331 if (!is_public_dep_chain) { |
394 if (has_direct_dependent_compiler_settings && | |
395 !direct_dependent_configs_apply) { | |
396 size_t problematic_index = GetDependentConfigChainProblemIndex(chain); | |
397 *err = Err(CreatePersistentRange(source_file, range), | 332 *err = Err(CreatePersistentRange(source_file, range), |
398 "Can't include this header from here.", | 333 "Can't include this header from here.", |
399 GetDependentConfigChainError(chain, problematic_index)); | 334 GetDependencyChainPublicError(chain)); |
400 return false; | 335 return false; |
401 } | 336 } |
402 | 337 |
403 found_dependency = true; | 338 found_dependency = true; |
404 } else if ( | 339 } else if ( |
405 to_target->allow_circular_includes_from().find(from_target->label()) != | 340 to_target->allow_circular_includes_from().find(from_target->label()) != |
406 to_target->allow_circular_includes_from().end()) { | 341 to_target->allow_circular_includes_from().end()) { |
407 // Not a dependency, but this include is whitelisted from the destination. | 342 // Not a dependency, but this include is whitelisted from the destination. |
408 found_dependency = true; | 343 found_dependency = true; |
409 } | 344 } |
(...skipping 12 matching lines...) Expand all Loading... | |
422 | 357 |
423 // Danger: must call CreatePersistentRange to put in Err. | 358 // Danger: must call CreatePersistentRange to put in Err. |
424 *err = Err(CreatePersistentRange(source_file, range), | 359 *err = Err(CreatePersistentRange(source_file, range), |
425 "Include not allowed.", msg); | 360 "Include not allowed.", msg); |
426 return false; | 361 return false; |
427 } | 362 } |
428 | 363 |
429 // One thing we didn't check for is targets that expose their dependents | 364 // One thing we didn't check for is targets that expose their dependents |
430 // headers in their own public headers. | 365 // headers in their own public headers. |
431 // | 366 // |
432 // Say we have A -> B -> C. If C has direct_dependent_configs, everybody | 367 // Say we have A -> B -> C. If C has public_configs, everybody getting headers |
433 // getting headers from C should get the configs also or things could be | 368 // from C should get the configs also or things could be out-of-sync. Above, |
434 // out-of-sync. Above, we check for A including C's headers directly, but A | 369 // we check for A including C's headers directly, but A could also include a |
435 // could also include a header from B that in turn includes a header from C. | 370 // header from B that in turn includes a header from C. |
436 // | 371 // |
437 // There are two ways to solve this: | 372 // There are two ways to solve this: |
438 // - If a public header in B includes C, force B to forward C's direct | 373 // - If a public header in B includes C, force B to forward C's direct |
439 // dependent configs. This is possible to check, but might be super | 374 // dependent configs. This is possible to check, but might be super |
440 // annoying because most targets (especially large leaf-node targets) | 375 // annoying because most targets (especially large leaf-node targets) |
441 // don't declare public/private headers and you'll get lots of false | 376 // don't declare public/private headers and you'll get lots of false |
442 // positives. | 377 // positives. |
443 // | 378 // |
444 // - Save the includes found in each file and actually compute the graph of | 379 // - Save the includes found in each file and actually compute the graph of |
445 // includes to detect when A implicitly includes C's header. This will not | 380 // includes to detect when A implicitly includes C's header. This will not |
446 // have the annoying false positive problem, but is complex to write. | 381 // have the annoying false positive problem, but is complex to write. |
447 | 382 |
448 return true; | 383 return true; |
449 } | 384 } |
450 | 385 |
451 bool HeaderChecker::IsDependencyOf(const Target* search_for, | 386 bool HeaderChecker::IsDependencyOf(const Target* search_for, |
452 const Target* search_from, | 387 const Target* search_from, |
453 bool prefer_direct_dependent_configs, | |
454 std::vector<const Target*>* chain, | 388 std::vector<const Target*>* chain, |
455 bool* direct_dependent_configs_apply) const { | 389 bool* is_public) const { |
456 if (search_for == search_from) | 390 if (search_for == search_from) { |
391 // A target is always visible from itself. | |
392 *is_public = true; | |
457 return false; | 393 return false; |
394 } | |
458 | 395 |
459 // Find the shortest chain that forwards dependent configs, if one exists. | 396 // Find the shortest public dependency chain. |
460 if (prefer_direct_dependent_configs && | 397 if (IsDependencyOf(search_for, search_from, true, chain)) { |
461 IsDependencyOf(search_for, search_from, true, chain)) { | 398 *is_public = true; |
462 if (direct_dependent_configs_apply) | |
463 *direct_dependent_configs_apply = true; | |
464 return true; | 399 return true; |
465 } | 400 } |
466 | 401 |
467 // If not, try to find any dependency chain at all. | 402 // If not, try to find any dependency chain at all. |
468 if (IsDependencyOf(search_for, search_from, false, chain)) { | 403 if (IsDependencyOf(search_for, search_from, false, chain)) { |
469 if (direct_dependent_configs_apply) | 404 *is_public = false; |
470 *direct_dependent_configs_apply = false; | |
471 return true; | 405 return true; |
472 } | 406 } |
473 | 407 |
408 *is_public = false; | |
474 return false; | 409 return false; |
475 } | 410 } |
476 | 411 |
477 bool HeaderChecker::IsDependencyOf(const Target* search_for, | 412 bool HeaderChecker::IsDependencyOf(const Target* search_for, |
478 const Target* search_from, | 413 const Target* search_from, |
479 bool requires_dependent_configs, | 414 bool require_public, |
480 std::vector<const Target*>* chain) const { | 415 std::vector<const Target*>* chain) const { |
481 // This method conducts a breadth-first search through the dependency graph | 416 // This method conducts a breadth-first search through the dependency graph |
482 // to find a shortest chain from search_from to search_for. | 417 // to find a shortest chain from search_from to search_for. |
483 // | 418 // |
484 // work_queue maintains a queue of targets which need to be considered as | 419 // work_queue maintains a queue of targets which need to be considered as |
485 // part of this chain, in the order they were first traversed. | 420 // part of this chain, in the order they were first traversed. |
486 // | 421 // |
487 // Each time a new transitive dependency of search_from is discovered for | 422 // Each time a new transitive dependency of search_from is discovered for |
488 // the first time, it is added to work_queue and a "breadcrumb" is added, | 423 // the first time, it is added to work_queue and a "breadcrumb" is added, |
489 // indicating which target it was reached from when first discovered. | 424 // indicating which target it was reached from when first discovered. |
(...skipping 14 matching lines...) Expand all Loading... | |
504 // Found it! Reconstruct the chain. | 439 // Found it! Reconstruct the chain. |
505 chain->clear(); | 440 chain->clear(); |
506 while (target != search_from) { | 441 while (target != search_from) { |
507 chain->push_back(target); | 442 chain->push_back(target); |
508 target = breadcrumbs[target]; | 443 target = breadcrumbs[target]; |
509 } | 444 } |
510 chain->push_back(search_from); | 445 chain->push_back(search_from); |
511 return true; | 446 return true; |
512 } | 447 } |
513 | 448 |
514 // If the callee requires direct-dependent configs be forwarded, then | 449 // Always consider public dependencies as possibilities. |
515 // only targets for which they will be forwarded should be explored. | 450 const LabelTargetVector& public_deps = target->public_deps(); |
516 // Groups implicitly forward direct-dependent configs of all of their deps. | 451 for (size_t i = 0; i < public_deps.size(); i++) { |
517 bool uses_all_deps = !requires_dependent_configs || target == search_from || | 452 if (breadcrumbs.insert(std::make_pair(public_deps[i].ptr, target)).second) |
518 target->output_type() == Target::GROUP; | 453 work_queue.push(public_deps[i].ptr); |
454 } | |
519 | 455 |
520 const LabelTargetVector& deps = | 456 if (!require_public) { |
521 uses_all_deps ? target->deps() | 457 // Consider all dependencies since all target paths are allowed, so add |
522 : target->forward_dependent_configs().vector(); | 458 // in private ones. |
523 for (size_t i = 0; i < deps.size(); i++) { | 459 const LabelTargetVector& private_deps = target->private_deps(); |
524 bool seeing_for_first_time = | 460 for (size_t i = 0; i < private_deps.size(); i++) { |
525 breadcrumbs.insert(std::make_pair(deps[i].ptr, target)).second; | 461 if (breadcrumbs.insert(std::make_pair(private_deps[i].ptr, target)) |
526 if (seeing_for_first_time) | 462 .second) |
527 work_queue.push(deps[i].ptr); | 463 work_queue.push(private_deps[i].ptr); |
464 } | |
528 } | 465 } |
529 } | 466 } |
530 | 467 |
531 return false; | 468 return false; |
532 } | 469 } |
533 | |
534 // static | |
535 size_t HeaderChecker::GetDependentConfigChainProblemIndex( | |
536 const std::vector<const Target*>& chain) { | |
537 // Direct dependent configs go up the chain one level with the following | |
538 // exceptions: | |
539 // - Skip over groups | |
540 // - Skip anything that explicitly forwards it | |
541 | |
542 // Chains of length less than three have no problems. | |
543 // These should have been filtered out earlier. | |
544 DCHECK(chain.size() >= 3); | |
545 | |
546 for (size_t i = 1; i < chain.size() - 1; i++) { | |
547 if (chain[i]->output_type() == Target::GROUP) | |
548 continue; // This one is OK, skip to next one. | |
549 | |
550 // The forward list on this target should have contained in it the target | |
551 // at the next lower level. | |
552 const UniqueVector<LabelTargetPair>& forwarded = | |
553 chain[i]->forward_dependent_configs(); | |
554 if (std::find_if(forwarded.begin(), forwarded.end(), | |
555 LabelPtrPtrEquals<Target>(chain[i - 1])) == | |
556 forwarded.end()) | |
557 return i; | |
558 } | |
559 | |
560 CHECK(false) << "Unable to diagnose dependent config chain problem."; | |
561 return 0; | |
562 } | |
OLD | NEW |