1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/media/webrtc/trunk/tools/clang/plugins/FindBadConstructs.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,435 @@ 1.4 +// Copyright (c) 2012 The Chromium Authors. All rights reserved. 1.5 +// Use of this source code is governed by a BSD-style license that can be 1.6 +// found in the LICENSE file. 1.7 + 1.8 +// This file defines a bunch of recurring problems in the Chromium C++ code. 1.9 +// 1.10 +// Checks that are implemented: 1.11 +// - Constructors/Destructors should not be inlined if they are of a complex 1.12 +// class type. 1.13 +// - Missing "virtual" keywords on methods that should be virtual. 1.14 +// - Non-annotated overriding virtual methods. 1.15 +// - Virtual methods with nonempty implementations in their headers. 1.16 +// - Classes that derive from base::RefCounted / base::RefCountedThreadSafe 1.17 +// should have protected or private destructors. 1.18 + 1.19 +#include "clang/Frontend/FrontendPluginRegistry.h" 1.20 +#include "clang/AST/ASTConsumer.h" 1.21 +#include "clang/AST/AST.h" 1.22 +#include "clang/AST/CXXInheritance.h" 1.23 +#include "clang/AST/TypeLoc.h" 1.24 +#include "clang/Basic/SourceManager.h" 1.25 +#include "clang/Frontend/CompilerInstance.h" 1.26 +#include "llvm/Support/raw_ostream.h" 1.27 + 1.28 +#include "ChromeClassTester.h" 1.29 + 1.30 +using namespace clang; 1.31 + 1.32 +namespace { 1.33 + 1.34 +bool TypeHasNonTrivialDtor(const Type* type) { 1.35 + if (const CXXRecordDecl* cxx_r = type->getCXXRecordDeclForPointerType()) 1.36 + return cxx_r->hasTrivialDestructor(); 1.37 + 1.38 + return false; 1.39 +} 1.40 + 1.41 +// Returns the underlying Type for |type| by expanding typedefs and removing 1.42 +// any namespace qualifiers. 1.43 +const Type* UnwrapType(const Type* type) { 1.44 + if (const ElaboratedType* elaborated = dyn_cast<ElaboratedType>(type)) 1.45 + return UnwrapType(elaborated->getNamedType().getTypePtr()); 1.46 + if (const TypedefType* typedefed = dyn_cast<TypedefType>(type)) 1.47 + return UnwrapType(typedefed->desugar().getTypePtr()); 1.48 + return type; 1.49 +} 1.50 + 1.51 +// Searches for constructs that we know we don't want in the Chromium code base. 1.52 +class FindBadConstructsConsumer : public ChromeClassTester { 1.53 + public: 1.54 + FindBadConstructsConsumer(CompilerInstance& instance, 1.55 + bool check_refcounted_dtors, 1.56 + bool check_virtuals_in_implementations) 1.57 + : ChromeClassTester(instance), 1.58 + check_refcounted_dtors_(check_refcounted_dtors), 1.59 + check_virtuals_in_implementations_(check_virtuals_in_implementations) { 1.60 + } 1.61 + 1.62 + virtual void CheckChromeClass(SourceLocation record_location, 1.63 + CXXRecordDecl* record) { 1.64 + bool implementation_file = InImplementationFile(record_location); 1.65 + 1.66 + if (!implementation_file) { 1.67 + // Only check for "heavy" constructors/destructors in header files; 1.68 + // within implementation files, there is no performance cost. 1.69 + CheckCtorDtorWeight(record_location, record); 1.70 + } 1.71 + 1.72 + if (!implementation_file || check_virtuals_in_implementations_) { 1.73 + bool warn_on_inline_bodies = !implementation_file; 1.74 + 1.75 + // Check that all virtual methods are marked accordingly with both 1.76 + // virtual and OVERRIDE. 1.77 + CheckVirtualMethods(record_location, record, warn_on_inline_bodies); 1.78 + } 1.79 + 1.80 + if (check_refcounted_dtors_) 1.81 + CheckRefCountedDtors(record_location, record); 1.82 + } 1.83 + 1.84 + private: 1.85 + bool check_refcounted_dtors_; 1.86 + bool check_virtuals_in_implementations_; 1.87 + 1.88 + // Returns true if |base| specifies one of the Chromium reference counted 1.89 + // classes (base::RefCounted / base::RefCountedThreadSafe). |user_data| is 1.90 + // ignored. 1.91 + static bool IsRefCountedCallback(const CXXBaseSpecifier* base, 1.92 + CXXBasePath& path, 1.93 + void* user_data) { 1.94 + FindBadConstructsConsumer* self = 1.95 + static_cast<FindBadConstructsConsumer*>(user_data); 1.96 + 1.97 + const TemplateSpecializationType* base_type = 1.98 + dyn_cast<TemplateSpecializationType>( 1.99 + UnwrapType(base->getType().getTypePtr())); 1.100 + if (!base_type) { 1.101 + // Base-most definition is not a template, so this cannot derive from 1.102 + // base::RefCounted. However, it may still be possible to use with a 1.103 + // scoped_refptr<> and support ref-counting, so this is not a perfect 1.104 + // guarantee of safety. 1.105 + return false; 1.106 + } 1.107 + 1.108 + TemplateName name = base_type->getTemplateName(); 1.109 + if (TemplateDecl* decl = name.getAsTemplateDecl()) { 1.110 + std::string base_name = decl->getNameAsString(); 1.111 + 1.112 + // Check for both base::RefCounted and base::RefCountedThreadSafe. 1.113 + if (base_name.compare(0, 10, "RefCounted") == 0 && 1.114 + self->GetNamespace(decl) == "base") { 1.115 + return true; 1.116 + } 1.117 + } 1.118 + return false; 1.119 + } 1.120 + 1.121 + // Prints errors if the destructor of a RefCounted class is public. 1.122 + void CheckRefCountedDtors(SourceLocation record_location, 1.123 + CXXRecordDecl* record) { 1.124 + // Skip anonymous structs. 1.125 + if (record->getIdentifier() == NULL) 1.126 + return; 1.127 + 1.128 + CXXBasePaths paths; 1.129 + if (!record->lookupInBases( 1.130 + &FindBadConstructsConsumer::IsRefCountedCallback, this, paths)) { 1.131 + return; // Class does not derive from a ref-counted base class. 1.132 + } 1.133 + 1.134 + if (!record->hasUserDeclaredDestructor()) { 1.135 + emitWarning( 1.136 + record_location, 1.137 + "Classes that are ref-counted should have explicit " 1.138 + "destructors that are protected or private."); 1.139 + } else if (CXXDestructorDecl* dtor = record->getDestructor()) { 1.140 + if (dtor->getAccess() == AS_public) { 1.141 + emitWarning( 1.142 + dtor->getInnerLocStart(), 1.143 + "Classes that are ref-counted should not have " 1.144 + "public destructors."); 1.145 + } 1.146 + } 1.147 + } 1.148 + 1.149 + // Prints errors if the constructor/destructor weight is too heavy. 1.150 + void CheckCtorDtorWeight(SourceLocation record_location, 1.151 + CXXRecordDecl* record) { 1.152 + // We don't handle anonymous structs. If this record doesn't have a 1.153 + // name, it's of the form: 1.154 + // 1.155 + // struct { 1.156 + // ... 1.157 + // } name_; 1.158 + if (record->getIdentifier() == NULL) 1.159 + return; 1.160 + 1.161 + // Count the number of templated base classes as a feature of whether the 1.162 + // destructor can be inlined. 1.163 + int templated_base_classes = 0; 1.164 + for (CXXRecordDecl::base_class_const_iterator it = record->bases_begin(); 1.165 + it != record->bases_end(); ++it) { 1.166 + if (it->getTypeSourceInfo()->getTypeLoc().getTypeLocClass() == 1.167 + TypeLoc::TemplateSpecialization) { 1.168 + ++templated_base_classes; 1.169 + } 1.170 + } 1.171 + 1.172 + // Count the number of trivial and non-trivial member variables. 1.173 + int trivial_member = 0; 1.174 + int non_trivial_member = 0; 1.175 + int templated_non_trivial_member = 0; 1.176 + for (RecordDecl::field_iterator it = record->field_begin(); 1.177 + it != record->field_end(); ++it) { 1.178 + CountType(it->getType().getTypePtr(), 1.179 + &trivial_member, 1.180 + &non_trivial_member, 1.181 + &templated_non_trivial_member); 1.182 + } 1.183 + 1.184 + // Check to see if we need to ban inlined/synthesized constructors. Note 1.185 + // that the cutoffs here are kind of arbitrary. Scores over 10 break. 1.186 + int dtor_score = 0; 1.187 + // Deriving from a templated base class shouldn't be enough to trigger 1.188 + // the ctor warning, but if you do *anything* else, it should. 1.189 + // 1.190 + // TODO(erg): This is motivated by templated base classes that don't have 1.191 + // any data members. Somehow detect when templated base classes have data 1.192 + // members and treat them differently. 1.193 + dtor_score += templated_base_classes * 9; 1.194 + // Instantiating a template is an insta-hit. 1.195 + dtor_score += templated_non_trivial_member * 10; 1.196 + // The fourth normal class member should trigger the warning. 1.197 + dtor_score += non_trivial_member * 3; 1.198 + 1.199 + int ctor_score = dtor_score; 1.200 + // You should be able to have 9 ints before we warn you. 1.201 + ctor_score += trivial_member; 1.202 + 1.203 + if (ctor_score >= 10) { 1.204 + if (!record->hasUserDeclaredConstructor()) { 1.205 + emitWarning(record_location, 1.206 + "Complex class/struct needs an explicit out-of-line " 1.207 + "constructor."); 1.208 + } else { 1.209 + // Iterate across all the constructors in this file and yell if we 1.210 + // find one that tries to be inline. 1.211 + for (CXXRecordDecl::ctor_iterator it = record->ctor_begin(); 1.212 + it != record->ctor_end(); ++it) { 1.213 + if (it->hasInlineBody()) { 1.214 + if (it->isCopyConstructor() && 1.215 + !record->hasUserDeclaredCopyConstructor()) { 1.216 + emitWarning(record_location, 1.217 + "Complex class/struct needs an explicit out-of-line " 1.218 + "copy constructor."); 1.219 + } else { 1.220 + emitWarning(it->getInnerLocStart(), 1.221 + "Complex constructor has an inlined body."); 1.222 + } 1.223 + } 1.224 + } 1.225 + } 1.226 + } 1.227 + 1.228 + // The destructor side is equivalent except that we don't check for 1.229 + // trivial members; 20 ints don't need a destructor. 1.230 + if (dtor_score >= 10 && !record->hasTrivialDestructor()) { 1.231 + if (!record->hasUserDeclaredDestructor()) { 1.232 + emitWarning( 1.233 + record_location, 1.234 + "Complex class/struct needs an explicit out-of-line " 1.235 + "destructor."); 1.236 + } else if (CXXDestructorDecl* dtor = record->getDestructor()) { 1.237 + if (dtor->hasInlineBody()) { 1.238 + emitWarning(dtor->getInnerLocStart(), 1.239 + "Complex destructor has an inline body."); 1.240 + } 1.241 + } 1.242 + } 1.243 + } 1.244 + 1.245 + void CheckVirtualMethod(const CXXMethodDecl* method, 1.246 + bool warn_on_inline_bodies) { 1.247 + if (!method->isVirtual()) 1.248 + return; 1.249 + 1.250 + if (!method->isVirtualAsWritten()) { 1.251 + SourceLocation loc = method->getTypeSpecStartLoc(); 1.252 + if (isa<CXXDestructorDecl>(method)) 1.253 + loc = method->getInnerLocStart(); 1.254 + emitWarning(loc, "Overriding method must have \"virtual\" keyword."); 1.255 + } 1.256 + 1.257 + // Virtual methods should not have inline definitions beyond "{}". This 1.258 + // only matters for header files. 1.259 + if (warn_on_inline_bodies && method->hasBody() && 1.260 + method->hasInlineBody()) { 1.261 + if (CompoundStmt* cs = dyn_cast<CompoundStmt>(method->getBody())) { 1.262 + if (cs->size()) { 1.263 + emitWarning( 1.264 + cs->getLBracLoc(), 1.265 + "virtual methods with non-empty bodies shouldn't be " 1.266 + "declared inline."); 1.267 + } 1.268 + } 1.269 + } 1.270 + } 1.271 + 1.272 + bool InTestingNamespace(const Decl* record) { 1.273 + return GetNamespace(record).find("testing") != std::string::npos; 1.274 + } 1.275 + 1.276 + bool IsMethodInBannedNamespace(const CXXMethodDecl* method) { 1.277 + if (InBannedNamespace(method)) 1.278 + return true; 1.279 + for (CXXMethodDecl::method_iterator i = method->begin_overridden_methods(); 1.280 + i != method->end_overridden_methods(); 1.281 + ++i) { 1.282 + const CXXMethodDecl* overridden = *i; 1.283 + if (IsMethodInBannedNamespace(overridden)) 1.284 + return true; 1.285 + } 1.286 + 1.287 + return false; 1.288 + } 1.289 + 1.290 + void CheckOverriddenMethod(const CXXMethodDecl* method) { 1.291 + if (!method->size_overridden_methods() || method->getAttr<OverrideAttr>()) 1.292 + return; 1.293 + 1.294 + if (isa<CXXDestructorDecl>(method) || method->isPure()) 1.295 + return; 1.296 + 1.297 + if (IsMethodInBannedNamespace(method)) 1.298 + return; 1.299 + 1.300 + SourceLocation loc = method->getTypeSpecStartLoc(); 1.301 + emitWarning(loc, "Overriding method must be marked with OVERRIDE."); 1.302 + } 1.303 + 1.304 + // Makes sure there is a "virtual" keyword on virtual methods. 1.305 + // 1.306 + // Gmock objects trigger these for each MOCK_BLAH() macro used. So we have a 1.307 + // trick to get around that. If a class has member variables whose types are 1.308 + // in the "testing" namespace (which is how gmock works behind the scenes), 1.309 + // there's a really high chance we won't care about these errors 1.310 + void CheckVirtualMethods(SourceLocation record_location, 1.311 + CXXRecordDecl* record, 1.312 + bool warn_on_inline_bodies) { 1.313 + for (CXXRecordDecl::field_iterator it = record->field_begin(); 1.314 + it != record->field_end(); ++it) { 1.315 + CXXRecordDecl* record_type = 1.316 + it->getTypeSourceInfo()->getTypeLoc().getTypePtr()-> 1.317 + getAsCXXRecordDecl(); 1.318 + if (record_type) { 1.319 + if (InTestingNamespace(record_type)) { 1.320 + return; 1.321 + } 1.322 + } 1.323 + } 1.324 + 1.325 + for (CXXRecordDecl::method_iterator it = record->method_begin(); 1.326 + it != record->method_end(); ++it) { 1.327 + if (it->isCopyAssignmentOperator() || isa<CXXConstructorDecl>(*it)) { 1.328 + // Ignore constructors and assignment operators. 1.329 + } else if (isa<CXXDestructorDecl>(*it) && 1.330 + !record->hasUserDeclaredDestructor()) { 1.331 + // Ignore non-user-declared destructors. 1.332 + } else { 1.333 + CheckVirtualMethod(*it, warn_on_inline_bodies); 1.334 + CheckOverriddenMethod(*it); 1.335 + } 1.336 + } 1.337 + } 1.338 + 1.339 + void CountType(const Type* type, 1.340 + int* trivial_member, 1.341 + int* non_trivial_member, 1.342 + int* templated_non_trivial_member) { 1.343 + switch (type->getTypeClass()) { 1.344 + case Type::Record: { 1.345 + // Simplifying; the whole class isn't trivial if the dtor is, but 1.346 + // we use this as a signal about complexity. 1.347 + if (TypeHasNonTrivialDtor(type)) 1.348 + (*trivial_member)++; 1.349 + else 1.350 + (*non_trivial_member)++; 1.351 + break; 1.352 + } 1.353 + case Type::TemplateSpecialization: { 1.354 + TemplateName name = 1.355 + dyn_cast<TemplateSpecializationType>(type)->getTemplateName(); 1.356 + bool whitelisted_template = false; 1.357 + 1.358 + // HACK: I'm at a loss about how to get the syntax checker to get 1.359 + // whether a template is exterened or not. For the first pass here, 1.360 + // just do retarded string comparisons. 1.361 + if (TemplateDecl* decl = name.getAsTemplateDecl()) { 1.362 + std::string base_name = decl->getNameAsString(); 1.363 + if (base_name == "basic_string") 1.364 + whitelisted_template = true; 1.365 + } 1.366 + 1.367 + if (whitelisted_template) 1.368 + (*non_trivial_member)++; 1.369 + else 1.370 + (*templated_non_trivial_member)++; 1.371 + break; 1.372 + } 1.373 + case Type::Elaborated: { 1.374 + CountType( 1.375 + dyn_cast<ElaboratedType>(type)->getNamedType().getTypePtr(), 1.376 + trivial_member, non_trivial_member, templated_non_trivial_member); 1.377 + break; 1.378 + } 1.379 + case Type::Typedef: { 1.380 + while (const TypedefType* TT = dyn_cast<TypedefType>(type)) { 1.381 + type = TT->getDecl()->getUnderlyingType().getTypePtr(); 1.382 + } 1.383 + CountType(type, trivial_member, non_trivial_member, 1.384 + templated_non_trivial_member); 1.385 + break; 1.386 + } 1.387 + default: { 1.388 + // Stupid assumption: anything we see that isn't the above is one of 1.389 + // the 20 integer types. 1.390 + (*trivial_member)++; 1.391 + break; 1.392 + } 1.393 + } 1.394 + } 1.395 +}; 1.396 + 1.397 +class FindBadConstructsAction : public PluginASTAction { 1.398 + public: 1.399 + FindBadConstructsAction() 1.400 + : check_refcounted_dtors_(true), 1.401 + check_virtuals_in_implementations_(true) { 1.402 + } 1.403 + 1.404 + protected: 1.405 + // Overridden from PluginASTAction: 1.406 + virtual ASTConsumer* CreateASTConsumer(CompilerInstance& instance, 1.407 + llvm::StringRef ref) { 1.408 + return new FindBadConstructsConsumer( 1.409 + instance, check_refcounted_dtors_, check_virtuals_in_implementations_); 1.410 + } 1.411 + 1.412 + virtual bool ParseArgs(const CompilerInstance& instance, 1.413 + const std::vector<std::string>& args) { 1.414 + bool parsed = true; 1.415 + 1.416 + for (size_t i = 0; i < args.size() && parsed; ++i) { 1.417 + if (args[i] == "skip-refcounted-dtors") { 1.418 + check_refcounted_dtors_ = false; 1.419 + } else if (args[i] == "skip-virtuals-in-implementations") { 1.420 + check_virtuals_in_implementations_ = false; 1.421 + } else { 1.422 + parsed = false; 1.423 + llvm::errs() << "Unknown argument: " << args[i] << "\n"; 1.424 + } 1.425 + } 1.426 + 1.427 + return parsed; 1.428 + } 1.429 + 1.430 + private: 1.431 + bool check_refcounted_dtors_; 1.432 + bool check_virtuals_in_implementations_; 1.433 +}; 1.434 + 1.435 +} // namespace 1.436 + 1.437 +static FrontendPluginRegistry::Add<FindBadConstructsAction> 1.438 +X("find-bad-constructs", "Finds bad C++ constructs");