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