OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2017 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 // See README.md for information and build instructions. | |
6 | |
7 #include "base/command_line.h" | |
8 #include "base/files/file_enumerator.h" | |
9 #include "base/files/file_path.h" | |
10 #include "base/files/file_util.h" | |
11 #include "base/files/scoped_temp_dir.h" | |
12 #include "base/logging.h" | |
13 #include "base/process/launch.h" | |
14 #include "base/strings/string_number_conversions.h" | |
15 #include "base/strings/string_split.h" | |
16 #include "base/strings/stringprintf.h" | |
17 #include "base/strings/utf_string_conversions.h" | |
18 #include "net/traffic_annotation/network_traffic_annotation.h" | |
19 #include "third_party/protobuf/src/google/protobuf/text_format.h" | |
20 | |
21 #include "tools/traffic_annotation/traffic_annotation.pb.h" | |
22 | |
23 namespace { | |
24 | |
25 // Holds an instance of network traffic annotation. | |
26 struct AnnotationInstance { | |
27 AnnotationInstance() : state(STATE_OK) {} | |
28 | |
29 // Protobuf of the annotation. | |
30 traffic_annotation::NetworkTrafficAnnotation proto; | |
31 // Unique id of the annotation. | |
32 std::string unique_id; | |
33 // State of this annotation. | |
34 enum State { STATE_OK, STATE_DUPLICATE, STATE_UNDEFINED } state; | |
35 }; | |
36 | |
37 // Runs clang tool, returns true if all steps are successful. Returns annotation | |
38 // extractor clang tool's exit code in |clang_tool_exit_code|. | |
39 bool RunClangTool(const base::FilePath& src_dir, | |
40 const base::FilePath& build_dir, | |
41 const base::FilePath& output_dir, | |
42 const base::CommandLine::StringVector& path_filters, | |
43 int* clang_tool_exit_code) { | |
44 #ifdef _WIN32 | |
45 // Run generate_win_compdb.py on Windows to repair compdb before running clang | |
46 // tool. | |
47 base::CommandLine::StringVector argv; | |
48 argv.push_back(L"python"); | |
49 argv.push_back(base::ASCIIToUTF16( | |
50 src_dir.AppendASCII("tools/clang/scripts/generate_win_compdb.py") | |
51 .MaybeAsASCII())); | |
52 argv.push_back(base::ASCIIToUTF16(build_dir.MaybeAsASCII())); | |
53 int exit_code = -1; | |
54 base::Process process = | |
55 base::LaunchProcess(base::CommandLine(argv), base::LaunchOptions()); | |
56 if (!process.IsValid() || !process.WaitForExit(&exit_code) || exit_code) { | |
57 LOG(INFO) << " Executing generate_win_compdb failed.\n"; | |
58 return false; | |
59 } | |
60 #endif | |
61 | |
62 for (auto& path : path_filters) { | |
63 // Create commandline based on OS. | |
64 base::CommandLine::StringVector argv; | |
65 #ifdef _WIN32 | |
66 argv.push_back(L"python"); | |
67 argv.push_back(base::ASCIIToUTF16( | |
68 src_dir.AppendASCII("tools/clang/scripts/run_tool.py").MaybeAsASCII())); | |
battre
2017/04/07 08:42:28
Do you need to go through ASCII here and in the fo
Ramin Halavati
2017/04/07 11:33:33
I couldn't make it work any simpler way. I will di
| |
69 argv.push_back(L"traffic_annotation_extractor"); | |
70 argv.push_back(base::ASCIIToUTF16(build_dir.MaybeAsASCII())); | |
71 argv.push_back(path); | |
72 argv.push_back(base::ASCIIToUTF16( | |
73 base::StringPrintf("--tool-args=-output-directory=%s", | |
74 output_dir.MaybeAsASCII().c_str()))); | |
75 #else | |
76 argv.push_back( | |
77 src_dir.AppendASCII("tools/clang/scripts/run_tool.py").MaybeAsASCII()); | |
78 argv.push_back("--generate-compdb"); | |
79 argv.push_back("traffic_annotation_extractor"); | |
80 argv.push_back(build_dir.MaybeAsASCII()); | |
81 argv.push_back(path); | |
82 argv.push_back(base::StringPrintf("--tool-args=-output-directory=%s", | |
83 output_dir.MaybeAsASCII().c_str())); | |
84 #endif | |
85 | |
86 base::Process process = | |
87 base::LaunchProcess(base::CommandLine(argv), base::LaunchOptions()); | |
88 if (!process.IsValid() || !process.WaitForExit(clang_tool_exit_code)) { | |
89 LOG(ERROR) << "Executing clang tool failed.\n"; | |
90 return false; | |
91 } | |
92 } | |
93 return true; | |
94 } | |
95 | |
96 // Reads an extracted annotation file and returns it in the output variables. | |
97 // The file starts with four |header_lines| with the following meaning: | |
98 // 0- File path. | |
99 // 1- Name of the function including this position. | |
100 // 2- Line number. | |
101 // 3- Unique id of annotation. | |
102 // The rest of the file is the protobuf text (|msg_text|). | |
103 bool ReadFile(const base::FilePath& file_path, | |
104 std::vector<std::string>* header_lines, | |
105 std::string* msg_text) { | |
106 std::string file_content; | |
107 if (!base::ReadFileToString(file_path, &file_content)) | |
108 return false; | |
109 | |
110 header_lines->clear(); | |
111 msg_text->clear(); | |
112 | |
113 std::vector<std::string> tokens = base::SplitString( | |
114 file_content, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL); | |
115 | |
116 // If enough data is extracted, populate outputs, otherwise leave them blank. | |
117 if (tokens.size() > 4) { | |
118 for (int i = 0; i < 4; i++) | |
119 header_lines->push_back(tokens[i]); | |
120 for (size_t i = 4; i < tokens.size(); i++) | |
121 *msg_text += tokens[i] + "\n"; | |
122 } | |
123 | |
124 return true; | |
125 } | |
126 | |
127 // Reads all extracted txt files from given input folder and populates instances | |
128 // and errors. Errors include not finding the file, incorrect content, or error | |
129 // passed from clang tool. | |
130 void ReadExtractedFiles(const base::FilePath& folder_name, | |
131 std::vector<AnnotationInstance>* instances, | |
132 std::vector<std::string>* errors) { | |
133 #ifdef _WIN32 | |
134 base::FileEnumerator file_iter(folder_name, false, | |
135 base::FileEnumerator::FILES, L"*.txt"); | |
136 #else | |
137 base::FileEnumerator file_iter(folder_name, false, | |
138 base::FileEnumerator::FILES, "*.txt"); | |
139 #endif | |
140 while (!file_iter.Next().empty()) { | |
141 std::string file_name = file_iter.GetInfo().GetName().AsUTF8Unsafe(); | |
142 LOG(INFO) << "Reading " << file_name.c_str() << "...\n"; | |
143 | |
144 std::vector<std::string> header_lines; | |
145 std::string msg_text; | |
146 if (!ReadFile(folder_name.Append(file_iter.GetInfo().GetName()), | |
147 &header_lines, &msg_text)) { | |
148 errors->push_back( | |
149 base::StringPrintf("Could not open file '%s'\n.", file_name.c_str())); | |
150 continue; | |
151 } | |
152 if (header_lines.size() < 4) { | |
153 errors->push_back(base::StringPrintf( | |
154 "Header lines are not complete for file '%s'\n.", file_name.c_str())); | |
155 continue; | |
156 } | |
157 | |
158 AnnotationInstance new_instance; | |
159 if (header_lines[3] == "\"Undefined\"") { | |
160 new_instance.state = AnnotationInstance::STATE_UNDEFINED; | |
161 } else if (!google::protobuf::TextFormat::ParseFromString( | |
162 msg_text, (google::protobuf::Message*)&new_instance)) { | |
163 errors->push_back(base::StringPrintf( | |
164 "Could not parse protobuf for file '%s'\n.", file_name.c_str())); | |
165 continue; | |
166 } | |
167 | |
168 // Add header data to new instance. | |
169 traffic_annotation::NetworkTrafficAnnotation_TrafficSource* src = | |
170 new_instance.proto.mutable_source(); | |
171 src->set_file(header_lines[0]); | |
172 src->set_function(header_lines[1]); | |
173 int line; | |
174 base::StringToInt(header_lines[2], &line); | |
175 src->set_line(line); | |
176 new_instance.unique_id = header_lines[3]; | |
177 instances->push_back(new_instance); | |
178 } | |
179 LOG(INFO) << instances->size() << " annotation instance(s) read.\n"; | |
180 } | |
181 | |
182 // Checks to see if unique ids are really unique and marks the ones which are | |
183 // not. | |
184 void MarkRepeatedUniqueIds(std::vector<AnnotationInstance>* instances) { | |
185 std::map<std::string, AnnotationInstance*> unique_ids; | |
186 for (auto& instance : *instances) { | |
187 if (instance.state == AnnotationInstance::STATE_OK) { | |
188 auto match = unique_ids.find(instance.unique_id); | |
189 if (match != unique_ids.end()) | |
battre
2017/04/07 08:42:28
please use {} if lines are wrapped also ein the el
Ramin Halavati
2017/04/07 11:33:33
Done.
| |
190 instance.state = match->second->state = | |
191 AnnotationInstance::STATE_DUPLICATE; | |
192 else | |
193 unique_ids.insert( | |
194 std::make_pair(std::string(instance.unique_id), &instance)); | |
195 } | |
196 } | |
197 } | |
198 | |
199 // Writes summary of annotation instances to a file. Returns true if successful. | |
200 bool WriteSummaryFile(int clang_tool_exit_code, | |
201 const std::vector<AnnotationInstance>& instances, | |
202 const std::vector<std::string>& errors, | |
203 const base::FilePath& file_path) { | |
204 std::string report = ""; | |
205 | |
206 if (errors.size() || clang_tool_exit_code) { | |
207 report = "[Errors]\n"; | |
208 | |
209 if (clang_tool_exit_code) | |
210 report += base::StringPrintf("Clang tool returned error: %i\n", | |
211 clang_tool_exit_code); | |
212 | |
213 for (const auto& error : errors) | |
214 report += error + "\n"; | |
215 } | |
216 | |
217 report += "[Annotations]\n"; | |
218 | |
219 for (const auto& instance : instances) { | |
220 report += | |
221 "------------------------------------------------------------" | |
222 "--------------------\n"; | |
223 report += base::StringPrintf("Unique ID: %s\n", instance.unique_id.c_str()); | |
224 if (instance.state == AnnotationInstance::STATE_UNDEFINED) | |
225 report += base::StringPrintf("WARNING: Undefined annotation.\n"); | |
226 else if (instance.state == AnnotationInstance::STATE_DUPLICATE) | |
227 report += base::StringPrintf("WARNING: Duplicate unique id.\n"); | |
228 std::string temp; | |
229 google::protobuf::TextFormat::PrintToString(instance.proto, &temp); | |
230 report += base::StringPrintf("Content:\n%s\n", temp.c_str()); | |
231 } | |
232 | |
233 if (base::WriteFile(file_path, report.c_str(), report.length()) == -1) { | |
234 LOG(INFO) << " Could not create output file: " | |
235 << file_path.MaybeAsASCII().c_str() << ".\n"; | |
236 return false; | |
237 } | |
238 | |
239 LOG(INFO) << "Output file " << file_path.MaybeAsASCII().c_str() | |
240 << " written for " << instances.size() << " instances.\n"; | |
241 return true; | |
242 } | |
243 | |
244 } // namespace | |
245 | |
246 #ifdef _WIN32 | |
247 int wmain(int argc, wchar_t* argv[]) { | |
248 #else | |
249 int main(int argc, char* argv[]) { | |
250 #endif | |
251 // Parse switches. | |
252 base::CommandLine command_line = base::CommandLine(argc, argv); | |
253 if (command_line.HasSwitch("help") || command_line.HasSwitch("h") || | |
254 argc == 1) { | |
255 LOG(INFO) | |
256 << "Usage: traffic_annotation_auditor [OPTION]... [path_filter]...\n" | |
257 "Extracts network traffic annotations from source files. If path " | |
258 "filter(s) are specified, only those directories of the source " | |
259 "will be analyzed.\n" | |
260 "Options:\n" | |
261 " -h, --help Shows help.\n" | |
262 " --build-dir Path to the build directory from which " | |
263 "the\n" | |
264 " annotations will be extracted.\n" | |
265 " --extractor-output-dir Path to the directory that extracted\n" | |
266 " partial files will be written to. " | |
267 "Will\n" | |
268 " be automatically generated and " | |
269 "deleted\n" | |
270 " if not specified.\n" | |
271 " --extracted-input-dir Path to a directory where extracted\n" | |
272 " partial annotations are already " | |
273 "stored.\n" | |
274 " If specified, build directory will be\n" | |
275 " ignored.\n" | |
276 " --summary-file Path to an output file with summary of\n" | |
277 " extracted annotations.\n" | |
278 "Example:\n" | |
279 " traffic_annotation_auditor --build-dir=out/Debug\n" | |
280 " --summary-file=report.txt\n"; | |
281 return 1; | |
282 } | |
283 base::FilePath extracted_files_dir = | |
284 command_line.GetSwitchValuePath("extracted-input-dir"); | |
285 base::FilePath build_dir = command_line.GetSwitchValuePath("build-dir"); | |
286 base::FilePath extractor_output_dir = | |
287 command_line.GetSwitchValuePath("extractor-output-dir"); | |
288 base::FilePath summary_file = command_line.GetSwitchValuePath("summary-file"); | |
289 base::ScopedTempDir temp_dir; | |
290 | |
291 if (summary_file.empty()) | |
292 LOG(ERROR) << "WARNING: Output file not specified.\n"; | |
293 | |
294 int clang_tool_exit_code = 0; | |
295 | |
296 // Extract annotations. | |
297 if (extracted_files_dir.empty()) { | |
298 // Get build directory, if it is empty issue an error. | |
299 if (build_dir.empty()) { | |
300 LOG(ERROR) | |
301 << "You must either specify build directory to run clang tool and " | |
302 "extract annotations, or specify extracted files's input " | |
battre
2017/04/07 08:42:28
You must either specify the build directory to run
Ramin Halavati
2017/04/07 11:33:33
Done.
| |
303 "directory where already extracted files exist.\n"; | |
304 return 1; | |
305 } | |
306 // If output directory is not provided, create a temporary one. | |
307 if (extractor_output_dir.empty()) { | |
308 if (!temp_dir.CreateUniqueTempDirUnderPath(build_dir)) { | |
309 LOG(ERROR) << "Could not create temporary directory in " | |
310 << build_dir.MaybeAsASCII().c_str() << ".\n"; | |
311 return 1; | |
312 } | |
313 extractor_output_dir = temp_dir.GetPath(); | |
314 } else { | |
315 // Ensure given directory is empty. | |
316 if (!base::FileEnumerator(extractor_output_dir, false, | |
317 base::FileEnumerator::FILES) | |
318 .Next() | |
319 .empty()) { | |
320 LOG(ERROR) << "Output directory " | |
321 << extractor_output_dir.MaybeAsASCII().c_str() | |
322 << "should be empty .\n"; | |
323 return 1; | |
324 } | |
325 } | |
326 | |
327 // Get path filters, if none is provided, just add all. | |
328 base::CommandLine::StringVector path_filters = command_line.GetArgs(); | |
329 if (!path_filters.size()) { | |
330 base::FilePath temp; | |
331 path_filters.push_back(temp.AppendASCII("./").value().c_str()); | |
332 } | |
333 | |
334 // Eexcutable is usually in out/[Build Dir], so the path to source is | |
335 // extracted by moving two directories up. | |
336 if (!RunClangTool(command_line.GetProgram().DirName().AppendASCII("../.."), | |
337 build_dir, extractor_output_dir, path_filters, | |
338 &clang_tool_exit_code)) { | |
339 return 1; | |
340 } | |
341 | |
342 extracted_files_dir = extractor_output_dir; | |
343 } | |
344 | |
345 // Read all extracted files. | |
346 std::vector<AnnotationInstance> instances; | |
347 std::vector<std::string> errors; | |
348 ReadExtractedFiles(extracted_files_dir, &instances, &errors); | |
349 | |
350 if (instances.empty()) { | |
351 LOG(ERROR) << "Could not read any file.\n"; | |
352 return 1; | |
353 } else { | |
354 MarkRepeatedUniqueIds(&instances); | |
355 } | |
356 | |
357 // Create Summary file if requested. | |
358 if (!summary_file.empty() && | |
359 !WriteSummaryFile(clang_tool_exit_code, instances, errors, summary_file)) | |
360 return 1; | |
361 | |
362 return 0; | |
363 } | |
OLD | NEW |