Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(40)

Side by Side Diff: tools/clang/plugins/FindBadConstructs.cpp

Issue 10407057: Optionally check base classes for refcounting issues (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Merge into one AST pass Created 8 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2012 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.
(...skipping 10 matching lines...) Expand all
21 #include "clang/Basic/SourceManager.h" 21 #include "clang/Basic/SourceManager.h"
22 #include "clang/Frontend/CompilerInstance.h" 22 #include "clang/Frontend/CompilerInstance.h"
23 #include "llvm/Support/raw_ostream.h" 23 #include "llvm/Support/raw_ostream.h"
24 24
25 #include "ChromeClassTester.h" 25 #include "ChromeClassTester.h"
26 26
27 using namespace clang; 27 using namespace clang;
28 28
29 namespace { 29 namespace {
30 30
31 static const char kNoExplicitDtor[] =
Nico 2012/07/21 18:36:37 const has implicit internal linkage, no need for s
32 "[chromium-style] Classes that are ref-counted should have explicit "
33 "destructors that are declared protected or private.";
34 static const char kPublicDtor[] =
35 "[chromium-style] Classes that are ref-counted should not have "
36 "public destructors.";
37 static const char kNoteInheritance[] =
38 "[chromium-style] %0 inherits from %1 here";
39 static const char kNoteImplicitDtor[] =
40 "[chromium-style] No explicit destructor for %0 defined";
41 static const char kNotePublicDtor[] =
42 "[chromium-style] Public destructor declared here";
Nico 2012/07/21 18:36:37 nice :-)
43
31 bool TypeHasNonTrivialDtor(const Type* type) { 44 bool TypeHasNonTrivialDtor(const Type* type) {
32 if (const CXXRecordDecl* cxx_r = type->getCXXRecordDeclForPointerType()) 45 if (const CXXRecordDecl* cxx_r = type->getCXXRecordDeclForPointerType())
33 return cxx_r->hasTrivialDestructor(); 46 return cxx_r->hasTrivialDestructor();
34 47
35 return false; 48 return false;
36 } 49 }
37 50
38 // Returns the underlying Type for |type| by expanding typedefs and removing 51 // Returns the underlying Type for |type| by expanding typedefs and removing
39 // any namespace qualifiers. 52 // any namespace qualifiers. This is similar to desugaring, except that for
53 // ElaboratedTypes, desugar will unwrap too much.
40 const Type* UnwrapType(const Type* type) { 54 const Type* UnwrapType(const Type* type) {
41 if (const ElaboratedType* elaborated = dyn_cast<ElaboratedType>(type)) 55 if (const ElaboratedType* elaborated = dyn_cast<ElaboratedType>(type))
42 return UnwrapType(elaborated->getNamedType().getTypePtr()); 56 return UnwrapType(elaborated->getNamedType().getTypePtr());
43 if (const TypedefType* typedefed = dyn_cast<TypedefType>(type)) 57 if (const TypedefType* typedefed = dyn_cast<TypedefType>(type))
44 return UnwrapType(typedefed->desugar().getTypePtr()); 58 return UnwrapType(typedefed->desugar().getTypePtr());
45 return type; 59 return type;
46 } 60 }
47 61
48 // Searches for constructs that we know we don't want in the Chromium code base. 62 // Searches for constructs that we know we don't want in the Chromium code base.
49 class FindBadConstructsConsumer : public ChromeClassTester { 63 class FindBadConstructsConsumer : public ChromeClassTester {
50 public: 64 public:
51 FindBadConstructsConsumer(CompilerInstance& instance, 65 FindBadConstructsConsumer(CompilerInstance& instance,
66 bool check_virtuals_in_implementations,
52 bool check_refcounted_dtors, 67 bool check_refcounted_dtors,
53 bool check_virtuals_in_implementations) 68 bool check_base_classes)
54 : ChromeClassTester(instance), 69 : ChromeClassTester(instance),
70 check_virtuals_in_implementations_(check_virtuals_in_implementations),
55 check_refcounted_dtors_(check_refcounted_dtors), 71 check_refcounted_dtors_(check_refcounted_dtors),
56 check_virtuals_in_implementations_(check_virtuals_in_implementations) { 72 check_base_classes_(check_base_classes) {
73 // Register warning/error messages.
74 diag_no_explicit_dtor_ = diagnostic().getCustomDiagID(
75 getErrorLevel(), kNoExplicitDtor);
76 diag_public_dtor_ = diagnostic().getCustomDiagID(
77 getErrorLevel(), kPublicDtor);
78
79 // Registers notes to make it easier to interpret warnings.
80 diag_note_inheritance_ = diagnostic().getCustomDiagID(
81 DiagnosticsEngine::Note, kNoteInheritance);
82 diag_note_implicit_dtor_ = diagnostic().getCustomDiagID(
83 DiagnosticsEngine::Note, kNoteImplicitDtor);
84 diag_note_public_dtor_ = diagnostic().getCustomDiagID(
85 DiagnosticsEngine::Note, kNotePublicDtor);
57 } 86 }
58 87
59 virtual void CheckChromeClass(SourceLocation record_location, 88 virtual void CheckChromeClass(SourceLocation record_location,
60 CXXRecordDecl* record) { 89 CXXRecordDecl* record) {
61 bool implementation_file = InImplementationFile(record_location); 90 bool implementation_file = InImplementationFile(record_location);
62 91
63 if (!implementation_file) { 92 if (!implementation_file) {
64 // Only check for "heavy" constructors/destructors in header files; 93 // Only check for "heavy" constructors/destructors in header files;
65 // within implementation files, there is no performance cost. 94 // within implementation files, there is no performance cost.
66 CheckCtorDtorWeight(record_location, record); 95 CheckCtorDtorWeight(record_location, record);
67 } 96 }
68 97
69 if (!implementation_file || check_virtuals_in_implementations_) { 98 if (!implementation_file || check_virtuals_in_implementations_) {
70 bool warn_on_inline_bodies = !implementation_file; 99 bool warn_on_inline_bodies = !implementation_file;
71 100
72 // Check that all virtual methods are marked accordingly with both 101 // Check that all virtual methods are marked accordingly with both
73 // virtual and OVERRIDE. 102 // virtual and OVERRIDE.
74 CheckVirtualMethods(record_location, record, warn_on_inline_bodies); 103 CheckVirtualMethods(record_location, record, warn_on_inline_bodies);
75 } 104 }
76 105
77 if (check_refcounted_dtors_) 106 if (check_refcounted_dtors_)
78 CheckRefCountedDtors(record_location, record); 107 CheckRefCountedDtors(record_location, record);
79 } 108 }
80 109
81 private: 110 private:
111 // The type of problematic ref-counting pattern that was encountered.
112 enum RefcountIssue {
113 None,
114 ImplicitDestructor,
115 PublicDestructor
116 };
117
118 bool check_virtuals_in_implementations_;
82 bool check_refcounted_dtors_; 119 bool check_refcounted_dtors_;
83 bool check_virtuals_in_implementations_; 120 bool check_base_classes_;
84 121
85 // Returns true if |base| specifies one of the Chromium reference counted 122 unsigned diag_no_explicit_dtor_;
86 // classes (base::RefCounted / base::RefCountedThreadSafe). |user_data| is 123 unsigned diag_public_dtor_;
87 // ignored. 124 unsigned diag_note_inheritance_;
88 static bool IsRefCountedCallback(const CXXBaseSpecifier* base, 125 unsigned diag_note_implicit_dtor_;
89 CXXBasePath& path, 126 unsigned diag_note_public_dtor_;
90 void* user_data) {
91 FindBadConstructsConsumer* self =
92 static_cast<FindBadConstructsConsumer*>(user_data);
93
94 const TemplateSpecializationType* base_type =
95 dyn_cast<TemplateSpecializationType>(
96 UnwrapType(base->getType().getTypePtr()));
97 if (!base_type) {
98 // Base-most definition is not a template, so this cannot derive from
99 // base::RefCounted. However, it may still be possible to use with a
100 // scoped_refptr<> and support ref-counting, so this is not a perfect
101 // guarantee of safety.
102 return false;
103 }
104
105 TemplateName name = base_type->getTemplateName();
106 if (TemplateDecl* decl = name.getAsTemplateDecl()) {
107 std::string base_name = decl->getNameAsString();
108
109 // Check for both base::RefCounted and base::RefCountedThreadSafe.
110 if (base_name.compare(0, 10, "RefCounted") == 0 &&
111 self->GetNamespace(decl) == "base") {
112 return true;
113 }
114 }
115 return false;
116 }
117
118 // Prints errors if the destructor of a RefCounted class is public.
119 void CheckRefCountedDtors(SourceLocation record_location,
120 CXXRecordDecl* record) {
121 // Skip anonymous structs.
122 if (record->getIdentifier() == NULL)
123 return;
124
125 CXXBasePaths paths;
126 if (!record->lookupInBases(
127 &FindBadConstructsConsumer::IsRefCountedCallback, this, paths)) {
128 return; // Class does not derive from a ref-counted base class.
129 }
130
131 if (!record->hasUserDeclaredDestructor()) {
132 emitWarning(
133 record_location,
134 "Classes that are ref-counted should have explicit "
135 "destructors that are protected or private.");
136 } else if (CXXDestructorDecl* dtor = record->getDestructor()) {
137 if (dtor->getAccess() == AS_public) {
138 emitWarning(
139 dtor->getInnerLocStart(),
140 "Classes that are ref-counted should not have "
141 "public destructors.");
142 }
143 }
144 }
Nico 2012/07/21 18:36:37 This is a bit harder to review since it moved arou
145 127
146 // Prints errors if the constructor/destructor weight is too heavy. 128 // Prints errors if the constructor/destructor weight is too heavy.
147 void CheckCtorDtorWeight(SourceLocation record_location, 129 void CheckCtorDtorWeight(SourceLocation record_location,
148 CXXRecordDecl* record) { 130 CXXRecordDecl* record) {
149 // We don't handle anonymous structs. If this record doesn't have a 131 // We don't handle anonymous structs. If this record doesn't have a
150 // name, it's of the form: 132 // name, it's of the form:
151 // 133 //
152 // struct { 134 // struct {
153 // ... 135 // ...
154 // } name_; 136 // } name_;
(...skipping 227 matching lines...) Expand 10 before | Expand all | Expand 10 after
382 break; 364 break;
383 } 365 }
384 default: { 366 default: {
385 // Stupid assumption: anything we see that isn't the above is one of 367 // Stupid assumption: anything we see that isn't the above is one of
386 // the 20 integer types. 368 // the 20 integer types.
387 (*trivial_member)++; 369 (*trivial_member)++;
388 break; 370 break;
389 } 371 }
390 } 372 }
391 } 373 }
374
375 // Check |record| for issues that are problematic for ref-counted types.
376 // Note that |record| may not be a ref-counted type, but a base class for
377 // a type that is.
378 // If there are issues, update |loc| with the SourceLocation of the issue
379 // and returns appropriately, or returns None if there are no issues.
380 static RefcountIssue CheckRecordForRefcountIssue(
381 const CXXRecordDecl* record,
382 SourceLocation &loc) {
383 if (!record->hasUserDeclaredDestructor()) {
384 loc = record->getLocation();
385 return ImplicitDestructor;
386 }
387
388 if (CXXDestructorDecl* dtor = record->getDestructor()) {
389 if (dtor->getAccess() == AS_public) {
390 loc = dtor->getInnerLocStart();
391 return PublicDestructor;
392 }
393 }
394
395 return None;
396 }
397
398 // Adds either a warning or error, based on the current handling of
399 // -Werror.
400 DiagnosticsEngine::Level getErrorLevel() {
Nico 2012/07/21 18:36:37 Do you need this? If you always emit a warning, do
Nico 2013/01/31 22:52:20 This question still stands.
Ryan Sleevi 2013/01/31 23:09:30 This matches ChromeClassTester::emitWarning() Cla
Ryan Sleevi 2013/02/01 00:19:36 Ah, sorry, brain job - http://llvm.org/viewvc/llvm
401 return diagnostic().getWarningsAsErrors() ?
402 DiagnosticsEngine::Error : DiagnosticsEngine::Warning;
403 }
404
405 // Returns true if |base| specifies one of the Chromium reference counted
406 // classes (base::RefCounted / base::RefCountedThreadSafe).
407 static bool IsRefCountedCallback(const CXXBaseSpecifier* base,
408 CXXBasePath& path,
409 void* user_data) {
410 FindBadConstructsConsumer* self =
411 static_cast<FindBadConstructsConsumer*>(user_data);
412
413 const TemplateSpecializationType* base_type =
414 dyn_cast<TemplateSpecializationType>(
415 UnwrapType(base->getType().getTypePtr()));
416
417 if (!base_type) {
418 // Base-most definition is not a template, so this cannot derive from
419 // base::RefCounted. However, it may still be possible to use with a
420 // scoped_refptr<> and support ref-counting, so this is not a perfect
421 // guarantee of safety.
422 return false;
423 }
424
425 TemplateName name = base_type->getTemplateName();
426 if (TemplateDecl* decl = name.getAsTemplateDecl()) {
427 std::string base_name = decl->getNameAsString();
428
429 // Check for both base::RefCounted and base::RefCountedThreadSafe.
430 if (base_name.compare(0, 10, "RefCounted") == 0 &&
431 self->GetNamespace(decl) == "base") {
432 return true;
433 }
434 }
435
436 return false;
437 }
438
439 // Returns true if |base| specifies a class that has a public destructor,
440 // either explicitly or implicitly.
441 static bool HasPublicDtorCallback(const CXXBaseSpecifier* base,
442 CXXBasePath& path,
443 void* user_data) {
444 // Only examine paths that have public inheritance, as they are the
445 // only ones which will result in the destructor potentially being
446 // exposed. This check is largely redundant, as Chromium code should be
447 // exclusively using public inheritance.
448 if (path.Access != AS_public)
449 return false;
450
451 CXXRecordDecl* record = dyn_cast<CXXRecordDecl>(
452 base->getType()->getAs<RecordType>()->getDecl());
453 SourceLocation unused;
454 return None != CheckRecordForRefcountIssue(record, unused);
455 }
456
457 // Outputs a C++ inheritance chain as a diagnostic aid.
458 void PrintInheritanceChain(const CXXBasePath& path) {
459 for (CXXBasePath::const_iterator it = path.begin(); it != path.end();
460 ++it) {
461 diagnostic().Report(it->Base->getLocStart(), diag_note_inheritance_)
462 << it->Class << it->Base->getType();
Nico 2012/07/21 18:36:37 This might get pretty wordy. Maybe elide some leve
463 }
464 }
465
466 // Check |record| to determine if it has any problematic refcounting
467 // issues and, if so, print them as warnings/errors based on the current
468 // value of getErrorLevel().
469 //
470 // If |record| is a C++ class, and if it inherits from one of the Chromium
471 // ref-counting classes (base::RefCounted / base::RefCountedThreadSafe),
472 // ensure that there are no public destructors in the class hierarchy. This
473 // is to guard against accidentally stack-allocating a RefCounted class or
474 // sticking it in a non-ref-counted container (like scoped_ptr<>).
475 void CheckRefCountedDtors(SourceLocation record_location,
476 CXXRecordDecl* record) {
477 // Skip anonymous structs.
478 if (record->getIdentifier() == NULL)
479 return;
480
481 // Determine if the current type is even ref-counted.
482 CXXBasePaths refcounted_path;
483 if (!record->lookupInBases(
484 &FindBadConstructsConsumer::IsRefCountedCallback, this,
485 refcounted_path)) {
486 return; // Class does not derive from a ref-counted base class.
487 }
488
489 // Easy check: Check to see if the current type is problematic.
490 SourceLocation loc;
491 RefcountIssue issue = CheckRecordForRefcountIssue(record, loc);
492 if (issue == ImplicitDestructor) {
493 diagnostic().Report(loc, diag_no_explicit_dtor_);
494 PrintInheritanceChain(refcounted_path.front());
495 return;
496 }
497 if (issue == PublicDestructor) {
498 diagnostic().Report(loc, diag_public_dtor_);
499 PrintInheritanceChain(refcounted_path.front());
500 return;
501 }
502
503 // Long check: Check all possible base classes for problematic
504 // destructors. This checks for situations involving multiple
505 // inheritance, where the ref-counted class may be implementing an
506 // interface that has a public or implicit destructor.
507 //
508 // struct SomeInterface {
509 // virtual void DoFoo();
510 // };
511 //
512 // struct RefCountedInterface
513 // : public base::RefCounted<RefCountedInterface>,
514 // public SomeInterface {
515 // private:
516 // friend class base::Refcounted<RefCountedInterface>;
517 // virtual ~RefCountedInterface() {}
518 // };
519 //
520 // While RefCountedInterface is "safe", in that its destructor is
521 // private, it's possible to do the following "unsafe" code:
522 // scoped_refptr<RefCountedInterface> some_class(
523 // new RefCountedInterface);
524 // // Calls SomeInterface::~SomeInterface(), which is unsafe.
525 // delete static_cast<SomeInterface*>(some_class.get());
526 if (!check_base_classes_)
527 return;
528
529 // Find all public destructors. This will record the class hierarchy
530 // that leads to the public destructor in |dtor_paths|.
531 CXXBasePaths dtor_paths;
532 if (!record->lookupInBases(
533 &FindBadConstructsConsumer::HasPublicDtorCallback, this,
534 dtor_paths)) {
535 return;
536 }
537
538 for (CXXBasePaths::const_paths_iterator it = dtor_paths.begin();
539 it != dtor_paths.end(); ++it) {
540 // The record with the problem will always be the last record
541 // in the path, since it is the record that stopped the search.
542 const CXXRecordDecl* problem_record = dyn_cast<CXXRecordDecl>(
543 it->back().Base->getType()->getAs<RecordType>()->getDecl());
544
545 issue = CheckRecordForRefcountIssue(problem_record, loc);
546
547 if (issue == ImplicitDestructor) {
548 diagnostic().Report(record_location, diag_no_explicit_dtor_);
549 PrintInheritanceChain(refcounted_path.front());
550 diagnostic().Report(loc, diag_note_implicit_dtor_) << problem_record;
551 PrintInheritanceChain(*it);
552 } else if (issue == PublicDestructor) {
553 diagnostic().Report(record_location, diag_public_dtor_);
554 PrintInheritanceChain(refcounted_path.front());
555 diagnostic().Report(loc, diag_note_public_dtor_);
556 PrintInheritanceChain(*it);
557 }
558 }
559 }
560
392 }; 561 };
393 562
394 class FindBadConstructsAction : public PluginASTAction { 563 class FindBadConstructsAction : public PluginASTAction {
395 public: 564 public:
396 FindBadConstructsAction() 565 FindBadConstructsAction()
397 : check_refcounted_dtors_(true), 566 : check_virtuals_in_implementations_(true),
398 check_virtuals_in_implementations_(true) { 567 check_refcounted_dtors_(true),
568 check_base_classes_(false) {
399 } 569 }
400 570
401 protected: 571 protected:
402 ASTConsumer* CreateASTConsumer(CompilerInstance &CI, llvm::StringRef ref) { 572 ASTConsumer* CreateASTConsumer(CompilerInstance &CI, llvm::StringRef ref) {
403 return new FindBadConstructsConsumer( 573 return new FindBadConstructsConsumer(
404 CI, check_refcounted_dtors_, check_virtuals_in_implementations_); 574 CI, check_virtuals_in_implementations_, check_refcounted_dtors_,
575 check_base_classes_);
405 } 576 }
406 577
407 bool ParseArgs(const CompilerInstance &CI, 578 bool ParseArgs(const CompilerInstance &CI,
408 const std::vector<std::string>& args) { 579 const std::vector<std::string>& args) {
409 bool parsed = true; 580 bool parsed = true;
410 581
411 for (size_t i = 0; i < args.size() && parsed; ++i) { 582 for (size_t i = 0; i < args.size() && parsed; ++i) {
412 if (args[i] == "skip-refcounted-dtors") { 583 if (args[i] == "skip-refcounted-dtors") {
413 check_refcounted_dtors_ = false; 584 check_refcounted_dtors_ = false;
414 } else if (args[i] == "skip-virtuals-in-implementations") { 585 } else if (args[i] == "skip-virtuals-in-implementations") {
415 check_virtuals_in_implementations_ = false; 586 check_virtuals_in_implementations_ = false;
Nico 2013/01/31 22:52:20 Are the two flags above still needed? (I think you
Ryan Sleevi 2013/01/31 23:09:30 I will remove --skip-refcounted-dtors, since that'
587 } else if (args[i] == "check-base-classes") {
588 check_base_classes_ = true;
Nico 2013/01/31 22:52:20 Should there be a link to a tracking bug for getti
Ryan Sleevi 2013/01/31 23:09:30 http://crbug.com/123295
416 } else { 589 } else {
417 parsed = false; 590 parsed = false;
418 llvm::errs() << "Unknown argument: " << args[i] << "\n"; 591 llvm::errs() << "Unknown argument: " << args[i] << "\n";
419 } 592 }
420 } 593 }
421 594
422 return parsed; 595 return parsed;
423 } 596 }
424 597
425 private: 598 private:
599 bool check_virtuals_in_implementations_;
Nico 2012/07/21 18:36:37 Why move this up? it seemed to be alphabetically o
426 bool check_refcounted_dtors_; 600 bool check_refcounted_dtors_;
427 bool check_virtuals_in_implementations_; 601 bool check_base_classes_;
428 }; 602 };
429 603
430 } // namespace 604 } // namespace
431 605
432 static FrontendPluginRegistry::Add<FindBadConstructsAction> 606 static FrontendPluginRegistry::Add<FindBadConstructsAction>
433 X("find-bad-constructs", "Finds bad C++ constructs"); 607 X("find-bad-constructs", "Finds bad C++ constructs");
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698