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