| // 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. | |
| #ifndef TOOLS_GN_HEADER_CHECKER_H_ | |
| #define TOOLS_GN_HEADER_CHECKER_H_ | |
| #include <array> | |
| #include <atomic> | |
| #include <condition_variable> | |
| #include <functional> | |
| #include <map> | |
| #include <mutex> | |
| #include <set> | |
| #include <shared_mutex> | |
| #include <string_view> | |
| #include <unordered_map> | |
| #include <vector> | |
| #include "base/atomic_ref_count.h" | |
| #include "base/gtest_prod_util.h" | |
| #include "base/memory/ref_counted.h" | |
| #include "gn/c_include_iterator.h" | |
| #include "gn/err.h" | |
| #include "gn/hash_table_base.h" | |
| #include "gn/source_dir.h" | |
| #include "gn/source_file.h" | |
| class BuildSettings; | |
| class InputFile; | |
| class Target; | |
| class WorkerPool; | |
| namespace base { | |
| class FilePath; | |
| } | |
| class HeaderChecker : public base::RefCountedThreadSafe<HeaderChecker> { | |
| public: | |
| // Represents a dependency chain. | |
| struct ChainLink { | |
| ChainLink() : target(nullptr), is_public(false) {} | |
| ChainLink(const Target* t, bool p) : target(t), is_public(p) {} | |
| const Target* target; | |
| // True when the dependency on this target is public. | |
| bool is_public; | |
| // Used for testing. | |
| bool operator==(const ChainLink& other) const { | |
| return target == other.target && is_public == other.is_public; | |
| } | |
| }; | |
| using Chain = std::vector<ChainLink>; | |
| // Represents a header dependency violation found during checking. | |
| struct Violation { | |
| // The diagnostic error describing the violation. | |
| Err error; | |
| // The source file that contained the invalid #include directive. | |
| SourceFile source_file; | |
| // The header file that was included without appropriate build dependency. | |
| // May be null if we were unable to find the header file. | |
| SourceFile included_file; | |
| }; | |
| // check_generated, if true, will also check generated | |
| // files. Something that can only be done after running a build that | |
| // has generated them. | |
| HeaderChecker(const BuildSettings* build_settings, | |
| const std::vector<const Target*>& targets, | |
| bool check_generated, | |
| bool check_system); | |
| // Runs the check. The targets in to_check will be checked. | |
| // | |
| // This assumes that the current thread already has a message loop. On | |
| // error, fills the given vector with the violations and returns false. | |
| // Returns true on success. | |
| // | |
| // force_check, if true, will override targets opting out of header checking | |
| // with "check_includes = false" and will check them anyway. | |
| bool Run(const std::vector<const Target*>& to_check, | |
| bool force_check, | |
| std::vector<Violation>* violations); | |
| private: | |
| friend class base::RefCountedThreadSafe<HeaderChecker>; | |
| FRIEND_TEST_ALL_PREFIXES(HeaderCheckerTest, IsDependencyOf); | |
| FRIEND_TEST_ALL_PREFIXES(HeaderCheckerTest, CheckInclude); | |
| FRIEND_TEST_ALL_PREFIXES(HeaderCheckerTest, PublicFirst); | |
| FRIEND_TEST_ALL_PREFIXES(HeaderCheckerTest, CheckIncludeAllowCircular); | |
| FRIEND_TEST_ALL_PREFIXES(HeaderCheckerTest, CheckIncludeSwiftModule); | |
| FRIEND_TEST_ALL_PREFIXES(HeaderCheckerTest, SourceFileForInclude); | |
| FRIEND_TEST_ALL_PREFIXES(HeaderCheckerTest, | |
| SourceFileForInclude_FileNotFound); | |
| FRIEND_TEST_ALL_PREFIXES(HeaderCheckerTest, Friend); | |
| FRIEND_TEST_ALL_PREFIXES(HeaderCheckerTest, CheckIncludesStrictTransitive); | |
| FRIEND_TEST_ALL_PREFIXES(HeaderCheckerTest, | |
| CheckIncludesStrictPrivateInPublicHeader); | |
| FRIEND_TEST_ALL_PREFIXES(HeaderCheckerTest, | |
| CheckIncludesStrictSameTargetPrivateHeader); | |
| ~HeaderChecker(); | |
| // Header checking structures ------------------------------------------------ | |
| // Data for BreadcrumbNode. | |
| // | |
| // This class is a trivial type so it can be used in HashTableBase. | |
| // To implement IsDependencyOf(from_target, to_target), a BFS starting from an | |
| // arbitrary `from_target` is performed, and a BreadCrumbTable is used to | |
| // record during the walk, that a given `|target|` is a dependency of | |
| // `|src_target|`, with `|is_public|` indicating the type of dependency. | |
| // | |
| // This table only records the first (src_target->target) dependency during | |
| // the BFS, since only the shortest dependency path is interesting. This also | |
| // means that if a target is the dependency of two distinct parents at the | |
| // same level, only the first parent will be recorded in the table. Consider | |
| // the following graph: | |
| // | |
| // ``` | |
| // A | |
| // / \ | |
| // B C | |
| // \ / | |
| // D | |
| // ``` | |
| // | |
| // The BFS will visit nodes in order: A, B, C and D, but will record only the | |
| // (D, B) edge, not the (D, C) one, even if B->D is private and C->D is | |
| // public. | |
| // | |
| // This information is later used to reconstruct the dependency chain when | |
| // `to_target` is found by the walk. | |
| struct BreadcrumbNode { | |
| const Target* target; | |
| const Target* src_target; | |
| bool is_public; | |
| bool is_null() const { return !target; } | |
| static bool is_tombstone() { return false; } | |
| bool is_valid() const { return !is_null(); } | |
| size_t hash_value() const { return std::hash<const Target*>()(target); } | |
| }; | |
| struct BreadcrumbTable : public HashTableBase<BreadcrumbNode> { | |
| using Base = HashTableBase<BreadcrumbNode>; | |
| using Node = Base::Node; | |
| // Since we only insert, we don't need to return success/failure. | |
| // We can also assume that key uniqueness is checked before insertion if | |
| // necessary, or that we simply overwrite (though BFS usually checks | |
| // existence first). | |
| // | |
| // In IsDependencyOf, we use the return value checking if it was already | |
| // there. So we need an Insert that returns whether it was new. | |
| bool Insert(const Target* target, | |
| const Target* src_target, | |
| bool is_public) { | |
| size_t hash = std::hash<const Target*>()(target); | |
| Node* node = NodeLookup( | |
| hash, [target](const Node* n) { return n->target == target; }); | |
| if (node->is_valid()) | |
| return false; | |
| node->target = target; | |
| node->src_target = src_target; | |
| node->is_public = is_public; | |
| UpdateAfterInsert(false); | |
| return true; | |
| } | |
| // Returns the ChainLink for the given target, or a null-target ChainLink if | |
| // not found. The returned link.target, if not nullptr, is a dependent of | |
| // the input target that was found during the BFS walk, with dependency | |
| // type link.is_public. | |
| ChainLink GetLink(const Target* target) const { | |
| size_t hash = std::hash<const Target*>()(target); | |
| const Node* node = NodeLookup( | |
| hash, [target](const Node* n) { return n->target == target; }); | |
| if (node->is_valid()) | |
| return ChainLink(node->src_target, node->is_public); | |
| return ChainLink(); | |
| } | |
| }; | |
| // Store the shortest-dependency-path information for all BFS walks starting | |
| // from a given `search_from` target. | |
| // | |
| // `permitted_breadcrumbs` corresponds to public dependencies only. | |
| // `any_breadcrumbs` corresponds to all dependencies. | |
| // | |
| // Each walk type needs only to be performed once, which is recorded by the | |
| // corresponding completion flag. | |
| class ReachabilityCache { | |
| public: | |
| ReachabilityCache(const Target* source) : source_target_(source) {} | |
| ReachabilityCache(const ReachabilityCache&) = delete; | |
| ReachabilityCache& operator=(const ReachabilityCache&) = delete; | |
| // Returns true if the given `search_for` target is reachable from | |
| // `source_target_`. | |
| // | |
| // If found, the vector given in `chain` will be filled with the reverse | |
| // dependency chain from the destination target to the source target. | |
| // | |
| // If `permitted` is true, only permitted (public) dependency paths are | |
| // searched. | |
| bool SearchForDependencyTo(const Target* search_for, | |
| bool permitted, | |
| Chain* chain); | |
| // Conducts a breadth-first search through the dependency graph to find a | |
| // shortest chain from source_target_. | |
| void PerformDependencyWalk(bool permitted); | |
| const Target* source_target() const { return source_target_; } | |
| private: | |
| // Reconstructs the shortest dependency chain to the given target if it was | |
| // found during a previous walk of the given type. Returns true on success. | |
| bool SearchBreadcrumbs(const Target* search_for, | |
| bool permitted, | |
| Chain* chain) const; | |
| const Target* source_target_; | |
| mutable std::shared_mutex lock_; | |
| // Breadcrumbs for the shortest permitted path. | |
| BreadcrumbTable permitted_breadcrumbs_; | |
| // Breadcrumbs for the shortest path of any type. | |
| BreadcrumbTable any_breadcrumbs_; | |
| std::atomic<bool> permitted_complete_ = false; | |
| std::atomic<bool> any_complete_ = false; | |
| }; | |
| struct TargetInfo { | |
| TargetInfo() : target(nullptr), is_public(false), is_generated(false) {} | |
| TargetInfo(const Target* t, bool is_pub, bool is_gen) | |
| : target(t), is_public(is_pub), is_generated(is_gen) {} | |
| const Target* target; | |
| // True if the file is public in the given target. | |
| bool is_public; | |
| // True if this file is generated and won't actually exist on disk. | |
| bool is_generated; | |
| }; | |
| using TargetVector = std::vector<TargetInfo>; | |
| using FileMap = std::map<SourceFile, TargetVector>; | |
| using PathExistsCallback = std::function<bool(const base::FilePath& path)>; | |
| // Backend for Run() that takes the list of files to check. The errors_ list | |
| // will be populate on failure. | |
| void RunCheckOverFiles(const FileMap& files, | |
| bool force_check, | |
| WorkerPool* pool); | |
| void DoWork(const TargetVector& targets, const SourceFile& file); | |
| // Adds the sources and public files from the given target to the given map. | |
| static void AddTargetToFileMap(const Target* target, FileMap* dest); | |
| // Returns true if the given file is in the output directory. | |
| bool IsFileInOuputDir(const SourceFile& file) const; | |
| // Resolves the contents of an include to a SourceFile. | |
| SourceFile SourceFileForInclude(const IncludeStringWithLocation& include, | |
| const std::vector<SourceDir>& include_dirs, | |
| const InputFile& source_file, | |
| Err* err) const; | |
| // targets is a list of targets using the source file. They will be used in | |
| // error messages. | |
| bool CheckFile(const TargetVector& targets, | |
| const SourceFile& file, | |
| std::vector<Violation>* violations) const; | |
| // Checks that the given file in the given target can include the | |
| // given include file. If disallowed, adds the error or errors to | |
| // the errors array. The range indicates the location of the | |
| // include in the file for error reporting. | |
| void CheckInclude(ReachabilityCache& from_target_cache, | |
| bool is_public_header, | |
| const InputFile& source_file, | |
| const SourceFile& include_file, | |
| const LocationRange& range, | |
| std::vector<Err>* errors) const; | |
| // Returns true if the given search_for target is a dependency of | |
| // the target associated with the given cache. | |
| // | |
| // If found, the vector given in "chain" will be filled with the reverse | |
| // dependency chain from the dest target (chain[0] = search_for) to the src | |
| // target (chain[chain.size() - 1] = search_from). | |
| // | |
| // Chains with permitted dependencies will be considered first. If a | |
| // permitted match is found, *is_permitted will be set to true. A chain with | |
| // indirect, non-public dependencies will only be considered if there are no | |
| // public or direct chains. In this case, *is_permitted will be false. | |
| // | |
| // A permitted dependency is a sequence of public dependencies. The first | |
| // one may be private, since a direct dependency always allows headers to be | |
| // included. | |
| bool IsDependencyOf(const Target* search_for, | |
| ReachabilityCache& from_target_cache, | |
| Chain* chain, | |
| bool* is_permitted) const; | |
| // Makes a very descriptive error message for when an include is disallowed | |
| // from a given from_target, with a missing dependency to one of the given | |
| // targets. | |
| static Err MakeUnreachableError(const InputFile& source_file, | |
| const LocationRange& range, | |
| const Target* from_target, | |
| const TargetVector& targets); | |
| // Non-locked variables ------------------------------------------------------ | |
| // | |
| // These are initialized during construction (which happens on one thread) | |
| // and are not modified after, so any thread can read these without locking. | |
| const BuildSettings* build_settings_; | |
| bool check_generated_; | |
| bool check_system_; | |
| // Maps source files to targets it appears in (usually just one target). | |
| FileMap file_map_; | |
| // Number of tasks posted by RunCheckOverFiles() that haven't completed their | |
| // execution. | |
| base::AtomicRefCount task_count_; | |
| static constexpr size_t kNumShards = 64; | |
| struct DependencyCacheShard { | |
| mutable std::shared_mutex lock; | |
| std::unordered_map<const Target*, std::unique_ptr<ReachabilityCache>> cache; | |
| }; | |
| // Locked variables ---------------------------------------------------------- | |
| // | |
| // These are mutable during runtime and require locking. | |
| mutable std::mutex errors_lock_; | |
| std::vector<Violation> violations_; | |
| mutable std::array<DependencyCacheShard, kNumShards> dependency_cache_; | |
| // Returns the cache for the given target, creating it if it doesn't exist. | |
| ReachabilityCache& GetReachabilityCacheForTarget(const Target* target) const; | |
| // Separate lock for task count synchronization since std::condition_variable | |
| // only works with std::unique_lock<std::mutex>. | |
| std::mutex task_count_lock_; | |
| // Signaled when |task_count_| becomes zero. | |
| std::condition_variable task_count_cv_; | |
| HeaderChecker(const HeaderChecker&) = delete; | |
| HeaderChecker& operator=(const HeaderChecker&) = delete; | |
| }; | |
| #endif // TOOLS_GN_HEADER_CHECKER_H_ |