OLD | NEW |
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 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 | 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) { |
| 41 if (const ElaboratedType* elaborated = dyn_cast<ElaboratedType>(type)) |
| 42 return UnwrapType(elaborated->getNamedType().getTypePtr()); |
| 43 if (const TypedefType* typedefed = dyn_cast<TypedefType>(type)) |
| 44 return UnwrapType(typedefed->desugar().getTypePtr()); |
| 45 return type; |
| 46 } |
| 47 |
39 // Searches for constructs that we know we don't want in the Chromium code base. | 48 // Searches for constructs that we know we don't want in the Chromium code base. |
40 class FindBadConstructsConsumer : public ChromeClassTester { | 49 class FindBadConstructsConsumer : public ChromeClassTester { |
41 public: | 50 public: |
42 FindBadConstructsConsumer(CompilerInstance& instance) | 51 FindBadConstructsConsumer(CompilerInstance& instance, |
43 : ChromeClassTester(instance) {} | 52 bool check_refcounted_dtors) |
| 53 : ChromeClassTester(instance), |
| 54 check_refcounted_dtors_(check_refcounted_dtors) { |
| 55 } |
44 | 56 |
45 virtual void CheckChromeClass(const SourceLocation& record_location, | 57 virtual void CheckChromeClass(SourceLocation record_location, |
46 CXXRecordDecl* record) { | 58 CXXRecordDecl* record) { |
47 CheckCtorDtorWeight(record_location, record); | 59 bool implementation_file = InImplementationFile(record_location); |
48 CheckVirtualMethods(record_location, record); | 60 |
| 61 if (!implementation_file) { |
| 62 // Only check for "heavy" constructors/destructors in header files; |
| 63 // within implementation files, there is no performance cost. |
| 64 CheckCtorDtorWeight(record_location, record); |
| 65 |
| 66 // Check that all virtual methods are marked accordingly with both |
| 67 // virtual and OVERRIDE. |
| 68 CheckVirtualMethods(record_location, record); |
| 69 } |
| 70 |
| 71 if (check_refcounted_dtors_) |
| 72 CheckRefCountedDtors(record_location, record); |
| 73 } |
| 74 |
| 75 private: |
| 76 bool check_refcounted_dtors_; |
| 77 |
| 78 // Returns true if |base| specifies one of the Chromium reference counted |
| 79 // classes (base::RefCounted / base::RefCountedThreadSafe). |user_data| is |
| 80 // ignored. |
| 81 static bool IsRefCountedCallback(const CXXBaseSpecifier* base, |
| 82 CXXBasePath& path, |
| 83 void* user_data) { |
| 84 FindBadConstructsConsumer* self = |
| 85 static_cast<FindBadConstructsConsumer*>(user_data); |
| 86 |
| 87 const TemplateSpecializationType* base_type = |
| 88 dyn_cast<TemplateSpecializationType>( |
| 89 UnwrapType(base->getType().getTypePtr())); |
| 90 if (!base_type) { |
| 91 // Base-most definition is not a template, so this cannot derive from |
| 92 // base::RefCounted. However, it may still be possible to use with a |
| 93 // scoped_refptr<> and support ref-counting, so this is not a perfect |
| 94 // guarantee of safety. |
| 95 return false; |
| 96 } |
| 97 |
| 98 TemplateName name = base_type->getTemplateName(); |
| 99 if (TemplateDecl* decl = name.getAsTemplateDecl()) { |
| 100 std::string base_name = decl->getNameAsString(); |
| 101 |
| 102 // Check for both base::RefCounted and base::RefCountedThreadSafe. |
| 103 if (base_name.compare(0, 10, "RefCounted") == 0 && |
| 104 self->GetNamespace(decl) == "base") { |
| 105 return true; |
| 106 } |
| 107 } |
| 108 return false; |
| 109 } |
| 110 |
| 111 // Prints errors if the destructor of a RefCounted class is public. |
| 112 void CheckRefCountedDtors(SourceLocation record_location, |
| 113 CXXRecordDecl* record) { |
| 114 // Skip anonymous structs. |
| 115 if (record->getIdentifier() == NULL) |
| 116 return; |
| 117 |
| 118 CXXBasePaths paths; |
| 119 if (!record->lookupInBases( |
| 120 &FindBadConstructsConsumer::IsRefCountedCallback, this, paths)) { |
| 121 return; // Class does not derive from a ref-counted base class. |
| 122 } |
| 123 |
| 124 if (!record->hasUserDeclaredDestructor()) { |
| 125 emitWarning( |
| 126 record_location, |
| 127 "Classes that are ref-counted should have explicit " |
| 128 "destructors that are protected or private."); |
| 129 } else if (CXXDestructorDecl* dtor = record->getDestructor()) { |
| 130 if (dtor->getAccess() == AS_public) { |
| 131 emitWarning( |
| 132 dtor->getInnerLocStart(), |
| 133 "Classes that are ref-counted should not have " |
| 134 "public destructors."); |
| 135 } |
| 136 } |
49 } | 137 } |
50 | 138 |
51 // Prints errors if the constructor/destructor weight is too heavy. | 139 // Prints errors if the constructor/destructor weight is too heavy. |
52 void CheckCtorDtorWeight(const SourceLocation& record_location, | 140 void CheckCtorDtorWeight(SourceLocation record_location, |
53 CXXRecordDecl* record) { | 141 CXXRecordDecl* record) { |
54 // We don't handle anonymous structs. If this record doesn't have a | 142 // We don't handle anonymous structs. If this record doesn't have a |
55 // name, it's of the form: | 143 // name, it's of the form: |
56 // | 144 // |
57 // struct { | 145 // struct { |
58 // ... | 146 // ... |
59 // } name_; | 147 // } name_; |
60 if (record->getIdentifier() == NULL) | 148 if (record->getIdentifier() == NULL) |
61 return; | 149 return; |
62 | 150 |
(...skipping 91 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
154 if (cs->size()) { | 242 if (cs->size()) { |
155 emitWarning( | 243 emitWarning( |
156 cs->getLBracLoc(), | 244 cs->getLBracLoc(), |
157 "virtual methods with non-empty bodies shouldn't be " | 245 "virtual methods with non-empty bodies shouldn't be " |
158 "declared inline."); | 246 "declared inline."); |
159 } | 247 } |
160 } | 248 } |
161 } | 249 } |
162 } | 250 } |
163 | 251 |
| 252 bool InTestingNamespace(const Decl* record) { |
| 253 return GetNamespace(record).find("testing") != std::string::npos; |
| 254 } |
| 255 |
164 bool IsMethodInBannedNamespace(const CXXMethodDecl* method) { | 256 bool IsMethodInBannedNamespace(const CXXMethodDecl* method) { |
165 if (InBannedNamespace(method)) | 257 if (InBannedNamespace(method)) |
166 return true; | 258 return true; |
167 for (CXXMethodDecl::method_iterator i = method->begin_overridden_methods(); | 259 for (CXXMethodDecl::method_iterator i = method->begin_overridden_methods(); |
168 i != method->end_overridden_methods(); | 260 i != method->end_overridden_methods(); |
169 ++i) { | 261 ++i) { |
170 const CXXMethodDecl* overridden = *i; | 262 const CXXMethodDecl* overridden = *i; |
171 if (IsMethodInBannedNamespace(overridden)) | 263 if (IsMethodInBannedNamespace(overridden)) |
172 return true; | 264 return true; |
173 } | 265 } |
(...skipping 15 matching lines...) Expand all Loading... |
189 emitWarning(loc, "Overriding method must be marked with OVERRIDE."); | 281 emitWarning(loc, "Overriding method must be marked with OVERRIDE."); |
190 } | 282 } |
191 | 283 |
192 // Makes sure there is a "virtual" keyword on virtual methods and that there | 284 // Makes sure there is a "virtual" keyword on virtual methods and that there |
193 // are no inline function bodies on them (but "{}" is allowed). | 285 // are no inline function bodies on them (but "{}" is allowed). |
194 // | 286 // |
195 // Gmock objects trigger these for each MOCK_BLAH() macro used. So we have a | 287 // 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 | 288 // 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), | 289 // 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 | 290 // there's a really high chance we won't care about these errors |
199 void CheckVirtualMethods(const SourceLocation& record_location, | 291 void CheckVirtualMethods(SourceLocation record_location, |
200 CXXRecordDecl* record) { | 292 CXXRecordDecl* record) { |
201 for (CXXRecordDecl::field_iterator it = record->field_begin(); | 293 for (CXXRecordDecl::field_iterator it = record->field_begin(); |
202 it != record->field_end(); ++it) { | 294 it != record->field_end(); ++it) { |
203 CXXRecordDecl* record_type = | 295 CXXRecordDecl* record_type = |
204 it->getTypeSourceInfo()->getTypeLoc().getTypePtr()-> | 296 it->getTypeSourceInfo()->getTypeLoc().getTypePtr()-> |
205 getAsCXXRecordDecl(); | 297 getAsCXXRecordDecl(); |
206 if (record_type) { | 298 if (record_type) { |
207 if (InTestingNamespace(record_type)) { | 299 if (InTestingNamespace(record_type)) { |
208 return; | 300 return; |
209 } | 301 } |
(...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 | 368 // Stupid assumption: anything we see that isn't the above is one of |
277 // the 20 integer types. | 369 // the 20 integer types. |
278 (*trivial_member)++; | 370 (*trivial_member)++; |
279 break; | 371 break; |
280 } | 372 } |
281 } | 373 } |
282 } | 374 } |
283 }; | 375 }; |
284 | 376 |
285 class FindBadConstructsAction : public PluginASTAction { | 377 class FindBadConstructsAction : public PluginASTAction { |
| 378 public: |
| 379 FindBadConstructsAction() : check_refcounted_dtors_(true) {} |
| 380 |
286 protected: | 381 protected: |
287 ASTConsumer* CreateASTConsumer(CompilerInstance &CI, llvm::StringRef ref) { | 382 ASTConsumer* CreateASTConsumer(CompilerInstance &CI, llvm::StringRef ref) { |
288 return new FindBadConstructsConsumer(CI); | 383 return new FindBadConstructsConsumer(CI, check_refcounted_dtors_); |
289 } | 384 } |
290 | 385 |
291 bool ParseArgs(const CompilerInstance &CI, | 386 bool ParseArgs(const CompilerInstance &CI, |
292 const std::vector<std::string>& args) { | 387 const std::vector<std::string>& args) { |
293 // We don't take any additional arguments here. | 388 bool parsed = true; |
294 return true; | 389 |
| 390 for (size_t i = 0; i < args.size() && parsed; ++i) { |
| 391 if (args[i] == "skip-refcounted-dtors") { |
| 392 check_refcounted_dtors_ = false; |
| 393 } else { |
| 394 parsed = false; |
| 395 llvm::errs() << "Unknown argument: " << args[i] << "\n"; |
| 396 } |
| 397 } |
| 398 |
| 399 return parsed; |
295 } | 400 } |
| 401 |
| 402 private: |
| 403 bool check_refcounted_dtors_; |
296 }; | 404 }; |
297 | 405 |
298 } // namespace | 406 } // namespace |
299 | 407 |
300 static FrontendPluginRegistry::Add<FindBadConstructsAction> | 408 static FrontendPluginRegistry::Add<FindBadConstructsAction> |
301 X("find-bad-constructs", "Finds bad C++ constructs"); | 409 X("find-bad-constructs", "Finds bad C++ constructs"); |
OLD | NEW |