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& banned_dir : banned_directories_) { |
| 154 // If any of the banned directories occur as a component in filename, |
| 155 // this file is rejected. |
| 156 assert(banned_dir.front() == '/' && "Banned dir must start with '/'"); |
| 157 assert(banned_dir.back() == '/' && "Banned dir must end with '/'"); |
| 158 |
| 159 if (filename.find(banned_dir) != std::string::npos) |
| 160 return true; |
| 161 } |
| 162 |
| 163 return false; |
| 164 } |
| 165 |
| 166 bool ChromeClassTester::InBannedNamespace(const Decl* record) { |
| 167 std::string n = GetNamespace(record); |
| 168 if (!n.empty()) { |
| 169 return std::find(banned_namespaces_.begin(), banned_namespaces_.end(), n) |
| 170 != banned_namespaces_.end(); |
| 171 } |
| 172 |
| 173 return false; |
| 174 } |
| 175 |
| 176 std::string ChromeClassTester::GetNamespace(const Decl* record) { |
| 177 return GetNamespaceImpl(record->getDeclContext(), ""); |
| 178 } |
| 179 |
| 180 bool ChromeClassTester::InImplementationFile(SourceLocation record_location) { |
| 181 std::string filename; |
| 182 if (!GetFilename(record_location, &filename)) |
| 183 return false; |
| 184 |
| 185 if (ends_with(filename, ".cc") || ends_with(filename, ".cpp") || |
| 186 ends_with(filename, ".mm")) { |
| 187 return true; |
| 188 } |
| 189 |
| 190 return false; |
| 191 } |
| 192 |
| 193 void ChromeClassTester::BuildBannedLists() { |
| 194 banned_namespaces_.push_back("std"); |
| 195 banned_namespaces_.push_back("__gnu_cxx"); |
| 196 |
| 197 if (!options_.enforce_blink) { |
| 198 banned_namespaces_.push_back("blink"); |
| 199 banned_namespaces_.push_back("WTF"); |
| 200 } |
| 201 |
| 202 banned_directories_.push_back("/third_party/"); |
| 203 banned_directories_.push_back("/native_client/"); |
| 204 banned_directories_.push_back("/breakpad/"); |
| 205 banned_directories_.push_back("/courgette/"); |
| 206 banned_directories_.push_back("/pdf/"); |
| 207 banned_directories_.push_back("/ppapi/"); |
| 208 banned_directories_.push_back("/usr/include/"); |
| 209 banned_directories_.push_back("/usr/lib/"); |
| 210 banned_directories_.push_back("/usr/local/include/"); |
| 211 banned_directories_.push_back("/usr/local/lib/"); |
| 212 banned_directories_.push_back("/testing/"); |
| 213 banned_directories_.push_back("/v8/"); |
| 214 banned_directories_.push_back("/dart/"); |
| 215 banned_directories_.push_back("/sdch/"); |
| 216 banned_directories_.push_back("/icu4c/"); |
| 217 banned_directories_.push_back("/frameworks/"); |
| 218 |
| 219 // Don't check autogenerated headers. |
| 220 // Make puts them below $(builddir_name)/.../gen and geni. |
| 221 // Ninja puts them below OUTPUT_DIR/.../gen |
| 222 // Xcode has a fixed output directory for everything. |
| 223 banned_directories_.push_back("/gen/"); |
| 224 banned_directories_.push_back("/geni/"); |
| 225 banned_directories_.push_back("/xcodebuild/"); |
| 226 |
| 227 // Used in really low level threading code that probably shouldn't be out of |
| 228 // lined. |
| 229 ignored_record_names_.insert("ThreadLocalBoolean"); |
| 230 |
| 231 // A complicated pickle derived struct that is all packed integers. |
| 232 ignored_record_names_.insert("Header"); |
| 233 |
| 234 // Part of the GPU system that uses multiple included header |
| 235 // weirdness. Never getting this right. |
| 236 ignored_record_names_.insert("Validators"); |
| 237 |
| 238 // Has a UNIT_TEST only constructor. Isn't *terribly* complex... |
| 239 ignored_record_names_.insert("AutocompleteController"); |
| 240 ignored_record_names_.insert("HistoryURLProvider"); |
| 241 |
| 242 // Used over in the net unittests. A large enough bundle of integers with 1 |
| 243 // non-pod class member. Probably harmless. |
| 244 ignored_record_names_.insert("MockTransaction"); |
| 245 |
| 246 // Enum type with _LAST members where _LAST doesn't mean last enum value. |
| 247 ignored_record_names_.insert("ServerFieldType"); |
| 248 |
| 249 // Used heavily in ui_base_unittests and once in views_unittests. Fixing this |
| 250 // isn't worth the overhead of an additional library. |
| 251 ignored_record_names_.insert("TestAnimationDelegate"); |
| 252 |
| 253 // Part of our public interface that nacl and friends use. (Arguably, this |
| 254 // should mean that this is a higher priority but fixing this looks hard.) |
| 255 ignored_record_names_.insert("PluginVersionInfo"); |
| 256 |
| 257 // Measured performance improvement on cc_perftests. See |
| 258 // https://codereview.chromium.org/11299290/ |
| 259 ignored_record_names_.insert("QuadF"); |
| 260 |
| 261 // Enum type with _LAST members where _LAST doesn't mean last enum value. |
| 262 ignored_record_names_.insert("ViewID"); |
| 263 } |
| 264 |
| 265 std::string ChromeClassTester::GetNamespaceImpl(const DeclContext* context, |
| 266 const std::string& candidate) { |
| 267 switch (context->getDeclKind()) { |
| 268 case Decl::TranslationUnit: { |
| 269 return candidate; |
| 270 } |
| 271 case Decl::Namespace: { |
| 272 const NamespaceDecl* decl = dyn_cast<NamespaceDecl>(context); |
| 273 std::string name_str; |
| 274 llvm::raw_string_ostream OS(name_str); |
| 275 if (decl->isAnonymousNamespace()) |
| 276 OS << "<anonymous namespace>"; |
| 277 else |
| 278 OS << *decl; |
| 279 return GetNamespaceImpl(context->getParent(), |
| 280 OS.str()); |
| 281 } |
| 282 default: { |
| 283 return GetNamespaceImpl(context->getParent(), candidate); |
| 284 } |
| 285 } |
| 286 } |
| 287 |
| 288 bool ChromeClassTester::IsIgnoredType(const std::string& base_name) { |
| 289 return ignored_record_names_.find(base_name) != ignored_record_names_.end(); |
| 290 } |
| 291 |
| 292 bool ChromeClassTester::GetFilename(SourceLocation loc, |
| 293 std::string* filename) { |
| 294 const SourceManager& source_manager = instance_.getSourceManager(); |
| 295 SourceLocation spelling_location = source_manager.getSpellingLoc(loc); |
| 296 PresumedLoc ploc = source_manager.getPresumedLoc(spelling_location); |
| 297 if (ploc.isInvalid()) { |
| 298 // If we're in an invalid location, we're looking at things that aren't |
| 299 // actually stated in the source. |
| 300 return false; |
| 301 } |
| 302 |
| 303 *filename = ploc.getFilename(); |
| 304 return true; |
| 305 } |
| 306 |
| 307 DiagnosticsEngine::Level ChromeClassTester::getErrorLevel() { |
| 308 if (options_.warn_only) |
| 309 return DiagnosticsEngine::Warning; |
| 310 |
| 311 return diagnostic().getWarningsAsErrors() ? DiagnosticsEngine::Error |
| 312 : DiagnosticsEngine::Warning; |
| 313 } |
OLD | NEW |