Index: tools/clang/plugins/FindBadConstructs.cpp |
diff --git a/tools/clang/plugins/FindBadConstructs.cpp b/tools/clang/plugins/FindBadConstructs.cpp |
index ef8cf7d1f9de0d4fa9b784e5abf3409fc9c3c2ff..59d4d6e3d80785ea7eab814634cd2cc8e3b52357 100644 |
--- a/tools/clang/plugins/FindBadConstructs.cpp |
+++ b/tools/clang/plugins/FindBadConstructs.cpp |
@@ -29,6 +29,19 @@ using namespace clang; |
namespace { |
+const char kNoExplicitDtor[] = |
+ "[chromium-style] Classes that are ref-counted should have explicit " |
+ "destructors that are declared protected or private."; |
+const char kPublicDtor[] = |
+ "[chromium-style] Classes that are ref-counted should not have " |
+ "public destructors."; |
+const char kNoteInheritance[] = |
+ "[chromium-style] %0 inherits from %1 here"; |
+const char kNoteImplicitDtor[] = |
+ "[chromium-style] No explicit destructor for %0 defined"; |
+const char kNotePublicDtor[] = |
+ "[chromium-style] Public destructor declared here"; |
+ |
bool TypeHasNonTrivialDtor(const Type* type) { |
if (const CXXRecordDecl* cxx_r = type->getPointeeCXXRecordDecl()) |
return cxx_r->hasTrivialDestructor(); |
@@ -37,7 +50,8 @@ bool TypeHasNonTrivialDtor(const Type* type) { |
} |
// Returns the underlying Type for |type| by expanding typedefs and removing |
-// any namespace qualifiers. |
+// any namespace qualifiers. This is similar to desugaring, except that for |
+// ElaboratedTypes, desugar will unwrap too much. |
const Type* UnwrapType(const Type* type) { |
if (const ElaboratedType* elaborated = dyn_cast<ElaboratedType>(type)) |
return UnwrapType(elaborated->getNamedType().getTypePtr()); |
@@ -50,11 +64,24 @@ const Type* UnwrapType(const Type* type) { |
class FindBadConstructsConsumer : public ChromeClassTester { |
public: |
FindBadConstructsConsumer(CompilerInstance& instance, |
- bool check_refcounted_dtors, |
+ bool check_base_classes, |
bool check_virtuals_in_implementations) |
: ChromeClassTester(instance), |
- check_refcounted_dtors_(check_refcounted_dtors), |
+ check_base_classes_(check_base_classes), |
check_virtuals_in_implementations_(check_virtuals_in_implementations) { |
+ // Register warning/error messages. |
+ diag_no_explicit_dtor_ = diagnostic().getCustomDiagID( |
+ getErrorLevel(), kNoExplicitDtor); |
+ diag_public_dtor_ = diagnostic().getCustomDiagID( |
+ getErrorLevel(), kPublicDtor); |
+ |
+ // Registers notes to make it easier to interpret warnings. |
+ diag_note_inheritance_ = diagnostic().getCustomDiagID( |
+ DiagnosticsEngine::Note, kNoteInheritance); |
+ diag_note_implicit_dtor_ = diagnostic().getCustomDiagID( |
+ DiagnosticsEngine::Note, kNoteImplicitDtor); |
+ diag_note_public_dtor_ = diagnostic().getCustomDiagID( |
+ DiagnosticsEngine::Note, kNotePublicDtor); |
} |
virtual void CheckChromeClass(SourceLocation record_location, |
@@ -75,74 +102,25 @@ class FindBadConstructsConsumer : public ChromeClassTester { |
CheckVirtualMethods(record_location, record, warn_on_inline_bodies); |
} |
- if (check_refcounted_dtors_) |
- CheckRefCountedDtors(record_location, record); |
+ CheckRefCountedDtors(record_location, record); |
} |
private: |
- bool check_refcounted_dtors_; |
+ // The type of problematic ref-counting pattern that was encountered. |
+ enum RefcountIssue { |
+ None, |
+ ImplicitDestructor, |
+ PublicDestructor |
+ }; |
+ |
+ bool check_base_classes_; |
bool check_virtuals_in_implementations_; |
- // Returns true if |base| specifies one of the Chromium reference counted |
- // classes (base::RefCounted / base::RefCountedThreadSafe). |user_data| is |
- // ignored. |
- static bool IsRefCountedCallback(const CXXBaseSpecifier* base, |
- CXXBasePath& path, |
- void* user_data) { |
- FindBadConstructsConsumer* self = |
- static_cast<FindBadConstructsConsumer*>(user_data); |
- |
- const TemplateSpecializationType* base_type = |
- dyn_cast<TemplateSpecializationType>( |
- UnwrapType(base->getType().getTypePtr())); |
- if (!base_type) { |
- // Base-most definition is not a template, so this cannot derive from |
- // base::RefCounted. However, it may still be possible to use with a |
- // scoped_refptr<> and support ref-counting, so this is not a perfect |
- // guarantee of safety. |
- return false; |
- } |
- |
- TemplateName name = base_type->getTemplateName(); |
- if (TemplateDecl* decl = name.getAsTemplateDecl()) { |
- std::string base_name = decl->getNameAsString(); |
- |
- // Check for both base::RefCounted and base::RefCountedThreadSafe. |
- if (base_name.compare(0, 10, "RefCounted") == 0 && |
- self->GetNamespace(decl) == "base") { |
- return true; |
- } |
- } |
- return false; |
- } |
- |
- // Prints errors if the destructor of a RefCounted class is public. |
- void CheckRefCountedDtors(SourceLocation record_location, |
- CXXRecordDecl* record) { |
- // Skip anonymous structs. |
- if (record->getIdentifier() == NULL) |
- return; |
- |
- CXXBasePaths paths; |
- if (!record->lookupInBases( |
- &FindBadConstructsConsumer::IsRefCountedCallback, this, paths)) { |
- return; // Class does not derive from a ref-counted base class. |
- } |
- |
- if (!record->hasUserDeclaredDestructor()) { |
- emitWarning( |
- record_location, |
- "Classes that are ref-counted should have explicit " |
- "destructors that are protected or private."); |
- } else if (CXXDestructorDecl* dtor = record->getDestructor()) { |
- if (dtor->getAccess() == AS_public) { |
- emitWarning( |
- dtor->getInnerLocStart(), |
- "Classes that are ref-counted should not have " |
- "public destructors."); |
- } |
- } |
- } |
+ unsigned diag_no_explicit_dtor_; |
+ unsigned diag_public_dtor_; |
+ unsigned diag_note_inheritance_; |
+ unsigned diag_note_implicit_dtor_; |
+ unsigned diag_note_public_dtor_; |
// Prints errors if the constructor/destructor weight is too heavy. |
void CheckCtorDtorWeight(SourceLocation record_location, |
@@ -390,12 +368,197 @@ class FindBadConstructsConsumer : public ChromeClassTester { |
} |
} |
} |
+ |
+ // Check |record| for issues that are problematic for ref-counted types. |
+ // Note that |record| may not be a ref-counted type, but a base class for |
+ // a type that is. |
+ // If there are issues, update |loc| with the SourceLocation of the issue |
+ // and returns appropriately, or returns None if there are no issues. |
+ static RefcountIssue CheckRecordForRefcountIssue( |
+ const CXXRecordDecl* record, |
+ SourceLocation &loc) { |
+ if (!record->hasUserDeclaredDestructor()) { |
+ loc = record->getLocation(); |
+ return ImplicitDestructor; |
+ } |
+ |
+ if (CXXDestructorDecl* dtor = record->getDestructor()) { |
+ if (dtor->getAccess() == AS_public) { |
+ loc = dtor->getInnerLocStart(); |
+ return PublicDestructor; |
+ } |
+ } |
+ |
+ return None; |
+ } |
+ |
+ // Adds either a warning or error, based on the current handling of |
+ // -Werror. |
+ DiagnosticsEngine::Level getErrorLevel() { |
+ return diagnostic().getWarningsAsErrors() ? |
+ DiagnosticsEngine::Error : DiagnosticsEngine::Warning; |
+ } |
+ |
+ // Returns true if |base| specifies one of the Chromium reference counted |
+ // classes (base::RefCounted / base::RefCountedThreadSafe). |
+ static bool IsRefCountedCallback(const CXXBaseSpecifier* base, |
+ CXXBasePath& path, |
+ void* user_data) { |
+ FindBadConstructsConsumer* self = |
+ static_cast<FindBadConstructsConsumer*>(user_data); |
+ |
+ const TemplateSpecializationType* base_type = |
+ dyn_cast<TemplateSpecializationType>( |
+ UnwrapType(base->getType().getTypePtr())); |
+ if (!base_type) { |
+ // Base-most definition is not a template, so this cannot derive from |
+ // base::RefCounted. However, it may still be possible to use with a |
+ // scoped_refptr<> and support ref-counting, so this is not a perfect |
+ // guarantee of safety. |
+ return false; |
+ } |
+ |
+ TemplateName name = base_type->getTemplateName(); |
+ if (TemplateDecl* decl = name.getAsTemplateDecl()) { |
+ std::string base_name = decl->getNameAsString(); |
+ |
+ // Check for both base::RefCounted and base::RefCountedThreadSafe. |
+ if (base_name.compare(0, 10, "RefCounted") == 0 && |
+ self->GetNamespace(decl) == "base") { |
+ return true; |
+ } |
+ } |
+ |
+ return false; |
+ } |
+ |
+ // Returns true if |base| specifies a class that has a public destructor, |
+ // either explicitly or implicitly. |
+ static bool HasPublicDtorCallback(const CXXBaseSpecifier* base, |
+ CXXBasePath& path, |
+ void* user_data) { |
+ // Only examine paths that have public inheritance, as they are the |
+ // only ones which will result in the destructor potentially being |
+ // exposed. This check is largely redundant, as Chromium code should be |
+ // exclusively using public inheritance. |
+ if (path.Access != AS_public) |
+ return false; |
+ |
+ CXXRecordDecl* record = dyn_cast<CXXRecordDecl>( |
+ base->getType()->getAs<RecordType>()->getDecl()); |
+ SourceLocation unused; |
+ return None != CheckRecordForRefcountIssue(record, unused); |
+ } |
+ |
+ // Outputs a C++ inheritance chain as a diagnostic aid. |
+ void PrintInheritanceChain(const CXXBasePath& path) { |
+ for (CXXBasePath::const_iterator it = path.begin(); it != path.end(); |
+ ++it) { |
+ diagnostic().Report(it->Base->getLocStart(), diag_note_inheritance_) |
+ << it->Class << it->Base->getType(); |
+ } |
+ } |
+ |
+ // Check |record| to determine if it has any problematic refcounting |
+ // issues and, if so, print them as warnings/errors based on the current |
+ // value of getErrorLevel(). |
+ // |
+ // If |record| is a C++ class, and if it inherits from one of the Chromium |
+ // ref-counting classes (base::RefCounted / base::RefCountedThreadSafe), |
+ // ensure that there are no public destructors in the class hierarchy. This |
+ // is to guard against accidentally stack-allocating a RefCounted class or |
+ // sticking it in a non-ref-counted container (like scoped_ptr<>). |
+ void CheckRefCountedDtors(SourceLocation record_location, |
+ CXXRecordDecl* record) { |
+ // Skip anonymous structs. |
+ if (record->getIdentifier() == NULL) |
+ return; |
+ |
+ // Determine if the current type is even ref-counted. |
+ CXXBasePaths refcounted_path; |
+ if (!record->lookupInBases( |
+ &FindBadConstructsConsumer::IsRefCountedCallback, this, |
+ refcounted_path)) { |
+ return; // Class does not derive from a ref-counted base class. |
+ } |
+ |
+ // Easy check: Check to see if the current type is problematic. |
+ SourceLocation loc; |
+ RefcountIssue issue = CheckRecordForRefcountIssue(record, loc); |
+ if (issue == ImplicitDestructor) { |
+ diagnostic().Report(loc, diag_no_explicit_dtor_); |
+ PrintInheritanceChain(refcounted_path.front()); |
+ return; |
+ } |
+ if (issue == PublicDestructor) { |
+ diagnostic().Report(loc, diag_public_dtor_); |
+ PrintInheritanceChain(refcounted_path.front()); |
+ return; |
+ } |
+ |
+ // Long check: Check all possible base classes for problematic |
+ // destructors. This checks for situations involving multiple |
+ // inheritance, where the ref-counted class may be implementing an |
+ // interface that has a public or implicit destructor. |
+ // |
+ // struct SomeInterface { |
+ // virtual void DoFoo(); |
+ // }; |
+ // |
+ // struct RefCountedInterface |
+ // : public base::RefCounted<RefCountedInterface>, |
+ // public SomeInterface { |
+ // private: |
+ // friend class base::Refcounted<RefCountedInterface>; |
+ // virtual ~RefCountedInterface() {} |
+ // }; |
+ // |
+ // While RefCountedInterface is "safe", in that its destructor is |
+ // private, it's possible to do the following "unsafe" code: |
+ // scoped_refptr<RefCountedInterface> some_class( |
+ // new RefCountedInterface); |
+ // // Calls SomeInterface::~SomeInterface(), which is unsafe. |
+ // delete static_cast<SomeInterface*>(some_class.get()); |
+ if (!check_base_classes_) |
+ return; |
+ |
+ // Find all public destructors. This will record the class hierarchy |
+ // that leads to the public destructor in |dtor_paths|. |
+ CXXBasePaths dtor_paths; |
+ if (!record->lookupInBases( |
+ &FindBadConstructsConsumer::HasPublicDtorCallback, this, |
+ dtor_paths)) { |
+ return; |
+ } |
+ |
+ for (CXXBasePaths::const_paths_iterator it = dtor_paths.begin(); |
+ it != dtor_paths.end(); ++it) { |
+ // The record with the problem will always be the last record |
+ // in the path, since it is the record that stopped the search. |
+ const CXXRecordDecl* problem_record = dyn_cast<CXXRecordDecl>( |
+ it->back().Base->getType()->getAs<RecordType>()->getDecl()); |
+ |
+ issue = CheckRecordForRefcountIssue(problem_record, loc); |
+ |
+ if (issue == ImplicitDestructor) { |
+ diagnostic().Report(record_location, diag_no_explicit_dtor_); |
+ PrintInheritanceChain(refcounted_path.front()); |
+ diagnostic().Report(loc, diag_note_implicit_dtor_) << problem_record; |
+ PrintInheritanceChain(*it); |
+ } else if (issue == PublicDestructor) { |
+ diagnostic().Report(record_location, diag_public_dtor_); |
+ PrintInheritanceChain(refcounted_path.front()); |
+ diagnostic().Report(loc, diag_note_public_dtor_); |
+ PrintInheritanceChain(*it); |
+ } |
+ } |
+ } |
}; |
class FindBadConstructsAction : public PluginASTAction { |
public: |
FindBadConstructsAction() |
- : check_refcounted_dtors_(true), |
+ : check_base_classes_(false), |
check_virtuals_in_implementations_(true) { |
} |
@@ -404,7 +567,7 @@ class FindBadConstructsAction : public PluginASTAction { |
virtual ASTConsumer* CreateASTConsumer(CompilerInstance& instance, |
llvm::StringRef ref) { |
return new FindBadConstructsConsumer( |
- instance, check_refcounted_dtors_, check_virtuals_in_implementations_); |
+ instance, check_base_classes_, check_virtuals_in_implementations_); |
} |
virtual bool ParseArgs(const CompilerInstance& instance, |
@@ -412,10 +575,12 @@ class FindBadConstructsAction : public PluginASTAction { |
bool parsed = true; |
for (size_t i = 0; i < args.size() && parsed; ++i) { |
- if (args[i] == "skip-refcounted-dtors") { |
- check_refcounted_dtors_ = false; |
- } else if (args[i] == "skip-virtuals-in-implementations") { |
+ if (args[i] == "skip-virtuals-in-implementations") { |
+ // TODO(rsleevi): Remove this once http://crbug.com/115047 is fixed. |
check_virtuals_in_implementations_ = false; |
+ } else if (args[i] == "check-base-classes") { |
+ // TODO(rsleevi): Remove this once http://crbug.com/123295 is fixed. |
+ check_base_classes_ = true; |
} else { |
parsed = false; |
llvm::errs() << "Unknown clang plugin argument: " << args[i] << "\n"; |
@@ -426,7 +591,7 @@ class FindBadConstructsAction : public PluginASTAction { |
} |
private: |
- bool check_refcounted_dtors_; |
+ bool check_base_classes_; |
bool check_virtuals_in_implementations_; |
}; |