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