blob: 5d68093529ed91c454f2b6093b658303ee8e236c [file] [log] [blame]
// 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.
// This clang tool does the following three tasks:
// 1) Finds all instances of the following functions and extracts the location
// info and content of annotation tags:
// - net::DefineNetworkTrafficAnnotation
// - net::DefinePartialNetworkTrafficAnnotation
// - net::CompleteNetworkTrafficAnnotation
// - net::BranchedCompleteNetworkTrafficAnnotation
// 2) Extracts all calls of the following network request creation functions
// and returns their source location and availability of a
// net::[Partial]NetworkTrafficAnnotation parameter in them:
// - URLFetcher::Create
// - URLRequestContext::CreateRequest
// 3) Finds all instances of initializing any of the following classes with list
// expressions or assignment of a value to |unique_id_hash_code| of the
// mutable ones, outside traffic annotation API functions:
// - net::NetworkTrafficAnnotationTag
// - net::PartialNetworkTrafficAnnotationTag
// - net::MutableNetworkTrafficAnnotationTag
// - net::MutablePartialNetworkTrafficAnnotationTag
// All outputs are written to to llvm::outs.
// Please refer to README.md for build and usage instructions.
#include <memory>
#include <vector>
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Frontend/FrontendActions.h"
#include "clang/Lex/Lexer.h"
#include "clang/Tooling/CommonOptionsParser.h"
#include "clang/Tooling/Refactoring.h"
#include "clang/Tooling/Tooling.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/TargetSelect.h"
using namespace clang::ast_matchers;
namespace {
// Information about location of a line of code.
struct Location {
std::string file_path;
int line_number = -1;
// Name of the function including this line. E.g., in the following code,
// |function_name| will be 'foo' for all |line_number| values 101-103.
//
// 100 void foo() {
// 101 NetworkTrafficAnnotationTag baz =
// 102 net::DefineNetworkTrafficAnnotation(...); }
// 103 bar(baz);
// 104 }
// If no function is found, 'Global Namespace' will be returned.
std::string function_name;
};
// An instance of a call to either of the 4 network traffic annotation
// definition functions.
struct NetworkAnnotationInstance {
// Annotation content. These are the arguments of the call to either of the 4
// network traffic annotation definition functions.
struct Annotation {
std::string unique_id;
std::string text;
// |extra_id| will have |completing_id| for
// net::DefinePartialNetworkTrafficAnnotation and |group_id| for
// net::BranchedCompleteNetworkTrafficAnnotation. It will be empty in other
// cases.
std::string extra_id;
};
Location location;
Annotation annotation;
// Specifying the function type.
enum FunctionType {
kDefinition, // net::DefineNetworkTrafficAnnotation
kPartial, // net::DefinePartialNetworkTrafficAnnotation
kCompleting, // net::CompleteNetworkTrafficAnnotation
kBranchedCompleting // net::BranchedCompleteNetworkTrafficAnnotation
};
FunctionType function_type;
const char* GetTypeName() const {
switch (function_type) {
case kDefinition:
return "Definition";
case kPartial:
return "Partial";
case kCompleting:
return "Completing";
case kBranchedCompleting:
return "BranchedCompleting";
}
assert(false);
return "";
}
};
// An instance of a call to one of the monitored function.
struct CallInstance {
// Location of the call.
Location location;
// Whether the function is annotated.
bool has_annotation = false;
// Name of the called function.
std::string called_function_name;
};
// A structure to keep detected annotation and call instances, and all code
// locations that include a direct value assignment to annotations using list
// expression constructors or mutable annotations' |unique_id_hash_code|.
struct Collector {
std::vector<NetworkAnnotationInstance> annotations;
std::vector<CallInstance> calls;
std::vector<Location> assignments;
};
// This class implements the call back functions for AST Matchers. The matchers
// are defined in RunMatchers function. When a pattern is found there,
// the run function in this class is called back with information on the matched
// location and description of the matched pattern.
class NetworkAnnotationTagCallback : public MatchFinder::MatchCallback {
public:
explicit NetworkAnnotationTagCallback(Collector* collector)
: collector_(collector) {}
~NetworkAnnotationTagCallback() override = default;
// Is called on any pattern found by ASTMathers that are defined in RunMathers
// function.
virtual void run(const MatchFinder::MatchResult& result) override {
if (const clang::CallExpr* call_expr =
result.Nodes.getNodeAs<clang::CallExpr>("monitored_function")) {
AddFunction(call_expr, result);
} else if (const clang::CXXConstructExpr* constructor_expr =
result.Nodes.getNodeAs<clang::CXXConstructExpr>(
"annotation_constructor")) {
AddConstructor(constructor_expr, result);
} else if (const clang::MemberExpr* member_expr =
result.Nodes.getNodeAs<clang::MemberExpr>(
"direct_assignment")) {
AddAssignment(member_expr, result);
} else {
AddAnnotation(result);
}
}
void GetInstanceLocation(const MatchFinder::MatchResult& result,
const clang::Expr* expr,
Location* location) {
clang::SourceLocation source_location = expr->getLocStart();
if (source_location.isMacroID())
source_location = result.SourceManager->getExpansionLoc(source_location);
location->file_path = result.SourceManager->getFilename(source_location);
location->line_number =
result.SourceManager->getSpellingLineNumber(source_location);
const clang::FunctionDecl* ancestor =
result.Nodes.getNodeAs<clang::FunctionDecl>("function_context");
if (ancestor)
location->function_name = ancestor->getQualifiedNameAsString();
else
location->function_name = "Global Namespace";
std::replace(location->file_path.begin(), location->file_path.end(), '\\',
'/');
// Trim leading "../"s from file path.
while (location->file_path.length() > 3 &&
location->file_path.substr(0, 3) == "../") {
location->file_path =
location->file_path.substr(3, location->file_path.length() - 3);
}
}
// Stores a function call that should be monitored.
void AddFunction(const clang::CallExpr* call_expr,
const MatchFinder::MatchResult& result) {
CallInstance instance;
GetInstanceLocation(result, call_expr, &instance.location);
instance.called_function_name =
call_expr->getDirectCallee()->getQualifiedNameAsString();
instance.has_annotation =
(result.Nodes.getNodeAs<clang::RecordDecl>("annotation") != nullptr);
collector_->calls.push_back(instance);
}
// Tests if the given function name belongs to the network traffic annotation
// API. These functions are all defined in
// 'net/traffic_annotation/network_traffic_annotation.h'.
bool IsAPIFunction(const std::string& function_name) {
return function_name == "net::NetworkTrafficAnnotationTag::NotReached" ||
function_name == "net::DefineNetworkTrafficAnnotation" ||
function_name == "net::DefinePartialNetworkTrafficAnnotation" ||
function_name == "net::CompleteNetworkTrafficAnnotation" ||
function_name == "net::BranchedCompleteNetworkTrafficAnnotation" ||
function_name ==
"net::MutableNetworkTrafficAnnotationTag::operator "
"NetworkTrafficAnnotationTag" ||
function_name ==
"net::MutablePartialNetworkTrafficAnnotationTag::operator "
"PartialNetworkTrafficAnnotationTag";
}
// Stores an annotation constructor called with list expression.
void AddConstructor(const clang::CXXConstructExpr* constructor_expr,
const MatchFinder::MatchResult& result) {
Location instance;
GetInstanceLocation(result, constructor_expr, &instance);
// Only report if the constructor is not in one of the API functions for
// network traffic annotations.
if (!IsAPIFunction(instance.function_name))
collector_->assignments.push_back(instance);
}
// Stores a value assignment to |unique_id_hash_code| of a mutable annotaton.
void AddAssignment(const clang::MemberExpr* member_expr,
const MatchFinder::MatchResult& result) {
Location instance;
GetInstanceLocation(result, member_expr, &instance);
// Only report if the assignment is not in one of the API functions for
// network traffic annotations.
if (!IsAPIFunction(instance.function_name))
collector_->assignments.push_back(instance);
}
// Stores an annotation.
void AddAnnotation(const MatchFinder::MatchResult& result) {
NetworkAnnotationInstance instance;
const clang::StringLiteral* unique_id =
result.Nodes.getNodeAs<clang::StringLiteral>("unique_id");
const clang::StringLiteral* annotation_text =
result.Nodes.getNodeAs<clang::StringLiteral>("annotation_text");
const clang::StringLiteral* group_id =
result.Nodes.getNodeAs<clang::StringLiteral>("group_id");
const clang::StringLiteral* completing_id =
result.Nodes.getNodeAs<clang::StringLiteral>("completing_id");
const clang::CallExpr* call_expr = nullptr;
if ((call_expr =
result.Nodes.getNodeAs<clang::CallExpr>("definition_function"))) {
instance.function_type = NetworkAnnotationInstance::kDefinition;
} else if ((call_expr = result.Nodes.getNodeAs<clang::CallExpr>(
"partial_function"))) {
instance.function_type = NetworkAnnotationInstance::kPartial;
assert(completing_id);
instance.annotation.extra_id = completing_id->getString();
} else if ((call_expr = result.Nodes.getNodeAs<clang::CallExpr>(
"completing_function"))) {
instance.function_type = NetworkAnnotationInstance::kCompleting;
} else if ((call_expr = result.Nodes.getNodeAs<clang::CallExpr>(
"branched_completing_function"))) {
instance.function_type = NetworkAnnotationInstance::kBranchedCompleting;
assert(group_id);
instance.annotation.extra_id = group_id->getString();
} else {
assert(false);
}
assert(unique_id && annotation_text);
instance.annotation.unique_id = unique_id->getString();
instance.annotation.text = annotation_text->getString();
GetInstanceLocation(result, call_expr, &instance.location);
collector_->annotations.push_back(instance);
}
private:
Collector* collector_;
};
// Sets up an ASTMatcher and runs clang tool to populate collector. Returns the
// result of running the clang tool.
int RunMatchers(clang::tooling::ClangTool* clang_tool, Collector* collector) {
NetworkAnnotationTagCallback callback(collector);
MatchFinder match_finder;
// Set up patterns to find network traffic annotation definition functions,
// their arguments, and their ancestor function (when possible).
auto bind_function_context_if_present =
anyOf(hasAncestor(functionDecl().bind("function_context")),
unless(hasAncestor(functionDecl())));
auto has_annotation_parameter = anyOf(
hasAnyParameter(hasType(
recordDecl(anyOf(hasName("net::NetworkTrafficAnnotationTag"),
hasName("net::PartialNetworkTrafficAnnotationTag")))
.bind("annotation"))),
unless(hasAnyParameter(hasType(recordDecl(
anyOf(hasName("net::NetworkTrafficAnnotationTag"),
hasName("net::PartialNetworkTrafficAnnotationTag")))))));
match_finder.addMatcher(
callExpr(hasDeclaration(functionDecl(
anyOf(hasName("DefineNetworkTrafficAnnotation"),
hasName("net::DefineNetworkTrafficAnnotation")))),
hasArgument(0, stringLiteral().bind("unique_id")),
hasArgument(1, stringLiteral().bind("annotation_text")),
bind_function_context_if_present)
.bind("definition_function"),
&callback);
match_finder.addMatcher(
callExpr(hasDeclaration(functionDecl(anyOf(
hasName("DefinePartialNetworkTrafficAnnotation"),
hasName("net::DefinePartialNetworkTrafficAnnotation")))),
hasArgument(0, stringLiteral().bind("unique_id")),
hasArgument(1, stringLiteral().bind("completing_id")),
hasArgument(2, stringLiteral().bind("annotation_text")),
bind_function_context_if_present)
.bind("partial_function"),
&callback);
match_finder.addMatcher(
callExpr(hasDeclaration(functionDecl(
anyOf(hasName("CompleteNetworkTrafficAnnotation"),
hasName("net::CompleteNetworkTrafficAnnotation")))),
hasArgument(0, stringLiteral().bind("unique_id")),
hasArgument(2, stringLiteral().bind("annotation_text")),
bind_function_context_if_present)
.bind("completing_function"),
&callback);
match_finder.addMatcher(
callExpr(hasDeclaration(functionDecl(anyOf(
hasName("BranchedCompleteNetworkTrafficAnnotation"),
hasName("net::BranchedCompleteNetworkTrafficAnnotation")))),
hasArgument(0, stringLiteral().bind("unique_id")),
hasArgument(1, stringLiteral().bind("group_id")),
hasArgument(3, stringLiteral().bind("annotation_text")),
bind_function_context_if_present)
.bind("branched_completing_function"),
&callback);
// Setup patterns to find functions that should be monitored.
match_finder.addMatcher(
callExpr(hasDeclaration(functionDecl(
anyOf(hasName("URLFetcher::Create"),
hasName("URLRequestContext::CreateRequest")),
has_annotation_parameter)),
bind_function_context_if_present)
.bind("monitored_function"),
&callback);
// Setup patterns to find constructors of different network traffic annotation
// tags that are initialized by list expressions.
match_finder.addMatcher(
cxxConstructExpr(
hasDeclaration(functionDecl(
anyOf(hasName("net::NetworkTrafficAnnotationTag::"
"NetworkTrafficAnnotationTag"),
hasName("net::PartialNetworkTrafficAnnotationTag::"
"PartialNetworkTrafficAnnotationTag"),
hasName("net::MutableNetworkTrafficAnnotationTag::"
"MutableNetworkTrafficAnnotationTag"),
hasName("net::MutablePartialNetworkTrafficAnnotationTag::"
"MutablePartialNetworkTrafficAnnotationTag")))),
hasDescendant(initListExpr()), bind_function_context_if_present)
.bind("annotation_constructor"),
&callback);
// Setup pattern to find direct assignment of value to |unique_id_hash_code|
// of net::MutableNetworkTrafficAnnotationTag or
// net::MutablePartialNetworkTrafficAnnotationTag.
match_finder.addMatcher(
memberExpr(
member(hasName("unique_id_hash_code")),
hasObjectExpression(hasType(cxxRecordDecl(anyOf(
hasName("net::MutableNetworkTrafficAnnotationTag"),
hasName("net::MutablePartialNetworkTrafficAnnotationTag"))))),
hasParent(binaryOperator(hasOperatorName("="))),
bind_function_context_if_present)
.bind("direct_assignment"),
&callback);
std::unique_ptr<clang::tooling::FrontendActionFactory> frontend_factory =
clang::tooling::newFrontendActionFactory(&match_finder);
return clang_tool->run(frontend_factory.get());
}
} // namespace
static llvm::cl::OptionCategory ToolCategory(
"traffic_annotation_extractor: Extract traffic annotation texts");
static llvm::cl::extrahelp CommonHelp(
clang::tooling::CommonOptionsParser::HelpMessage);
int main(int argc, const char* argv[]) {
clang::tooling::CommonOptionsParser options(argc, argv, ToolCategory);
clang::tooling::ClangTool tool(options.getCompilations(),
options.getSourcePathList());
Collector collector;
llvm::InitializeNativeTarget();
llvm::InitializeNativeTargetAsmParser();
int result = RunMatchers(&tool, &collector);
if (result != 0)
return result;
// For each call to any of the functions that define a network traffic
// annotation, write annotation text and relevant meta data into llvm::outs().
for (const NetworkAnnotationInstance& instance : collector.annotations) {
llvm::outs() << "==== NEW ANNOTATION ====\n";
llvm::outs() << instance.location.file_path << "\n";
llvm::outs() << instance.location.function_name << "\n";
llvm::outs() << instance.location.line_number << "\n";
llvm::outs() << instance.GetTypeName() << "\n";
llvm::outs() << instance.annotation.unique_id << "\n";
llvm::outs() << instance.annotation.extra_id << "\n";
llvm::outs() << instance.annotation.text << "\n";
llvm::outs() << "==== ANNOTATION ENDS ====\n";
}
// For each call, write annotation text and relevant meta data.
for (const CallInstance& instance : collector.calls) {
llvm::outs() << "==== NEW CALL ====\n";
llvm::outs() << instance.location.file_path << "\n";
llvm::outs() << instance.location.function_name << "\n";
llvm::outs() << instance.location.line_number << "\n";
llvm::outs() << instance.called_function_name << "\n";
llvm::outs() << instance.has_annotation << "\n";
llvm::outs() << "==== CALL ENDS ====\n";
}
// For each assignment, write relevant meta data.
for (const Location& instance : collector.assignments) {
llvm::outs() << "==== NEW ASSIGNMENT ====\n";
llvm::outs() << instance.file_path << "\n";
llvm::outs() << instance.function_name << "\n";
llvm::outs() << instance.line_number << "\n";
llvm::outs() << "==== ASSIGNMENT ENDS ====\n";
}
return 0;
}