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

Unified 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, 11 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | tools/clang/plugins/tests/base_refcounted.h » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: tools/clang/plugins/FindBadConstructs.cpp
diff --git a/tools/clang/plugins/FindBadConstructs.cpp b/tools/clang/plugins/FindBadConstructs.cpp
index 99d22270af36962103b233f502858f43a588f6b2..4a16db4ec4c09d9e57551e609fe031bfe030f1f0 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,26 @@ const Type* UnwrapType(const Type* type) {
class FindBadConstructsConsumer : public ChromeClassTester {
public:
FindBadConstructsConsumer(CompilerInstance& instance,
+ bool check_base_classes,
bool check_refcounted_dtors,
bool check_virtuals_in_implementations)
: ChromeClassTester(instance),
+ check_base_classes_(check_base_classes),
check_refcounted_dtors_(check_refcounted_dtors),
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,
@@ -80,8 +109,22 @@ class FindBadConstructsConsumer : public ChromeClassTester {
}
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_;
+ bool check_refcounted_dtors_;
+
+ 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,
@@ -330,9 +373,38 @@ 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). |user_data| is
- // ignored.
+ // classes (base::RefCounted / base::RefCountedThreadSafe).
static bool IsRefCountedCallback(const CXXBaseSpecifier* base,
CXXBasePath& path,
void* user_data) {
@@ -360,33 +432,128 @@ class FindBadConstructsConsumer : public ChromeClassTester {
return true;
}
}
+
return false;
}
- // Prints errors if the destructor of a RefCounted class is public.
+ // 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;
- CXXBasePaths paths;
+ // Determine if the current type is even ref-counted.
+ CXXBasePaths refcounted_path;
if (!record->lookupInBases(
- &FindBadConstructsConsumer::IsRefCountedCallback, this, paths)) {
+ &FindBadConstructsConsumer::IsRefCountedCallback, this,
+ refcounted_path)) {
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.");
+ // 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);
}
}
}
@@ -395,7 +562,8 @@ class FindBadConstructsConsumer : public ChromeClassTester {
class FindBadConstructsAction : public PluginASTAction {
public:
FindBadConstructsAction()
- : check_refcounted_dtors_(true),
+ : check_base_classes_(false),
+ check_refcounted_dtors_(true),
check_virtuals_in_implementations_(true) {
}
@@ -404,7 +572,8 @@ 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_refcounted_dtors_,
+ check_virtuals_in_implementations_);
}
virtual bool ParseArgs(const CompilerInstance& instance,
@@ -416,6 +585,8 @@ class FindBadConstructsAction : public PluginASTAction {
check_refcounted_dtors_ = false;
} else if (args[i] == "skip-virtuals-in-implementations") {
check_virtuals_in_implementations_ = false;
+ } else if (args[i] == "check-base-classes") {
+ check_base_classes_ = true;
} else {
parsed = false;
llvm::errs() << "Unknown clang plugin argument: " << args[i] << "\n";
@@ -426,6 +597,7 @@ class FindBadConstructsAction : public PluginASTAction {
}
private:
+ bool check_base_classes_;
bool check_refcounted_dtors_;
bool check_virtuals_in_implementations_;
};
« 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