• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
3 // found in the LICENSE file.
4 
5 #include "tools/gn/header_checker.h"
6 
7 #include <algorithm>
8 
9 #include "base/bind.h"
10 #include "base/files/file_util.h"
11 #include "base/message_loop/message_loop.h"
12 #include "base/strings/string_util.h"
13 #include "base/threading/sequenced_worker_pool.h"
14 #include "tools/gn/build_settings.h"
15 #include "tools/gn/builder.h"
16 #include "tools/gn/c_include_iterator.h"
17 #include "tools/gn/config.h"
18 #include "tools/gn/err.h"
19 #include "tools/gn/filesystem_utils.h"
20 #include "tools/gn/scheduler.h"
21 #include "tools/gn/source_file_type.h"
22 #include "tools/gn/target.h"
23 #include "tools/gn/trace.h"
24 
25 namespace {
26 
27 struct PublicGeneratedPair {
PublicGeneratedPair__anonf25324f70111::PublicGeneratedPair28   PublicGeneratedPair() : is_public(false), is_generated(false) {}
29   bool is_public;
30   bool is_generated;
31 };
32 
33 // If the given file is in the "gen" folder, trims this so it treats the gen
34 // directory as the source root:
35 //   //out/Debug/gen/foo/bar.h -> //foo/bar.h
36 // If the file isn't in the generated root, returns the input unchanged.
RemoveRootGenDirFromFile(const Target * target,const SourceFile & file)37 SourceFile RemoveRootGenDirFromFile(const Target* target,
38                                     const SourceFile& file) {
39   const SourceDir& gen = target->settings()->toolchain_gen_dir();
40   if (!gen.is_null() && StartsWithASCII(file.value(), gen.value(), true))
41     return SourceFile("//" + file.value().substr(gen.value().size()));
42   return file;
43 }
44 
45 // This class makes InputFiles on the stack as it reads files to check. When
46 // we throw an error, the Err indicates a locatin which has a pointer to
47 // an InputFile that must persist as long as the Err does.
48 //
49 // To make this work, this function creates a clone of the InputFile managed
50 // by the InputFileManager so the error can refer to something that
51 // persists. This means that the current file contents will live as long as
52 // the program, but this is OK since we're erroring out anyway.
CreatePersistentRange(const InputFile & input_file,const LocationRange & range)53 LocationRange CreatePersistentRange(const InputFile& input_file,
54                                     const LocationRange& range) {
55   InputFile* clone_input_file;
56   std::vector<Token>* tokens;  // Don't care about this.
57   scoped_ptr<ParseNode>* parse_root;  // Don't care about this.
58 
59   g_scheduler->input_file_manager()->AddDynamicInput(
60       input_file.name(), &clone_input_file, &tokens, &parse_root);
61   clone_input_file->SetContents(input_file.contents());
62 
63   return LocationRange(Location(clone_input_file,
64                                 range.begin().line_number(),
65                                 range.begin().char_offset(),
66                                 -1 /* TODO(scottmg) */),
67                        Location(clone_input_file,
68                                 range.end().line_number(),
69                                 range.end().char_offset(),
70                                 -1 /* TODO(scottmg) */));
71 }
72 
73 // Given a reverse dependency chain where the target chain[0]'s includes are
74 // being used by chain[end] and not all deps are public, returns the string
75 // describing the error.
GetDependencyChainPublicError(const HeaderChecker::Chain & chain)76 std::string GetDependencyChainPublicError(
77     const HeaderChecker::Chain& chain) {
78   std::string ret = "The target:\n  " +
79       chain[chain.size() - 1].target->label().GetUserVisibleName(false) +
80       "\nis including a file from the target:\n  " +
81       chain[0].target->label().GetUserVisibleName(false) +
82       "\n";
83 
84   // Invalid chains should always be 0 (no chain) or more than two
85   // (intermediate private dependencies). 1 and 2 are impossible because a
86   // target can always include headers from itself and its direct dependents.
87   DCHECK(chain.size() != 1 && chain.size() != 2);
88   if (chain.empty()) {
89     ret += "There is no dependency chain between these targets.";
90   } else {
91     // Indirect dependency chain, print the chain.
92     ret += "\nIt's usually best to depend directly on the destination target.\n"
93         "In some cases, the destination target is considered a subcomponent\n"
94         "of an intermediate target. In this case, the intermediate target\n"
95         "should depend publicly on the destination to forward the ability\n"
96         "to include headers.\n"
97         "\n"
98         "Dependency chain (there may also be others):\n";
99 
100     for (int i = static_cast<int>(chain.size()) - 1; i >= 0; i--) {
101       ret.append("  " + chain[i].target->label().GetUserVisibleName(false));
102       if (i != 0) {
103         // Identify private dependencies so the user can see where in the
104         // dependency chain things went bad. Don't list this for the first link
105         // in the chain since direct dependencies are OK, and listing that as
106         // "private" may make people feel like they need to fix it.
107         if (i == static_cast<int>(chain.size()) - 1 || chain[i - 1].is_public)
108           ret.append(" -->");
109         else
110           ret.append(" --[private]-->");
111       }
112       ret.append("\n");
113     }
114   }
115   return ret;
116 }
117 
118 }  // namespace
119 
HeaderChecker(const BuildSettings * build_settings,const std::vector<const Target * > & targets)120 HeaderChecker::HeaderChecker(const BuildSettings* build_settings,
121                              const std::vector<const Target*>& targets)
122     : main_loop_(base::MessageLoop::current()),
123       build_settings_(build_settings) {
124   for (size_t i = 0; i < targets.size(); i++)
125     AddTargetToFileMap(targets[i], &file_map_);
126 }
127 
~HeaderChecker()128 HeaderChecker::~HeaderChecker() {
129 }
130 
Run(const std::vector<const Target * > & to_check,bool force_check,std::vector<Err> * errors)131 bool HeaderChecker::Run(const std::vector<const Target*>& to_check,
132                         bool force_check,
133                         std::vector<Err>* errors) {
134   if (to_check.empty()) {
135     // Check all files.
136     RunCheckOverFiles(file_map_, force_check);
137   } else {
138     // Run only over the files in the given targets.
139     FileMap files_to_check;
140     for (size_t i = 0; i < to_check.size(); i++)
141       AddTargetToFileMap(to_check[i], &files_to_check);
142     RunCheckOverFiles(files_to_check, force_check);
143   }
144 
145   if (errors_.empty())
146     return true;
147   *errors = errors_;
148   return false;
149 }
150 
RunCheckOverFiles(const FileMap & files,bool force_check)151 void HeaderChecker::RunCheckOverFiles(const FileMap& files, bool force_check) {
152   if (files.empty())
153     return;
154 
155   scoped_refptr<base::SequencedWorkerPool> pool(
156       new base::SequencedWorkerPool(16, "HeaderChecker"));
157   for (FileMap::const_iterator file_i = files.begin();
158        file_i != files.end(); ++file_i) {
159     const TargetVector& vect = file_i->second;
160 
161     // Only check C-like source files (RC files also have includes).
162     SourceFileType type = GetSourceFileType(file_i->first);
163     if (type != SOURCE_CC && type != SOURCE_H && type != SOURCE_C &&
164         type != SOURCE_M && type != SOURCE_MM && type != SOURCE_RC)
165       continue;
166 
167     // Do a first pass to find if this should be skipped. All targets including
168     // this source file must exclude it from checking, or any target
169     // must mark it as generated (for cases where one target generates a file,
170     // and another lists it as a source to compile it).
171     if (!force_check) {
172       bool check_includes = false;
173       bool is_generated = false;
174       for (size_t vect_i = 0; vect_i < vect.size(); ++vect_i) {
175         check_includes |= vect[vect_i].target->check_includes();
176         is_generated |= vect[vect_i].is_generated;
177       }
178       if (!check_includes || is_generated)
179         continue;
180     }
181 
182     for (size_t vect_i = 0; vect_i < vect.size(); ++vect_i) {
183       pool->PostWorkerTaskWithShutdownBehavior(
184           FROM_HERE,
185           base::Bind(&HeaderChecker::DoWork, this,
186                      vect[vect_i].target, file_i->first),
187           base::SequencedWorkerPool::BLOCK_SHUTDOWN);
188     }
189   }
190 
191   // After this call we're single-threaded again.
192   pool->Shutdown();
193 }
194 
DoWork(const Target * target,const SourceFile & file)195 void HeaderChecker::DoWork(const Target* target, const SourceFile& file) {
196   Err err;
197   if (!CheckFile(target, file, &err)) {
198     base::AutoLock lock(lock_);
199     errors_.push_back(err);
200   }
201 }
202 
203 // static
AddTargetToFileMap(const Target * target,FileMap * dest)204 void HeaderChecker::AddTargetToFileMap(const Target* target, FileMap* dest) {
205   // Files in the sources have this public bit by default.
206   bool default_public = target->all_headers_public();
207 
208   std::map<SourceFile, PublicGeneratedPair> files_to_public;
209 
210   // First collect the normal files, they get the default visibility. Always
211   // trim the root gen dir if it exists. This will only exist on outputs of an
212   // action, but those are often then wired into the sources of a compiled
213   // target to actually compile generated code. If you depend on the compiled
214   // target, it should be enough to be able to include the header.
215   const Target::FileList& sources = target->sources();
216   for (size_t i = 0; i < sources.size(); i++) {
217     SourceFile file = RemoveRootGenDirFromFile(target, sources[i]);
218     files_to_public[file].is_public = default_public;
219   }
220 
221   // Add in the public files, forcing them to public. This may overwrite some
222   // entries, and it may add new ones.
223   const Target::FileList& public_list = target->public_headers();
224   if (default_public)
225     DCHECK(public_list.empty());  // List only used when default is not public.
226   for (size_t i = 0; i < public_list.size(); i++) {
227     SourceFile file = RemoveRootGenDirFromFile(target, public_list[i]);
228     files_to_public[file].is_public = true;
229   }
230 
231   // Add in outputs from actions. These are treated as public (since if other
232   // targets can't use them, then there wouldn't be any point in outputting).
233   std::vector<SourceFile> outputs;
234   target->action_values().GetOutputsAsSourceFiles(target, &outputs);
235   for (size_t i = 0; i < outputs.size(); i++) {
236     // For generated files in the "gen" directory, add the filename to the
237     // map assuming "gen" is the source root. This means that when files include
238     // the generated header relative to there (the recommended practice), we'll
239     // find the file.
240     SourceFile output_file = RemoveRootGenDirFromFile(target, outputs[i]);
241     PublicGeneratedPair* pair = &files_to_public[output_file];
242     pair->is_public = true;
243     pair->is_generated = true;
244   }
245 
246   // Add the merged list to the master list of all files.
247   for (std::map<SourceFile, PublicGeneratedPair>::const_iterator i =
248            files_to_public.begin();
249        i != files_to_public.end(); ++i) {
250     (*dest)[i->first].push_back(TargetInfo(
251         target, i->second.is_public, i->second.is_generated));
252   }
253 }
254 
IsFileInOuputDir(const SourceFile & file) const255 bool HeaderChecker::IsFileInOuputDir(const SourceFile& file) const {
256   const std::string& build_dir = build_settings_->build_dir().value();
257   return file.value().compare(0, build_dir.size(), build_dir) == 0;
258 }
259 
260 // This current assumes all include paths are relative to the source root
261 // which is generally the case for Chromium.
262 //
263 // A future enhancement would be to search the include path for the target
264 // containing the source file containing this include and find the file to
265 // handle the cases where people do weird things with the paths.
SourceFileForInclude(const base::StringPiece & input) const266 SourceFile HeaderChecker::SourceFileForInclude(
267     const base::StringPiece& input) const {
268   std::string str("//");
269   input.AppendToString(&str);
270   return SourceFile(str);
271 }
272 
CheckFile(const Target * from_target,const SourceFile & file,Err * err) const273 bool HeaderChecker::CheckFile(const Target* from_target,
274                               const SourceFile& file,
275                               Err* err) const {
276   ScopedTrace trace(TraceItem::TRACE_CHECK_HEADER, file.value());
277 
278   // Sometimes you have generated source files included as sources in another
279   // target. These won't exist at checking time. Since we require all generated
280   // files to be somewhere in the output tree, we can just check the name to
281   // see if they should be skipped.
282   if (IsFileInOuputDir(file))
283     return true;
284 
285   base::FilePath path = build_settings_->GetFullPath(file);
286   std::string contents;
287   if (!base::ReadFileToString(path, &contents)) {
288     *err = Err(from_target->defined_from(), "Source file not found.",
289         "The target:\n  " + from_target->label().GetUserVisibleName(false) +
290         "\nhas a source file:\n  " + file.value() +
291         "\nwhich was not found.");
292     return false;
293   }
294 
295   InputFile input_file(file);
296   input_file.SetContents(contents);
297 
298   CIncludeIterator iter(&input_file);
299   base::StringPiece current_include;
300   LocationRange range;
301   while (iter.GetNextIncludeString(&current_include, &range)) {
302     SourceFile include = SourceFileForInclude(current_include);
303     if (!CheckInclude(from_target, input_file, include, range, err))
304       return false;
305   }
306 
307   return true;
308 }
309 
310 // If the file exists:
311 //  - It must be in one or more dependencies of the given target.
312 //  - Those dependencies must have visibility from the source file.
313 //  - The header must be in the public section of those dependeices.
314 //  - Those dependencies must either have no direct dependent configs with
315 //    flags that affect the compiler, or those direct dependent configs apply
316 //    to the "from_target" (it's one "hop" away). This ensures that if the
317 //    include file needs needs compiler settings to compile it, that those
318 //    settings are applied to the file including it.
CheckInclude(const Target * from_target,const InputFile & source_file,const SourceFile & include_file,const LocationRange & range,Err * err) const319 bool HeaderChecker::CheckInclude(const Target* from_target,
320                                  const InputFile& source_file,
321                                  const SourceFile& include_file,
322                                  const LocationRange& range,
323                                  Err* err) const {
324   // Assume if the file isn't declared in our sources that we don't need to
325   // check it. It would be nice if we could give an error if this happens, but
326   // our include finder is too primitive and returns all includes, even if
327   // they're in a #if not executed in the current build. In that case, it's
328   // not unusual for the buildfiles to not specify that header at all.
329   FileMap::const_iterator found = file_map_.find(include_file);
330   if (found == file_map_.end())
331     return true;
332 
333   const TargetVector& targets = found->second;
334   Chain chain;  // Prevent reallocating in the loop.
335 
336   // For all targets containing this file, we require that at least one be
337   // a direct or public dependency of the current target, and that the header
338   // is public within the target.
339   //
340   // If there is more than one target containing this header, we may encounter
341   // some error cases before finding a good one. This error stores the previous
342   // one encountered, which we may or may not throw away.
343   Err last_error;
344 
345   bool found_dependency = false;
346   for (size_t i = 0; i < targets.size(); i++) {
347     // We always allow source files in a target to include headers also in that
348     // target.
349     const Target* to_target = targets[i].target;
350     if (to_target == from_target)
351       return true;
352 
353     bool is_permitted_chain = false;
354     if (IsDependencyOf(to_target, from_target, &chain, &is_permitted_chain)) {
355       DCHECK(chain.size() >= 2);
356       DCHECK(chain[0].target == to_target);
357       DCHECK(chain[chain.size() - 1].target == from_target);
358 
359       found_dependency = true;
360 
361       if (targets[i].is_public && is_permitted_chain) {
362         // This one is OK, we're done.
363         last_error = Err();
364         break;
365       }
366 
367       // Diagnose the error.
368       if (!targets[i].is_public) {
369         // Danger: must call CreatePersistentRange to put in Err.
370         last_error = Err(
371             CreatePersistentRange(source_file, range),
372             "Including a private header.",
373             "This file is private to the target " +
374                 targets[i].target->label().GetUserVisibleName(false));
375       } else if (!is_permitted_chain) {
376         last_error = Err(
377             CreatePersistentRange(source_file, range),
378             "Can't include this header from here.",
379                 GetDependencyChainPublicError(chain));
380       } else {
381         NOTREACHED();
382       }
383     } else if (
384         to_target->allow_circular_includes_from().find(from_target->label()) !=
385         to_target->allow_circular_includes_from().end()) {
386       // Not a dependency, but this include is whitelisted from the destination.
387       found_dependency = true;
388       last_error = Err();
389       break;
390     }
391   }
392 
393   if (!found_dependency) {
394     DCHECK(!last_error.has_error());
395 
396     std::string msg = "It is not in any dependency of " +
397         from_target->label().GetUserVisibleName(false);
398     msg += "\nThe include file is in the target(s):\n";
399     for (size_t i = 0; i < targets.size(); i++)
400       msg += "  " + targets[i].target->label().GetUserVisibleName(false) + "\n";
401     if (targets.size() > 1)
402       msg += "at least one of ";
403     msg += "which should somehow be reachable from " +
404         from_target->label().GetUserVisibleName(false);
405 
406     // Danger: must call CreatePersistentRange to put in Err.
407     *err = Err(CreatePersistentRange(source_file, range),
408                "Include not allowed.", msg);
409     return false;
410   }
411   if (last_error.has_error()) {
412     // Found at least one dependency chain above, but it had an error.
413     *err = last_error;
414     return false;
415   }
416 
417   // One thing we didn't check for is targets that expose their dependents
418   // headers in their own public headers.
419   //
420   // Say we have A -> B -> C. If C has public_configs, everybody getting headers
421   // from C should get the configs also or things could be out-of-sync. Above,
422   // we check for A including C's headers directly, but A could also include a
423   // header from B that in turn includes a header from C.
424   //
425   // There are two ways to solve this:
426   //  - If a public header in B includes C, force B to publicly depend on C.
427   //    This is possible to check, but might be super annoying because most
428   //    targets (especially large leaf-node targets) don't declare
429   //    public/private headers and you'll get lots of false positives.
430   //
431   //  - Save the includes found in each file and actually compute the graph of
432   //    includes to detect when A implicitly includes C's header. This will not
433   //    have the annoying false positive problem, but is complex to write.
434 
435   return true;
436 }
437 
IsDependencyOf(const Target * search_for,const Target * search_from,Chain * chain,bool * is_permitted) const438 bool HeaderChecker::IsDependencyOf(const Target* search_for,
439                                    const Target* search_from,
440                                    Chain* chain,
441                                    bool* is_permitted) const {
442   if (search_for == search_from) {
443     // A target is always visible from itself.
444     *is_permitted = true;
445     return false;
446   }
447 
448   // Find the shortest public dependency chain.
449   if (IsDependencyOf(search_for, search_from, true, chain)) {
450     *is_permitted = true;
451     return true;
452   }
453 
454   // If not, try to find any dependency chain at all.
455   if (IsDependencyOf(search_for, search_from, false, chain)) {
456     *is_permitted = false;
457     return true;
458   }
459 
460   *is_permitted = false;
461   return false;
462 }
463 
IsDependencyOf(const Target * search_for,const Target * search_from,bool require_permitted,Chain * chain) const464 bool HeaderChecker::IsDependencyOf(const Target* search_for,
465                                    const Target* search_from,
466                                    bool require_permitted,
467                                    Chain* chain) const {
468   // This method conducts a breadth-first search through the dependency graph
469   // to find a shortest chain from search_from to search_for.
470   //
471   // work_queue maintains a queue of targets which need to be considered as
472   // part of this chain, in the order they were first traversed.
473   //
474   // Each time a new transitive dependency of search_from is discovered for
475   // the first time, it is added to work_queue and a "breadcrumb" is added,
476   // indicating which target it was reached from when first discovered.
477   //
478   // Once this search finds search_for, the breadcrumbs are used to reconstruct
479   // a shortest dependency chain (in reverse order) from search_from to
480   // search_for.
481 
482   std::map<const Target*, ChainLink> breadcrumbs;
483   std::queue<ChainLink> work_queue;
484   work_queue.push(ChainLink(search_from, true));
485 
486   bool first_time = true;
487   while (!work_queue.empty()) {
488     ChainLink cur_link = work_queue.front();
489     const Target* target = cur_link.target;
490     work_queue.pop();
491 
492     if (target == search_for) {
493       // Found it! Reconstruct the chain.
494       chain->clear();
495       while (target != search_from) {
496         chain->push_back(cur_link);
497         cur_link = breadcrumbs[target];
498         target = cur_link.target;
499       }
500       chain->push_back(ChainLink(search_from, true));
501       return true;
502     }
503 
504     // Always consider public dependencies as possibilities.
505     const LabelTargetVector& public_deps = target->public_deps();
506     for (size_t i = 0; i < public_deps.size(); i++) {
507       if (breadcrumbs.insert(
508               std::make_pair(public_deps[i].ptr, cur_link)).second)
509         work_queue.push(ChainLink(public_deps[i].ptr, true));
510     }
511 
512     if (first_time || !require_permitted) {
513       // Consider all dependencies since all target paths are allowed, so add
514       // in private ones. Also do this the first time through the loop, since
515       // a target can include headers from its direct deps regardless of
516       // public/private-ness.
517       first_time = false;
518       const LabelTargetVector& private_deps = target->private_deps();
519       for (size_t i = 0; i < private_deps.size(); i++) {
520         if (breadcrumbs.insert(
521                 std::make_pair(private_deps[i].ptr, cur_link)).second)
522           work_queue.push(ChainLink(private_deps[i].ptr, false));
523       }
524     }
525   }
526 
527   return false;
528 }
529