blob: 6565cf816cf660115840f872e9dfd779ff292817 [file] [log] [blame]
// Copyright (c) 2011 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.
// A general interface for filtering and only acting on classes in Chromium C++
// code.
#include "ChromeClassTester.h"
using namespace clang;
namespace {
bool starts_with(const std::string& one, const std::string& two) {
return one.substr(0, two.size()) == two;
}
bool ends_with(const std::string& one, const std::string& two) {
if (two.size() > one.size())
return false;
return one.substr(one.size() - two.size(), two.size()) == two;
}
} // namespace
ChromeClassTester::ChromeClassTester(CompilerInstance& instance)
: instance_(instance),
diagnostic_(instance.getDiagnostics()) {
banned_namespaces_.push_back("std");
banned_namespaces_.push_back("__gnu_cxx");
banned_directories_.push_back("third_party");
banned_directories_.push_back("native_client");
banned_directories_.push_back("breakpad");
banned_directories_.push_back("courgette");
banned_directories_.push_back("ppapi");
banned_directories_.push_back("/usr");
banned_directories_.push_back("testing");
banned_directories_.push_back("googleurl");
banned_directories_.push_back("v8");
banned_directories_.push_back("sdch");
// You are standing in a mazy of twisty dependencies, all resolved by
// putting everything in the header.
banned_directories_.push_back("chrome/test/automation");
// Used in really low level threading code that probably shouldn't be out of
// lined.
ignored_record_names_.push_back("ThreadLocalBoolean");
// A complicated pickle derived struct that is all packed integers.
ignored_record_names_.push_back("Header");
// Part of the GPU system that uses multiple included header
// weirdness. Never getting this right.
ignored_record_names_.push_back("Validators");
// RAII class that's simple enough (media/base/callback.h).
ignored_record_names_.push_back("AutoTaskRunner");
ignored_record_names_.push_back("AutoCallbackRunner");
// Has a UNIT_TEST only constructor. Isn't *terribly* complex...
ignored_record_names_.push_back("AutocompleteController");
ignored_record_names_.push_back("HistoryURLProvider");
// Because of chrome frame
ignored_record_names_.push_back("ReliabilityTestSuite");
// Used over in the net unittests. A large enough bundle of integers with 1
// non-pod class member. Probably harmless.
ignored_record_names_.push_back("MockTransaction");
// Used heavily in app_unittests and once in views_unittests. Fixing this
// isn't worth the overhead of an additional library.
ignored_record_names_.push_back("TestAnimationDelegate");
// Part of our public interface that nacl and friends use. (Arguably, this
// should mean that this is a higher priority but fixing this looks hard.)
ignored_record_names_.push_back("PluginVersionInfo");
}
ChromeClassTester::~ChromeClassTester() {}
void ChromeClassTester::HandleTagDeclDefinition(TagDecl* tag) {
if (CXXRecordDecl* record = dyn_cast<CXXRecordDecl>(tag)) {
// If this is a POD or a class template or a type dependent on a
// templated class, assume there's no ctor/dtor/virtual method
// optimization that we can do.
if (record->isPOD() ||
record->getDescribedClassTemplate() ||
record->getTemplateSpecializationKind() ||
record->isDependentType())
return;
if (InBannedNamespace(record))
return;
SourceLocation record_location = record->getInnerLocStart();
if (InBannedDirectory(record_location))
return;
// We sadly need to maintain a blacklist of types that violate these
// rules, but do so for good reason or due to limitations of this
// checker (i.e., we don't handle extern templates very well).
std::string base_name = record->getNameAsString();
if (IsIgnoredType(base_name))
return;
// We ignore all classes that end with "Matcher" because they're probably
// GMock artifacts.
if (ends_with(base_name, "Matcher"))
return;
CheckChromeClass(record_location, record);
}
}
void ChromeClassTester::emitWarning(SourceLocation loc, const char* raw_error) {
FullSourceLoc full(loc, instance().getSourceManager());
std::string err;
err = "[chromium-style] ";
err += raw_error;
unsigned id = diagnostic().getCustomDiagID(Diagnostic::Warning, err);
DiagnosticBuilder B = diagnostic().Report(full, id);
}
bool ChromeClassTester::InTestingNamespace(Decl* record) {
return GetNamespace(record).find("testing") != std::string::npos;
}
bool ChromeClassTester::InBannedNamespace(Decl* record) {
std::string n = GetNamespace(record);
if (n != "") {
return std::find(banned_namespaces_.begin(), banned_namespaces_.end(), n)
!= banned_namespaces_.end();
}
return false;
}
std::string ChromeClassTester::GetNamespace(Decl* record) {
return GetNamespaceImpl(record->getDeclContext(), "");
}
std::string ChromeClassTester::GetNamespaceImpl(const DeclContext* context,
std::string candidate) {
switch (context->getDeclKind()) {
case Decl::TranslationUnit: {
return candidate;
}
case Decl::Namespace: {
const NamespaceDecl* decl = dyn_cast<NamespaceDecl>(context);
std::string name_str;
llvm::raw_string_ostream OS(name_str);
if (decl->isAnonymousNamespace())
OS << "<anonymous namespace>";
else
OS << decl;
return GetNamespaceImpl(context->getParent(),
OS.str());
}
default: {
return GetNamespaceImpl(context->getParent(), candidate);
}
}
}
bool ChromeClassTester::InBannedDirectory(const SourceLocation& loc) {
if (loc.isFileID() && loc.isValid()) {
bool invalid = false;
const char* buffer_name = instance_.getSourceManager().getBufferName(
loc, &invalid);
if (!invalid && buffer_name) {
std::string b(buffer_name);
// Don't complain about these things in implementation files.
if (ends_with(b, ".cc") || ends_with(b, ".cpp") || ends_with(b, ".mm")) {
return true;
}
// Don't complain about autogenerated protobuf files.
if (ends_with(b, ".pb.h")) {
return true;
}
// Strip out all preceding path garbage. Linux and mac builds have
// different path garbage, but after doing this, the path should be
// relative to the root of the source tree. (If we didn't require
// relative paths, we could have just used realpath().)
if (!b.empty() && b[0] != '/') {
size_t i = 0;
for (; i < b.size() && (b[i] == '.' || b[i] == '/'); ++i) {}
b = b.substr(i);
}
for (std::vector<std::string>::const_iterator it =
banned_directories_.begin();
it != banned_directories_.end(); ++it) {
if (starts_with(b, *it))
return true;
}
}
}
return false;
}
bool ChromeClassTester::IsIgnoredType(const std::string& base_name) {
for (std::vector<std::string>::const_iterator it =
ignored_record_names_.begin();
it != ignored_record_names_.end(); ++it) {
if (base_name == *it)
return true;
}
return false;
}