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 16 using namespace clang; 17 18 namespace { 19 20 bool starts_with(const std::string& one, const std::string& two) { 21 return one.compare(0, two.size(), two) == 0; 22 } 23 24 std::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 30 bool 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 39 ChromeClassTester::ChromeClassTester(CompilerInstance& instance) 40 : instance_(instance), 41 diagnostic_(instance.getDiagnostics()) { 42 BuildBannedLists(); 43 } 44 45 ChromeClassTester::~ChromeClassTester() {} 46 47 void ChromeClassTester::HandleTagDeclDefinition(TagDecl* tag) { 48 pending_class_decls_.push_back(tag); 49 } 50 51 bool 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 59 void 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 107 void 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 121 bool 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 131 std::string ChromeClassTester::GetNamespace(const Decl* record) { 132 return GetNamespaceImpl(record->getDeclContext(), ""); 133 } 134 135 bool 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 148 void ChromeClassTester::BuildBannedLists() { 149 banned_namespaces_.push_back("std"); 150 banned_namespaces_.push_back("__gnu_cxx"); 151 152 // We're in the process of renaming WebKit to blink. 153 // TODO(abarth): Remove WebKit once the rename is complete. 154 banned_namespaces_.push_back("WebKit"); 155 banned_namespaces_.push_back("blink"); 156 157 banned_directories_.push_back("third_party/"); 158 banned_directories_.push_back("native_client/"); 159 banned_directories_.push_back("breakpad/"); 160 banned_directories_.push_back("courgette/"); 161 banned_directories_.push_back("pdf/"); 162 banned_directories_.push_back("ppapi/"); 163 banned_directories_.push_back("usr/"); 164 banned_directories_.push_back("testing/"); 165 banned_directories_.push_back("v8/"); 166 banned_directories_.push_back("dart/"); 167 banned_directories_.push_back("sdch/"); 168 banned_directories_.push_back("icu4c/"); 169 banned_directories_.push_back("frameworks/"); 170 171 // Don't check autogenerated headers. 172 // Make puts them below $(builddir_name)/.../gen and geni. 173 // Ninja puts them below OUTPUT_DIR/.../gen 174 // Xcode has a fixed output directory for everything. 175 banned_directories_.push_back("gen/"); 176 banned_directories_.push_back("geni/"); 177 banned_directories_.push_back("xcodebuild/"); 178 179 // You are standing in a mazy of twisty dependencies, all resolved by 180 // putting everything in the header. 181 banned_directories_.push_back("automation/"); 182 183 // Don't check system headers. 184 banned_directories_.push_back("/Developer/"); 185 186 // Used in really low level threading code that probably shouldn't be out of 187 // lined. 188 ignored_record_names_.insert("ThreadLocalBoolean"); 189 190 // A complicated pickle derived struct that is all packed integers. 191 ignored_record_names_.insert("Header"); 192 193 // Part of the GPU system that uses multiple included header 194 // weirdness. Never getting this right. 195 ignored_record_names_.insert("Validators"); 196 197 // Has a UNIT_TEST only constructor. Isn't *terribly* complex... 198 ignored_record_names_.insert("AutocompleteController"); 199 ignored_record_names_.insert("HistoryURLProvider"); 200 201 // Because of chrome frame 202 ignored_record_names_.insert("ReliabilityTestSuite"); 203 204 // Used over in the net unittests. A large enough bundle of integers with 1 205 // non-pod class member. Probably harmless. 206 ignored_record_names_.insert("MockTransaction"); 207 208 // Enum type with _LAST members where _LAST doesn't mean last enum value. 209 ignored_record_names_.insert("ServerFieldType"); 210 211 // Used heavily in ui_unittests and once in views_unittests. Fixing this 212 // isn't worth the overhead of an additional library. 213 ignored_record_names_.insert("TestAnimationDelegate"); 214 215 // Part of our public interface that nacl and friends use. (Arguably, this 216 // should mean that this is a higher priority but fixing this looks hard.) 217 ignored_record_names_.insert("PluginVersionInfo"); 218 219 // Measured performance improvement on cc_perftests. See 220 // https://codereview.chromium.org/11299290/ 221 ignored_record_names_.insert("QuadF"); 222 223 // Enum type with _LAST members where _LAST doesn't mean last enum value. 224 ignored_record_names_.insert("ViewID"); 225 } 226 227 std::string ChromeClassTester::GetNamespaceImpl(const DeclContext* context, 228 const std::string& candidate) { 229 switch (context->getDeclKind()) { 230 case Decl::TranslationUnit: { 231 return candidate; 232 } 233 case Decl::Namespace: { 234 const NamespaceDecl* decl = dyn_cast<NamespaceDecl>(context); 235 std::string name_str; 236 llvm::raw_string_ostream OS(name_str); 237 if (decl->isAnonymousNamespace()) 238 OS << "<anonymous namespace>"; 239 else 240 OS << *decl; 241 return GetNamespaceImpl(context->getParent(), 242 OS.str()); 243 } 244 default: { 245 return GetNamespaceImpl(context->getParent(), candidate); 246 } 247 } 248 } 249 250 bool ChromeClassTester::InBannedDirectory(SourceLocation loc) { 251 std::string filename; 252 if (!GetFilename(loc, &filename)) { 253 // If the filename cannot be determined, simply treat this as a banned 254 // location, instead of going through the full lookup process. 255 return true; 256 } 257 258 // We need to special case scratch space; which is where clang does its 259 // macro expansion. We explicitly want to allow people to do otherwise bad 260 // things through macros that were defined due to third party libraries. 261 if (filename == "<scratch space>") 262 return true; 263 264 // Don't complain about autogenerated protobuf files. 265 if (ends_with(filename, ".pb.h")) { 266 return true; 267 } 268 269 // We need to munge the paths so that they are relative to the repository 270 // srcroot. We first resolve the symlinktastic relative path and then 271 // remove our known srcroot from it if needed. 272 char resolvedPath[MAXPATHLEN]; 273 if (realpath(filename.c_str(), resolvedPath)) { 274 filename = resolvedPath; 275 } 276 277 // On linux, chrome is often checked out to /usr/local/google. Due to the 278 // "usr" rule in banned_directories_, all diagnostics would be suppressed 279 // in that case. As a workaround, strip that prefix. 280 filename = lstrip(filename, "/usr/local/google"); 281 282 for (std::vector<std::string>::const_iterator it = 283 banned_directories_.begin(); 284 it != banned_directories_.end(); ++it) { 285 // If we can find any of the banned path components in this path, then 286 // this file is rejected. 287 size_t index = filename.find(*it); 288 if (index != std::string::npos) { 289 bool matches_full_dir_name = index == 0 || filename[index - 1] == '/'; 290 if ((*it)[0] == '/') 291 matches_full_dir_name = true; 292 if (matches_full_dir_name) 293 return true; 294 } 295 } 296 297 return false; 298 } 299 300 bool ChromeClassTester::IsIgnoredType(const std::string& base_name) { 301 return ignored_record_names_.find(base_name) != ignored_record_names_.end(); 302 } 303 304 bool ChromeClassTester::GetFilename(SourceLocation loc, 305 std::string* filename) { 306 const SourceManager& source_manager = instance_.getSourceManager(); 307 SourceLocation spelling_location = source_manager.getSpellingLoc(loc); 308 PresumedLoc ploc = source_manager.getPresumedLoc(spelling_location); 309 if (ploc.isInvalid()) { 310 // If we're in an invalid location, we're looking at things that aren't 311 // actually stated in the source. 312 return false; 313 } 314 315 *filename = ploc.getFilename(); 316 return true; 317 } 318