Index: tools/traffic_annotation/auditor/traffic_annotation_auditor.cc |
diff --git a/tools/traffic_annotation/auditor/traffic_annotation_auditor.cc b/tools/traffic_annotation/auditor/traffic_annotation_auditor.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..a3f9aece487a00d9409ef0a597a6bcfbee74e436 |
--- /dev/null |
+++ b/tools/traffic_annotation/auditor/traffic_annotation_auditor.cc |
@@ -0,0 +1,360 @@ |
+// Copyright 2017 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+// See README.md for information and build instructions. |
+ |
+#include "base/command_line.h" |
+#include "base/files/file_enumerator.h" |
+#include "base/files/file_path.h" |
+#include "base/files/file_util.h" |
+#include "base/files/scoped_temp_dir.h" |
+#include "base/logging.h" |
+#include "base/process/launch.h" |
+#include "base/strings/string_number_conversions.h" |
+#include "base/strings/string_split.h" |
+#include "base/strings/stringprintf.h" |
+#include "net/traffic_annotation/network_traffic_annotation.h" |
+#include "third_party/protobuf/src/google/protobuf/text_format.h" |
+ |
+#include "tools/traffic_annotation/traffic_annotation.pb.h" |
+ |
+namespace { |
+ |
+// Holds an instance of network traffic annotation. |
+struct AnnotationInstance { |
+ AnnotationInstance() : state(STATE_OK) {} |
+ |
+ // Protobuf of the annotation. |
+ traffic_annotation::NetworkTrafficAnnotation proto; |
+ // Unique id of the annotation. |
+ std::string unique_id; |
+ // The function that is called with this annotation. |
+ std::string callee; |
+ // State of this annotation. |
+ enum State { STATE_OK, STATE_DUPLICATE, STATE_UNDEFINED } state; |
+}; |
+ |
+// Runs clang tool, returns true if all steps are successful. |
+bool RunClangTool(const base::FilePath& src_dir, |
+ const base::FilePath& build_dir, |
+ const base::FilePath& output_dir, |
+ const base::CommandLine::StringVector& path_filters) { |
+#ifdef _WIN32 |
+ // Run generate_win_compdb.py on Windows to repair compdb before running clang |
+ // tool. |
+ base::CommandLine::StringVector argv; |
+ argv.push_back("python"); |
+ argv.push_back( |
+ src_dir.AppendASCII("tools/clang/scripts/generate_win_compdb.py") |
+ .MaybeAsASCII()); |
+ argv.push_back(build_dir.MaybeAsASCII()); |
+ int exit_code = -1; |
+ base::Process process = |
+ base::LaunchProcess(base::CommandLine(argv), base::LaunchOptions()); |
+ if (!process.IsValid() || !process.WaitForExit(&exit_code) || exit_code) { |
+ LOG(INFO) << " Executing generate_win_compdb failed.\n"; |
+ return false; |
+ } |
+#endif |
+ |
+ for (auto& path : path_filters) { |
+ // Create commandline based on OS. |
+ base::CommandLine::StringVector argv; |
+#ifdef _WIN32 |
+ argv.push_back("python"); |
+ argv.push_back( |
+ src_dir.AppendASCII("tools/clang/scripts/run_tool.py").MaybeAsASCII()); |
+#else |
+ argv.push_back( |
+ src_dir.AppendASCII("tools/clang/scripts/run_tool.py").MaybeAsASCII()); |
+ argv.push_back("--generate-compdb"); |
+#endif |
+ argv.push_back("traffic_annotation_extractor"); |
+ argv.push_back(build_dir.MaybeAsASCII()); |
+ argv.push_back(path); // TODO(rhalavati@): CHECK ON WINDOWS FOR UNICODE |
+ argv.push_back(base::StringPrintf("--tool-args=%s", |
+ output_dir.MaybeAsASCII().c_str())); |
+ |
+ int exit_code = -1; |
+ base::Process process = |
+ base::LaunchProcess(base::CommandLine(argv), base::LaunchOptions()); |
+ if (!process.IsValid() || !process.WaitForExit(&exit_code) || exit_code) { |
+ LOG(ERROR) << "Executing clang tool failed.\n"; |
+ return false; |
+ } |
+ } |
+ return true; |
+} |
+ |
+// Reads an extracted annotation file and returns it in the output variables. |
+// The file starts with six |header_lines| with the following meaning: |
+// 0- File path. |
+// 1- Name of the function that is called using annotation. |
+// 2- Line number. |
+// 3- Name of the function including this position. |
+// 4- Error text associated with this annotation. |
+// 5- Unique id of annotation. |
+// The rest of the file is the protobuf text (|msg_text|). |
+bool ReadFile(const base::FilePath& file_path, |
+ std::vector<std::string>* header_lines, |
+ std::string* msg_text) { |
+ std::string file_content; |
+ if (!base::ReadFileToString(file_path, &file_content)) |
+ return false; |
+ |
+ header_lines->clear(); |
+ msg_text->clear(); |
+ |
+ std::vector<std::string> tokens = base::SplitString( |
+ file_content, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL); |
+ |
+ // If enough data is extracted, populate outputs, otherwise leave them blank. |
+ if (tokens.size() > 6) { |
+ for (int i = 0; i < 6; i++) |
+ header_lines->push_back(tokens[i]); |
+ for (size_t i = 6; i < tokens.size(); i++) |
+ *msg_text += tokens[i] + "\n"; |
+ } |
+ |
+ return true; |
+} |
+ |
+// Reads all extracted txt files from given input folder and populates instances |
+// and errors. Errors include not finding the file, incorrect content, or error |
+// passed from clang tool. |
+void ReadExtractedFiles(const base::FilePath& folder_name, |
+ std::vector<AnnotationInstance>* instances, |
+ std::vector<std::string>* errors) { |
+#ifdef _WIN32 |
+ base::FileEnumerator file_iter(folder_name, false, |
+ base::FileEnumerator::FILES, L"*.txt"); |
+#else |
+ base::FileEnumerator file_iter(folder_name, false, |
+ base::FileEnumerator::FILES, "*.txt"); |
+#endif |
+ while (!file_iter.Next().empty()) { |
+ std::string file_name = file_iter.GetInfo().GetName().AsUTF8Unsafe(); |
+ LOG(INFO) << "\tReading " << file_name.c_str() << "...\n"; |
+ |
+ std::vector<std::string> header_lines; |
+ std::string msg_text; |
+ if (!ReadFile(folder_name.Append(file_iter.GetInfo().GetName()), |
+ &header_lines, &msg_text)) { |
+ errors->push_back( |
+ base::StringPrintf("Could not open file '%s'\n.", file_name.c_str())); |
+ continue; |
+ } |
+ if (header_lines.size() < 6) { |
+ errors->push_back(base::StringPrintf( |
+ "Header lines are not complete for file '%s'\n.", file_name.c_str())); |
+ continue; |
+ } |
+ if (!header_lines[4].empty()) { |
+ errors->push_back( |
+ base::StringPrintf("Error passed from extractor for file '%s': %s\n.", |
+ file_name.c_str(), header_lines[4].c_str())); |
+ continue; |
+ } |
+ |
+ AnnotationInstance new_instance; |
+ if (header_lines[5] == "\"Undefined\"") { |
+ new_instance.state = AnnotationInstance::STATE_UNDEFINED; |
+ } else if (!google::protobuf::TextFormat::ParseFromString( |
+ msg_text, (google::protobuf::Message*)&new_instance)) { |
+ errors->push_back(base::StringPrintf( |
+ "Could not parse protobuf for file '%s'\n.", file_name.c_str())); |
+ continue; |
+ } |
+ |
+ // Add header data to new instance. |
+ traffic_annotation::NetworkTrafficAnnotation_TrafficSource* src = |
+ new_instance.proto.mutable_source(); |
+ src->set_file(header_lines[0]); |
+ src->set_function(header_lines[1]); |
+ int line; |
+ base::StringToInt(header_lines[2], &line); |
+ src->set_line(line); |
+ new_instance.callee = header_lines[3]; |
+ new_instance.unique_id = header_lines[5]; |
+ instances->push_back(new_instance); |
+ } |
+ LOG(INFO) << instances->size() << " annotation instance(s) read.\n"; |
+} |
+ |
+// Checks to see if unique ids are really unique and marks the ones which are |
+// not. |
+void MarkRepeatedUniqueIds(std::vector<AnnotationInstance>* instances) { |
+ std::map<std::string, AnnotationInstance*> unique_ids; |
+ for (auto& instance : *instances) { |
+ if (instance.state == AnnotationInstance::STATE_OK) { |
+ auto match = unique_ids.find(instance.unique_id); |
+ if (match != unique_ids.end()) |
+ instance.state = match->second->state = |
+ AnnotationInstance::STATE_DUPLICATE; |
+ else |
+ unique_ids.insert( |
+ std::make_pair(std::string(instance.unique_id), &instance)); |
+ } |
+ } |
+} |
+ |
+// Writes summary of annotation instances to a file. Returns true if successful. |
+bool WriteSummaryFile(const std::vector<AnnotationInstance>& instances, |
+ const std::vector<std::string>& errors, |
+ const base::FilePath& file_path) { |
+ std::string report = ""; |
+ |
+ if (errors.size()) { |
+ report = "[Errors]\n"; |
+ |
+ for (const auto& error : errors) |
+ report += error + "\n"; |
+ } |
+ |
+ report += "[Annotations]\n"; |
+ |
+ for (const auto& instance : instances) { |
+ report += |
+ "------------------------------------------------------------" |
+ "--------------------\n"; |
+ report += base::StringPrintf("Unique ID: %s\n", instance.unique_id.c_str()); |
+ if (instance.state == AnnotationInstance::STATE_UNDEFINED) |
+ report += base::StringPrintf("WARNING: Undefined annotation.\n"); |
+ else if (instance.state == AnnotationInstance::STATE_DUPLICATE) |
+ report += base::StringPrintf("WARNING: Duplicate unique id.\n"); |
+ report += |
+ base::StringPrintf("Calling Function: %s\n", instance.callee.c_str()); |
+ std::string temp; |
+ google::protobuf::TextFormat::PrintToString(instance.proto, &temp); |
+ report += base::StringPrintf("Content:\n%s\n", temp.c_str()); |
+ } |
+ |
+ if (base::WriteFile(file_path, report.c_str(), report.length()) == -1) { |
+ LOG(INFO) << " Could not create output file: " |
+ << file_path.MaybeAsASCII().c_str() << ".\n"; |
+ return false; |
+ } |
+ |
+ LOG(INFO) << "Output file " << file_path.MaybeAsASCII().c_str() |
+ << " written for " << instances.size() << " instances.\n"; |
+ return true; |
+} |
+ |
+} // namespace |
+ |
+#ifdef _WIN32 |
+int wmain(int argc, wchar_t* argv[]) { |
+#else |
+int main(int argc, char* argv[]) { |
+#endif |
+ // Parse switches. |
+ base::CommandLine command_line = base::CommandLine(argc, argv); |
+ if (command_line.HasSwitch("help") || command_line.HasSwitch("h") || |
+ argc == 1) { |
+ LOG(INFO) |
+ << "Usage: traffic_annotation_auditor [OPTION]... [path_filter]...\n" |
+ "Extracts network traffic annotations from source files. If path " |
+ "filter(s) are specified, only those directories of the source " |
+ "will be analyzed.\n" |
+ "Options:\n" |
+ " -h, --help Shows help.\n" |
+ " --build-dir Path to the build directory from which " |
+ "the\n" |
+ " annotations will be extracted.\n" |
+ " --extractor-output-dir Path to the directory that extracted\n" |
+ " partial files will be written to. " |
+ "Will\n" |
+ " be automatically generated and " |
+ "deleted\n" |
+ " if not specified.\n" |
+ " --extracted-input-dir Path to a directory where extracted\n" |
+ " partial annotations are already " |
+ "stored.\n" |
+ " If specified, build directory will be\n" |
+ " ignored.\n" |
+ " --summary-file Path to an output file with summary of\n" |
+ " extracted annotations.\n" |
+ "Example:\n" |
+ " traffic_annotation_auditor --build-dir=out/Debug\n" |
+ " --summary-file=report.txt\n"; |
+ return 1; |
+ } |
+ base::FilePath extracted_files_dir = |
+ command_line.GetSwitchValuePath("extracted-input-dir"); |
+ base::FilePath build_dir = command_line.GetSwitchValuePath("build-dir"); |
+ base::FilePath extractor_output_dir = |
+ command_line.GetSwitchValuePath("extractor-output-dir"); |
+ base::FilePath summary_file = command_line.GetSwitchValuePath("summary-file"); |
+ base::ScopedTempDir temp_dir; |
+ |
+ if (summary_file.empty()) |
+ LOG(ERROR) << "WARNING: Output file not specified.\n"; |
+ |
+ // Extract annotations. |
+ if (extracted_files_dir.empty()) { |
+ // Get build directory, if it is empty issue an error. |
+ if (build_dir.empty()) { |
+ LOG(ERROR) |
+ << "You must either specify build directory to run clang tool and " |
+ "extract annotations, or specify extracted files's input " |
+ "directory where already extracted files exist.\n"; |
+ return 1; |
+ } |
+ // If output directory is not provided, create a temporary one. |
+ if (extractor_output_dir.empty()) { |
+ if (!temp_dir.CreateUniqueTempDirUnderPath(build_dir)) { |
+ LOG(ERROR) << "Could not create temporary directory in " |
+ << build_dir.MaybeAsASCII().c_str() << ".\n"; |
+ return 1; |
+ } |
+ extractor_output_dir = temp_dir.GetPath(); |
+ } else { |
+ // Ensure given directory is empty. |
+ if (!base::FileEnumerator(extractor_output_dir, false, |
+ base::FileEnumerator::FILES) |
+ .Next() |
+ .empty()) { |
+ LOG(ERROR) << "Output directory " |
+ << extractor_output_dir.MaybeAsASCII().c_str() |
+ << "should be empty .\n"; |
+ return 1; |
+ } |
+ } |
+ |
+ // Get path filters, if none is provided, just add all. |
+ base::CommandLine::StringVector path_filters = command_line.GetArgs(); |
+ if (!path_filters.size()) { |
+ base::FilePath temp; |
+ path_filters.push_back(temp.AppendASCII("./").value().c_str()); |
+ } |
+ |
+ // Eexcutable is usually in out/[Build Dir], so the path to source is |
+ // extracted by moving two directories up. |
+ if (!RunClangTool(command_line.GetProgram().DirName().AppendASCII("../.."), |
+ build_dir, extractor_output_dir, path_filters)) { |
+ return 1; |
+ } |
+ |
+ extracted_files_dir = extractor_output_dir; |
+ } |
+ |
+ // Read all extracted files. |
+ std::vector<AnnotationInstance> instances; |
+ std::vector<std::string> errors; |
+ ReadExtractedFiles(extracted_files_dir, &instances, &errors); |
+ |
+ if (instances.empty()) { |
+ LOG(ERROR) << "Could not read any file.\n"; |
+ return 1; |
+ } else { |
+ MarkRepeatedUniqueIds(&instances); |
+ } |
+ |
+ // Create Summary file if requested. |
+ if (!summary_file.empty() && |
+ !WriteSummaryFile(instances, errors, summary_file)) |
+ return 1; |
+ |
+ return 0; |
+} |