Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(699)

Side by Side Diff: tools/traffic_annotation/auditor/traffic_annotation_auditor.cc

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

Powered by Google App Engine
This is Rietveld 408576698