• 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 "gn/header_checker.h"
6 
7 #include <algorithm>
8 
9 #include "base/containers/queue.h"
10 #include "base/files/file_util.h"
11 #include "base/strings/string_util.h"
12 #include "gn/build_settings.h"
13 #include "gn/builder.h"
14 #include "gn/c_include_iterator.h"
15 #include "gn/config.h"
16 #include "gn/config_values_extractors.h"
17 #include "gn/err.h"
18 #include "gn/filesystem_utils.h"
19 #include "gn/scheduler.h"
20 #include "gn/swift_values.h"
21 #include "gn/target.h"
22 #include "gn/trace.h"
23 #include "util/worker_pool.h"
24 
25 namespace {
26 
27 struct PublicGeneratedPair {
PublicGeneratedPair__anona8f4ff940111::PublicGeneratedPair28   PublicGeneratedPair() : is_public(false), is_generated(false) {}
29   bool is_public;
30   bool is_generated;
31 };
32 
33 // This class makes InputFiles on the stack as it reads files to check. When
34 // we throw an error, the Err indicates a locatin which has a pointer to
35 // an InputFile that must persist as long as the Err does.
36 //
37 // To make this work, this function creates a clone of the InputFile managed
38 // by the InputFileManager so the error can refer to something that
39 // persists. This means that the current file contents will live as long as
40 // the program, but this is OK since we're erroring out anyway.
CreatePersistentRange(const InputFile & input_file,const LocationRange & range)41 LocationRange CreatePersistentRange(const InputFile& input_file,
42                                     const LocationRange& range) {
43   InputFile* clone_input_file;
44   std::vector<Token>* tokens;              // Don't care about this.
45   std::unique_ptr<ParseNode>* parse_root;  // Don't care about this.
46 
47   g_scheduler->input_file_manager()->AddDynamicInput(
48       input_file.name(), &clone_input_file, &tokens, &parse_root);
49   clone_input_file->SetContents(input_file.contents());
50 
51   return LocationRange(Location(clone_input_file, range.begin().line_number(),
52                                 range.begin().column_number()),
53                        Location(clone_input_file, range.end().line_number(),
54                                 range.end().column_number()));
55 }
56 
57 // Given a reverse dependency chain where the target chain[0]'s includes are
58 // being used by chain[end] and not all deps are public, returns the string
59 // describing the error.
GetDependencyChainPublicError(const HeaderChecker::Chain & chain)60 std::string GetDependencyChainPublicError(const HeaderChecker::Chain& chain) {
61   std::string ret =
62       "The target:\n  " +
63       chain[chain.size() - 1].target->label().GetUserVisibleName(false) +
64       "\nis including a file from the target:\n  " +
65       chain[0].target->label().GetUserVisibleName(false) + "\n";
66 
67   // Invalid chains should always be 0 (no chain) or more than two
68   // (intermediate private dependencies). 1 and 2 are impossible because a
69   // target can always include headers from itself and its direct dependents.
70   DCHECK(chain.size() != 1 && chain.size() != 2);
71   if (chain.empty()) {
72     ret += "There is no dependency chain between these targets.";
73   } else {
74     // Indirect dependency chain, print the chain.
75     ret +=
76         "\nIt's usually best to depend directly on the destination target.\n"
77         "In some cases, the destination target is considered a subcomponent\n"
78         "of an intermediate target. In this case, the intermediate target\n"
79         "should depend publicly on the destination to forward the ability\n"
80         "to include headers.\n"
81         "\n"
82         "Dependency chain (there may also be others):\n";
83 
84     for (int i = static_cast<int>(chain.size()) - 1; i >= 0; i--) {
85       ret.append("  " + chain[i].target->label().GetUserVisibleName(false));
86       if (i != 0) {
87         // Identify private dependencies so the user can see where in the
88         // dependency chain things went bad. Don't list this for the first link
89         // in the chain since direct dependencies are OK, and listing that as
90         // "private" may make people feel like they need to fix it.
91         if (i == static_cast<int>(chain.size()) - 1 || chain[i - 1].is_public)
92           ret.append(" -->");
93         else
94           ret.append(" --[private]-->");
95       }
96       ret.append("\n");
97     }
98   }
99   return ret;
100 }
101 
102 // Returns true if the two targets have the same label not counting the
103 // toolchain.
TargetLabelsMatchExceptToolchain(const Target * a,const Target * b)104 bool TargetLabelsMatchExceptToolchain(const Target* a, const Target* b) {
105   return a->label().dir() == b->label().dir() &&
106          a->label().name() == b->label().name();
107 }
108 
109 // Returns true if the target |annotation_on| includes a friend annotation
110 // that allows |is_marked_friend| as a friend.
FriendMatches(const Target * annotation_on,const Target * is_marked_friend)111 bool FriendMatches(const Target* annotation_on,
112                    const Target* is_marked_friend) {
113   return LabelPattern::VectorMatches(annotation_on->friends(),
114                                      is_marked_friend->label());
115 }
116 
117 }  // namespace
118 
HeaderChecker(const BuildSettings * build_settings,const std::vector<const Target * > & targets,bool check_generated,bool check_system)119 HeaderChecker::HeaderChecker(const BuildSettings* build_settings,
120                              const std::vector<const Target*>& targets,
121                              bool check_generated,
122                              bool check_system)
123     : build_settings_(build_settings),
124       check_generated_(check_generated),
125       check_system_(check_system),
126       lock_(),
127       task_count_cv_() {
128   for (auto* target : targets)
129     AddTargetToFileMap(target, &file_map_);
130 }
131 
132 HeaderChecker::~HeaderChecker() = default;
133 
Run(const std::vector<const Target * > & to_check,bool force_check,std::vector<Err> * errors)134 bool HeaderChecker::Run(const std::vector<const Target*>& to_check,
135                         bool force_check,
136                         std::vector<Err>* errors) {
137   FileMap files_to_check;
138   for (auto* check : to_check) {
139     // This function will get called with all target types, but check only
140     // applies to binary targets.
141     if (check->IsBinary())
142       AddTargetToFileMap(check, &files_to_check);
143   }
144   RunCheckOverFiles(files_to_check, force_check);
145 
146   if (errors_.empty())
147     return true;
148   *errors = errors_;
149   return false;
150 }
151 
RunCheckOverFiles(const FileMap & files,bool force_check)152 void HeaderChecker::RunCheckOverFiles(const FileMap& files, bool force_check) {
153   WorkerPool pool;
154 
155   for (const auto& file : files) {
156     // Only check C-like source files (RC files also have includes).
157     const SourceFile::Type type = file.first.GetType();
158     if (type != SourceFile::SOURCE_CPP && type != SourceFile::SOURCE_H &&
159         type != SourceFile::SOURCE_C && type != SourceFile::SOURCE_M &&
160         type != SourceFile::SOURCE_MM && type != SourceFile::SOURCE_RC)
161       continue;
162 
163     if (!check_generated_) {
164       // If any target marks it as generated, don't check it. We have to check
165       // file_map_, which includes all known files; files only includes those
166       // being checked.
167       bool is_generated = false;
168       for (const auto& vect_i : file_map_[file.first])
169         is_generated |= vect_i.is_generated;
170       if (is_generated)
171         continue;
172     }
173 
174     for (const auto& vect_i : file.second) {
175       if (vect_i.target->check_includes()) {
176         task_count_.Increment();
177         pool.PostTask([this, target = vect_i.target, file = file.first]() {
178           DoWork(target, file);
179         });
180       }
181     }
182   }
183 
184   // Wait for all tasks posted by this method to complete.
185   std::unique_lock<std::mutex> auto_lock(lock_);
186   while (!task_count_.IsZero())
187     task_count_cv_.wait(auto_lock);
188 }
189 
DoWork(const Target * target,const SourceFile & file)190 void HeaderChecker::DoWork(const Target* target, const SourceFile& file) {
191   std::vector<Err> errors;
192   if (!CheckFile(target, file, &errors)) {
193     std::lock_guard<std::mutex> lock(lock_);
194     errors_.insert(errors_.end(), errors.begin(), errors.end());
195   }
196 
197   if (!task_count_.Decrement()) {
198     // Signal |task_count_cv_| when |task_count_| becomes zero.
199     std::unique_lock<std::mutex> auto_lock(lock_);
200     task_count_cv_.notify_one();
201   }
202 }
203 
204 // static
AddTargetToFileMap(const Target * target,FileMap * dest)205 void HeaderChecker::AddTargetToFileMap(const Target* target, FileMap* dest) {
206   // Files in the sources have this public bit by default.
207   bool default_public = target->all_headers_public();
208 
209   std::map<SourceFile, PublicGeneratedPair> files_to_public;
210 
211   // First collect the normal files, they get the default visibility. If you
212   // depend on the compiled target, it should be enough to be able to include
213   // the header.
214   for (const auto& source : target->sources()) {
215     files_to_public[source].is_public = default_public;
216   }
217 
218   // Add in the public files, forcing them to public. This may overwrite some
219   // entries, and it may add new ones.
220   if (default_public)  // List only used when default is not public.
221     DCHECK(target->public_headers().empty());
222   for (const auto& source : target->public_headers()) {
223     files_to_public[source].is_public = true;
224   }
225 
226   // If target generates a swiftmodule, then
227   //  - it may use a bridge header which has default visibility
228   //  - it may generate public header which must be considered public
229   if (target->builds_swift_module()) {
230     const SourceFile& bridge_header = target->swift_values().bridge_header();
231     if (!bridge_header.is_null()) {
232       files_to_public[bridge_header].is_public = default_public;
233     }
234 
235     std::vector<SourceFile> outputs;
236     target->swift_values().GetOutputsAsSourceFiles(target, &outputs);
237 
238     for (const SourceFile& output : outputs) {
239       if (output.GetType() == SourceFile::SOURCE_H) {
240         PublicGeneratedPair* pair = &files_to_public[output];
241         pair->is_public = true;
242         pair->is_generated = true;
243       }
244     }
245   }
246 
247   // Add in outputs from actions. These are treated as public (since if other
248   // targets can't use them, then there wouldn't be any point in outputting).
249   std::vector<SourceFile> outputs;
250   target->action_values().GetOutputsAsSourceFiles(target, &outputs);
251   for (const auto& output : outputs) {
252     PublicGeneratedPair* pair = &files_to_public[output];
253     pair->is_public = true;
254     pair->is_generated = true;
255   }
256 
257   // Add the merged list to the master list of all files.
258   for (const auto& cur : files_to_public) {
259     (*dest)[cur.first].push_back(
260         TargetInfo(target, cur.second.is_public, cur.second.is_generated));
261   }
262 }
263 
IsFileInOuputDir(const SourceFile & file) const264 bool HeaderChecker::IsFileInOuputDir(const SourceFile& file) const {
265   const std::string& build_dir = build_settings_->build_dir().value();
266   return file.value().compare(0, build_dir.size(), build_dir) == 0;
267 }
268 
SourceFileForInclude(const IncludeStringWithLocation & include,const std::vector<SourceDir> & include_dirs,const InputFile & source_file,Err * err) const269 SourceFile HeaderChecker::SourceFileForInclude(
270     const IncludeStringWithLocation& include,
271     const std::vector<SourceDir>& include_dirs,
272     const InputFile& source_file,
273     Err* err) const {
274   using base::FilePath;
275 
276   Value relative_file_value(nullptr, std::string(include.contents));
277 
278   auto find_predicate = [relative_file_value, err,
279                          this](const SourceDir& dir) -> bool {
280     SourceFile include_file = dir.ResolveRelativeFile(relative_file_value, err);
281     return file_map_.find(include_file) != file_map_.end();
282   };
283   if (!include.system_style_include) {
284     const SourceDir& file_dir = source_file.dir();
285     if (find_predicate(file_dir)) {
286       return file_dir.ResolveRelativeFile(relative_file_value, err);
287     }
288   }
289 
290   auto it =
291       std::find_if(include_dirs.begin(), include_dirs.end(), find_predicate);
292 
293   if (it != include_dirs.end())
294     return it->ResolveRelativeFile(relative_file_value, err);
295 
296   return SourceFile();
297 }
298 
CheckFile(const Target * from_target,const SourceFile & file,std::vector<Err> * errors) const299 bool HeaderChecker::CheckFile(const Target* from_target,
300                               const SourceFile& file,
301                               std::vector<Err>* errors) const {
302   ScopedTrace trace(TraceItem::TRACE_CHECK_HEADER, file.value());
303 
304   // Sometimes you have generated source files included as sources in another
305   // target. These won't exist at checking time. Since we require all generated
306   // files to be somewhere in the output tree, we can just check the name to
307   // see if they should be skipped.
308   if (!check_generated_ && IsFileInOuputDir(file))
309     return true;
310 
311   base::FilePath path = build_settings_->GetFullPath(file);
312   std::string contents;
313   if (!base::ReadFileToString(path, &contents)) {
314     // A missing (not yet) generated file is an acceptable problem
315     // considering this code does not understand conditional includes.
316     if (IsFileInOuputDir(file))
317       return true;
318 
319     errors->emplace_back(from_target->defined_from(), "Source file not found.",
320                          "The target:\n  " +
321                              from_target->label().GetUserVisibleName(false) +
322                              "\nhas a source file:\n  " + file.value() +
323                              "\nwhich was not found.");
324     return false;
325   }
326 
327   InputFile input_file(file);
328   input_file.SetContents(contents);
329 
330   std::vector<SourceDir> include_dirs;
331   for (ConfigValuesIterator iter(from_target); !iter.done(); iter.Next()) {
332     const std::vector<SourceDir>& target_include_dirs =
333         iter.cur().include_dirs();
334     include_dirs.insert(include_dirs.end(), target_include_dirs.begin(),
335                         target_include_dirs.end());
336   }
337 
338   size_t error_count_before = errors->size();
339   CIncludeIterator iter(&input_file);
340 
341   IncludeStringWithLocation include;
342 
343   std::set<std::pair<const Target*, const Target*>> no_dependency_cache;
344 
345   while (iter.GetNextIncludeString(&include)) {
346     if (include.system_style_include && !check_system_)
347       continue;
348 
349     Err err;
350     SourceFile included_file =
351         SourceFileForInclude(include, include_dirs, input_file, &err);
352     if (!included_file.is_null()) {
353       CheckInclude(from_target, input_file, included_file, include.location,
354                    &no_dependency_cache, errors);
355     }
356   }
357 
358   return errors->size() == error_count_before;
359 }
360 
361 // If the file exists:
362 //  - The header must be in the public section of a target, or it must
363 //    be in the sources with no public list (everything is implicitly public).
364 //  - The dependency path to the included target must follow only public_deps.
365 //  - If there are multiple targets with the header in it, only one need be
366 //    valid for the check to pass.
CheckInclude(const Target * from_target,const InputFile & source_file,const SourceFile & include_file,const LocationRange & range,std::set<std::pair<const Target *,const Target * >> * no_dependency_cache,std::vector<Err> * errors) const367 void HeaderChecker::CheckInclude(
368     const Target* from_target,
369     const InputFile& source_file,
370     const SourceFile& include_file,
371     const LocationRange& range,
372     std::set<std::pair<const Target*, const Target*>>* no_dependency_cache,
373     std::vector<Err>* errors) const {
374   // Assume if the file isn't declared in our sources that we don't need to
375   // check it. It would be nice if we could give an error if this happens, but
376   // our include finder is too primitive and returns all includes, even if
377   // they're in a #if not executed in the current build. In that case, it's
378   // not unusual for the buildfiles to not specify that header at all.
379   FileMap::const_iterator found = file_map_.find(include_file);
380   if (found == file_map_.end())
381     return;
382 
383   const TargetVector& targets = found->second;
384   Chain chain;  // Prevent reallocating in the loop.
385 
386   // If the file is unknown in the current toolchain (rather than being private
387   // or in a target not visible to the current target), ignore it. This is a
388   // bit of a hack to account for the fact that the include finder doesn't
389   // understand the preprocessor.
390   //
391   // When not cross-compiling, if a platform specific header is conditionally
392   // included in the build, and preprocessor conditions around #includes of
393   // that match the build conditions, everything will be OK because the file
394   // won't be known to GN even though the #include finder identified the file.
395   //
396   // Cross-compiling breaks this. When compiling Android on Linux, for example,
397   // we might see both Linux and Android definitions of a target and know
398   // about the union of all headers in the build. Since the #include finder
399   // ignores preprocessor, we will find the Linux headers in the Android
400   // build and note that a dependency from the Android target to the Linux
401   // one is missing (these might even be the same target in different
402   // toolchains!).
403   bool present_in_current_toolchain = false;
404   for (const auto& target : targets) {
405     if (from_target->label().ToolchainsEqual(target.target->label())) {
406       present_in_current_toolchain = true;
407       break;
408     }
409   }
410   if (!present_in_current_toolchain)
411     return;
412 
413   // For all targets containing this file, we require that at least one be
414   // a direct or public dependency of the current target, and either (1) the
415   // header is public within the target, or (2) there is a friend definition
416   // whitelisting the includor.
417   //
418   // If there is more than one target containing this header, we may encounter
419   // some error cases before finding a good one. This error stores the previous
420   // one encountered, which we may or may not throw away.
421   Err last_error;
422 
423   bool found_dependency = false;
424   for (const auto& target : targets) {
425     // We always allow source files in a target to include headers also in that
426     // target.
427     const Target* to_target = target.target;
428     if (to_target == from_target)
429       return;
430 
431     bool is_permitted_chain = false;
432 
433     bool cached_no_dependency =
434         no_dependency_cache->find(std::make_pair(to_target, from_target)) !=
435         no_dependency_cache->end();
436 
437     bool add_to_cache = !cached_no_dependency;
438 
439     if (!cached_no_dependency &&
440         IsDependencyOf(to_target, from_target, &chain, &is_permitted_chain)) {
441       add_to_cache = false;
442 
443       DCHECK(chain.size() >= 2);
444       DCHECK(chain[0].target == to_target);
445       DCHECK(chain[chain.size() - 1].target == from_target);
446 
447       found_dependency = true;
448 
449       bool effectively_public =
450           target.is_public || FriendMatches(to_target, from_target);
451 
452       if (effectively_public && is_permitted_chain) {
453         // This one is OK, we're done.
454         last_error = Err();
455         break;
456       }
457 
458       // Diagnose the error.
459       if (!effectively_public) {
460         // Danger: must call CreatePersistentRange to put in Err.
461         last_error = Err(CreatePersistentRange(source_file, range),
462                          "Including a private header.",
463                          "This file is private to the target " +
464                              target.target->label().GetUserVisibleName(false));
465       } else if (!is_permitted_chain) {
466         last_error = Err(CreatePersistentRange(source_file, range),
467                          "Can't include this header from here.",
468                          GetDependencyChainPublicError(chain));
469       } else {
470         NOTREACHED();
471       }
472     } else if (to_target->allow_circular_includes_from().find(
473                    from_target->label()) !=
474                to_target->allow_circular_includes_from().end()) {
475       // Not a dependency, but this include is whitelisted from the destination.
476       found_dependency = true;
477       last_error = Err();
478       break;
479     }
480 
481     if (add_to_cache) {
482       no_dependency_cache->emplace(to_target, from_target);
483     }
484   }
485 
486   if (!found_dependency || last_error.has_error()) {
487     if (!found_dependency) {
488       DCHECK(!last_error.has_error());
489       Err err = MakeUnreachableError(source_file, range, from_target, targets);
490       errors->push_back(std::move(err));
491     } else {
492       // Found at least one dependency chain above, but it had an error.
493       errors->push_back(std::move(last_error));
494     }
495     return;
496   }
497 
498   // One thing we didn't check for is targets that expose their dependents
499   // headers in their own public headers.
500   //
501   // Say we have A -> B -> C. If C has public_configs, everybody getting headers
502   // from C should get the configs also or things could be out-of-sync. Above,
503   // we check for A including C's headers directly, but A could also include a
504   // header from B that in turn includes a header from C.
505   //
506   // There are two ways to solve this:
507   //  - If a public header in B includes C, force B to publicly depend on C.
508   //    This is possible to check, but might be super annoying because most
509   //    targets (especially large leaf-node targets) don't declare
510   //    public/private headers and you'll get lots of false positives.
511   //
512   //  - Save the includes found in each file and actually compute the graph of
513   //    includes to detect when A implicitly includes C's header. This will not
514   //    have the annoying false positive problem, but is complex to write.
515 }
516 
IsDependencyOf(const Target * search_for,const Target * search_from,Chain * chain,bool * is_permitted) const517 bool HeaderChecker::IsDependencyOf(const Target* search_for,
518                                    const Target* search_from,
519                                    Chain* chain,
520                                    bool* is_permitted) const {
521   if (search_for == search_from) {
522     // A target is always visible from itself.
523     *is_permitted = true;
524     return false;
525   }
526 
527   // Find the shortest public dependency chain.
528   if (IsDependencyOf(search_for, search_from, true, chain)) {
529     *is_permitted = true;
530     return true;
531   }
532 
533   // If not, try to find any dependency chain at all.
534   if (IsDependencyOf(search_for, search_from, false, chain)) {
535     *is_permitted = false;
536     return true;
537   }
538 
539   *is_permitted = false;
540   return false;
541 }
542 
IsDependencyOf(const Target * search_for,const Target * search_from,bool require_permitted,Chain * chain) const543 bool HeaderChecker::IsDependencyOf(const Target* search_for,
544                                    const Target* search_from,
545                                    bool require_permitted,
546                                    Chain* chain) const {
547   // This method conducts a breadth-first search through the dependency graph
548   // to find a shortest chain from search_from to search_for.
549   //
550   // work_queue maintains a queue of targets which need to be considered as
551   // part of this chain, in the order they were first traversed.
552   //
553   // Each time a new transitive dependency of search_from is discovered for
554   // the first time, it is added to work_queue and a "breadcrumb" is added,
555   // indicating which target it was reached from when first discovered.
556   //
557   // Once this search finds search_for, the breadcrumbs are used to reconstruct
558   // a shortest dependency chain (in reverse order) from search_from to
559   // search_for.
560 
561   std::map<const Target*, ChainLink> breadcrumbs;
562   base::queue<ChainLink> work_queue;
563   work_queue.push(ChainLink(search_from, true));
564 
565   bool first_time = true;
566   while (!work_queue.empty()) {
567     ChainLink cur_link = work_queue.front();
568     const Target* target = cur_link.target;
569     work_queue.pop();
570 
571     if (target == search_for) {
572       // Found it! Reconstruct the chain.
573       chain->clear();
574       while (target != search_from) {
575         chain->push_back(cur_link);
576         cur_link = breadcrumbs[target];
577         target = cur_link.target;
578       }
579       chain->push_back(ChainLink(search_from, true));
580       return true;
581     }
582 
583     // Always consider public dependencies as possibilities.
584     for (const auto& dep : target->public_deps()) {
585       if (breadcrumbs.insert(std::make_pair(dep.ptr, cur_link)).second)
586         work_queue.push(ChainLink(dep.ptr, true));
587     }
588 
589     if (first_time || !require_permitted) {
590       // Consider all dependencies since all target paths are allowed, so add
591       // in private ones. Also do this the first time through the loop, since
592       // a target can include headers from its direct deps regardless of
593       // public/private-ness.
594       first_time = false;
595       for (const auto& dep : target->private_deps()) {
596         if (breadcrumbs.insert(std::make_pair(dep.ptr, cur_link)).second)
597           work_queue.push(ChainLink(dep.ptr, false));
598       }
599     }
600   }
601 
602   return false;
603 }
604 
MakeUnreachableError(const InputFile & source_file,const LocationRange & range,const Target * from_target,const TargetVector & targets)605 Err HeaderChecker::MakeUnreachableError(const InputFile& source_file,
606                                         const LocationRange& range,
607                                         const Target* from_target,
608                                         const TargetVector& targets) {
609   // Normally the toolchains will all match, but when cross-compiling, we can
610   // get targets with more than one toolchain in the list of possibilities.
611   std::vector<const Target*> targets_with_matching_toolchains;
612   std::vector<const Target*> targets_with_other_toolchains;
613   for (const TargetInfo& candidate : targets) {
614     if (candidate.target->toolchain() == from_target->toolchain())
615       targets_with_matching_toolchains.push_back(candidate.target);
616     else
617       targets_with_other_toolchains.push_back(candidate.target);
618   }
619 
620   // It's common when cross-compiling to have a target with the same file in
621   // more than one toolchain. We could output all of them, but this is
622   // generally confusing to people (most end-users won't understand toolchains
623   // well).
624   //
625   // So delete any candidates in other toolchains that also appear in the same
626   // toolchain as the from_target.
627   for (int other_index = 0;
628        other_index < static_cast<int>(targets_with_other_toolchains.size());
629        other_index++) {
630     for (const Target* cur_matching : targets_with_matching_toolchains) {
631       if (TargetLabelsMatchExceptToolchain(
632               cur_matching, targets_with_other_toolchains[other_index])) {
633         // Found a duplicate, erase it.
634         targets_with_other_toolchains.erase(
635             targets_with_other_toolchains.begin() + other_index);
636         other_index--;
637         break;
638       }
639     }
640   }
641 
642   // Only display toolchains on labels if they don't all match.
643   bool include_toolchain = !targets_with_other_toolchains.empty();
644 
645   std::string msg = "It is not in any dependency of\n  " +
646                     from_target->label().GetUserVisibleName(include_toolchain);
647   msg += "\nThe include file is in the target(s):\n";
648   for (auto* target : targets_with_matching_toolchains)
649     msg += "  " + target->label().GetUserVisibleName(include_toolchain) + "\n";
650   for (auto* target : targets_with_other_toolchains)
651     msg += "  " + target->label().GetUserVisibleName(include_toolchain) + "\n";
652   if (targets_with_other_toolchains.size() +
653           targets_with_matching_toolchains.size() >
654       1)
655     msg += "at least one of ";
656   msg += "which should somehow be reachable.";
657 
658   // Danger: must call CreatePersistentRange to put in Err.
659   return Err(CreatePersistentRange(source_file, range), "Include not allowed.",
660              msg);
661 }
662