• 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/file_util.h"
11 #include "base/message_loop/message_loop.h"
12 #include "base/threading/sequenced_worker_pool.h"
13 #include "tools/gn/build_settings.h"
14 #include "tools/gn/builder.h"
15 #include "tools/gn/c_include_iterator.h"
16 #include "tools/gn/config.h"
17 #include "tools/gn/err.h"
18 #include "tools/gn/filesystem_utils.h"
19 #include "tools/gn/scheduler.h"
20 #include "tools/gn/target.h"
21 #include "tools/gn/trace.h"
22 
23 namespace {
24 
25 // This class makes InputFiles on the stack as it reads files to check. When
26 // we throw an error, the Err indicates a locatin which has a pointer to
27 // an InputFile that must persist as long as the Err does.
28 //
29 // To make this work, this function creates a clone of the InputFile managed
30 // by the InputFileManager so the error can refer to something that
31 // persists. This means that the current file contents will live as long as
32 // the program, but this is OK since we're erroring out anyway.
CreatePersistentRange(const InputFile & input_file,const LocationRange & range)33 LocationRange CreatePersistentRange(const InputFile& input_file,
34                                     const LocationRange& range) {
35   InputFile* clone_input_file;
36   std::vector<Token>* tokens;  // Don't care about this.
37   scoped_ptr<ParseNode>* parse_root;  // Don't care about this.
38 
39   g_scheduler->input_file_manager()->AddDynamicInput(
40       input_file.name(), &clone_input_file, &tokens, &parse_root);
41   clone_input_file->SetContents(input_file.contents());
42 
43   return LocationRange(
44       Location(clone_input_file, range.begin().line_number(),
45                range.begin().char_offset()),
46       Location(clone_input_file, range.end().line_number(),
47                range.end().char_offset()));
48 }
49 
50 // Returns true if the given config could affect how the compiler runs (rather
51 // than being empty or just affecting linker flags).
ConfigHasCompilerSettings(const Config * config)52 bool ConfigHasCompilerSettings(const Config* config) {
53   const ConfigValues& values = config->config_values();
54   return
55       !values.cflags().empty() ||
56       !values.cflags_c().empty() ||
57       !values.cflags_cc().empty() ||
58       !values.cflags_objc().empty() ||
59       !values.cflags_objcc().empty() ||
60       !values.defines().empty() ||
61       !values.include_dirs().empty();
62 }
63 
64 // Returns true if the given target has any direct dependent configs with
65 // compiler settings in it.
HasDirectDependentCompilerSettings(const Target * target)66 bool HasDirectDependentCompilerSettings(const Target* target) {
67   const LabelConfigVector& direct = target->direct_dependent_configs();
68   for (size_t i = 0; i < direct.size(); i++) {
69     if (ConfigHasCompilerSettings(direct[i].ptr))
70       return true;
71   }
72   return false;
73 }
74 
75 // Given a reverse dependency chain where the target chain[0]'s dependent
76 // configs don't apply to chain[end], returns the string describing the error.
77 // The problematic index is the target where the dependent configs were lost.
GetDependentConfigChainError(const std::vector<const Target * > & chain,size_t problematic_index)78 std::string GetDependentConfigChainError(
79     const std::vector<const Target*>& chain,
80     size_t problematic_index) {
81   // Erroneous dependent config chains are always at least three long, since
82   // dependent configs would apply if it was length two.
83   DCHECK(chain.size() >= 3);
84 
85   std::string from_label =
86       chain[chain.size() - 1]->label().GetUserVisibleName(false);
87   std::string to_label =
88       chain[0]->label().GetUserVisibleName(false);
89   std::string problematic_label =
90       chain[problematic_index]->label().GetUserVisibleName(false);
91   std::string problematic_upstream_label =
92       chain[problematic_index - 1]->label().GetUserVisibleName(false);
93 
94   return
95       "You have the dependency tree:  SOURCE -> MID -> DEST\n"
96       "Where a file from:\n"
97       "  SOURCE = " + from_label + "\n"
98       "is including a header from:\n"
99       "  DEST = " + to_label + "\n"
100       "\n"
101       "DEST has direct_dependent_configs, and they don't apply to SOURCE "
102       "because\nSOURCE is more than one hop away. This means that DEST's "
103       "headers might not\nreceive the expected compiler flags.\n"
104       "\n"
105       "To fix this, make SOURCE depend directly on DEST.\n"
106       "\n"
107       "Alternatively, if the target:\n"
108       "  MID = " + problematic_label + "\n"
109       "exposes DEST as part of its public API, you can declare this by "
110       "adding:\n"
111       "  forward_dependent_configs_from = [\n"
112       "    \"" + problematic_upstream_label + "\"\n"
113       "  ]\n"
114       "to MID. This will apply DEST's direct dependent configs to SOURCE.\n";
115 }
116 
117 }  // namespace
118 
HeaderChecker(const BuildSettings * build_settings,const std::vector<const Target * > & targets)119 HeaderChecker::HeaderChecker(const BuildSettings* build_settings,
120                              const std::vector<const Target*>& targets)
121     : main_loop_(base::MessageLoop::current()),
122       build_settings_(build_settings) {
123   for (size_t i = 0; i < targets.size(); i++)
124     AddTargetToFileMap(targets[i]);
125 }
126 
~HeaderChecker()127 HeaderChecker::~HeaderChecker() {
128 }
129 
Run(std::vector<Err> * errors)130 bool HeaderChecker::Run(std::vector<Err>* errors) {
131   ScopedTrace trace(TraceItem::TRACE_CHECK_HEADERS, "Check headers");
132 
133   if (file_map_.empty())
134     return true;
135 
136   scoped_refptr<base::SequencedWorkerPool> pool(
137       new base::SequencedWorkerPool(16, "HeaderChecker"));
138   for (FileMap::const_iterator file_i = file_map_.begin();
139        file_i != file_map_.end(); ++file_i) {
140     const TargetVector& vect = file_i->second;
141 
142     // Only check C-like source files (RC files also have includes).
143     SourceFileType type = GetSourceFileType(file_i->first);
144     if (type != SOURCE_CC && type != SOURCE_H && type != SOURCE_C &&
145         type != SOURCE_M && type != SOURCE_MM && type != SOURCE_RC)
146       continue;
147 
148     for (size_t vect_i = 0; vect_i < vect.size(); ++vect_i) {
149       pool->PostWorkerTaskWithShutdownBehavior(
150           FROM_HERE,
151           base::Bind(&HeaderChecker::DoWork, this,
152                      vect[vect_i].target, file_i->first),
153           base::SequencedWorkerPool::BLOCK_SHUTDOWN);
154     }
155   }
156 
157   // After this call we're single-threaded again.
158   pool->Shutdown();
159 
160   if (errors_.empty())
161     return true;
162   *errors = errors_;
163   return false;
164 }
165 
DoWork(const Target * target,const SourceFile & file)166 void HeaderChecker::DoWork(const Target* target, const SourceFile& file) {
167   Err err;
168   if (!CheckFile(target, file, &err)) {
169     base::AutoLock lock(lock_);
170     errors_.push_back(err);
171   }
172 }
173 
AddTargetToFileMap(const Target * target)174 void HeaderChecker::AddTargetToFileMap(const Target* target) {
175   // Files in the sources have this public bit by default.
176   bool default_public = target->all_headers_public();
177 
178   // First collect the normal files, they get the default visibility.
179   std::map<SourceFile, bool> files_to_public;
180   const Target::FileList& sources = target->sources();
181   for (size_t i = 0; i < sources.size(); i++)
182     files_to_public[sources[i]] = default_public;
183 
184   // Add in the public files, forcing them to public. This may overwrite some
185   // entries, and it may add new ones.
186   const Target::FileList& public_list = target->public_headers();
187   if (default_public)
188     DCHECK(public_list.empty());  // List only used when default is not public.
189   for (size_t i = 0; i < public_list.size(); i++)
190     files_to_public[public_list[i]] = true;
191 
192   // Add the merged list to the master list of all files.
193   for (std::map<SourceFile, bool>::const_iterator i = files_to_public.begin();
194        i != files_to_public.end(); ++i)
195     file_map_[i->first].push_back(TargetInfo(target, i->second));
196 }
197 
IsFileInOuputDir(const SourceFile & file) const198 bool HeaderChecker::IsFileInOuputDir(const SourceFile& file) const {
199   const std::string& build_dir = build_settings_->build_dir().value();
200   return file.value().compare(0, build_dir.size(), build_dir) == 0;
201 }
202 
203 // This current assumes all include paths are relative to the source root
204 // which is generally the case for Chromium.
205 //
206 // A future enhancement would be to search the include path for the target
207 // containing the source file containing this include and find the file to
208 // handle the cases where people do weird things with the paths.
SourceFileForInclude(const base::StringPiece & input) const209 SourceFile HeaderChecker::SourceFileForInclude(
210     const base::StringPiece& input) const {
211   std::string str("//");
212   input.AppendToString(&str);
213   return SourceFile(str);
214 }
215 
CheckFile(const Target * from_target,const SourceFile & file,Err * err) const216 bool HeaderChecker::CheckFile(const Target* from_target,
217                               const SourceFile& file,
218                               Err* err) const {
219   ScopedTrace trace(TraceItem::TRACE_CHECK_HEADER, file.value());
220 
221   // Sometimes you have generated source files included as sources in another
222   // target. These won't exist at checking time. Since we require all generated
223   // files to be somewhere in the output tree, we can just check the name to
224   // see if they should be skipped.
225   if (IsFileInOuputDir(file))
226     return true;
227 
228   base::FilePath path = build_settings_->GetFullPath(file);
229   std::string contents;
230   if (!base::ReadFileToString(path, &contents)) {
231     *err = Err(from_target->defined_from(), "Source file not found.",
232         "This target includes as a source:\n  " + file.value() +
233         "\nwhich was not found.");
234     return false;
235   }
236 
237   InputFile input_file(file);
238   input_file.SetContents(contents);
239 
240   CIncludeIterator iter(&input_file);
241   base::StringPiece current_include;
242   LocationRange range;
243   while (iter.GetNextIncludeString(&current_include, &range)) {
244     SourceFile include = SourceFileForInclude(current_include);
245     if (!CheckInclude(from_target, input_file, include, range, err))
246       return false;
247   }
248 
249   return true;
250 }
251 
252 // If the file exists:
253 //  - It must be in one or more dependencies of the given target.
254 //  - Those dependencies must have visibility from the source file.
255 //  - The header must be in the public section of those dependeices.
256 //  - Those dependencies must either have no direct dependent configs with
257 //    flags that affect the compiler, or those direct dependent configs apply
258 //    to the "from_target" (it's one "hop" away). This ensures that if the
259 //    include file needs needs compiler settings to compile it, that those
260 //    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) const261 bool HeaderChecker::CheckInclude(const Target* from_target,
262                                  const InputFile& source_file,
263                                  const SourceFile& include_file,
264                                  const LocationRange& range,
265                                  Err* err) const {
266   // Assume if the file isn't declared in our sources that we don't need to
267   // check it. It would be nice if we could give an error if this happens, but
268   // our include finder is too primitive and returns all includes, even if
269   // they're in a #if not executed in the current build. In that case, it's
270   // not unusual for the buildfiles to not specify that header at all.
271   FileMap::const_iterator found = file_map_.find(include_file);
272   if (found == file_map_.end())
273     return true;
274 
275   const TargetVector& targets = found->second;
276   std::vector<const Target*> chain;  // Prevent reallocating in the loop.
277 
278   // For all targets containing this file, we require that at least one be
279   // a dependency of the current target, and all targets that are dependencies
280   // must have the file listed in the public section.
281   bool found_dependency = false;
282   for (size_t i = 0; i < targets.size(); i++) {
283     // We always allow source files in a target to include headers also in that
284     // target.
285     const Target* to_target = targets[i].target;
286     if (to_target == from_target)
287       return true;
288 
289     if (IsDependencyOf(to_target, from_target, &chain)) {
290       DCHECK(chain.size() >= 2);
291       DCHECK(chain[0] == to_target);
292       DCHECK(chain[chain.size() - 1] == from_target);
293 
294       // The include is in a target that's a proper dependency. Verify that
295       // the including target has visibility.
296       if (!to_target->visibility().CanSeeMe(from_target->label())) {
297         std::string msg = "The included file is in " +
298             to_target->label().GetUserVisibleName(false) +
299             "\nwhich is not visible from " +
300             from_target->label().GetUserVisibleName(false) +
301             "\n(see \"gn help visibility\").";
302 
303         // Danger: must call CreatePersistentRange to put in Err.
304         *err = Err(CreatePersistentRange(source_file, range),
305             "Including a header from non-visible target.", msg);
306         return false;
307       }
308 
309       // The file must be public in the target.
310       if (!targets[i].is_public) {
311         // Danger: must call CreatePersistentRange to put in Err.
312         *err = Err(CreatePersistentRange(source_file, range),
313                    "Including a private header.",
314                    "This file is private to the target " +
315                        targets[i].target->label().GetUserVisibleName(false));
316         return false;
317       }
318 
319       // If the to_target has direct_dependent_configs, they must apply to the
320       // from_target.
321       if (HasDirectDependentCompilerSettings(to_target)) {
322         size_t problematic_index;
323         if (!DoDirectDependentConfigsApply(chain, &problematic_index)) {
324           *err = Err(CreatePersistentRange(source_file, range),
325                      "Can't include this header from here.",
326                      GetDependentConfigChainError(chain, problematic_index));
327           return false;
328         }
329       }
330 
331       found_dependency = true;
332     }
333   }
334 
335   if (!found_dependency) {
336     std::string msg = "It is not in any dependency of " +
337         from_target->label().GetUserVisibleName(false);
338     msg += "\nThe include file is in the target(s):\n";
339     for (size_t i = 0; i < targets.size(); i++)
340       msg += "  " + targets[i].target->label().GetUserVisibleName(false) + "\n";
341     if (targets.size() > 1)
342       msg += "at least one of ";
343     msg += "which should somehow be reachable from " +
344         from_target->label().GetUserVisibleName(false);
345 
346     // Danger: must call CreatePersistentRange to put in Err.
347     *err = Err(CreatePersistentRange(source_file, range),
348                "Include not allowed.", msg);
349     return false;
350   }
351 
352   // One thing we didn't check for is targets that expose their dependents
353   // headers in their own public headers.
354   //
355   // Say we have A -> B -> C. If C has direct_dependent_configs, everybody
356   // getting headers from C should get the configs also or things could be
357   // out-of-sync. Above, we check for A including C's headers directly, but A
358   // could also include a header from B that in turn includes a header from C.
359   //
360   // There are two ways to solve this:
361   //  - If a public header in B includes C, force B to forward C's direct
362   //    dependent configs. This is possible to check, but might be super
363   //    annoying because most targets (especially large leaf-node targets)
364   //    don't declare public/private headers and you'll get lots of false
365   //    positives.
366   //
367   //  - Save the includes found in each file and actually compute the graph of
368   //    includes to detect when A implicitly includes C's header. This will not
369   //    have the annoying false positive problem, but is complex to write.
370 
371   return true;
372 }
373 
IsDependencyOf(const Target * search_for,const Target * search_from,std::vector<const Target * > * chain) const374 bool HeaderChecker::IsDependencyOf(const Target* search_for,
375                                    const Target* search_from,
376                                    std::vector<const Target*>* chain) const {
377   std::set<const Target*> checked;
378   return IsDependencyOf(search_for, search_from, chain, &checked);
379 }
380 
IsDependencyOf(const Target * search_for,const Target * search_from,std::vector<const Target * > * chain,std::set<const Target * > * checked) const381 bool HeaderChecker::IsDependencyOf(const Target* search_for,
382                                    const Target* search_from,
383                                    std::vector<const Target*>* chain,
384                                    std::set<const Target*>* checked) const {
385   if (checked->find(search_for) != checked->end())
386     return false;  // Already checked this subtree.
387 
388   const LabelTargetVector& deps = search_from->deps();
389   for (size_t i = 0; i < deps.size(); i++) {
390     if (deps[i].ptr == search_for) {
391       // Found it.
392       chain->clear();
393       chain->push_back(deps[i].ptr);
394       chain->push_back(search_from);
395       return true;
396     }
397 
398     // Recursive search.
399     checked->insert(deps[i].ptr);
400     if (IsDependencyOf(search_for, deps[i].ptr, chain, checked)) {
401       chain->push_back(search_from);
402       return true;
403     }
404   }
405 
406   return false;
407 }
408 
409 // static
DoDirectDependentConfigsApply(const std::vector<const Target * > & chain,size_t * problematic_index)410 bool HeaderChecker::DoDirectDependentConfigsApply(
411     const std::vector<const Target*>& chain,
412     size_t* problematic_index) {
413   // Direct dependent configs go up the chain one level with the following
414   // exceptions:
415   // - Skip over groups
416   // - Skip anything that explicitly forwards it
417 
418   // All chains should be at least two (or it wouldn't be a chain).
419   DCHECK(chain.size() >= 2);
420 
421   // A chain of length 2 is always OK as far as direct dependent configs are
422   // concerned since the targets are direct dependents.
423   if (chain.size() == 2)
424     return true;
425 
426   // Check the middle configs to make sure they're either groups or configs
427   // are forwarded.
428   for (size_t i = 1; i < chain.size() - 1; i++) {
429     if (chain[i]->output_type() == Target::GROUP)
430       continue;  // This one is OK, skip to next one.
431 
432     // The forward list on this target should have contained in it the target
433     // at the next lower level.
434     const LabelTargetVector& forwarded = chain[i]->forward_dependent_configs();
435     if (std::find_if(forwarded.begin(), forwarded.end(),
436                      LabelPtrPtrEquals<Target>(chain[i - 1])) ==
437         forwarded.end()) {
438       *problematic_index = i;
439       return false;
440     }
441   }
442 
443   return true;
444 }
445