|  | // Copyright 2014 The Chromium Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include <ostream> | 
|  | #include <vector> | 
|  |  | 
|  | #include "tools/gn/config.h" | 
|  | #include "tools/gn/header_checker.h" | 
|  | #include "tools/gn/scheduler.h" | 
|  | #include "tools/gn/target.h" | 
|  | #include "tools/gn/test_with_scheduler.h" | 
|  | #include "tools/gn/test_with_scope.h" | 
|  | #include "util/test/test.h" | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | class HeaderCheckerTest : public TestWithScheduler { | 
|  | public: | 
|  | HeaderCheckerTest() | 
|  | : a_(setup_.settings(), Label(SourceDir("//a/"), "a")), | 
|  | b_(setup_.settings(), Label(SourceDir("//b/"), "b")), | 
|  | c_(setup_.settings(), Label(SourceDir("//c/"), "c")), | 
|  | d_(setup_.settings(), Label(SourceDir("//d/"), "d")) { | 
|  | a_.set_output_type(Target::SOURCE_SET); | 
|  | b_.set_output_type(Target::SOURCE_SET); | 
|  | c_.set_output_type(Target::SOURCE_SET); | 
|  | d_.set_output_type(Target::SOURCE_SET); | 
|  |  | 
|  | Err err; | 
|  | a_.SetToolchain(setup_.toolchain(), &err); | 
|  | b_.SetToolchain(setup_.toolchain(), &err); | 
|  | c_.SetToolchain(setup_.toolchain(), &err); | 
|  | d_.SetToolchain(setup_.toolchain(), &err); | 
|  |  | 
|  | a_.public_deps().push_back(LabelTargetPair(&b_)); | 
|  | b_.public_deps().push_back(LabelTargetPair(&c_)); | 
|  |  | 
|  | // Start with all public visibility. | 
|  | a_.visibility().SetPublic(); | 
|  | b_.visibility().SetPublic(); | 
|  | c_.visibility().SetPublic(); | 
|  | d_.visibility().SetPublic(); | 
|  |  | 
|  | d_.OnResolved(&err); | 
|  | c_.OnResolved(&err); | 
|  | b_.OnResolved(&err); | 
|  | a_.OnResolved(&err); | 
|  |  | 
|  | targets_.push_back(&a_); | 
|  | targets_.push_back(&b_); | 
|  | targets_.push_back(&c_); | 
|  | targets_.push_back(&d_); | 
|  | } | 
|  |  | 
|  | protected: | 
|  | TestWithScope setup_; | 
|  |  | 
|  | // Some headers that are automatically set up with a public dependency chain. | 
|  | // a -> b -> c. D is unconnected. | 
|  | Target a_; | 
|  | Target b_; | 
|  | Target c_; | 
|  | Target d_; | 
|  |  | 
|  | std::vector<const Target*> targets_; | 
|  | }; | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | void PrintTo(const SourceFile& source_file, ::std::ostream* os) { | 
|  | *os << source_file.value(); | 
|  | } | 
|  |  | 
|  | TEST_F(HeaderCheckerTest, IsDependencyOf) { | 
|  | scoped_refptr<HeaderChecker> checker( | 
|  | new HeaderChecker(setup_.build_settings(), targets_)); | 
|  |  | 
|  | // Add a target P ("private") that privately depends on C, and hook up the | 
|  | // chain so that A -> P -> C. A will depend on C via two different paths. | 
|  | Err err; | 
|  | Target p(setup_.settings(), Label(SourceDir("//p/"), "p")); | 
|  | p.set_output_type(Target::SOURCE_SET); | 
|  | p.SetToolchain(setup_.toolchain(), &err); | 
|  | EXPECT_FALSE(err.has_error()); | 
|  | p.private_deps().push_back(LabelTargetPair(&c_)); | 
|  | p.visibility().SetPublic(); | 
|  | p.OnResolved(&err); | 
|  |  | 
|  | a_.public_deps().push_back(LabelTargetPair(&p)); | 
|  |  | 
|  | // A does not depend on itself. | 
|  | bool is_permitted = false; | 
|  | HeaderChecker::Chain chain; | 
|  | EXPECT_FALSE(checker->IsDependencyOf(&a_, &a_, &chain, &is_permitted)); | 
|  |  | 
|  | // A depends publicly on B. | 
|  | chain.clear(); | 
|  | is_permitted = false; | 
|  | EXPECT_TRUE(checker->IsDependencyOf(&b_, &a_, &chain, &is_permitted)); | 
|  | ASSERT_EQ(2u, chain.size()); | 
|  | EXPECT_EQ(HeaderChecker::ChainLink(&b_, true), chain[0]); | 
|  | EXPECT_EQ(HeaderChecker::ChainLink(&a_, true), chain[1]); | 
|  | EXPECT_TRUE(is_permitted); | 
|  |  | 
|  | // A indirectly depends on C. The "public" dependency path through B should | 
|  | // be identified. | 
|  | chain.clear(); | 
|  | is_permitted = false; | 
|  | EXPECT_TRUE(checker->IsDependencyOf(&c_, &a_, &chain, &is_permitted)); | 
|  | ASSERT_EQ(3u, chain.size()); | 
|  | EXPECT_EQ(HeaderChecker::ChainLink(&c_, true), chain[0]); | 
|  | EXPECT_EQ(HeaderChecker::ChainLink(&b_, true), chain[1]); | 
|  | EXPECT_EQ(HeaderChecker::ChainLink(&a_, true), chain[2]); | 
|  | EXPECT_TRUE(is_permitted); | 
|  |  | 
|  | // C does not depend on A. | 
|  | chain.clear(); | 
|  | is_permitted = false; | 
|  | EXPECT_FALSE(checker->IsDependencyOf(&a_, &c_, &chain, &is_permitted)); | 
|  | EXPECT_TRUE(chain.empty()); | 
|  | EXPECT_FALSE(is_permitted); | 
|  |  | 
|  | // Remove the B -> C public dependency, leaving P's private dep on C the only | 
|  | // path from A to C. This should now be found. | 
|  | chain.clear(); | 
|  | EXPECT_EQ(&c_, b_.public_deps()[0].ptr);  // Validate it's the right one. | 
|  | b_.public_deps().erase(b_.public_deps().begin()); | 
|  | EXPECT_TRUE(checker->IsDependencyOf(&c_, &a_, &chain, &is_permitted)); | 
|  | EXPECT_EQ(3u, chain.size()); | 
|  | EXPECT_EQ(HeaderChecker::ChainLink(&c_, false), chain[0]); | 
|  | EXPECT_EQ(HeaderChecker::ChainLink(&p, true), chain[1]); | 
|  | EXPECT_EQ(HeaderChecker::ChainLink(&a_, true), chain[2]); | 
|  | EXPECT_FALSE(is_permitted); | 
|  |  | 
|  | // P privately depends on C. That dependency should be OK since it's only | 
|  | // one hop. | 
|  | chain.clear(); | 
|  | is_permitted = false; | 
|  | EXPECT_TRUE(checker->IsDependencyOf(&c_, &p, &chain, &is_permitted)); | 
|  | ASSERT_EQ(2u, chain.size()); | 
|  | EXPECT_EQ(HeaderChecker::ChainLink(&c_, false), chain[0]); | 
|  | EXPECT_EQ(HeaderChecker::ChainLink(&p, true), chain[1]); | 
|  | EXPECT_TRUE(is_permitted); | 
|  | } | 
|  |  | 
|  | TEST_F(HeaderCheckerTest, CheckInclude) { | 
|  | InputFile input_file(SourceFile("//some_file.cc")); | 
|  | input_file.SetContents(std::string()); | 
|  | LocationRange range;  // Dummy value. | 
|  |  | 
|  | // Add a disconnected target d with a header to check that you have to have | 
|  | // to depend on a target listing a header. | 
|  | SourceFile d_header("//d_header.h"); | 
|  | d_.sources().push_back(SourceFile(d_header)); | 
|  |  | 
|  | // Add a header on B and say everything in B is public. | 
|  | SourceFile b_public("//b_public.h"); | 
|  | b_.sources().push_back(b_public); | 
|  | c_.set_all_headers_public(true); | 
|  |  | 
|  | // Add a public and private header on C. | 
|  | SourceFile c_public("//c_public.h"); | 
|  | SourceFile c_private("//c_private.h"); | 
|  | c_.sources().push_back(c_private); | 
|  | c_.public_headers().push_back(c_public); | 
|  | c_.set_all_headers_public(false); | 
|  |  | 
|  | // Create another toolchain. | 
|  | Settings other_settings(setup_.build_settings(), "other/"); | 
|  | Toolchain other_toolchain(&other_settings, | 
|  | Label(SourceDir("//toolchain/"), "other")); | 
|  | TestWithScope::SetupToolchain(&other_toolchain); | 
|  | other_settings.set_toolchain_label(other_toolchain.label()); | 
|  | other_settings.set_default_toolchain_label(setup_.toolchain()->label()); | 
|  |  | 
|  | // Add a target in the other toolchain with a header in it that is not | 
|  | // connected to any targets in the main toolchain. | 
|  | Target otc(&other_settings, | 
|  | Label(SourceDir("//p/"), "otc", other_toolchain.label().dir(), | 
|  | other_toolchain.label().name())); | 
|  | otc.set_output_type(Target::SOURCE_SET); | 
|  | Err err; | 
|  | EXPECT_TRUE(otc.SetToolchain(&other_toolchain, &err)); | 
|  | otc.visibility().SetPublic(); | 
|  | targets_.push_back(&otc); | 
|  |  | 
|  | SourceFile otc_header("//otc_header.h"); | 
|  | otc.sources().push_back(otc_header); | 
|  | EXPECT_TRUE(otc.OnResolved(&err)); | 
|  |  | 
|  | scoped_refptr<HeaderChecker> checker( | 
|  | new HeaderChecker(setup_.build_settings(), targets_)); | 
|  |  | 
|  | std::set<std::pair<const Target*, const Target*>> no_dependency_cache; | 
|  | // A file in target A can't include a header from D because A has no | 
|  | // dependency on D. | 
|  | std::vector<Err> errors; | 
|  | checker->CheckInclude(&a_, input_file, d_header, range, &no_dependency_cache, | 
|  | &errors); | 
|  | EXPECT_GT(errors.size(), 0); | 
|  |  | 
|  | // A can include the public header in B. | 
|  | errors.clear(); | 
|  | no_dependency_cache.clear(); | 
|  | checker->CheckInclude(&a_, input_file, b_public, range, &no_dependency_cache, | 
|  | &errors); | 
|  | EXPECT_EQ(errors.size(), 0); | 
|  |  | 
|  | // Check A depending on the public and private headers in C. | 
|  | errors.clear(); | 
|  | no_dependency_cache.clear(); | 
|  | checker->CheckInclude(&a_, input_file, c_public, range, &no_dependency_cache, | 
|  | &errors); | 
|  | EXPECT_EQ(errors.size(), 0); | 
|  | errors.clear(); | 
|  | no_dependency_cache.clear(); | 
|  | checker->CheckInclude(&a_, input_file, c_private, range, &no_dependency_cache, | 
|  | &errors); | 
|  | EXPECT_GT(errors.size(), 0); | 
|  |  | 
|  | // A can depend on a random file unknown to the build. | 
|  | errors.clear(); | 
|  | no_dependency_cache.clear(); | 
|  | checker->CheckInclude(&a_, input_file, SourceFile("//random.h"), range, | 
|  | &no_dependency_cache, &errors); | 
|  | EXPECT_EQ(errors.size(), 0); | 
|  |  | 
|  | // A can depend on a file present only in another toolchain even with no | 
|  | // dependency path. | 
|  | errors.clear(); | 
|  | no_dependency_cache.clear(); | 
|  | checker->CheckInclude(&a_, input_file, otc_header, range, | 
|  | &no_dependency_cache, &errors); | 
|  | EXPECT_EQ(errors.size(), 0); | 
|  | } | 
|  |  | 
|  | // A public chain of dependencies should always be identified first, even if | 
|  | // it is longer than a private one. | 
|  | TEST_F(HeaderCheckerTest, PublicFirst) { | 
|  | // Now make a A -> Z -> D private dependency chain (one shorter than the | 
|  | // public one to get to D). | 
|  | Target z(setup_.settings(), Label(SourceDir("//a/"), "a")); | 
|  | z.set_output_type(Target::SOURCE_SET); | 
|  | Err err; | 
|  | EXPECT_TRUE(z.SetToolchain(setup_.toolchain(), &err)); | 
|  | z.private_deps().push_back(LabelTargetPair(&d_)); | 
|  | EXPECT_TRUE(z.OnResolved(&err)); | 
|  | targets_.push_back(&z); | 
|  |  | 
|  | a_.private_deps().push_back(LabelTargetPair(&z)); | 
|  |  | 
|  | // Check that D can be found from A, but since it's private, it will be | 
|  | // marked as not permitted. | 
|  | bool is_permitted = false; | 
|  | HeaderChecker::Chain chain; | 
|  | scoped_refptr<HeaderChecker> checker( | 
|  | new HeaderChecker(setup_.build_settings(), targets_)); | 
|  | EXPECT_TRUE(checker->IsDependencyOf(&d_, &a_, &chain, &is_permitted)); | 
|  |  | 
|  | EXPECT_FALSE(is_permitted); | 
|  | ASSERT_EQ(3u, chain.size()); | 
|  | EXPECT_EQ(HeaderChecker::ChainLink(&d_, false), chain[0]); | 
|  | EXPECT_EQ(HeaderChecker::ChainLink(&z, false), chain[1]); | 
|  | EXPECT_EQ(HeaderChecker::ChainLink(&a_, true), chain[2]); | 
|  |  | 
|  | // Hook up D to the existing public A -> B -> C chain to make a long one, and | 
|  | // search for D again. | 
|  | c_.public_deps().push_back(LabelTargetPair(&d_)); | 
|  | checker = new HeaderChecker(setup_.build_settings(), targets_); | 
|  | chain.clear(); | 
|  | EXPECT_TRUE(checker->IsDependencyOf(&d_, &a_, &chain, &is_permitted)); | 
|  |  | 
|  | // This should have found the long public one. | 
|  | EXPECT_TRUE(is_permitted); | 
|  | ASSERT_EQ(4u, chain.size()); | 
|  | EXPECT_EQ(HeaderChecker::ChainLink(&d_, true), chain[0]); | 
|  | EXPECT_EQ(HeaderChecker::ChainLink(&c_, true), chain[1]); | 
|  | EXPECT_EQ(HeaderChecker::ChainLink(&b_, true), chain[2]); | 
|  | EXPECT_EQ(HeaderChecker::ChainLink(&a_, true), chain[3]); | 
|  | } | 
|  |  | 
|  | // Checks that the allow_circular_includes_from list works. | 
|  | TEST_F(HeaderCheckerTest, CheckIncludeAllowCircular) { | 
|  | InputFile input_file(SourceFile("//some_file.cc")); | 
|  | input_file.SetContents(std::string()); | 
|  | LocationRange range;  // Dummy value. | 
|  |  | 
|  | // Add an include file to A. | 
|  | SourceFile a_public("//a_public.h"); | 
|  | a_.sources().push_back(a_public); | 
|  |  | 
|  | scoped_refptr<HeaderChecker> checker( | 
|  | new HeaderChecker(setup_.build_settings(), targets_)); | 
|  |  | 
|  | // A depends on B. So B normally can't include headers from A. | 
|  | std::vector<Err> errors; | 
|  | std::set<std::pair<const Target*, const Target*>> no_dependency_cache; | 
|  | checker->CheckInclude(&b_, input_file, a_public, range, &no_dependency_cache, | 
|  | &errors); | 
|  | EXPECT_GT(errors.size(), 0); | 
|  |  | 
|  | // Add an allow_circular_includes_from on A that lists B. | 
|  | a_.allow_circular_includes_from().insert(b_.label()); | 
|  |  | 
|  | // Now the include from B to A should be allowed. | 
|  | errors.clear(); | 
|  | no_dependency_cache.clear(); | 
|  | checker->CheckInclude(&b_, input_file, a_public, range, &no_dependency_cache, | 
|  | &errors); | 
|  | EXPECT_EQ(errors.size(), 0); | 
|  | } | 
|  |  | 
|  | TEST_F(HeaderCheckerTest, SourceFileForInclude) { | 
|  | using base::FilePath; | 
|  | const std::vector<SourceDir> kIncludeDirs = { | 
|  | SourceDir("/c/custom_include/"), SourceDir("//"), SourceDir("//subdir")}; | 
|  | a_.sources().push_back(SourceFile("//lib/header1.h")); | 
|  | b_.sources().push_back(SourceFile("/c/custom_include/header2.h")); | 
|  |  | 
|  | InputFile dummy_input_file(SourceFile("//some_file.cc")); | 
|  | dummy_input_file.SetContents(std::string()); | 
|  | LocationRange dummy_range; | 
|  |  | 
|  | scoped_refptr<HeaderChecker> checker( | 
|  | new HeaderChecker(setup_.build_settings(), targets_)); | 
|  | { | 
|  | Err err; | 
|  | SourceFile source_file = checker->SourceFileForInclude( | 
|  | "lib/header1.h", kIncludeDirs, dummy_input_file, dummy_range, &err); | 
|  | EXPECT_FALSE(err.has_error()); | 
|  | EXPECT_EQ(SourceFile("//lib/header1.h"), source_file); | 
|  | } | 
|  |  | 
|  | { | 
|  | Err err; | 
|  | SourceFile source_file = checker->SourceFileForInclude( | 
|  | "header2.h", kIncludeDirs, dummy_input_file, dummy_range, &err); | 
|  | EXPECT_FALSE(err.has_error()); | 
|  | EXPECT_EQ(SourceFile("/c/custom_include/header2.h"), source_file); | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST_F(HeaderCheckerTest, SourceFileForInclude_FileNotFound) { | 
|  | using base::FilePath; | 
|  | const char kFileContents[] = "Some dummy contents"; | 
|  | const std::vector<SourceDir> kIncludeDirs = {SourceDir("//")}; | 
|  | scoped_refptr<HeaderChecker> checker( | 
|  | new HeaderChecker(setup_.build_settings(), targets_)); | 
|  |  | 
|  | Err err; | 
|  | InputFile input_file(SourceFile("//input.cc")); | 
|  | input_file.SetContents(std::string(kFileContents)); | 
|  | const int kLineNumber = 10; | 
|  | const int kColumnNumber = 16; | 
|  | const int kLength = 8; | 
|  | const int kByteNumber = 100; | 
|  | LocationRange range( | 
|  | Location(&input_file, kLineNumber, kColumnNumber, kByteNumber), | 
|  | Location(&input_file, kLineNumber, kColumnNumber + kLength, kByteNumber)); | 
|  |  | 
|  | SourceFile source_file = checker->SourceFileForInclude( | 
|  | "header.h", kIncludeDirs, input_file, range, &err); | 
|  | EXPECT_TRUE(source_file.is_null()); | 
|  | EXPECT_FALSE(err.has_error()); | 
|  | } | 
|  |  | 
|  | TEST_F(HeaderCheckerTest, Friend) { | 
|  | // Note: we have a public dependency chain A -> B -> C set up already. | 
|  | InputFile input_file(SourceFile("//some_file.cc")); | 
|  | input_file.SetContents(std::string()); | 
|  | LocationRange range;  // Dummy value. | 
|  |  | 
|  | // Add a private header on C. | 
|  | SourceFile c_private("//c_private.h"); | 
|  | c_.sources().push_back(c_private); | 
|  | c_.set_all_headers_public(false); | 
|  |  | 
|  | // List A as a friend of C. | 
|  | Err err; | 
|  | c_.friends().push_back( | 
|  | LabelPattern::GetPattern(SourceDir("//"), Value(nullptr, "//a:*"), &err)); | 
|  | ASSERT_FALSE(err.has_error()); | 
|  |  | 
|  | // Must be after setting everything up for it to find the files. | 
|  | scoped_refptr<HeaderChecker> checker( | 
|  | new HeaderChecker(setup_.build_settings(), targets_)); | 
|  |  | 
|  | // B should not be allowed to include C's private header. | 
|  | std::vector<Err> errors; | 
|  | std::set<std::pair<const Target*, const Target*>> no_dependency_cache; | 
|  | checker->CheckInclude(&b_, input_file, c_private, range, &no_dependency_cache, | 
|  | &errors); | 
|  | EXPECT_GT(errors.size(), 0); | 
|  |  | 
|  | // A should be able to because of the friend declaration. | 
|  | errors.clear(); | 
|  | no_dependency_cache.clear(); | 
|  | checker->CheckInclude(&a_, input_file, c_private, range, &no_dependency_cache, | 
|  | &errors); | 
|  | EXPECT_EQ(errors.size(), 0); | 
|  | } |