1// Copyright (c) 2012 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// A general interface for filtering and only acting on classes in Chromium C++ 6// code. 7 8#include "ChromeClassTester.h" 9 10#include <sys/param.h> 11 12#include "clang/AST/AST.h" 13#include "clang/Basic/FileManager.h" 14#include "clang/Basic/SourceManager.h" 15 16using namespace clang; 17 18namespace { 19 20bool starts_with(const std::string& one, const std::string& two) { 21 return one.compare(0, two.size(), two) == 0; 22} 23 24std::string lstrip(const std::string& one, const std::string& two) { 25 if (starts_with(one, two)) 26 return one.substr(two.size()); 27 return one; 28} 29 30bool ends_with(const std::string& one, const std::string& two) { 31 if (two.size() > one.size()) 32 return false; 33 34 return one.compare(one.size() - two.size(), two.size(), two) == 0; 35} 36 37} // namespace 38 39ChromeClassTester::ChromeClassTester(CompilerInstance& instance) 40 : instance_(instance), 41 diagnostic_(instance.getDiagnostics()) { 42 BuildBannedLists(); 43} 44 45ChromeClassTester::~ChromeClassTester() {} 46 47void ChromeClassTester::HandleTagDeclDefinition(TagDecl* tag) { 48 pending_class_decls_.push_back(tag); 49} 50 51bool ChromeClassTester::HandleTopLevelDecl(DeclGroupRef group_ref) { 52 for (size_t i = 0; i < pending_class_decls_.size(); ++i) 53 CheckTag(pending_class_decls_[i]); 54 pending_class_decls_.clear(); 55 56 return true; // true means continue parsing. 57} 58 59void ChromeClassTester::CheckTag(TagDecl* tag) { 60 // We handle class types here where we have semantic information. We can only 61 // check structs/classes/enums here, but we get a bunch of nice semantic 62 // information instead of just parsing information. 63 64 if (CXXRecordDecl* record = dyn_cast<CXXRecordDecl>(tag)) { 65 // If this is a POD or a class template or a type dependent on a 66 // templated class, assume there's no ctor/dtor/virtual method 67 // optimization that we can do. 68 if (record->isPOD() || 69 record->getDescribedClassTemplate() || 70 record->getTemplateSpecializationKind() || 71 record->isDependentType()) 72 return; 73 74 if (InBannedNamespace(record)) 75 return; 76 77 SourceLocation record_location = record->getInnerLocStart(); 78 if (InBannedDirectory(record_location)) 79 return; 80 81 // We sadly need to maintain a blacklist of types that violate these 82 // rules, but do so for good reason or due to limitations of this 83 // checker (i.e., we don't handle extern templates very well). 84 std::string base_name = record->getNameAsString(); 85 if (IsIgnoredType(base_name)) 86 return; 87 88 // We ignore all classes that end with "Matcher" because they're probably 89 // GMock artifacts. 90 if (ends_with(base_name, "Matcher")) 91 return; 92 93 CheckChromeClass(record_location, record); 94 } else if (EnumDecl* enum_decl = dyn_cast<EnumDecl>(tag)) { 95 SourceLocation enum_location = enum_decl->getInnerLocStart(); 96 if (InBannedDirectory(enum_location)) 97 return; 98 99 std::string base_name = enum_decl->getNameAsString(); 100 if (IsIgnoredType(base_name)) 101 return; 102 103 CheckChromeEnum(enum_location, enum_decl); 104 } 105} 106 107void ChromeClassTester::emitWarning(SourceLocation loc, 108 const char* raw_error) { 109 FullSourceLoc full(loc, instance().getSourceManager()); 110 std::string err; 111 err = "[chromium-style] "; 112 err += raw_error; 113 DiagnosticIDs::Level level = 114 diagnostic().getWarningsAsErrors() ? 115 DiagnosticIDs::Error : 116 DiagnosticIDs::Warning; 117 unsigned id = diagnostic().getDiagnosticIDs()->getCustomDiagID(level, err); 118 DiagnosticBuilder builder = diagnostic().Report(full, id); 119} 120 121bool ChromeClassTester::InBannedNamespace(const Decl* record) { 122 std::string n = GetNamespace(record); 123 if (!n.empty()) { 124 return std::find(banned_namespaces_.begin(), banned_namespaces_.end(), n) 125 != banned_namespaces_.end(); 126 } 127 128 return false; 129} 130 131std::string ChromeClassTester::GetNamespace(const Decl* record) { 132 return GetNamespaceImpl(record->getDeclContext(), ""); 133} 134 135bool ChromeClassTester::InImplementationFile(SourceLocation record_location) { 136 std::string filename; 137 if (!GetFilename(record_location, &filename)) 138 return false; 139 140 if (ends_with(filename, ".cc") || ends_with(filename, ".cpp") || 141 ends_with(filename, ".mm")) { 142 return true; 143 } 144 145 return false; 146} 147 148void ChromeClassTester::BuildBannedLists() { 149 banned_namespaces_.push_back("std"); 150 banned_namespaces_.push_back("__gnu_cxx"); 151 152 banned_namespaces_.push_back("blink"); 153 banned_namespaces_.push_back("WTF"); 154 155 banned_directories_.push_back("/third_party/"); 156 banned_directories_.push_back("/native_client/"); 157 banned_directories_.push_back("/breakpad/"); 158 banned_directories_.push_back("/courgette/"); 159 banned_directories_.push_back("/pdf/"); 160 banned_directories_.push_back("/ppapi/"); 161 banned_directories_.push_back("/usr/"); 162 banned_directories_.push_back("/testing/"); 163 banned_directories_.push_back("/v8/"); 164 banned_directories_.push_back("/dart/"); 165 banned_directories_.push_back("/sdch/"); 166 banned_directories_.push_back("/icu4c/"); 167 banned_directories_.push_back("/frameworks/"); 168 169 // Don't check autogenerated headers. 170 // Make puts them below $(builddir_name)/.../gen and geni. 171 // Ninja puts them below OUTPUT_DIR/.../gen 172 // Xcode has a fixed output directory for everything. 173 banned_directories_.push_back("/gen/"); 174 banned_directories_.push_back("/geni/"); 175 banned_directories_.push_back("/xcodebuild/"); 176 177 // You are standing in a mazy of twisty dependencies, all resolved by 178 // putting everything in the header. 179 banned_directories_.push_back("/automation/"); 180 181 // Don't check system headers. 182 banned_directories_.push_back("/Developer/"); 183 184 // Used in really low level threading code that probably shouldn't be out of 185 // lined. 186 ignored_record_names_.insert("ThreadLocalBoolean"); 187 188 // A complicated pickle derived struct that is all packed integers. 189 ignored_record_names_.insert("Header"); 190 191 // Part of the GPU system that uses multiple included header 192 // weirdness. Never getting this right. 193 ignored_record_names_.insert("Validators"); 194 195 // Has a UNIT_TEST only constructor. Isn't *terribly* complex... 196 ignored_record_names_.insert("AutocompleteController"); 197 ignored_record_names_.insert("HistoryURLProvider"); 198 199 // Because of chrome frame 200 ignored_record_names_.insert("ReliabilityTestSuite"); 201 202 // Used over in the net unittests. A large enough bundle of integers with 1 203 // non-pod class member. Probably harmless. 204 ignored_record_names_.insert("MockTransaction"); 205 206 // Enum type with _LAST members where _LAST doesn't mean last enum value. 207 ignored_record_names_.insert("ServerFieldType"); 208 209 // Used heavily in ui_unittests and once in views_unittests. Fixing this 210 // isn't worth the overhead of an additional library. 211 ignored_record_names_.insert("TestAnimationDelegate"); 212 213 // Part of our public interface that nacl and friends use. (Arguably, this 214 // should mean that this is a higher priority but fixing this looks hard.) 215 ignored_record_names_.insert("PluginVersionInfo"); 216 217 // Measured performance improvement on cc_perftests. See 218 // https://codereview.chromium.org/11299290/ 219 ignored_record_names_.insert("QuadF"); 220 221 // Enum type with _LAST members where _LAST doesn't mean last enum value. 222 ignored_record_names_.insert("ViewID"); 223} 224 225std::string ChromeClassTester::GetNamespaceImpl(const DeclContext* context, 226 const std::string& candidate) { 227 switch (context->getDeclKind()) { 228 case Decl::TranslationUnit: { 229 return candidate; 230 } 231 case Decl::Namespace: { 232 const NamespaceDecl* decl = dyn_cast<NamespaceDecl>(context); 233 std::string name_str; 234 llvm::raw_string_ostream OS(name_str); 235 if (decl->isAnonymousNamespace()) 236 OS << "<anonymous namespace>"; 237 else 238 OS << *decl; 239 return GetNamespaceImpl(context->getParent(), 240 OS.str()); 241 } 242 default: { 243 return GetNamespaceImpl(context->getParent(), candidate); 244 } 245 } 246} 247 248bool ChromeClassTester::InBannedDirectory(SourceLocation loc) { 249 std::string filename; 250 if (!GetFilename(loc, &filename)) { 251 // If the filename cannot be determined, simply treat this as a banned 252 // location, instead of going through the full lookup process. 253 return true; 254 } 255 256 // We need to special case scratch space; which is where clang does its 257 // macro expansion. We explicitly want to allow people to do otherwise bad 258 // things through macros that were defined due to third party libraries. 259 if (filename == "<scratch space>") 260 return true; 261 262 // Don't complain about autogenerated protobuf files. 263 if (ends_with(filename, ".pb.h")) { 264 return true; 265 } 266 267 // We need to munge the paths so that they are relative to the repository 268 // srcroot. We first resolve the symlinktastic relative path and then 269 // remove our known srcroot from it if needed. 270 char resolvedPath[MAXPATHLEN]; 271 if (realpath(filename.c_str(), resolvedPath)) { 272 filename = resolvedPath; 273 } 274 275 // On linux, chrome is often checked out to /usr/local/google. Due to the 276 // "usr" rule in banned_directories_, all diagnostics would be suppressed 277 // in that case. As a workaround, strip that prefix. 278 filename = lstrip(filename, "/usr/local/google"); 279 280 for (size_t i = 0; i < banned_directories_.size(); ++i) { 281 // If any of the banned directories occur as a component in filename, 282 // this file is rejected. 283 const std::string& banned_dir = banned_directories_[i]; 284 assert(banned_dir.front() == '/' && "Banned dir must start with '/'"); 285 assert(banned_dir.back() == '/' && "Banned dir must end with '/'"); 286 287 if (filename.find(banned_dir) != std::string::npos) 288 return true; 289 } 290 291 return false; 292} 293 294bool ChromeClassTester::IsIgnoredType(const std::string& base_name) { 295 return ignored_record_names_.find(base_name) != ignored_record_names_.end(); 296} 297 298bool ChromeClassTester::GetFilename(SourceLocation loc, 299 std::string* filename) { 300 const SourceManager& source_manager = instance_.getSourceManager(); 301 SourceLocation spelling_location = source_manager.getSpellingLoc(loc); 302 PresumedLoc ploc = source_manager.getPresumedLoc(spelling_location); 303 if (ploc.isInvalid()) { 304 // If we're in an invalid location, we're looking at things that aren't 305 // actually stated in the source. 306 return false; 307 } 308 309 *filename = ploc.getFilename(); 310 return true; 311} 312