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

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: Add unittest with per-test flags Created 7 years, 10 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
« no previous file with comments | « no previous file | tools/clang/plugins/tests/base_refcounted.h » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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 11 matching lines...) Expand all
22 #include "clang/Frontend/CompilerInstance.h" 22 #include "clang/Frontend/CompilerInstance.h"
23 #include "clang/Frontend/FrontendPluginRegistry.h" 23 #include "clang/Frontend/FrontendPluginRegistry.h"
24 #include "llvm/Support/raw_ostream.h" 24 #include "llvm/Support/raw_ostream.h"
25 25
26 #include "ChromeClassTester.h" 26 #include "ChromeClassTester.h"
27 27
28 using namespace clang; 28 using namespace clang;
29 29
30 namespace { 30 namespace {
31 31
32 const char kNoExplicitDtor[] =
33 "[chromium-style] Classes that are ref-counted should have explicit "
34 "destructors that are declared protected or private.";
35 const char kPublicDtor[] =
36 "[chromium-style] Classes that are ref-counted should not have "
37 "public destructors.";
38 const char kNoteInheritance[] =
39 "[chromium-style] %0 inherits from %1 here";
40 const char kNoteImplicitDtor[] =
41 "[chromium-style] No explicit destructor for %0 defined";
42 const char kNotePublicDtor[] =
43 "[chromium-style] Public destructor declared here";
44
32 bool TypeHasNonTrivialDtor(const Type* type) { 45 bool TypeHasNonTrivialDtor(const Type* type) {
33 if (const CXXRecordDecl* cxx_r = type->getPointeeCXXRecordDecl()) 46 if (const CXXRecordDecl* cxx_r = type->getPointeeCXXRecordDecl())
34 return cxx_r->hasTrivialDestructor(); 47 return cxx_r->hasTrivialDestructor();
35 48
36 return false; 49 return false;
37 } 50 }
38 51
39 // Returns the underlying Type for |type| by expanding typedefs and removing 52 // Returns the underlying Type for |type| by expanding typedefs and removing
40 // any namespace qualifiers. 53 // any namespace qualifiers. This is similar to desugaring, except that for
54 // ElaboratedTypes, desugar will unwrap too much.
41 const Type* UnwrapType(const Type* type) { 55 const Type* UnwrapType(const Type* type) {
42 if (const ElaboratedType* elaborated = dyn_cast<ElaboratedType>(type)) 56 if (const ElaboratedType* elaborated = dyn_cast<ElaboratedType>(type))
43 return UnwrapType(elaborated->getNamedType().getTypePtr()); 57 return UnwrapType(elaborated->getNamedType().getTypePtr());
44 if (const TypedefType* typedefed = dyn_cast<TypedefType>(type)) 58 if (const TypedefType* typedefed = dyn_cast<TypedefType>(type))
45 return UnwrapType(typedefed->desugar().getTypePtr()); 59 return UnwrapType(typedefed->desugar().getTypePtr());
46 return type; 60 return type;
47 } 61 }
48 62
49 // Searches for constructs that we know we don't want in the Chromium code base. 63 // Searches for constructs that we know we don't want in the Chromium code base.
50 class FindBadConstructsConsumer : public ChromeClassTester { 64 class FindBadConstructsConsumer : public ChromeClassTester {
51 public: 65 public:
52 FindBadConstructsConsumer(CompilerInstance& instance, 66 FindBadConstructsConsumer(CompilerInstance& instance,
67 bool check_base_classes,
53 bool check_refcounted_dtors, 68 bool check_refcounted_dtors,
54 bool check_virtuals_in_implementations) 69 bool check_virtuals_in_implementations)
55 : ChromeClassTester(instance), 70 : ChromeClassTester(instance),
71 check_base_classes_(check_base_classes),
56 check_refcounted_dtors_(check_refcounted_dtors), 72 check_refcounted_dtors_(check_refcounted_dtors),
57 check_virtuals_in_implementations_(check_virtuals_in_implementations) { 73 check_virtuals_in_implementations_(check_virtuals_in_implementations) {
74 // Register warning/error messages.
75 diag_no_explicit_dtor_ = diagnostic().getCustomDiagID(
76 getErrorLevel(), kNoExplicitDtor);
77 diag_public_dtor_ = diagnostic().getCustomDiagID(
78 getErrorLevel(), kPublicDtor);
79
80 // Registers notes to make it easier to interpret warnings.
81 diag_note_inheritance_ = diagnostic().getCustomDiagID(
82 DiagnosticsEngine::Note, kNoteInheritance);
83 diag_note_implicit_dtor_ = diagnostic().getCustomDiagID(
84 DiagnosticsEngine::Note, kNoteImplicitDtor);
85 diag_note_public_dtor_ = diagnostic().getCustomDiagID(
86 DiagnosticsEngine::Note, kNotePublicDtor);
58 } 87 }
59 88
60 virtual void CheckChromeClass(SourceLocation record_location, 89 virtual void CheckChromeClass(SourceLocation record_location,
61 CXXRecordDecl* record) { 90 CXXRecordDecl* record) {
62 bool implementation_file = InImplementationFile(record_location); 91 bool implementation_file = InImplementationFile(record_location);
63 92
64 if (!implementation_file) { 93 if (!implementation_file) {
65 // Only check for "heavy" constructors/destructors in header files; 94 // Only check for "heavy" constructors/destructors in header files;
66 // within implementation files, there is no performance cost. 95 // within implementation files, there is no performance cost.
67 CheckCtorDtorWeight(record_location, record); 96 CheckCtorDtorWeight(record_location, record);
68 } 97 }
69 98
70 if (!implementation_file || check_virtuals_in_implementations_) { 99 if (!implementation_file || check_virtuals_in_implementations_) {
71 bool warn_on_inline_bodies = !implementation_file; 100 bool warn_on_inline_bodies = !implementation_file;
72 101
73 // Check that all virtual methods are marked accordingly with both 102 // Check that all virtual methods are marked accordingly with both
74 // virtual and OVERRIDE. 103 // virtual and OVERRIDE.
75 CheckVirtualMethods(record_location, record, warn_on_inline_bodies); 104 CheckVirtualMethods(record_location, record, warn_on_inline_bodies);
76 } 105 }
77 106
78 if (check_refcounted_dtors_) 107 if (check_refcounted_dtors_)
79 CheckRefCountedDtors(record_location, record); 108 CheckRefCountedDtors(record_location, record);
80 } 109 }
81 110
82 private: 111 private:
112 // The type of problematic ref-counting pattern that was encountered.
113 enum RefcountIssue {
114 None,
115 ImplicitDestructor,
116 PublicDestructor
117 };
118
119 bool check_base_classes_;
120 bool check_virtuals_in_implementations_;
83 bool check_refcounted_dtors_; 121 bool check_refcounted_dtors_;
84 bool check_virtuals_in_implementations_; 122
123 unsigned diag_no_explicit_dtor_;
124 unsigned diag_public_dtor_;
125 unsigned diag_note_inheritance_;
126 unsigned diag_note_implicit_dtor_;
127 unsigned diag_note_public_dtor_;
85 128
86 // Prints errors if the constructor/destructor weight is too heavy. 129 // Prints errors if the constructor/destructor weight is too heavy.
87 void CheckCtorDtorWeight(SourceLocation record_location, 130 void CheckCtorDtorWeight(SourceLocation record_location,
88 CXXRecordDecl* record) { 131 CXXRecordDecl* record) {
89 // We don't handle anonymous structs. If this record doesn't have a 132 // We don't handle anonymous structs. If this record doesn't have a
90 // name, it's of the form: 133 // name, it's of the form:
91 // 134 //
92 // struct { 135 // struct {
93 // ... 136 // ...
94 // } name_; 137 // } name_;
(...skipping 228 matching lines...) Expand 10 before | Expand all | Expand 10 after
323 } 366 }
324 default: { 367 default: {
325 // 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
326 // the 20 integer types. 369 // the 20 integer types.
327 (*trivial_member)++; 370 (*trivial_member)++;
328 break; 371 break;
329 } 372 }
330 } 373 }
331 } 374 }
332 375
376 // Check |record| for issues that are problematic for ref-counted types.
377 // Note that |record| may not be a ref-counted type, but a base class for
378 // a type that is.
379 // If there are issues, update |loc| with the SourceLocation of the issue
380 // and returns appropriately, or returns None if there are no issues.
381 static RefcountIssue CheckRecordForRefcountIssue(
382 const CXXRecordDecl* record,
383 SourceLocation &loc) {
384 if (!record->hasUserDeclaredDestructor()) {
385 loc = record->getLocation();
386 return ImplicitDestructor;
387 }
388
389 if (CXXDestructorDecl* dtor = record->getDestructor()) {
390 if (dtor->getAccess() == AS_public) {
391 loc = dtor->getInnerLocStart();
392 return PublicDestructor;
393 }
394 }
395
396 return None;
397 }
398
399 // Adds either a warning or error, based on the current handling of
400 // -Werror.
401 DiagnosticsEngine::Level getErrorLevel() {
402 return diagnostic().getWarningsAsErrors() ?
403 DiagnosticsEngine::Error : DiagnosticsEngine::Warning;
404 }
405
333 // Returns true if |base| specifies one of the Chromium reference counted 406 // Returns true if |base| specifies one of the Chromium reference counted
334 // classes (base::RefCounted / base::RefCountedThreadSafe). |user_data| is 407 // classes (base::RefCounted / base::RefCountedThreadSafe).
335 // ignored.
336 static bool IsRefCountedCallback(const CXXBaseSpecifier* base, 408 static bool IsRefCountedCallback(const CXXBaseSpecifier* base,
337 CXXBasePath& path, 409 CXXBasePath& path,
338 void* user_data) { 410 void* user_data) {
339 FindBadConstructsConsumer* self = 411 FindBadConstructsConsumer* self =
340 static_cast<FindBadConstructsConsumer*>(user_data); 412 static_cast<FindBadConstructsConsumer*>(user_data);
341 413
342 const TemplateSpecializationType* base_type = 414 const TemplateSpecializationType* base_type =
343 dyn_cast<TemplateSpecializationType>( 415 dyn_cast<TemplateSpecializationType>(
344 UnwrapType(base->getType().getTypePtr())); 416 UnwrapType(base->getType().getTypePtr()));
345 if (!base_type) { 417 if (!base_type) {
346 // Base-most definition is not a template, so this cannot derive from 418 // Base-most definition is not a template, so this cannot derive from
347 // base::RefCounted. However, it may still be possible to use with a 419 // base::RefCounted. However, it may still be possible to use with a
348 // scoped_refptr<> and support ref-counting, so this is not a perfect 420 // scoped_refptr<> and support ref-counting, so this is not a perfect
349 // guarantee of safety. 421 // guarantee of safety.
350 return false; 422 return false;
351 } 423 }
352 424
353 TemplateName name = base_type->getTemplateName(); 425 TemplateName name = base_type->getTemplateName();
354 if (TemplateDecl* decl = name.getAsTemplateDecl()) { 426 if (TemplateDecl* decl = name.getAsTemplateDecl()) {
355 std::string base_name = decl->getNameAsString(); 427 std::string base_name = decl->getNameAsString();
356 428
357 // Check for both base::RefCounted and base::RefCountedThreadSafe. 429 // Check for both base::RefCounted and base::RefCountedThreadSafe.
358 if (base_name.compare(0, 10, "RefCounted") == 0 && 430 if (base_name.compare(0, 10, "RefCounted") == 0 &&
359 self->GetNamespace(decl) == "base") { 431 self->GetNamespace(decl) == "base") {
360 return true; 432 return true;
361 } 433 }
362 } 434 }
435
363 return false; 436 return false;
364 } 437 }
365 438
366 // Prints errors if the destructor of a RefCounted class is public. 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();
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<>).
367 void CheckRefCountedDtors(SourceLocation record_location, 475 void CheckRefCountedDtors(SourceLocation record_location,
368 CXXRecordDecl* record) { 476 CXXRecordDecl* record) {
369 // Skip anonymous structs. 477 // Skip anonymous structs.
370 if (record->getIdentifier() == NULL) 478 if (record->getIdentifier() == NULL)
371 return; 479 return;
372 480
373 CXXBasePaths paths; 481 // Determine if the current type is even ref-counted.
482 CXXBasePaths refcounted_path;
374 if (!record->lookupInBases( 483 if (!record->lookupInBases(
375 &FindBadConstructsConsumer::IsRefCountedCallback, this, paths)) { 484 &FindBadConstructsConsumer::IsRefCountedCallback, this,
485 refcounted_path)) {
376 return; // Class does not derive from a ref-counted base class. 486 return; // Class does not derive from a ref-counted base class.
377 } 487 }
378 488
379 if (!record->hasUserDeclaredDestructor()) { 489 // Easy check: Check to see if the current type is problematic.
380 emitWarning( 490 SourceLocation loc;
381 record_location, 491 RefcountIssue issue = CheckRecordForRefcountIssue(record, loc);
382 "Classes that are ref-counted should have explicit " 492 if (issue == ImplicitDestructor) {
383 "destructors that are protected or private."); 493 diagnostic().Report(loc, diag_no_explicit_dtor_);
384 } else if (CXXDestructorDecl* dtor = record->getDestructor()) { 494 PrintInheritanceChain(refcounted_path.front());
385 if (dtor->getAccess() == AS_public) { 495 return;
386 emitWarning( 496 }
387 dtor->getInnerLocStart(), 497 if (issue == PublicDestructor) {
388 "Classes that are ref-counted should not have " 498 diagnostic().Report(loc, diag_public_dtor_);
389 "public destructors."); 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);
390 } 557 }
391 } 558 }
392 } 559 }
393 }; 560 };
394 561
395 class FindBadConstructsAction : public PluginASTAction { 562 class FindBadConstructsAction : public PluginASTAction {
396 public: 563 public:
397 FindBadConstructsAction() 564 FindBadConstructsAction()
398 : check_refcounted_dtors_(true), 565 : check_base_classes_(false),
566 check_refcounted_dtors_(true),
399 check_virtuals_in_implementations_(true) { 567 check_virtuals_in_implementations_(true) {
400 } 568 }
401 569
402 protected: 570 protected:
403 // Overridden from PluginASTAction: 571 // Overridden from PluginASTAction:
404 virtual ASTConsumer* CreateASTConsumer(CompilerInstance& instance, 572 virtual ASTConsumer* CreateASTConsumer(CompilerInstance& instance,
405 llvm::StringRef ref) { 573 llvm::StringRef ref) {
406 return new FindBadConstructsConsumer( 574 return new FindBadConstructsConsumer(
407 instance, check_refcounted_dtors_, check_virtuals_in_implementations_); 575 instance, check_base_classes_, check_refcounted_dtors_,
576 check_virtuals_in_implementations_);
408 } 577 }
409 578
410 virtual bool ParseArgs(const CompilerInstance& instance, 579 virtual bool ParseArgs(const CompilerInstance& instance,
411 const std::vector<std::string>& args) { 580 const std::vector<std::string>& args) {
412 bool parsed = true; 581 bool parsed = true;
413 582
414 for (size_t i = 0; i < args.size() && parsed; ++i) { 583 for (size_t i = 0; i < args.size() && parsed; ++i) {
415 if (args[i] == "skip-refcounted-dtors") { 584 if (args[i] == "skip-refcounted-dtors") {
Nico 2013/02/01 00:24:32 Add "Also removed unused flag -skip-refcounted-dto
416 check_refcounted_dtors_ = false; 585 check_refcounted_dtors_ = false;
417 } else if (args[i] == "skip-virtuals-in-implementations") { 586 } else if (args[i] == "skip-virtuals-in-implementations") {
418 check_virtuals_in_implementations_ = false; 587 check_virtuals_in_implementations_ = false;
588 } else if (args[i] == "check-base-classes") {
589 check_base_classes_ = true;
419 } else { 590 } else {
420 parsed = false; 591 parsed = false;
421 llvm::errs() << "Unknown clang plugin argument: " << args[i] << "\n"; 592 llvm::errs() << "Unknown clang plugin argument: " << args[i] << "\n";
422 } 593 }
423 } 594 }
424 595
425 return parsed; 596 return parsed;
426 } 597 }
427 598
428 private: 599 private:
600 bool check_base_classes_;
429 bool check_refcounted_dtors_; 601 bool check_refcounted_dtors_;
430 bool check_virtuals_in_implementations_; 602 bool check_virtuals_in_implementations_;
431 }; 603 };
432 604
433 } // namespace 605 } // namespace
434 606
435 static FrontendPluginRegistry::Add<FindBadConstructsAction> 607 static FrontendPluginRegistry::Add<FindBadConstructsAction>
436 X("find-bad-constructs", "Finds bad C++ constructs"); 608 X("find-bad-constructs", "Finds bad C++ constructs");
OLDNEW
« no previous file with comments | « no previous file | tools/clang/plugins/tests/base_refcounted.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698