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