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 destination target.\n" |
82 } | 82 "In some cases, the destination target is considered a subcomponent\n" |
| 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.\n" |
| 86 "\n" |
| 87 "Dependency chain (there may be others):\n"; |
83 | 88 |
84 // Returns true if the given target has any direct dependent configs with | 89 for (int i = static_cast<int>(chain.size()) - 1; i >= 0; i--) { |
85 // compiler settings in it. | 90 ret.append(" " + chain[i]->label().GetUserVisibleName(false)); |
86 bool HasDirectDependentCompilerSettings(const Target* target) { | 91 if (i != 0) |
87 const UniqueVector<LabelConfigPair>& direct = | 92 ret.append(" ->"); |
88 target->direct_dependent_configs(); | 93 ret.append("\n"); |
89 for (size_t i = 0; i < direct.size(); i++) { | 94 } |
90 if (ConfigHasCompilerSettings(direct[i].ptr)) | |
91 return true; | |
92 } | 95 } |
93 return false; | 96 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 } | 97 } |
137 | 98 |
138 } // namespace | 99 } // namespace |
139 | 100 |
140 HeaderChecker::HeaderChecker(const BuildSettings* build_settings, | 101 HeaderChecker::HeaderChecker(const BuildSettings* build_settings, |
141 const std::vector<const Target*>& targets) | 102 const std::vector<const Target*>& targets) |
142 : main_loop_(base::MessageLoop::current()), | 103 : main_loop_(base::MessageLoop::current()), |
143 build_settings_(build_settings) { | 104 build_settings_(build_settings) { |
144 for (size_t i = 0; i < targets.size(); i++) | 105 for (size_t i = 0; i < targets.size(); i++) |
145 AddTargetToFileMap(targets[i], &file_map_); | 106 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 | 294 // 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 | 295 // 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 | 296 // 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. | 297 // not unusual for the buildfiles to not specify that header at all. |
337 FileMap::const_iterator found = file_map_.find(include_file); | 298 FileMap::const_iterator found = file_map_.find(include_file); |
338 if (found == file_map_.end()) | 299 if (found == file_map_.end()) |
339 return true; | 300 return true; |
340 | 301 |
341 const TargetVector& targets = found->second; | 302 const TargetVector& targets = found->second; |
342 std::vector<const Target*> chain; // Prevent reallocating in the loop. | 303 std::vector<const Target*> chain; // Prevent reallocating in the loop. |
343 bool direct_dependent_configs_apply = false; | |
344 | 304 |
345 // For all targets containing this file, we require that at least one be | 305 // 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 | 306 // a dependency of the current target, and all targets that are dependencies |
347 // must have the file listed in the public section. | 307 // must have the file listed in the public section. |
348 bool found_dependency = false; | 308 bool found_dependency = false; |
349 for (size_t i = 0; i < targets.size(); i++) { | 309 for (size_t i = 0; i < targets.size(); i++) { |
350 // We always allow source files in a target to include headers also in that | 310 // We always allow source files in a target to include headers also in that |
351 // target. | 311 // target. |
352 const Target* to_target = targets[i].target; | 312 const Target* to_target = targets[i].target; |
353 if (to_target == from_target) | 313 if (to_target == from_target) |
354 return true; | 314 return true; |
355 | 315 |
356 bool has_direct_dependent_compiler_settings = | 316 bool is_public_dep_chain = false; |
357 HasDirectDependentCompilerSettings(to_target); | 317 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); | 318 DCHECK(chain.size() >= 2); |
364 DCHECK(chain[0] == to_target); | 319 DCHECK(chain[0] == to_target); |
365 DCHECK(chain[chain.size() - 1] == from_target); | 320 DCHECK(chain[chain.size() - 1] == from_target); |
366 | 321 |
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. | 322 // The file must be public in the target. |
383 if (!targets[i].is_public) { | 323 if (!targets[i].is_public) { |
384 // Danger: must call CreatePersistentRange to put in Err. | 324 // Danger: must call CreatePersistentRange to put in Err. |
385 *err = Err(CreatePersistentRange(source_file, range), | 325 *err = Err(CreatePersistentRange(source_file, range), |
386 "Including a private header.", | 326 "Including a private header.", |
387 "This file is private to the target " + | 327 "This file is private to the target " + |
388 targets[i].target->label().GetUserVisibleName(false)); | 328 targets[i].target->label().GetUserVisibleName(false)); |
389 return false; | 329 return false; |
390 } | 330 } |
391 | 331 |
392 // If the to_target has direct_dependent_configs, they must apply to the | 332 // The dependency chain must be public. |
393 // from_target. | 333 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), | 334 *err = Err(CreatePersistentRange(source_file, range), |
398 "Can't include this header from here.", | 335 "Can't include this header from here.", |
399 GetDependentConfigChainError(chain, problematic_index)); | 336 GetDependencyChainPublicError(chain)); |
400 return false; | 337 return false; |
401 } | 338 } |
402 | 339 |
403 found_dependency = true; | 340 found_dependency = true; |
404 } else if ( | 341 } else if ( |
405 to_target->allow_circular_includes_from().find(from_target->label()) != | 342 to_target->allow_circular_includes_from().find(from_target->label()) != |
406 to_target->allow_circular_includes_from().end()) { | 343 to_target->allow_circular_includes_from().end()) { |
407 // Not a dependency, but this include is whitelisted from the destination. | 344 // Not a dependency, but this include is whitelisted from the destination. |
408 found_dependency = true; | 345 found_dependency = true; |
409 } | 346 } |
(...skipping 12 matching lines...) Expand all Loading... |
422 | 359 |
423 // Danger: must call CreatePersistentRange to put in Err. | 360 // Danger: must call CreatePersistentRange to put in Err. |
424 *err = Err(CreatePersistentRange(source_file, range), | 361 *err = Err(CreatePersistentRange(source_file, range), |
425 "Include not allowed.", msg); | 362 "Include not allowed.", msg); |
426 return false; | 363 return false; |
427 } | 364 } |
428 | 365 |
429 // One thing we didn't check for is targets that expose their dependents | 366 // One thing we didn't check for is targets that expose their dependents |
430 // headers in their own public headers. | 367 // headers in their own public headers. |
431 // | 368 // |
432 // Say we have A -> B -> C. If C has direct_dependent_configs, everybody | 369 // 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 | 370 // 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 | 371 // 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. | 372 // header from B that in turn includes a header from C. |
436 // | 373 // |
437 // There are two ways to solve this: | 374 // There are two ways to solve this: |
438 // - If a public header in B includes C, force B to forward C's direct | 375 // - 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 | 376 // dependent configs. This is possible to check, but might be super |
440 // annoying because most targets (especially large leaf-node targets) | 377 // annoying because most targets (especially large leaf-node targets) |
441 // don't declare public/private headers and you'll get lots of false | 378 // don't declare public/private headers and you'll get lots of false |
442 // positives. | 379 // positives. |
443 // | 380 // |
444 // - Save the includes found in each file and actually compute the graph of | 381 // - 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 | 382 // 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. | 383 // have the annoying false positive problem, but is complex to write. |
447 | 384 |
448 return true; | 385 return true; |
449 } | 386 } |
450 | 387 |
451 bool HeaderChecker::IsDependencyOf(const Target* search_for, | 388 bool HeaderChecker::IsDependencyOf(const Target* search_for, |
452 const Target* search_from, | 389 const Target* search_from, |
453 bool prefer_direct_dependent_configs, | |
454 std::vector<const Target*>* chain, | 390 std::vector<const Target*>* chain, |
455 bool* direct_dependent_configs_apply) const { | 391 bool* is_public) const { |
456 if (search_for == search_from) | 392 if (search_for == search_from) { |
| 393 // A target is always visible from itself. |
| 394 *is_public = true; |
457 return false; | 395 return false; |
| 396 } |
458 | 397 |
459 // Find the shortest chain that forwards dependent configs, if one exists. | 398 // Find the shortest public dependency chain. |
460 if (prefer_direct_dependent_configs && | 399 if (IsDependencyOf(search_for, search_from, true, chain)) { |
461 IsDependencyOf(search_for, search_from, true, chain)) { | 400 *is_public = true; |
462 if (direct_dependent_configs_apply) | |
463 *direct_dependent_configs_apply = true; | |
464 return true; | 401 return true; |
465 } | 402 } |
466 | 403 |
467 // If not, try to find any dependency chain at all. | 404 // If not, try to find any dependency chain at all. |
468 if (IsDependencyOf(search_for, search_from, false, chain)) { | 405 if (IsDependencyOf(search_for, search_from, false, chain)) { |
469 if (direct_dependent_configs_apply) | 406 *is_public = false; |
470 *direct_dependent_configs_apply = false; | |
471 return true; | 407 return true; |
472 } | 408 } |
473 | 409 |
| 410 *is_public = false; |
474 return false; | 411 return false; |
475 } | 412 } |
476 | 413 |
477 bool HeaderChecker::IsDependencyOf(const Target* search_for, | 414 bool HeaderChecker::IsDependencyOf(const Target* search_for, |
478 const Target* search_from, | 415 const Target* search_from, |
479 bool requires_dependent_configs, | 416 bool require_public, |
480 std::vector<const Target*>* chain) const { | 417 std::vector<const Target*>* chain) const { |
481 // This method conducts a breadth-first search through the dependency graph | 418 // This method conducts a breadth-first search through the dependency graph |
482 // to find a shortest chain from search_from to search_for. | 419 // to find a shortest chain from search_from to search_for. |
483 // | 420 // |
484 // work_queue maintains a queue of targets which need to be considered as | 421 // 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. | 422 // part of this chain, in the order they were first traversed. |
486 // | 423 // |
487 // Each time a new transitive dependency of search_from is discovered for | 424 // 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, | 425 // 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. | 426 // indicating which target it was reached from when first discovered. |
(...skipping 14 matching lines...) Expand all Loading... |
504 // Found it! Reconstruct the chain. | 441 // Found it! Reconstruct the chain. |
505 chain->clear(); | 442 chain->clear(); |
506 while (target != search_from) { | 443 while (target != search_from) { |
507 chain->push_back(target); | 444 chain->push_back(target); |
508 target = breadcrumbs[target]; | 445 target = breadcrumbs[target]; |
509 } | 446 } |
510 chain->push_back(search_from); | 447 chain->push_back(search_from); |
511 return true; | 448 return true; |
512 } | 449 } |
513 | 450 |
514 // If the callee requires direct-dependent configs be forwarded, then | 451 // Always consider public dependencies as possibilities. |
515 // only targets for which they will be forwarded should be explored. | 452 const LabelTargetVector& public_deps = target->public_deps(); |
516 // Groups implicitly forward direct-dependent configs of all of their deps. | 453 for (size_t i = 0; i < public_deps.size(); i++) { |
517 bool uses_all_deps = !requires_dependent_configs || target == search_from || | 454 if (breadcrumbs.insert(std::make_pair(public_deps[i].ptr, target)).second) |
518 target->output_type() == Target::GROUP; | 455 work_queue.push(public_deps[i].ptr); |
| 456 } |
519 | 457 |
520 const LabelTargetVector& deps = | 458 if (!require_public) { |
521 uses_all_deps ? target->deps() | 459 // Consider all dependencies since all target paths are allowed, so add |
522 : target->forward_dependent_configs().vector(); | 460 // in private ones. |
523 for (size_t i = 0; i < deps.size(); i++) { | 461 const LabelTargetVector& private_deps = target->private_deps(); |
524 bool seeing_for_first_time = | 462 for (size_t i = 0; i < private_deps.size(); i++) { |
525 breadcrumbs.insert(std::make_pair(deps[i].ptr, target)).second; | 463 if (breadcrumbs.insert(std::make_pair(private_deps[i].ptr, target)) |
526 if (seeing_for_first_time) | 464 .second) |
527 work_queue.push(deps[i].ptr); | 465 work_queue.push(private_deps[i].ptr); |
| 466 } |
528 } | 467 } |
529 } | 468 } |
530 | 469 |
531 return false; | 470 return false; |
532 } | 471 } |
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 |