OLD | NEW |
(Empty) | |
| 1 // Copyright 2016 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.txt for information and build instructions. |
| 6 |
| 7 #include <cstdio> |
| 8 #include <cstdlib> |
| 9 #include <fstream> |
| 10 #include <map> |
| 11 #include <string> |
| 12 |
| 13 #include "base/command_line.h" |
| 14 #include "base/files/file_enumerator.h" |
| 15 #include "base/files/file_path.h" |
| 16 #include "base/files/scoped_temp_dir.h" |
| 17 #include "base/strings/stringprintf.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 // Holds an instance of network traffic annotation |
| 24 struct Instance { |
| 25 Instance() : hash_code(0), state(kOK) {} |
| 26 |
| 27 traffic_annotation::NetworkTrafficAnnotation proto; |
| 28 std::string unique_id; |
| 29 std::string callee; |
| 30 unsigned int hash_code; |
| 31 enum { kOK, kDuplicate, kUndefined } state; |
| 32 }; |
| 33 |
| 34 // Runs clang tool, returns true if all OK. |
| 35 bool RunClangTool(const base::FilePath& src_dir, |
| 36 const base::FilePath& build_dir, |
| 37 const base::FilePath& output_dir, |
| 38 const base::CommandLine::StringVector& path_filters) { |
| 39 #ifdef _WIN32 |
| 40 int result1 = system( |
| 41 base::StringPrintf( |
| 42 "python %s %s", |
| 43 src_dir.AppendASCII("tools/clang/scripts/generate_win_compdb.py") |
| 44 .MaybeAsASCII() |
| 45 .c_str(), |
| 46 build_dir.MaybeAsASCII().c_str()) |
| 47 .c_str()); |
| 48 if (result1) { |
| 49 printf(" Executing generate_win_compdb returned code: %i.\n", result1); |
| 50 return false; |
| 51 } |
| 52 #endif |
| 53 for (auto& path : path_filters) { |
| 54 #ifdef _WIN32 |
| 55 std::string command( |
| 56 "python %s traffic_annotation_extractor %s %S --tool-params " |
| 57 "\"output_dir=%s\""); |
| 58 #else |
| 59 std::string command( |
| 60 "%s traffic_annotation_extractor --generate-compdb %s %s --tool-params " |
| 61 "'output_dir=%s'"); |
| 62 #endif |
| 63 int result2 = |
| 64 system(base::StringPrintf( |
| 65 command.c_str(), |
| 66 src_dir.AppendASCII("tools/clang/scripts/run_tool.py") |
| 67 .MaybeAsASCII() |
| 68 .c_str(), |
| 69 build_dir.MaybeAsASCII().c_str(), path.c_str(), |
| 70 output_dir.MaybeAsASCII().c_str()) |
| 71 .c_str()); |
| 72 if (result2) { |
| 73 printf(" Executing clang tool returned code: %i.\n", result2); |
| 74 return false; |
| 75 } |
| 76 } |
| 77 return true; |
| 78 } |
| 79 |
| 80 // Reads an extrated annotation file. The first 6 lines of the file include: |
| 81 // 0- File path. |
| 82 // 1- Name of the function that is called using annotation. |
| 83 // 2- Line number. |
| 84 // 3- Name of the function including this position. |
| 85 // 4- Error text associated with this annotation. |
| 86 // 5- Unique id of annotation. |
| 87 // The rest of the file is the protobuf text. |
| 88 bool ReadFile(const base::FilePath& file_path, |
| 89 std::vector<std::string>* header_lines, |
| 90 std::string* msg_text) { |
| 91 std::ifstream input_file(file_path.value()); |
| 92 if (!input_file.is_open()) |
| 93 return false; |
| 94 |
| 95 header_lines->clear(); |
| 96 msg_text->clear(); |
| 97 |
| 98 while (!input_file.eof()) { |
| 99 std::string line; |
| 100 getline(input_file, line); |
| 101 if (header_lines->size() < 6) |
| 102 header_lines->push_back(line); |
| 103 else |
| 104 *msg_text = *msg_text + "\n" + line; |
| 105 } |
| 106 input_file.close(); |
| 107 return true; |
| 108 } |
| 109 |
| 110 // Reads all extracted txt files form given input folder and populates instances |
| 111 // and errors. |
| 112 void ReadExtractedFiles(const base::FilePath& folder_name, |
| 113 std::vector<Instance>* instances) { |
| 114 #ifdef _WIN32 |
| 115 base::FileEnumerator file_iter(folder_name, false, |
| 116 base::FileEnumerator::FILES, L"*.txt"); |
| 117 #else |
| 118 base::FileEnumerator file_iter(folder_name, false, |
| 119 base::FileEnumerator::FILES, "*.txt"); |
| 120 #endif |
| 121 while (!file_iter.Next().empty()) { |
| 122 std::string file_name = file_iter.GetInfo().GetName().AsUTF8Unsafe(); |
| 123 printf("\tReading %s...\n", file_name.c_str()); |
| 124 |
| 125 std::vector<std::string> header_lines; |
| 126 std::string msg_text; |
| 127 if (!ReadFile(folder_name.Append(file_iter.GetInfo().GetName()), |
| 128 &header_lines, &msg_text)) { |
| 129 fprintf(stderr, "Could not open file '%s'\n.", file_name.c_str()); |
| 130 continue; |
| 131 } |
| 132 if (header_lines.size() < 6) { |
| 133 fprintf(stderr, "Header lines are not complete for file '%s'\n.", |
| 134 file_name.c_str()); |
| 135 continue; |
| 136 } |
| 137 if (!header_lines[4].empty()) { |
| 138 fprintf(stderr, "Error passed from extractor for file '%s': %s\n.", |
| 139 file_name.c_str(), header_lines[4].c_str()); |
| 140 continue; |
| 141 } |
| 142 |
| 143 Instance new_instance; |
| 144 if (header_lines[5] == "\"Undefined\"") { |
| 145 new_instance.state = Instance::kUndefined; |
| 146 } else if (!google::protobuf::TextFormat::ParseFromString( |
| 147 msg_text, (google::protobuf::Message*)&new_instance)) { |
| 148 fprintf(stderr, "Could not parse protobuf for file '%s'\n.", |
| 149 file_name.c_str()); |
| 150 continue; |
| 151 } |
| 152 |
| 153 // Add header data to new instance. |
| 154 traffic_annotation::NetworkTrafficAnnotation_TrafficSource* src = |
| 155 new_instance.proto.mutable_source(); |
| 156 src->set_file(header_lines[0]); |
| 157 src->set_function(header_lines[1]); |
| 158 src->set_line(atoi(header_lines[2].c_str())); |
| 159 new_instance.callee = header_lines[3]; |
| 160 new_instance.unique_id = header_lines[5]; |
| 161 new_instance.hash_code = |
| 162 COMPUTE_STRING_HASH(new_instance.unique_id.c_str()); |
| 163 instances->push_back(new_instance); |
| 164 } |
| 165 printf("%i annotation instance(s) read.\n", (int)instances->size()); |
| 166 } |
| 167 |
| 168 // Checks to see if unique ids have similar hash codes and marks them if so. |
| 169 void MarkHashCollisions(std::vector<Instance>* instances) { |
| 170 std::map<unsigned int, Instance*> hashed_ids; |
| 171 for (auto& instance : *instances) { |
| 172 if (instance.state == Instance::kOK) { |
| 173 auto match = hashed_ids.find(instance.hash_code); |
| 174 if (match != hashed_ids.end()) |
| 175 instance.state = match->second->state = Instance::kDuplicate; |
| 176 else |
| 177 hashed_ids.insert(std::make_pair(instance.hash_code, &instance)); |
| 178 } |
| 179 } |
| 180 } |
| 181 |
| 182 // Writes summary of annotation instances to a file. Returns true if successful. |
| 183 bool WriteSummaryFile(const std::vector<Instance>& instances, |
| 184 const base::FilePath& file_path) { |
| 185 FILE* output_file = fopen(file_path.MaybeAsASCII().c_str(), "wt"); |
| 186 if (!output_file) { |
| 187 printf(" Could not create output file: %s", |
| 188 file_path.MaybeAsASCII().c_str()); |
| 189 return false; |
| 190 } |
| 191 for (const auto& instance : instances) { |
| 192 fprintf(output_file, "Unique ID: %s\tHash code:%u\n", |
| 193 instance.unique_id.c_str(), instance.hash_code); |
| 194 if (instance.state == Instance::kUndefined) |
| 195 fprintf(output_file, "WARNING: Undefined annotation.\n"); |
| 196 else if (instance.state == Instance::kDuplicate) |
| 197 fprintf(output_file, "WARNING: Duplicate hash code for unique id.\n"); |
| 198 fprintf(output_file, "Calling Function: %s\n", instance.callee.c_str()); |
| 199 std::string temp; |
| 200 google::protobuf::TextFormat::PrintToString(instance.proto, &temp); |
| 201 fprintf(output_file, "Content:\n%s\n", temp.c_str()); |
| 202 fprintf(output_file, "------------------------------------------------\n"); |
| 203 } |
| 204 fclose(output_file); |
| 205 printf("Output file %s written for %i instances.\n", |
| 206 file_path.MaybeAsASCII().c_str(), (int)instances.size()); |
| 207 return true; |
| 208 } |
| 209 |
| 210 #ifdef _WIN32 |
| 211 int wmain(int argc, wchar_t* argv[]) { |
| 212 #else |
| 213 int main(int argc, char* argv[]) { |
| 214 #endif |
| 215 // Parse switches. |
| 216 base::CommandLine command_line = base::CommandLine(argc, argv); |
| 217 if (command_line.HasSwitch("help") || command_line.HasSwitch("h") || |
| 218 argc == 1) { |
| 219 printf( |
| 220 "Usage: traffic_annotation_auditor [OPTION]... [path_filter]...\n" |
| 221 "Extracts network traffic annotations from source files. If path " |
| 222 "filter(s) are specified, only those directories of the source " |
| 223 "will be analyzed.\n" |
| 224 "Options:\n" |
| 225 " -h, --help Shows help.\n" |
| 226 " --build_dir Path to the build directory from which the\n" |
| 227 " annotations will be extracted.\n" |
| 228 " --extractor_output_dir Path to the directory that extracted\n" |
| 229 " partial files will be written to. Will\n" |
| 230 " be automatically generated and deleted\n" |
| 231 " if not specified.\n" |
| 232 " --extracted_input_dir Path to a directory where extracted\n" |
| 233 " partial annotations are already stored.\n" |
| 234 " If specified, build directory will be\n" |
| 235 " ignored.\n" |
| 236 " --summary_file Path to an output file with summary of\n" |
| 237 " extracted annotations.\n" |
| 238 "Example:\n" |
| 239 " traffic_annotation_auditor --build_dir=out/Debug\n" |
| 240 " --summary_file=report.txt\n"); |
| 241 return 1; |
| 242 } |
| 243 base::FilePath extracted_files_dir = |
| 244 command_line.GetSwitchValuePath("extracted_input_dir"); |
| 245 base::FilePath build_dir = command_line.GetSwitchValuePath("build_dir"); |
| 246 base::FilePath extractor_output_dir = |
| 247 command_line.GetSwitchValuePath("extractor_output_dir"); |
| 248 base::FilePath summary_file = command_line.GetSwitchValuePath("summary_file"); |
| 249 base::ScopedTempDir temp_dir; |
| 250 |
| 251 if (summary_file.empty()) |
| 252 fprintf(stderr, "WARNING: Output file not specified.\n"); |
| 253 |
| 254 // Extract annotations. |
| 255 if (extracted_files_dir.empty()) { |
| 256 // Get build directory, if it is empty issue an error. |
| 257 if (build_dir.empty()) { |
| 258 fprintf( |
| 259 stderr, |
| 260 "You must either specify build directory to run clang " |
| 261 "tool and extract annotations, or specify extracted files's input " |
| 262 "directory where already extracted files exist.\n"); |
| 263 return 1; |
| 264 } |
| 265 // If output directory is not provided, create a temporary one. |
| 266 if (extractor_output_dir.empty()) { |
| 267 if (!temp_dir.CreateUniqueTempDirUnderPath(build_dir)) { |
| 268 fprintf(stderr, "Could not create temporary directory in %s.\n", |
| 269 build_dir.MaybeAsASCII().c_str()); |
| 270 return 1; |
| 271 } |
| 272 extractor_output_dir = temp_dir.GetPath(); |
| 273 } else { |
| 274 // Ensure given directory is empty. |
| 275 if (!base::FileEnumerator(extractor_output_dir, false, |
| 276 base::FileEnumerator::FILES) |
| 277 .Next() |
| 278 .empty()) { |
| 279 fprintf(stderr, "Output directory (%s) should be empty.\n", |
| 280 extractor_output_dir.MaybeAsASCII().c_str()); |
| 281 return 1; |
| 282 } |
| 283 } |
| 284 |
| 285 // Get path filters, if none is provided, just add all. |
| 286 base::CommandLine::StringVector path_filters = command_line.GetArgs(); |
| 287 if (!path_filters.size()) { |
| 288 base::FilePath temp; |
| 289 path_filters.push_back(temp.AppendASCII("./").value().c_str()); |
| 290 } |
| 291 |
| 292 if (!RunClangTool(command_line.GetProgram().DirName().AppendASCII("../.."), |
| 293 build_dir, extractor_output_dir, path_filters)) { |
| 294 return 1; |
| 295 } |
| 296 |
| 297 extracted_files_dir = extractor_output_dir; |
| 298 } |
| 299 |
| 300 // Read all extracted files. |
| 301 std::vector<Instance> instances; |
| 302 ReadExtractedFiles(extracted_files_dir, &instances); |
| 303 |
| 304 if (instances.empty()) { |
| 305 fprintf(stderr, "Could not read any file.\n"); |
| 306 return 1; |
| 307 } else { |
| 308 MarkHashCollisions(&instances); |
| 309 } |
| 310 |
| 311 // Create Summary file if requested. |
| 312 if (!summary_file.empty() && !WriteSummaryFile(instances, summary_file)) |
| 313 return 1; |
| 314 |
| 315 return 0; |
| 316 } |
OLD | NEW |