media/webrtc/trunk/tools/clang/plugins/FindBadConstructs.cpp

Wed, 31 Dec 2014 13:27:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 13:27:57 +0100
branch
TOR_BUG_3246
changeset 6
8bccb770b82d
permissions
-rw-r--r--

Ignore runtime configuration files generated during quality assurance.

     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
     3 // found in the LICENSE file.
     5 // This file defines a bunch of recurring problems in the Chromium C++ code.
     6 //
     7 // Checks that are implemented:
     8 // - Constructors/Destructors should not be inlined if they are of a complex
     9 //   class type.
    10 // - Missing "virtual" keywords on methods that should be virtual.
    11 // - Non-annotated overriding virtual methods.
    12 // - Virtual methods with nonempty implementations in their headers.
    13 // - Classes that derive from base::RefCounted / base::RefCountedThreadSafe
    14 //   should have protected or private destructors.
    16 #include "clang/Frontend/FrontendPluginRegistry.h"
    17 #include "clang/AST/ASTConsumer.h"
    18 #include "clang/AST/AST.h"
    19 #include "clang/AST/CXXInheritance.h"
    20 #include "clang/AST/TypeLoc.h"
    21 #include "clang/Basic/SourceManager.h"
    22 #include "clang/Frontend/CompilerInstance.h"
    23 #include "llvm/Support/raw_ostream.h"
    25 #include "ChromeClassTester.h"
    27 using namespace clang;
    29 namespace {
    31 bool TypeHasNonTrivialDtor(const Type* type) {
    32   if (const CXXRecordDecl* cxx_r = type->getCXXRecordDeclForPointerType())
    33     return cxx_r->hasTrivialDestructor();
    35   return false;
    36 }
    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 }
    48 // Searches for constructs that we know we don't want in the Chromium code base.
    49 class FindBadConstructsConsumer : public ChromeClassTester {
    50  public:
    51   FindBadConstructsConsumer(CompilerInstance& instance,
    52                             bool check_refcounted_dtors,
    53                             bool check_virtuals_in_implementations)
    54       : ChromeClassTester(instance),
    55         check_refcounted_dtors_(check_refcounted_dtors),
    56         check_virtuals_in_implementations_(check_virtuals_in_implementations) {
    57   }
    59   virtual void CheckChromeClass(SourceLocation record_location,
    60                                 CXXRecordDecl* record) {
    61     bool implementation_file = InImplementationFile(record_location);
    63     if (!implementation_file) {
    64       // Only check for "heavy" constructors/destructors in header files;
    65       // within implementation files, there is no performance cost.
    66       CheckCtorDtorWeight(record_location, record);
    67     }
    69     if (!implementation_file || check_virtuals_in_implementations_) {
    70       bool warn_on_inline_bodies = !implementation_file;
    72       // Check that all virtual methods are marked accordingly with both
    73       // virtual and OVERRIDE.
    74       CheckVirtualMethods(record_location, record, warn_on_inline_bodies);
    75     }
    77     if (check_refcounted_dtors_)
    78       CheckRefCountedDtors(record_location, record);
    79   }
    81  private:
    82   bool check_refcounted_dtors_;
    83   bool check_virtuals_in_implementations_;
    85   // Returns true if |base| specifies one of the Chromium reference counted
    86   // classes (base::RefCounted / base::RefCountedThreadSafe). |user_data| is
    87   // ignored.
    88   static bool IsRefCountedCallback(const CXXBaseSpecifier* base,
    89                                    CXXBasePath& path,
    90                                    void* user_data) {
    91     FindBadConstructsConsumer* self =
    92         static_cast<FindBadConstructsConsumer*>(user_data);
    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     }
   105     TemplateName name = base_type->getTemplateName();
   106     if (TemplateDecl* decl = name.getAsTemplateDecl()) {
   107       std::string base_name = decl->getNameAsString();
   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   }
   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;
   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     }
   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   }
   146   // Prints errors if the constructor/destructor weight is too heavy.
   147   void CheckCtorDtorWeight(SourceLocation record_location,
   148                            CXXRecordDecl* record) {
   149     // We don't handle anonymous structs. If this record doesn't have a
   150     // name, it's of the form:
   151     //
   152     // struct {
   153     //   ...
   154     // } name_;
   155     if (record->getIdentifier() == NULL)
   156       return;
   158     // Count the number of templated base classes as a feature of whether the
   159     // destructor can be inlined.
   160     int templated_base_classes = 0;
   161     for (CXXRecordDecl::base_class_const_iterator it = record->bases_begin();
   162          it != record->bases_end(); ++it) {
   163       if (it->getTypeSourceInfo()->getTypeLoc().getTypeLocClass() ==
   164           TypeLoc::TemplateSpecialization) {
   165         ++templated_base_classes;
   166       }
   167     }
   169     // Count the number of trivial and non-trivial member variables.
   170     int trivial_member = 0;
   171     int non_trivial_member = 0;
   172     int templated_non_trivial_member = 0;
   173     for (RecordDecl::field_iterator it = record->field_begin();
   174          it != record->field_end(); ++it) {
   175       CountType(it->getType().getTypePtr(),
   176                 &trivial_member,
   177                 &non_trivial_member,
   178                 &templated_non_trivial_member);
   179     }
   181     // Check to see if we need to ban inlined/synthesized constructors. Note
   182     // that the cutoffs here are kind of arbitrary. Scores over 10 break.
   183     int dtor_score = 0;
   184     // Deriving from a templated base class shouldn't be enough to trigger
   185     // the ctor warning, but if you do *anything* else, it should.
   186     //
   187     // TODO(erg): This is motivated by templated base classes that don't have
   188     // any data members. Somehow detect when templated base classes have data
   189     // members and treat them differently.
   190     dtor_score += templated_base_classes * 9;
   191     // Instantiating a template is an insta-hit.
   192     dtor_score += templated_non_trivial_member * 10;
   193     // The fourth normal class member should trigger the warning.
   194     dtor_score += non_trivial_member * 3;
   196     int ctor_score = dtor_score;
   197     // You should be able to have 9 ints before we warn you.
   198     ctor_score += trivial_member;
   200     if (ctor_score >= 10) {
   201       if (!record->hasUserDeclaredConstructor()) {
   202         emitWarning(record_location,
   203                     "Complex class/struct needs an explicit out-of-line "
   204                     "constructor.");
   205       } else {
   206         // Iterate across all the constructors in this file and yell if we
   207         // find one that tries to be inline.
   208         for (CXXRecordDecl::ctor_iterator it = record->ctor_begin();
   209              it != record->ctor_end(); ++it) {
   210           if (it->hasInlineBody()) {
   211             if (it->isCopyConstructor() &&
   212                 !record->hasUserDeclaredCopyConstructor()) {
   213               emitWarning(record_location,
   214                           "Complex class/struct needs an explicit out-of-line "
   215                           "copy constructor.");
   216             } else {
   217               emitWarning(it->getInnerLocStart(),
   218                           "Complex constructor has an inlined body.");
   219             }
   220           }
   221         }
   222       }
   223     }
   225     // The destructor side is equivalent except that we don't check for
   226     // trivial members; 20 ints don't need a destructor.
   227     if (dtor_score >= 10 && !record->hasTrivialDestructor()) {
   228       if (!record->hasUserDeclaredDestructor()) {
   229         emitWarning(
   230             record_location,
   231             "Complex class/struct needs an explicit out-of-line "
   232             "destructor.");
   233       } else if (CXXDestructorDecl* dtor = record->getDestructor()) {
   234         if (dtor->hasInlineBody()) {
   235           emitWarning(dtor->getInnerLocStart(),
   236                       "Complex destructor has an inline body.");
   237         }
   238       }
   239     }
   240   }
   242   void CheckVirtualMethod(const CXXMethodDecl* method,
   243                           bool warn_on_inline_bodies) {
   244     if (!method->isVirtual())
   245       return;
   247     if (!method->isVirtualAsWritten()) {
   248       SourceLocation loc = method->getTypeSpecStartLoc();
   249       if (isa<CXXDestructorDecl>(method))
   250         loc = method->getInnerLocStart();
   251       emitWarning(loc, "Overriding method must have \"virtual\" keyword.");
   252     }
   254     // Virtual methods should not have inline definitions beyond "{}". This
   255     // only matters for header files.
   256     if (warn_on_inline_bodies && method->hasBody() &&
   257         method->hasInlineBody()) {
   258       if (CompoundStmt* cs = dyn_cast<CompoundStmt>(method->getBody())) {
   259         if (cs->size()) {
   260           emitWarning(
   261               cs->getLBracLoc(),
   262               "virtual methods with non-empty bodies shouldn't be "
   263               "declared inline.");
   264         }
   265       }
   266     }
   267   }
   269   bool InTestingNamespace(const Decl* record) {
   270     return GetNamespace(record).find("testing") != std::string::npos;
   271   }
   273   bool IsMethodInBannedNamespace(const CXXMethodDecl* method) {
   274     if (InBannedNamespace(method))
   275       return true;
   276     for (CXXMethodDecl::method_iterator i = method->begin_overridden_methods();
   277          i != method->end_overridden_methods();
   278          ++i) {
   279       const CXXMethodDecl* overridden = *i;
   280       if (IsMethodInBannedNamespace(overridden))
   281         return true;
   282     }
   284     return false;
   285   }
   287   void CheckOverriddenMethod(const CXXMethodDecl* method) {
   288     if (!method->size_overridden_methods() || method->getAttr<OverrideAttr>())
   289       return;
   291     if (isa<CXXDestructorDecl>(method) || method->isPure())
   292       return;
   294     if (IsMethodInBannedNamespace(method))
   295       return;
   297     SourceLocation loc = method->getTypeSpecStartLoc();
   298     emitWarning(loc, "Overriding method must be marked with OVERRIDE.");
   299   }
   301   // Makes sure there is a "virtual" keyword on virtual methods.
   302   //
   303   // Gmock objects trigger these for each MOCK_BLAH() macro used. So we have a
   304   // trick to get around that. If a class has member variables whose types are
   305   // in the "testing" namespace (which is how gmock works behind the scenes),
   306   // there's a really high chance we won't care about these errors
   307   void CheckVirtualMethods(SourceLocation record_location,
   308                            CXXRecordDecl* record,
   309                            bool warn_on_inline_bodies) {
   310     for (CXXRecordDecl::field_iterator it = record->field_begin();
   311          it != record->field_end(); ++it) {
   312       CXXRecordDecl* record_type =
   313           it->getTypeSourceInfo()->getTypeLoc().getTypePtr()->
   314           getAsCXXRecordDecl();
   315       if (record_type) {
   316         if (InTestingNamespace(record_type)) {
   317           return;
   318         }
   319       }
   320     }
   322     for (CXXRecordDecl::method_iterator it = record->method_begin();
   323          it != record->method_end(); ++it) {
   324       if (it->isCopyAssignmentOperator() || isa<CXXConstructorDecl>(*it)) {
   325         // Ignore constructors and assignment operators.
   326       } else if (isa<CXXDestructorDecl>(*it) &&
   327           !record->hasUserDeclaredDestructor()) {
   328         // Ignore non-user-declared destructors.
   329       } else {
   330         CheckVirtualMethod(*it, warn_on_inline_bodies);
   331         CheckOverriddenMethod(*it);
   332       }
   333     }
   334   }
   336   void CountType(const Type* type,
   337                  int* trivial_member,
   338                  int* non_trivial_member,
   339                  int* templated_non_trivial_member) {
   340     switch (type->getTypeClass()) {
   341       case Type::Record: {
   342         // Simplifying; the whole class isn't trivial if the dtor is, but
   343         // we use this as a signal about complexity.
   344         if (TypeHasNonTrivialDtor(type))
   345           (*trivial_member)++;
   346         else
   347           (*non_trivial_member)++;
   348         break;
   349       }
   350       case Type::TemplateSpecialization: {
   351         TemplateName name =
   352             dyn_cast<TemplateSpecializationType>(type)->getTemplateName();
   353         bool whitelisted_template = false;
   355         // HACK: I'm at a loss about how to get the syntax checker to get
   356         // whether a template is exterened or not. For the first pass here,
   357         // just do retarded string comparisons.
   358         if (TemplateDecl* decl = name.getAsTemplateDecl()) {
   359           std::string base_name = decl->getNameAsString();
   360           if (base_name == "basic_string")
   361             whitelisted_template = true;
   362         }
   364         if (whitelisted_template)
   365           (*non_trivial_member)++;
   366         else
   367           (*templated_non_trivial_member)++;
   368         break;
   369       }
   370       case Type::Elaborated: {
   371         CountType(
   372             dyn_cast<ElaboratedType>(type)->getNamedType().getTypePtr(),
   373             trivial_member, non_trivial_member, templated_non_trivial_member);
   374         break;
   375       }
   376       case Type::Typedef: {
   377         while (const TypedefType* TT = dyn_cast<TypedefType>(type)) {
   378           type = TT->getDecl()->getUnderlyingType().getTypePtr();
   379         }
   380         CountType(type, trivial_member, non_trivial_member,
   381                   templated_non_trivial_member);
   382         break;
   383       }
   384       default: {
   385         // Stupid assumption: anything we see that isn't the above is one of
   386         // the 20 integer types.
   387         (*trivial_member)++;
   388         break;
   389       }
   390     }
   391   }
   392 };
   394 class FindBadConstructsAction : public PluginASTAction {
   395  public:
   396   FindBadConstructsAction()
   397       : check_refcounted_dtors_(true),
   398         check_virtuals_in_implementations_(true) {
   399   }
   401  protected:
   402   // Overridden from PluginASTAction:
   403   virtual ASTConsumer* CreateASTConsumer(CompilerInstance& instance,
   404                                          llvm::StringRef ref) {
   405     return new FindBadConstructsConsumer(
   406         instance, check_refcounted_dtors_, check_virtuals_in_implementations_);
   407   }
   409   virtual bool ParseArgs(const CompilerInstance& instance,
   410                          const std::vector<std::string>& args) {
   411     bool parsed = true;
   413     for (size_t i = 0; i < args.size() && parsed; ++i) {
   414       if (args[i] == "skip-refcounted-dtors") {
   415         check_refcounted_dtors_ = false;
   416       } else if (args[i] == "skip-virtuals-in-implementations") {
   417         check_virtuals_in_implementations_ = false;
   418       } else {
   419         parsed = false;
   420         llvm::errs() << "Unknown argument: " << args[i] << "\n";
   421       }
   422     }
   424     return parsed;
   425   }
   427  private:
   428   bool check_refcounted_dtors_;
   429   bool check_virtuals_in_implementations_;
   430 };
   432 }  // namespace
   434 static FrontendPluginRegistry::Add<FindBadConstructsAction>
   435 X("find-bad-constructs", "Finds bad C++ constructs");

mercurial