Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 // This file defines a bunch of recurring problems in the Chromium C++ code. | 5 // This file defines a bunch of recurring problems in the Chromium C++ code. |
| 6 // | 6 // |
| 7 // Checks that are implemented: | 7 // Checks that are implemented: |
| 8 // - Constructors/Destructors should not be inlined if they are of a complex | 8 // - Constructors/Destructors should not be inlined if they are of a complex |
| 9 // class type. | 9 // class type. |
| 10 // - Missing "virtual" keywords on methods that should be virtual. | 10 // - Missing "virtual" keywords on methods that should be virtual. |
| 11 // - Non-annotated overriding virtual methods. | 11 // - Non-annotated overriding virtual methods. |
| 12 // - Virtual methods with nonempty implementations in their headers. | 12 // - Virtual methods with nonempty implementations in their headers. |
| 13 // | 13 // - Classes that derive from base::RefCounted / base::RefCountedThreadSafe |
| 14 // Things that are still TODO: | 14 // should have protected or private destructors. |
| 15 // - Deriving from base::RefCounted and friends should mandate non-public | |
| 16 // destructors. | |
| 17 | 15 |
| 18 #include "clang/Frontend/FrontendPluginRegistry.h" | 16 #include "clang/Frontend/FrontendPluginRegistry.h" |
| 19 #include "clang/AST/ASTConsumer.h" | 17 #include "clang/AST/ASTConsumer.h" |
| 20 #include "clang/AST/AST.h" | 18 #include "clang/AST/AST.h" |
| 19 #include "clang/AST/CXXInheritance.h" | |
| 21 #include "clang/AST/TypeLoc.h" | 20 #include "clang/AST/TypeLoc.h" |
| 22 #include "clang/Basic/SourceManager.h" | 21 #include "clang/Basic/SourceManager.h" |
| 23 #include "clang/Frontend/CompilerInstance.h" | 22 #include "clang/Frontend/CompilerInstance.h" |
| 24 #include "llvm/Support/raw_ostream.h" | 23 #include "llvm/Support/raw_ostream.h" |
| 25 | 24 |
| 26 #include "ChromeClassTester.h" | 25 #include "ChromeClassTester.h" |
| 27 | 26 |
| 28 using namespace clang; | 27 using namespace clang; |
| 29 | 28 |
| 30 namespace { | 29 namespace { |
| 31 | 30 |
| 32 bool TypeHasNonTrivialDtor(const Type* type) { | 31 bool TypeHasNonTrivialDtor(const Type* type) { |
| 33 if (const CXXRecordDecl* cxx_r = type->getCXXRecordDeclForPointerType()) | 32 if (const CXXRecordDecl* cxx_r = type->getCXXRecordDeclForPointerType()) |
| 34 return cxx_r->hasTrivialDestructor(); | 33 return cxx_r->hasTrivialDestructor(); |
| 35 | 34 |
| 36 return false; | 35 return false; |
| 37 } | 36 } |
| 38 | 37 |
| 38 // Returns the underlying Type for |type| by expanding typedefs and removing | |
| 39 // any namespace qualifiers. | |
| 40 const Type* UnwrapType(const Type* type) { | |
|
Nico
2012/04/13 22:40:55
Does Type::getDesugaredType() do some of this?
If
Ryan Sleevi
2012/04/14 00:16:29
As far as I can tell, it does too much or not enou
| |
| 41 switch (type->getTypeClass()) { | |
| 42 case Type::Elaborated: { | |
| 43 type = UnwrapType( | |
| 44 dyn_cast<ElaboratedType>(type)->getNamedType().getTypePtr()); | |
| 45 break; | |
| 46 } | |
| 47 case Type::Typedef: { | |
| 48 type = UnwrapType(dyn_cast<TypedefType>(type)->desugar().getTypePtr()); | |
| 49 break; | |
| 50 } | |
| 51 default: { | |
| 52 break; | |
| 53 } | |
| 54 } | |
| 55 return type; | |
| 56 } | |
| 57 | |
| 39 // Searches for constructs that we know we don't want in the Chromium code base. | 58 // Searches for constructs that we know we don't want in the Chromium code base. |
| 40 class FindBadConstructsConsumer : public ChromeClassTester { | 59 class FindBadConstructsConsumer : public ChromeClassTester { |
| 41 public: | 60 public: |
| 42 FindBadConstructsConsumer(CompilerInstance& instance) | 61 FindBadConstructsConsumer(CompilerInstance& instance, |
| 43 : ChromeClassTester(instance) {} | 62 bool skip_refcounted_dtors) |
| 63 : ChromeClassTester(instance), | |
| 64 skip_refcounted_dtors_(skip_refcounted_dtors) { | |
| 65 } | |
| 44 | 66 |
| 45 virtual void CheckChromeClass(const SourceLocation& record_location, | 67 virtual void CheckChromeClass(SourceLocation record_location, |
| 46 CXXRecordDecl* record) { | 68 CXXRecordDecl* record) { |
| 47 CheckCtorDtorWeight(record_location, record); | 69 bool implementation_file = InImplementationFile(record_location); |
| 48 CheckVirtualMethods(record_location, record); | 70 |
| 71 if (!implementation_file) { | |
| 72 // Only check for "heavy" constructors/destructors in header files; | |
| 73 // within implementation files, there is no performance cost. | |
| 74 CheckCtorDtorWeight(record_location, record); | |
| 75 | |
| 76 // Check that all virtual methods are marked accordingly with both | |
| 77 // virtual and OVERRIDE. | |
| 78 CheckVirtualMethods(record_location, record); | |
| 79 } | |
| 80 | |
| 81 if (!skip_refcounted_dtors_) | |
|
Nico
2012/04/13 22:40:55
double negation like this is confusing. Call the v
| |
| 82 CheckRefCountedDtors(record_location, record); | |
| 83 } | |
| 84 | |
| 85 private: | |
| 86 bool skip_refcounted_dtors_; | |
| 87 | |
| 88 // Returns true if |base| specifies one of the Chromium reference counted | |
| 89 // classes (base::RefCounted / base::RefCountedThreadSafe). |user_data| is | |
| 90 // ignored. | |
| 91 static bool IsRefCountedCallback(const CXXBaseSpecifier* base, | |
| 92 CXXBasePath& path, | |
| 93 void* user_data) { | |
| 94 FindBadConstructsConsumer* self = | |
| 95 static_cast<FindBadConstructsConsumer*>(user_data); | |
| 96 | |
| 97 const TemplateSpecializationType* base_type = | |
| 98 dyn_cast<TemplateSpecializationType>( | |
| 99 UnwrapType(base->getType().getTypePtr())); | |
| 100 if (!base_type) { | |
| 101 // Base-most definition is not a template, so this cannot derive from | |
| 102 // base::RefCounted. However, it may still be possible to use with a | |
| 103 // scoped_refptr<> and support ref-counting, so this is not a perfect | |
| 104 // guarantee of safety. | |
| 105 return false; | |
| 106 } | |
| 107 | |
| 108 TemplateName name = base_type->getTemplateName(); | |
| 109 if (TemplateDecl* decl = name.getAsTemplateDecl()) { | |
| 110 std::string base_name = decl->getNameAsString(); | |
| 111 | |
| 112 // Check for both base::RefCounted and base::RefCountedThreadSafe. | |
| 113 if (base_name.compare(0, 10, "RefCounted") == 0 && | |
| 114 self->GetNamespace(decl) == "base") { | |
| 115 return true; | |
| 116 } | |
| 117 } | |
| 118 return false; | |
| 119 } | |
| 120 | |
| 121 // Prints errors if the destructor of a RefCounted class is public. | |
| 122 void CheckRefCountedDtors(SourceLocation record_location, | |
| 123 CXXRecordDecl* record) { | |
| 124 // Skip anonymous structs. | |
| 125 if (record->getIdentifier() == NULL) | |
| 126 return; | |
| 127 | |
| 128 CXXBasePaths paths; | |
| 129 if (!record->lookupInBases( | |
| 130 &FindBadConstructsConsumer::IsRefCountedCallback, this, paths)) { | |
| 131 return; // Class does not derive from a ref-counted base class. | |
| 132 } | |
| 133 | |
| 134 if (!record->hasUserDeclaredDestructor()) { | |
| 135 emitWarning( | |
| 136 record_location, | |
| 137 "Classes that are ref-counted should have explicit " | |
| 138 "destructors that are protected or private."); | |
| 139 } else if (CXXDestructorDecl* dtor = record->getDestructor()) { | |
| 140 if (dtor->getAccess() == AS_public) { | |
| 141 emitWarning( | |
| 142 dtor->getInnerLocStart(), | |
| 143 "Classes that are ref-counted should not have " | |
| 144 "public destructors."); | |
| 145 } | |
| 146 } | |
| 49 } | 147 } |
| 50 | 148 |
| 51 // Prints errors if the constructor/destructor weight is too heavy. | 149 // Prints errors if the constructor/destructor weight is too heavy. |
| 52 void CheckCtorDtorWeight(const SourceLocation& record_location, | 150 void CheckCtorDtorWeight(SourceLocation record_location, |
| 53 CXXRecordDecl* record) { | 151 CXXRecordDecl* record) { |
| 54 // We don't handle anonymous structs. If this record doesn't have a | 152 // We don't handle anonymous structs. If this record doesn't have a |
| 55 // name, it's of the form: | 153 // name, it's of the form: |
| 56 // | 154 // |
| 57 // struct { | 155 // struct { |
| 58 // ... | 156 // ... |
| 59 // } name_; | 157 // } name_; |
| 60 if (record->getIdentifier() == NULL) | 158 if (record->getIdentifier() == NULL) |
| 61 return; | 159 return; |
| 62 | 160 |
| (...skipping 91 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 154 if (cs->size()) { | 252 if (cs->size()) { |
| 155 emitWarning( | 253 emitWarning( |
| 156 cs->getLBracLoc(), | 254 cs->getLBracLoc(), |
| 157 "virtual methods with non-empty bodies shouldn't be " | 255 "virtual methods with non-empty bodies shouldn't be " |
| 158 "declared inline."); | 256 "declared inline."); |
| 159 } | 257 } |
| 160 } | 258 } |
| 161 } | 259 } |
| 162 } | 260 } |
| 163 | 261 |
| 262 bool InTestingNamespace(const Decl* record) { | |
| 263 return GetNamespace(record).find("testing") != std::string::npos; | |
| 264 } | |
| 265 | |
| 164 bool IsMethodInBannedNamespace(const CXXMethodDecl* method) { | 266 bool IsMethodInBannedNamespace(const CXXMethodDecl* method) { |
| 165 if (InBannedNamespace(method)) | 267 if (InBannedNamespace(method)) |
| 166 return true; | 268 return true; |
| 167 for (CXXMethodDecl::method_iterator i = method->begin_overridden_methods(); | 269 for (CXXMethodDecl::method_iterator i = method->begin_overridden_methods(); |
| 168 i != method->end_overridden_methods(); | 270 i != method->end_overridden_methods(); |
| 169 ++i) { | 271 ++i) { |
| 170 const CXXMethodDecl* overridden = *i; | 272 const CXXMethodDecl* overridden = *i; |
| 171 if (IsMethodInBannedNamespace(overridden)) | 273 if (IsMethodInBannedNamespace(overridden)) |
| 172 return true; | 274 return true; |
| 173 } | 275 } |
| (...skipping 15 matching lines...) Expand all Loading... | |
| 189 emitWarning(loc, "Overriding method must be marked with OVERRIDE."); | 291 emitWarning(loc, "Overriding method must be marked with OVERRIDE."); |
| 190 } | 292 } |
| 191 | 293 |
| 192 // Makes sure there is a "virtual" keyword on virtual methods and that there | 294 // Makes sure there is a "virtual" keyword on virtual methods and that there |
| 193 // are no inline function bodies on them (but "{}" is allowed). | 295 // are no inline function bodies on them (but "{}" is allowed). |
| 194 // | 296 // |
| 195 // Gmock objects trigger these for each MOCK_BLAH() macro used. So we have a | 297 // Gmock objects trigger these for each MOCK_BLAH() macro used. So we have a |
| 196 // trick to get around that. If a class has member variables whose types are | 298 // trick to get around that. If a class has member variables whose types are |
| 197 // in the "testing" namespace (which is how gmock works behind the scenes), | 299 // in the "testing" namespace (which is how gmock works behind the scenes), |
| 198 // there's a really high chance we won't care about these errors | 300 // there's a really high chance we won't care about these errors |
| 199 void CheckVirtualMethods(const SourceLocation& record_location, | 301 void CheckVirtualMethods(SourceLocation record_location, |
| 200 CXXRecordDecl* record) { | 302 CXXRecordDecl* record) { |
| 201 for (CXXRecordDecl::field_iterator it = record->field_begin(); | 303 for (CXXRecordDecl::field_iterator it = record->field_begin(); |
| 202 it != record->field_end(); ++it) { | 304 it != record->field_end(); ++it) { |
| 203 CXXRecordDecl* record_type = | 305 CXXRecordDecl* record_type = |
| 204 it->getTypeSourceInfo()->getTypeLoc().getTypePtr()-> | 306 it->getTypeSourceInfo()->getTypeLoc().getTypePtr()-> |
| 205 getAsCXXRecordDecl(); | 307 getAsCXXRecordDecl(); |
| 206 if (record_type) { | 308 if (record_type) { |
| 207 if (InTestingNamespace(record_type)) { | 309 if (InTestingNamespace(record_type)) { |
| 208 return; | 310 return; |
| 209 } | 311 } |
| (...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 276 // Stupid assumption: anything we see that isn't the above is one of | 378 // Stupid assumption: anything we see that isn't the above is one of |
| 277 // the 20 integer types. | 379 // the 20 integer types. |
| 278 (*trivial_member)++; | 380 (*trivial_member)++; |
| 279 break; | 381 break; |
| 280 } | 382 } |
| 281 } | 383 } |
| 282 } | 384 } |
| 283 }; | 385 }; |
| 284 | 386 |
| 285 class FindBadConstructsAction : public PluginASTAction { | 387 class FindBadConstructsAction : public PluginASTAction { |
| 388 public: | |
| 389 FindBadConstructsAction() : skip_refcounted_dtors_(false) {} | |
| 390 | |
| 286 protected: | 391 protected: |
| 287 ASTConsumer* CreateASTConsumer(CompilerInstance &CI, llvm::StringRef ref) { | 392 ASTConsumer* CreateASTConsumer(CompilerInstance &CI, llvm::StringRef ref) { |
| 288 return new FindBadConstructsConsumer(CI); | 393 return new FindBadConstructsConsumer(CI, skip_refcounted_dtors_); |
| 289 } | 394 } |
| 290 | 395 |
| 291 bool ParseArgs(const CompilerInstance &CI, | 396 bool ParseArgs(const CompilerInstance &CI, |
| 292 const std::vector<std::string>& args) { | 397 const std::vector<std::string>& args) { |
| 293 // We don't take any additional arguments here. | 398 bool parsed = true; |
| 294 return true; | 399 |
| 400 for (size_t i = 0; i < args.size() && parsed; ++i) { | |
| 401 if (args[i] == "skip-refcounted-dtors") { | |
| 402 skip_refcounted_dtors_ = true; | |
| 403 } else { | |
| 404 parsed = false; | |
| 405 llvm::errs() << "Unknown argument: " << args[i] << "\n"; | |
| 406 } | |
| 407 } | |
| 408 | |
| 409 return parsed; | |
| 295 } | 410 } |
| 411 | |
| 412 private: | |
| 413 bool skip_refcounted_dtors_; | |
| 296 }; | 414 }; |
| 297 | 415 |
| 298 } // namespace | 416 } // namespace |
| 299 | 417 |
| 300 static FrontendPluginRegistry::Add<FindBadConstructsAction> | 418 static FrontendPluginRegistry::Add<FindBadConstructsAction> |
| 301 X("find-bad-constructs", "Finds bad C++ constructs"); | 419 X("find-bad-constructs", "Finds bad C++ constructs"); |
| OLD | NEW |