michael@0: // Copyright (c) 2012 The Chromium Authors. All rights reserved. michael@0: // Use of this source code is governed by a BSD-style license that can be michael@0: // found in the LICENSE file. michael@0: michael@0: // A general interface for filtering and only acting on classes in Chromium C++ michael@0: // code. michael@0: michael@0: #include "ChromeClassTester.h" michael@0: michael@0: #include michael@0: michael@0: #include "clang/AST/AST.h" michael@0: #include "clang/Basic/FileManager.h" michael@0: #include "clang/Basic/SourceManager.h" michael@0: michael@0: using namespace clang; michael@0: michael@0: namespace { michael@0: michael@0: bool starts_with(const std::string& one, const std::string& two) { michael@0: return one.compare(0, two.size(), two) == 0; michael@0: } michael@0: michael@0: std::string lstrip(const std::string& one, const std::string& two) { michael@0: if (starts_with(one, two)) michael@0: return one.substr(two.size()); michael@0: return one; michael@0: } michael@0: michael@0: bool ends_with(const std::string& one, const std::string& two) { michael@0: if (two.size() > one.size()) michael@0: return false; michael@0: michael@0: return one.compare(one.size() - two.size(), two.size(), two) == 0; michael@0: } michael@0: michael@0: } // namespace michael@0: michael@0: ChromeClassTester::ChromeClassTester(CompilerInstance& instance) michael@0: : instance_(instance), michael@0: diagnostic_(instance.getDiagnostics()) { michael@0: BuildBannedLists(); michael@0: } michael@0: michael@0: ChromeClassTester::~ChromeClassTester() {} michael@0: michael@0: void ChromeClassTester::HandleTagDeclDefinition(TagDecl* tag) { michael@0: pending_class_decls_.push_back(tag); michael@0: } michael@0: michael@0: bool ChromeClassTester::HandleTopLevelDecl(DeclGroupRef group_ref) { michael@0: for (size_t i = 0; i < pending_class_decls_.size(); ++i) michael@0: CheckTag(pending_class_decls_[i]); michael@0: pending_class_decls_.clear(); michael@0: michael@0: return true; // true means continue parsing. michael@0: } michael@0: michael@0: void ChromeClassTester::CheckTag(TagDecl* tag) { michael@0: // We handle class types here where we have semantic information. We can only michael@0: // check structs/classes/enums here, but we get a bunch of nice semantic michael@0: // information instead of just parsing information. michael@0: michael@0: if (CXXRecordDecl* record = dyn_cast(tag)) { michael@0: // If this is a POD or a class template or a type dependent on a michael@0: // templated class, assume there's no ctor/dtor/virtual method michael@0: // optimization that we can do. michael@0: if (record->isPOD() || michael@0: record->getDescribedClassTemplate() || michael@0: record->getTemplateSpecializationKind() || michael@0: record->isDependentType()) michael@0: return; michael@0: michael@0: if (InBannedNamespace(record)) michael@0: return; michael@0: michael@0: SourceLocation record_location = record->getInnerLocStart(); michael@0: if (InBannedDirectory(record_location)) michael@0: return; michael@0: michael@0: // We sadly need to maintain a blacklist of types that violate these michael@0: // rules, but do so for good reason or due to limitations of this michael@0: // checker (i.e., we don't handle extern templates very well). michael@0: std::string base_name = record->getNameAsString(); michael@0: if (IsIgnoredType(base_name)) michael@0: return; michael@0: michael@0: // We ignore all classes that end with "Matcher" because they're probably michael@0: // GMock artifacts. michael@0: if (ends_with(base_name, "Matcher")) michael@0: return; michael@0: michael@0: CheckChromeClass(record_location, record); michael@0: } michael@0: } michael@0: michael@0: void ChromeClassTester::emitWarning(SourceLocation loc, michael@0: const char* raw_error) { michael@0: FullSourceLoc full(loc, instance().getSourceManager()); michael@0: std::string err; michael@0: err = "[chromium-style] "; michael@0: err += raw_error; michael@0: DiagnosticsEngine::Level level = michael@0: diagnostic().getWarningsAsErrors() ? michael@0: DiagnosticsEngine::Error : michael@0: DiagnosticsEngine::Warning; michael@0: unsigned id = diagnostic().getCustomDiagID(level, err); michael@0: DiagnosticBuilder builder = diagnostic().Report(full, id); michael@0: } michael@0: michael@0: bool ChromeClassTester::InBannedNamespace(const Decl* record) { michael@0: std::string n = GetNamespace(record); michael@0: if (!n.empty()) { michael@0: return std::find(banned_namespaces_.begin(), banned_namespaces_.end(), n) michael@0: != banned_namespaces_.end(); michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: std::string ChromeClassTester::GetNamespace(const Decl* record) { michael@0: return GetNamespaceImpl(record->getDeclContext(), ""); michael@0: } michael@0: michael@0: bool ChromeClassTester::InImplementationFile(SourceLocation record_location) { michael@0: std::string filename; michael@0: if (!GetFilename(record_location, &filename)) michael@0: return false; michael@0: michael@0: if (ends_with(filename, ".cc") || ends_with(filename, ".cpp") || michael@0: ends_with(filename, ".mm")) { michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: void ChromeClassTester::BuildBannedLists() { michael@0: banned_namespaces_.push_back("std"); michael@0: banned_namespaces_.push_back("__gnu_cxx"); michael@0: banned_namespaces_.push_back("WebKit"); michael@0: michael@0: banned_directories_.push_back("third_party/"); michael@0: banned_directories_.push_back("native_client/"); michael@0: banned_directories_.push_back("breakpad/"); michael@0: banned_directories_.push_back("courgette/"); michael@0: banned_directories_.push_back("pdf/"); michael@0: banned_directories_.push_back("ppapi/"); michael@0: banned_directories_.push_back("usr/"); michael@0: banned_directories_.push_back("testing/"); michael@0: banned_directories_.push_back("googleurl/"); michael@0: banned_directories_.push_back("v8/"); michael@0: banned_directories_.push_back("dart/"); michael@0: banned_directories_.push_back("sdch/"); michael@0: banned_directories_.push_back("icu4c/"); michael@0: banned_directories_.push_back("frameworks/"); michael@0: michael@0: // Don't check autogenerated headers. michael@0: // Make puts them below $(builddir_name)/.../gen and geni. michael@0: // Ninja puts them below OUTPUT_DIR/.../gen michael@0: // Xcode has a fixed output directory for everything. michael@0: banned_directories_.push_back("gen/"); michael@0: banned_directories_.push_back("geni/"); michael@0: banned_directories_.push_back("xcodebuild/"); michael@0: michael@0: // You are standing in a mazy of twisty dependencies, all resolved by michael@0: // putting everything in the header. michael@0: banned_directories_.push_back("automation/"); michael@0: michael@0: // Don't check system headers. michael@0: banned_directories_.push_back("/Developer/"); michael@0: michael@0: // Used in really low level threading code that probably shouldn't be out of michael@0: // lined. michael@0: ignored_record_names_.insert("ThreadLocalBoolean"); michael@0: michael@0: // A complicated pickle derived struct that is all packed integers. michael@0: ignored_record_names_.insert("Header"); michael@0: michael@0: // Part of the GPU system that uses multiple included header michael@0: // weirdness. Never getting this right. michael@0: ignored_record_names_.insert("Validators"); michael@0: michael@0: // Has a UNIT_TEST only constructor. Isn't *terribly* complex... michael@0: ignored_record_names_.insert("AutocompleteController"); michael@0: ignored_record_names_.insert("HistoryURLProvider"); michael@0: michael@0: // Because of chrome frame michael@0: ignored_record_names_.insert("ReliabilityTestSuite"); michael@0: michael@0: // Used over in the net unittests. A large enough bundle of integers with 1 michael@0: // non-pod class member. Probably harmless. michael@0: ignored_record_names_.insert("MockTransaction"); michael@0: michael@0: // Used heavily in ui_unittests and once in views_unittests. Fixing this michael@0: // isn't worth the overhead of an additional library. michael@0: ignored_record_names_.insert("TestAnimationDelegate"); michael@0: michael@0: // Part of our public interface that nacl and friends use. (Arguably, this michael@0: // should mean that this is a higher priority but fixing this looks hard.) michael@0: ignored_record_names_.insert("PluginVersionInfo"); michael@0: } michael@0: michael@0: std::string ChromeClassTester::GetNamespaceImpl(const DeclContext* context, michael@0: const std::string& candidate) { michael@0: switch (context->getDeclKind()) { michael@0: case Decl::TranslationUnit: { michael@0: return candidate; michael@0: } michael@0: case Decl::Namespace: { michael@0: const NamespaceDecl* decl = dyn_cast(context); michael@0: std::string name_str; michael@0: llvm::raw_string_ostream OS(name_str); michael@0: if (decl->isAnonymousNamespace()) michael@0: OS << ""; michael@0: else michael@0: OS << *decl; michael@0: return GetNamespaceImpl(context->getParent(), michael@0: OS.str()); michael@0: } michael@0: default: { michael@0: return GetNamespaceImpl(context->getParent(), candidate); michael@0: } michael@0: } michael@0: } michael@0: michael@0: bool ChromeClassTester::InBannedDirectory(SourceLocation loc) { michael@0: std::string filename; michael@0: if (!GetFilename(loc, &filename)) { michael@0: // If the filename cannot be determined, simply treat this as a banned michael@0: // location, instead of going through the full lookup process. michael@0: return true; michael@0: } michael@0: michael@0: // We need to special case scratch space; which is where clang does its michael@0: // macro expansion. We explicitly want to allow people to do otherwise bad michael@0: // things through macros that were defined due to third party libraries. michael@0: if (filename == "") michael@0: return true; michael@0: michael@0: // Don't complain about autogenerated protobuf files. michael@0: if (ends_with(filename, ".pb.h")) { michael@0: return true; michael@0: } michael@0: michael@0: // We need to munge the paths so that they are relative to the repository michael@0: // srcroot. We first resolve the symlinktastic relative path and then michael@0: // remove our known srcroot from it if needed. michael@0: char resolvedPath[MAXPATHLEN]; michael@0: if (realpath(filename.c_str(), resolvedPath)) { michael@0: filename = resolvedPath; michael@0: } michael@0: michael@0: // On linux, chrome is often checked out to /usr/local/google. Due to the michael@0: // "usr" rule in banned_directories_, all diagnostics would be suppressed michael@0: // in that case. As a workaround, strip that prefix. michael@0: filename = lstrip(filename, "/usr/local/google"); michael@0: michael@0: for (std::vector::const_iterator it = michael@0: banned_directories_.begin(); michael@0: it != banned_directories_.end(); ++it) { michael@0: // If we can find any of the banned path components in this path, then michael@0: // this file is rejected. michael@0: size_t index = filename.find(*it); michael@0: if (index != std::string::npos) { michael@0: bool matches_full_dir_name = index == 0 || filename[index - 1] == '/'; michael@0: if ((*it)[0] == '/') michael@0: matches_full_dir_name = true; michael@0: if (matches_full_dir_name) michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: bool ChromeClassTester::IsIgnoredType(const std::string& base_name) { michael@0: return ignored_record_names_.find(base_name) != ignored_record_names_.end(); michael@0: } michael@0: michael@0: bool ChromeClassTester::GetFilename(SourceLocation loc, michael@0: std::string* filename) { michael@0: const SourceManager& source_manager = instance_.getSourceManager(); michael@0: SourceLocation spelling_location = source_manager.getSpellingLoc(loc); michael@0: PresumedLoc ploc = source_manager.getPresumedLoc(spelling_location); michael@0: if (ploc.isInvalid()) { michael@0: // If we're in an invalid location, we're looking at things that aren't michael@0: // actually stated in the source. michael@0: return false; michael@0: } michael@0: michael@0: *filename = ploc.getFilename(); michael@0: return true; michael@0: }