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(¤t_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