Chromium Code Reviews| 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 |