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.
5 #include "tools/gn/header_checker.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/err.h"
17 #include "tools/gn/filesystem_utils.h"
18 #include "tools/gn/scheduler.h"
19 #include "tools/gn/target.h"
20 #include "tools/gn/trace.h"
24 // This class makes InputFiles on the stack as it reads files to check. When
25 // we throw an error, the Err indicates a locatin which has a pointer to
26 // an InputFile that must persist as long as the Err does.
28 // To make this work, this function creates a clone of the InputFile managed
29 // by the InputFileManager so the error can refer to something that
30 // persists. This means that the current file contents will live as long as
31 // the program, but this is OK since we're erroring out anyway.
32 LocationRange
CreatePersistentRange(const InputFile
& input_file
,
33 const LocationRange
& range
) {
34 InputFile
* clone_input_file
;
35 std::vector
<Token
>* tokens
; // Don't care about this.
36 scoped_ptr
<ParseNode
>* parse_root
; // Don't care about this.
38 g_scheduler
->input_file_manager()->AddDynamicInput(
39 input_file
.name(), &clone_input_file
, &tokens
, &parse_root
);
40 clone_input_file
->SetContents(input_file
.contents());
43 Location(clone_input_file
, range
.begin().line_number(),
44 range
.begin().char_offset()),
45 Location(clone_input_file
, range
.end().line_number(),
46 range
.end().char_offset()));
51 HeaderChecker::HeaderChecker(const BuildSettings
* build_settings
,
52 const std::vector
<const Target
*>& targets
)
53 : main_loop_(base::MessageLoop::current()),
54 build_settings_(build_settings
) {
55 for (size_t i
= 0; i
< targets
.size(); i
++)
56 AddTargetToFileMap(targets
[i
]);
59 HeaderChecker::~HeaderChecker() {
62 bool HeaderChecker::Run(std::vector
<Err
>* errors
) {
63 ScopedTrace
trace(TraceItem::TRACE_CHECK_HEADERS
, "Check headers");
65 if (file_map_
.empty())
68 scoped_refptr
<base::SequencedWorkerPool
> pool(
69 new base::SequencedWorkerPool(16, "HeaderChecker"));
70 for (FileMap::const_iterator file_i
= file_map_
.begin();
71 file_i
!= file_map_
.end(); ++file_i
) {
72 const TargetVector
& vect
= file_i
->second
;
74 // Only check C-like source files (RC files also have includes).
75 SourceFileType type
= GetSourceFileType(file_i
->first
);
76 if (type
!= SOURCE_CC
&& type
!= SOURCE_H
&& type
!= SOURCE_C
&&
77 type
!= SOURCE_M
&& type
!= SOURCE_MM
&& type
!= SOURCE_RC
)
80 for (size_t vect_i
= 0; vect_i
< vect
.size(); ++vect_i
) {
81 pool
->PostWorkerTaskWithShutdownBehavior(
83 base::Bind(&HeaderChecker::DoWork
, this,
84 vect
[vect_i
].target
, file_i
->first
),
85 base::SequencedWorkerPool::BLOCK_SHUTDOWN
);
89 // After this call we're single-threaded again.
98 void HeaderChecker::DoWork(const Target
* target
, const SourceFile
& file
) {
100 if (!CheckFile(target
, file
, &err
)) {
101 base::AutoLock
lock(lock_
);
102 errors_
.push_back(err
);
106 void HeaderChecker::AddTargetToFileMap(const Target
* target
) {
107 // Files in the sources have this public bit by default.
108 bool default_public
= target
->all_headers_public();
110 // First collect the normal files, they get the default visibility.
111 std::map
<SourceFile
, bool> files_to_public
;
112 const Target::FileList
& sources
= target
->sources();
113 for (size_t i
= 0; i
< sources
.size(); i
++)
114 files_to_public
[sources
[i
]] = default_public
;
116 // Add in the public files, forcing them to public. This may overwrite some
117 // entries, and it may add new ones.
118 const Target::FileList
& public_list
= target
->public_headers();
120 DCHECK(public_list
.empty()); // List only used when default is not public.
121 for (size_t i
= 0; i
< public_list
.size(); i
++)
122 files_to_public
[public_list
[i
]] = true;
124 // Add the merged list to the master list of all files.
125 for (std::map
<SourceFile
, bool>::const_iterator i
= files_to_public
.begin();
126 i
!= files_to_public
.end(); ++i
)
127 file_map_
[i
->first
].push_back(TargetInfo(target
, i
->second
));
130 bool HeaderChecker::IsFileInOuputDir(const SourceFile
& file
) const {
131 const std::string
& build_dir
= build_settings_
->build_dir().value();
132 return file
.value().compare(0, build_dir
.size(), build_dir
) == 0;
135 // This current assumes all include paths are relative to the source root
136 // which is generally the case for Chromium.
138 // A future enhancement would be to search the include path for the target
139 // containing the source file containing this include and find the file to
140 // handle the cases where people do weird things with the paths.
141 SourceFile
HeaderChecker::SourceFileForInclude(
142 const base::StringPiece
& input
) const {
143 std::string
str("//");
144 input
.AppendToString(&str
);
145 return SourceFile(str
);
148 bool HeaderChecker::CheckFile(const Target
* from_target
,
149 const SourceFile
& file
,
151 ScopedTrace
trace(TraceItem::TRACE_CHECK_HEADER
, file
.value());
153 // Sometimes you have generated source files included as sources in another
154 // target. These won't exist at checking time. Since we require all generated
155 // files to be somewhere in the output tree, we can just check the name to
156 // see if they should be skipped.
157 if (IsFileInOuputDir(file
))
160 base::FilePath path
= build_settings_
->GetFullPath(file
);
161 std::string contents
;
162 if (!base::ReadFileToString(path
, &contents
)) {
163 *err
= Err(from_target
->defined_from(), "Source file not found.",
164 "This target includes as a source:\n " + file
.value() +
165 "\nwhich was not found.");
169 InputFile
input_file(file
);
170 input_file
.SetContents(contents
);
172 CIncludeIterator
iter(&input_file
);
173 base::StringPiece current_include
;
175 while (iter
.GetNextIncludeString(¤t_include
, &range
)) {
176 SourceFile include
= SourceFileForInclude(current_include
);
177 if (!CheckInclude(from_target
, input_file
, include
, range
, err
))
184 // If the file exists, it must be in a dependency of the given target, and it
185 // must be public in that dependency.
186 bool HeaderChecker::CheckInclude(const Target
* from_target
,
187 const InputFile
& source_file
,
188 const SourceFile
& include_file
,
189 const LocationRange
& range
,
191 // Assume if the file isn't declared in our sources that we don't need to
192 // check it. It would be nice if we could give an error if this happens, but
193 // our include finder is too primitive and returns all includes, even if
194 // they're in a #if not executed in the current build. In that case, it's
195 // not unusual for the buildfiles to not specify that header at all.
196 FileMap::const_iterator found
= file_map_
.find(include_file
);
197 if (found
== file_map_
.end())
200 const TargetVector
& targets
= found
->second
;
202 // For all targets containing this file, we require that at least one be
203 // a dependency of the current target, and all targets that are dependencies
204 // must have the file listed in the public section.
205 bool found_dependency
= false;
206 for (size_t i
= 0; i
< targets
.size(); i
++) {
207 // We always allow source files in a target to include headers also in that
209 if (targets
[i
].target
== from_target
)
212 if (IsDependencyOf(targets
[i
].target
, from_target
)) {
213 // The include is in a target that's a proper dependency. Verify that
214 // the including target has visibility.
215 if (!targets
[i
].target
->visibility().CanSeeMe(from_target
->label())) {
216 std::string msg
= "The included file is in " +
217 targets
[i
].target
->label().GetUserVisibleName(false) +
218 "\nwhich is not visible from " +
219 from_target
->label().GetUserVisibleName(false) +
220 "\n(see \"gn help visibility\").";
222 // Danger: must call CreatePersistentRange to put in Err.
223 *err
= Err(CreatePersistentRange(source_file
, range
),
224 "Including a header from non-visible target.", msg
);
228 // The file must also be public in the target.
229 if (!targets
[i
].is_public
) {
230 // Danger: must call CreatePersistentRange to put in Err.
231 *err
= Err(CreatePersistentRange(source_file
, range
),
232 "Including a private header.",
233 "This file is private to the target " +
234 targets
[i
].target
->label().GetUserVisibleName(false));
237 found_dependency
= true;
241 if (!found_dependency
) {
242 std::string msg
= "It is not in any dependency of " +
243 from_target
->label().GetUserVisibleName(false);
244 msg
+= "\nThe include file is in the target(s):\n";
245 for (size_t i
= 0; i
< targets
.size(); i
++)
246 msg
+= " " + targets
[i
].target
->label().GetUserVisibleName(false) + "\n";
247 if (targets
.size() > 1)
248 msg
+= "at least one of ";
249 msg
+= "which should somehow be reachable from " +
250 from_target
->label().GetUserVisibleName(false);
252 // Danger: must call CreatePersistentRange to put in Err.
253 *err
= Err(CreatePersistentRange(source_file
, range
),
254 "Include not allowed.", msg
);
261 bool HeaderChecker::IsDependencyOf(const Target
* search_for
,
262 const Target
* search_from
) const {
263 std::set
<const Target
*> checked
;
264 return IsDependencyOf(search_for
, search_from
, &checked
);
267 bool HeaderChecker::IsDependencyOf(const Target
* search_for
,
268 const Target
* search_from
,
269 std::set
<const Target
*>* checked
) const {
270 if (checked
->find(search_for
) != checked
->end())
271 return false; // Already checked this subtree.
273 const LabelTargetVector
& deps
= search_from
->deps();
274 for (size_t i
= 0; i
< deps
.size(); i
++) {
275 if (deps
[i
].ptr
== search_for
)
276 return true; // Found it.
279 checked
->insert(deps
[i
].ptr
);
280 if (IsDependencyOf(search_for
, deps
[i
].ptr
, checked
))