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/target.h"
21 #include "gn/trace.h"
22 #include "util/worker_pool.h"
23
24 namespace {
25
26 struct PublicGeneratedPair {
PublicGeneratedPair__anon42ac35f70111::PublicGeneratedPair27 PublicGeneratedPair() : is_public(false), is_generated(false) {}
28 bool is_public;
29 bool is_generated;
30 };
31
32 // This class makes InputFiles on the stack as it reads files to check. When
33 // we throw an error, the Err indicates a locatin which has a pointer to
34 // an InputFile that must persist as long as the Err does.
35 //
36 // To make this work, this function creates a clone of the InputFile managed
37 // by the InputFileManager so the error can refer to something that
38 // persists. This means that the current file contents will live as long as
39 // the program, but this is OK since we're erroring out anyway.
CreatePersistentRange(const InputFile & input_file,const LocationRange & range)40 LocationRange CreatePersistentRange(const InputFile& input_file,
41 const LocationRange& range) {
42 InputFile* clone_input_file;
43 std::vector<Token>* tokens; // Don't care about this.
44 std::unique_ptr<ParseNode>* parse_root; // Don't care about this.
45
46 g_scheduler->input_file_manager()->AddDynamicInput(
47 input_file.name(), &clone_input_file, &tokens, &parse_root);
48 clone_input_file->SetContents(input_file.contents());
49
50 return LocationRange(
51 Location(clone_input_file, range.begin().line_number(),
52 range.begin().column_number(), -1 /* TODO(scottmg) */),
53 Location(clone_input_file, range.end().line_number(),
54 range.end().column_number(), -1 /* TODO(scottmg) */));
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 SourceFile::Type type = file.first.type();
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 // Add in outputs from actions. These are treated as public (since if other
227 // targets can't use them, then there wouldn't be any point in outputting).
228 std::vector<SourceFile> outputs;
229 target->action_values().GetOutputsAsSourceFiles(target, &outputs);
230 for (const auto& output : outputs) {
231 PublicGeneratedPair* pair = &files_to_public[output];
232 pair->is_public = true;
233 pair->is_generated = true;
234 }
235
236 // Add the merged list to the master list of all files.
237 for (const auto& cur : files_to_public) {
238 (*dest)[cur.first].push_back(
239 TargetInfo(target, cur.second.is_public, cur.second.is_generated));
240 }
241 }
242
IsFileInOuputDir(const SourceFile & file) const243 bool HeaderChecker::IsFileInOuputDir(const SourceFile& file) const {
244 const std::string& build_dir = build_settings_->build_dir().value();
245 return file.value().compare(0, build_dir.size(), build_dir) == 0;
246 }
247
SourceFileForInclude(const IncludeStringWithLocation & include,const std::vector<SourceDir> & include_dirs,const InputFile & source_file,Err * err) const248 SourceFile HeaderChecker::SourceFileForInclude(
249 const IncludeStringWithLocation& include,
250 const std::vector<SourceDir>& include_dirs,
251 const InputFile& source_file,
252 Err* err) const {
253 using base::FilePath;
254
255 Value relative_file_value(nullptr, std::string(include.contents));
256
257 auto find_predicate = [relative_file_value, err, this](const SourceDir& dir) -> bool {
258 SourceFile include_file =
259 dir.ResolveRelativeFile(relative_file_value, err);
260 return file_map_.find(include_file) != file_map_.end();
261 };
262 if (!include.system_style_include) {
263 const SourceDir& file_dir = source_file.dir();
264 if (find_predicate(file_dir)) {
265 return file_dir.ResolveRelativeFile(relative_file_value, err);
266 }
267 }
268
269 auto it = std::find_if(
270 include_dirs.begin(), include_dirs.end(), find_predicate);
271
272 if (it != include_dirs.end())
273 return it->ResolveRelativeFile(relative_file_value, err);
274
275 return SourceFile();
276 }
277
CheckFile(const Target * from_target,const SourceFile & file,std::vector<Err> * errors) const278 bool HeaderChecker::CheckFile(const Target* from_target,
279 const SourceFile& file,
280 std::vector<Err>* errors) const {
281 ScopedTrace trace(TraceItem::TRACE_CHECK_HEADER, file.value());
282
283 // Sometimes you have generated source files included as sources in another
284 // target. These won't exist at checking time. Since we require all generated
285 // files to be somewhere in the output tree, we can just check the name to
286 // see if they should be skipped.
287 if (!check_generated_ && IsFileInOuputDir(file))
288 return true;
289
290 base::FilePath path = build_settings_->GetFullPath(file);
291 std::string contents;
292 if (!base::ReadFileToString(path, &contents)) {
293 // A missing (not yet) generated file is an acceptable problem
294 // considering this code does not understand conditional includes.
295 if (IsFileInOuputDir(file))
296 return true;
297
298 errors->emplace_back(from_target->defined_from(), "Source file not found.",
299 "The target:\n " +
300 from_target->label().GetUserVisibleName(false) +
301 "\nhas a source file:\n " + file.value() +
302 "\nwhich was not found.");
303 return false;
304 }
305
306 InputFile input_file(file);
307 input_file.SetContents(contents);
308
309 std::vector<SourceDir> include_dirs;
310 for (ConfigValuesIterator iter(from_target); !iter.done(); iter.Next()) {
311 const std::vector<SourceDir>& target_include_dirs =
312 iter.cur().include_dirs();
313 include_dirs.insert(include_dirs.end(), target_include_dirs.begin(),
314 target_include_dirs.end());
315 }
316
317 size_t error_count_before = errors->size();
318 CIncludeIterator iter(&input_file);
319
320 IncludeStringWithLocation include;
321
322 std::set<std::pair<const Target*, const Target*>> no_dependency_cache;
323
324 while (iter.GetNextIncludeString(&include)) {
325 if (include.system_style_include && !check_system_)
326 continue;
327
328 Err err;
329 SourceFile included_file = SourceFileForInclude(include,
330 include_dirs,
331 input_file,
332 &err);
333 if (!included_file.is_null()) {
334 CheckInclude(from_target, input_file, included_file, include.location,
335 &no_dependency_cache, errors);
336 }
337 }
338
339 return errors->size() == error_count_before;
340 }
341
342 // If the file exists:
343 // - The header must be in the public section of a target, or it must
344 // be in the sources with no public list (everything is implicitly public).
345 // - The dependency path to the included target must follow only public_deps.
346 // - If there are multiple targets with the header in it, only one need be
347 // 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) const348 void HeaderChecker::CheckInclude(
349 const Target* from_target,
350 const InputFile& source_file,
351 const SourceFile& include_file,
352 const LocationRange& range,
353 std::set<std::pair<const Target*, const Target*>>* no_dependency_cache,
354 std::vector<Err>* errors) const {
355 // Assume if the file isn't declared in our sources that we don't need to
356 // check it. It would be nice if we could give an error if this happens, but
357 // our include finder is too primitive and returns all includes, even if
358 // they're in a #if not executed in the current build. In that case, it's
359 // not unusual for the buildfiles to not specify that header at all.
360 FileMap::const_iterator found = file_map_.find(include_file);
361 if (found == file_map_.end())
362 return;
363
364 const TargetVector& targets = found->second;
365 Chain chain; // Prevent reallocating in the loop.
366
367 // If the file is unknown in the current toolchain (rather than being private
368 // or in a target not visible to the current target), ignore it. This is a
369 // bit of a hack to account for the fact that the include finder doesn't
370 // understand the preprocessor.
371 //
372 // When not cross-compiling, if a platform specific header is conditionally
373 // included in the build, and preprocessor conditions around #includes of
374 // that match the build conditions, everything will be OK because the file
375 // won't be known to GN even though the #include finder identified the file.
376 //
377 // Cross-compiling breaks this. When compiling Android on Linux, for example,
378 // we might see both Linux and Android definitions of a target and know
379 // about the union of all headers in the build. Since the #include finder
380 // ignores preprocessor, we will find the Linux headers in the Android
381 // build and note that a dependency from the Android target to the Linux
382 // one is missing (these might even be the same target in different
383 // toolchains!).
384 bool present_in_current_toolchain = false;
385 for (const auto& target : targets) {
386 if (from_target->label().ToolchainsEqual(target.target->label())) {
387 present_in_current_toolchain = true;
388 break;
389 }
390 }
391 if (!present_in_current_toolchain)
392 return;
393
394 // For all targets containing this file, we require that at least one be
395 // a direct or public dependency of the current target, and either (1) the
396 // header is public within the target, or (2) there is a friend definition
397 // whitelisting the includor.
398 //
399 // If there is more than one target containing this header, we may encounter
400 // some error cases before finding a good one. This error stores the previous
401 // one encountered, which we may or may not throw away.
402 Err last_error;
403
404 bool found_dependency = false;
405 for (const auto& target : targets) {
406 // We always allow source files in a target to include headers also in that
407 // target.
408 const Target* to_target = target.target;
409 if (to_target == from_target)
410 return;
411
412 bool is_permitted_chain = false;
413
414 bool cached_no_dependency =
415 no_dependency_cache->find(std::make_pair(to_target, from_target)) !=
416 no_dependency_cache->end();
417
418 bool add_to_cache = !cached_no_dependency;
419
420 if (!cached_no_dependency &&
421 IsDependencyOf(to_target, from_target, &chain, &is_permitted_chain)) {
422 add_to_cache = false;
423
424 DCHECK(chain.size() >= 2);
425 DCHECK(chain[0].target == to_target);
426 DCHECK(chain[chain.size() - 1].target == from_target);
427
428 found_dependency = true;
429
430 bool effectively_public =
431 target.is_public || FriendMatches(to_target, from_target);
432
433 if (effectively_public && is_permitted_chain) {
434 // This one is OK, we're done.
435 last_error = Err();
436 break;
437 }
438
439 // Diagnose the error.
440 if (!effectively_public) {
441 // Danger: must call CreatePersistentRange to put in Err.
442 last_error = Err(CreatePersistentRange(source_file, range),
443 "Including a private header.",
444 "This file is private to the target " +
445 target.target->label().GetUserVisibleName(false));
446 } else if (!is_permitted_chain) {
447 last_error = Err(CreatePersistentRange(source_file, range),
448 "Can't include this header from here.",
449 GetDependencyChainPublicError(chain));
450 } else {
451 NOTREACHED();
452 }
453 } else if (to_target->allow_circular_includes_from().find(
454 from_target->label()) !=
455 to_target->allow_circular_includes_from().end()) {
456 // Not a dependency, but this include is whitelisted from the destination.
457 found_dependency = true;
458 last_error = Err();
459 break;
460 }
461
462 if (add_to_cache) {
463 no_dependency_cache->emplace(to_target, from_target);
464 }
465 }
466
467 if (!found_dependency || last_error.has_error()) {
468 if (!found_dependency) {
469 DCHECK(!last_error.has_error());
470 Err err = MakeUnreachableError(source_file, range, from_target, targets);
471 errors->push_back(std::move(err));
472 } else {
473 // Found at least one dependency chain above, but it had an error.
474 errors->push_back(std::move(last_error));
475 }
476 return;
477 }
478
479 // One thing we didn't check for is targets that expose their dependents
480 // headers in their own public headers.
481 //
482 // Say we have A -> B -> C. If C has public_configs, everybody getting headers
483 // from C should get the configs also or things could be out-of-sync. Above,
484 // we check for A including C's headers directly, but A could also include a
485 // header from B that in turn includes a header from C.
486 //
487 // There are two ways to solve this:
488 // - If a public header in B includes C, force B to publicly depend on C.
489 // This is possible to check, but might be super annoying because most
490 // targets (especially large leaf-node targets) don't declare
491 // public/private headers and you'll get lots of false positives.
492 //
493 // - Save the includes found in each file and actually compute the graph of
494 // includes to detect when A implicitly includes C's header. This will not
495 // have the annoying false positive problem, but is complex to write.
496 }
497
IsDependencyOf(const Target * search_for,const Target * search_from,Chain * chain,bool * is_permitted) const498 bool HeaderChecker::IsDependencyOf(const Target* search_for,
499 const Target* search_from,
500 Chain* chain,
501 bool* is_permitted) const {
502 if (search_for == search_from) {
503 // A target is always visible from itself.
504 *is_permitted = true;
505 return false;
506 }
507
508 // Find the shortest public dependency chain.
509 if (IsDependencyOf(search_for, search_from, true, chain)) {
510 *is_permitted = true;
511 return true;
512 }
513
514 // If not, try to find any dependency chain at all.
515 if (IsDependencyOf(search_for, search_from, false, chain)) {
516 *is_permitted = false;
517 return true;
518 }
519
520 *is_permitted = false;
521 return false;
522 }
523
IsDependencyOf(const Target * search_for,const Target * search_from,bool require_permitted,Chain * chain) const524 bool HeaderChecker::IsDependencyOf(const Target* search_for,
525 const Target* search_from,
526 bool require_permitted,
527 Chain* chain) const {
528 // This method conducts a breadth-first search through the dependency graph
529 // to find a shortest chain from search_from to search_for.
530 //
531 // work_queue maintains a queue of targets which need to be considered as
532 // part of this chain, in the order they were first traversed.
533 //
534 // Each time a new transitive dependency of search_from is discovered for
535 // the first time, it is added to work_queue and a "breadcrumb" is added,
536 // indicating which target it was reached from when first discovered.
537 //
538 // Once this search finds search_for, the breadcrumbs are used to reconstruct
539 // a shortest dependency chain (in reverse order) from search_from to
540 // search_for.
541
542 std::map<const Target*, ChainLink> breadcrumbs;
543 base::queue<ChainLink> work_queue;
544 work_queue.push(ChainLink(search_from, true));
545
546 bool first_time = true;
547 while (!work_queue.empty()) {
548 ChainLink cur_link = work_queue.front();
549 const Target* target = cur_link.target;
550 work_queue.pop();
551
552 if (target == search_for) {
553 // Found it! Reconstruct the chain.
554 chain->clear();
555 while (target != search_from) {
556 chain->push_back(cur_link);
557 cur_link = breadcrumbs[target];
558 target = cur_link.target;
559 }
560 chain->push_back(ChainLink(search_from, true));
561 return true;
562 }
563
564 // Always consider public dependencies as possibilities.
565 for (const auto& dep : target->public_deps()) {
566 if (breadcrumbs.insert(std::make_pair(dep.ptr, cur_link)).second)
567 work_queue.push(ChainLink(dep.ptr, true));
568 }
569
570 if (first_time || !require_permitted) {
571 // Consider all dependencies since all target paths are allowed, so add
572 // in private ones. Also do this the first time through the loop, since
573 // a target can include headers from its direct deps regardless of
574 // public/private-ness.
575 first_time = false;
576 for (const auto& dep : target->private_deps()) {
577 if (breadcrumbs.insert(std::make_pair(dep.ptr, cur_link)).second)
578 work_queue.push(ChainLink(dep.ptr, false));
579 }
580 }
581 }
582
583 return false;
584 }
585
MakeUnreachableError(const InputFile & source_file,const LocationRange & range,const Target * from_target,const TargetVector & targets)586 Err HeaderChecker::MakeUnreachableError(const InputFile& source_file,
587 const LocationRange& range,
588 const Target* from_target,
589 const TargetVector& targets) {
590 // Normally the toolchains will all match, but when cross-compiling, we can
591 // get targets with more than one toolchain in the list of possibilities.
592 std::vector<const Target*> targets_with_matching_toolchains;
593 std::vector<const Target*> targets_with_other_toolchains;
594 for (const TargetInfo& candidate : targets) {
595 if (candidate.target->toolchain() == from_target->toolchain())
596 targets_with_matching_toolchains.push_back(candidate.target);
597 else
598 targets_with_other_toolchains.push_back(candidate.target);
599 }
600
601 // It's common when cross-compiling to have a target with the same file in
602 // more than one toolchain. We could output all of them, but this is
603 // generally confusing to people (most end-users won't understand toolchains
604 // well).
605 //
606 // So delete any candidates in other toolchains that also appear in the same
607 // toolchain as the from_target.
608 for (int other_index = 0;
609 other_index < static_cast<int>(targets_with_other_toolchains.size());
610 other_index++) {
611 for (const Target* cur_matching : targets_with_matching_toolchains) {
612 if (TargetLabelsMatchExceptToolchain(
613 cur_matching, targets_with_other_toolchains[other_index])) {
614 // Found a duplicate, erase it.
615 targets_with_other_toolchains.erase(
616 targets_with_other_toolchains.begin() + other_index);
617 other_index--;
618 break;
619 }
620 }
621 }
622
623 // Only display toolchains on labels if they don't all match.
624 bool include_toolchain = !targets_with_other_toolchains.empty();
625
626 std::string msg = "It is not in any dependency of\n " +
627 from_target->label().GetUserVisibleName(include_toolchain);
628 msg += "\nThe include file is in the target(s):\n";
629 for (auto* target : targets_with_matching_toolchains)
630 msg += " " + target->label().GetUserVisibleName(include_toolchain) + "\n";
631 for (auto* target : targets_with_other_toolchains)
632 msg += " " + target->label().GetUserVisibleName(include_toolchain) + "\n";
633 if (targets_with_other_toolchains.size() +
634 targets_with_matching_toolchains.size() >
635 1)
636 msg += "at least one of ";
637 msg += "which should somehow be reachable.";
638
639 // Danger: must call CreatePersistentRange to put in Err.
640 return Err(CreatePersistentRange(source_file, range), "Include not allowed.",
641 msg);
642 }
643