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

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/media/webrtc/trunk/tools/clang/plugins/FindBadConstructs.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,435 @@
     1.4 +// Copyright (c) 2012 The Chromium Authors. All rights reserved.
     1.5 +// Use of this source code is governed by a BSD-style license that can be
     1.6 +// found in the LICENSE file.
     1.7 +
     1.8 +// This file defines a bunch of recurring problems in the Chromium C++ code.
     1.9 +//
    1.10 +// Checks that are implemented:
    1.11 +// - Constructors/Destructors should not be inlined if they are of a complex
    1.12 +//   class type.
    1.13 +// - Missing "virtual" keywords on methods that should be virtual.
    1.14 +// - Non-annotated overriding virtual methods.
    1.15 +// - Virtual methods with nonempty implementations in their headers.
    1.16 +// - Classes that derive from base::RefCounted / base::RefCountedThreadSafe
    1.17 +//   should have protected or private destructors.
    1.18 +
    1.19 +#include "clang/Frontend/FrontendPluginRegistry.h"
    1.20 +#include "clang/AST/ASTConsumer.h"
    1.21 +#include "clang/AST/AST.h"
    1.22 +#include "clang/AST/CXXInheritance.h"
    1.23 +#include "clang/AST/TypeLoc.h"
    1.24 +#include "clang/Basic/SourceManager.h"
    1.25 +#include "clang/Frontend/CompilerInstance.h"
    1.26 +#include "llvm/Support/raw_ostream.h"
    1.27 +
    1.28 +#include "ChromeClassTester.h"
    1.29 +
    1.30 +using namespace clang;
    1.31 +
    1.32 +namespace {
    1.33 +
    1.34 +bool TypeHasNonTrivialDtor(const Type* type) {
    1.35 +  if (const CXXRecordDecl* cxx_r = type->getCXXRecordDeclForPointerType())
    1.36 +    return cxx_r->hasTrivialDestructor();
    1.37 +
    1.38 +  return false;
    1.39 +}
    1.40 +
    1.41 +// Returns the underlying Type for |type| by expanding typedefs and removing
    1.42 +// any namespace qualifiers.
    1.43 +const Type* UnwrapType(const Type* type) {
    1.44 +  if (const ElaboratedType* elaborated = dyn_cast<ElaboratedType>(type))
    1.45 +    return UnwrapType(elaborated->getNamedType().getTypePtr());
    1.46 +  if (const TypedefType* typedefed = dyn_cast<TypedefType>(type))
    1.47 +    return UnwrapType(typedefed->desugar().getTypePtr());
    1.48 +  return type;
    1.49 +}
    1.50 +
    1.51 +// Searches for constructs that we know we don't want in the Chromium code base.
    1.52 +class FindBadConstructsConsumer : public ChromeClassTester {
    1.53 + public:
    1.54 +  FindBadConstructsConsumer(CompilerInstance& instance,
    1.55 +                            bool check_refcounted_dtors,
    1.56 +                            bool check_virtuals_in_implementations)
    1.57 +      : ChromeClassTester(instance),
    1.58 +        check_refcounted_dtors_(check_refcounted_dtors),
    1.59 +        check_virtuals_in_implementations_(check_virtuals_in_implementations) {
    1.60 +  }
    1.61 +
    1.62 +  virtual void CheckChromeClass(SourceLocation record_location,
    1.63 +                                CXXRecordDecl* record) {
    1.64 +    bool implementation_file = InImplementationFile(record_location);
    1.65 +
    1.66 +    if (!implementation_file) {
    1.67 +      // Only check for "heavy" constructors/destructors in header files;
    1.68 +      // within implementation files, there is no performance cost.
    1.69 +      CheckCtorDtorWeight(record_location, record);
    1.70 +    }
    1.71 +
    1.72 +    if (!implementation_file || check_virtuals_in_implementations_) {
    1.73 +      bool warn_on_inline_bodies = !implementation_file;
    1.74 +
    1.75 +      // Check that all virtual methods are marked accordingly with both
    1.76 +      // virtual and OVERRIDE.
    1.77 +      CheckVirtualMethods(record_location, record, warn_on_inline_bodies);
    1.78 +    }
    1.79 +
    1.80 +    if (check_refcounted_dtors_)
    1.81 +      CheckRefCountedDtors(record_location, record);
    1.82 +  }
    1.83 +
    1.84 + private:
    1.85 +  bool check_refcounted_dtors_;
    1.86 +  bool check_virtuals_in_implementations_;
    1.87 +
    1.88 +  // Returns true if |base| specifies one of the Chromium reference counted
    1.89 +  // classes (base::RefCounted / base::RefCountedThreadSafe). |user_data| is
    1.90 +  // ignored.
    1.91 +  static bool IsRefCountedCallback(const CXXBaseSpecifier* base,
    1.92 +                                   CXXBasePath& path,
    1.93 +                                   void* user_data) {
    1.94 +    FindBadConstructsConsumer* self =
    1.95 +        static_cast<FindBadConstructsConsumer*>(user_data);
    1.96 +
    1.97 +    const TemplateSpecializationType* base_type =
    1.98 +        dyn_cast<TemplateSpecializationType>(
    1.99 +            UnwrapType(base->getType().getTypePtr()));
   1.100 +    if (!base_type) {
   1.101 +      // Base-most definition is not a template, so this cannot derive from
   1.102 +      // base::RefCounted. However, it may still be possible to use with a
   1.103 +      // scoped_refptr<> and support ref-counting, so this is not a perfect
   1.104 +      // guarantee of safety.
   1.105 +      return false;
   1.106 +    }
   1.107 +
   1.108 +    TemplateName name = base_type->getTemplateName();
   1.109 +    if (TemplateDecl* decl = name.getAsTemplateDecl()) {
   1.110 +      std::string base_name = decl->getNameAsString();
   1.111 +
   1.112 +      // Check for both base::RefCounted and base::RefCountedThreadSafe.
   1.113 +      if (base_name.compare(0, 10, "RefCounted") == 0 &&
   1.114 +          self->GetNamespace(decl) == "base") {
   1.115 +        return true;
   1.116 +      }
   1.117 +    }
   1.118 +    return false;
   1.119 +  }
   1.120 +
   1.121 +  // Prints errors if the destructor of a RefCounted class is public.
   1.122 +  void CheckRefCountedDtors(SourceLocation record_location,
   1.123 +                            CXXRecordDecl* record) {
   1.124 +    // Skip anonymous structs.
   1.125 +    if (record->getIdentifier() == NULL)
   1.126 +      return;
   1.127 +
   1.128 +    CXXBasePaths paths;
   1.129 +    if (!record->lookupInBases(
   1.130 +            &FindBadConstructsConsumer::IsRefCountedCallback, this, paths)) {
   1.131 +      return;  // Class does not derive from a ref-counted base class.
   1.132 +    }
   1.133 +
   1.134 +    if (!record->hasUserDeclaredDestructor()) {
   1.135 +      emitWarning(
   1.136 +          record_location,
   1.137 +          "Classes that are ref-counted should have explicit "
   1.138 +          "destructors that are protected or private.");
   1.139 +    } else if (CXXDestructorDecl* dtor = record->getDestructor()) {
   1.140 +      if (dtor->getAccess() == AS_public) {
   1.141 +        emitWarning(
   1.142 +            dtor->getInnerLocStart(),
   1.143 +            "Classes that are ref-counted should not have "
   1.144 +            "public destructors.");
   1.145 +      }
   1.146 +    }
   1.147 +  }
   1.148 +
   1.149 +  // Prints errors if the constructor/destructor weight is too heavy.
   1.150 +  void CheckCtorDtorWeight(SourceLocation record_location,
   1.151 +                           CXXRecordDecl* record) {
   1.152 +    // We don't handle anonymous structs. If this record doesn't have a
   1.153 +    // name, it's of the form:
   1.154 +    //
   1.155 +    // struct {
   1.156 +    //   ...
   1.157 +    // } name_;
   1.158 +    if (record->getIdentifier() == NULL)
   1.159 +      return;
   1.160 +
   1.161 +    // Count the number of templated base classes as a feature of whether the
   1.162 +    // destructor can be inlined.
   1.163 +    int templated_base_classes = 0;
   1.164 +    for (CXXRecordDecl::base_class_const_iterator it = record->bases_begin();
   1.165 +         it != record->bases_end(); ++it) {
   1.166 +      if (it->getTypeSourceInfo()->getTypeLoc().getTypeLocClass() ==
   1.167 +          TypeLoc::TemplateSpecialization) {
   1.168 +        ++templated_base_classes;
   1.169 +      }
   1.170 +    }
   1.171 +
   1.172 +    // Count the number of trivial and non-trivial member variables.
   1.173 +    int trivial_member = 0;
   1.174 +    int non_trivial_member = 0;
   1.175 +    int templated_non_trivial_member = 0;
   1.176 +    for (RecordDecl::field_iterator it = record->field_begin();
   1.177 +         it != record->field_end(); ++it) {
   1.178 +      CountType(it->getType().getTypePtr(),
   1.179 +                &trivial_member,
   1.180 +                &non_trivial_member,
   1.181 +                &templated_non_trivial_member);
   1.182 +    }
   1.183 +
   1.184 +    // Check to see if we need to ban inlined/synthesized constructors. Note
   1.185 +    // that the cutoffs here are kind of arbitrary. Scores over 10 break.
   1.186 +    int dtor_score = 0;
   1.187 +    // Deriving from a templated base class shouldn't be enough to trigger
   1.188 +    // the ctor warning, but if you do *anything* else, it should.
   1.189 +    //
   1.190 +    // TODO(erg): This is motivated by templated base classes that don't have
   1.191 +    // any data members. Somehow detect when templated base classes have data
   1.192 +    // members and treat them differently.
   1.193 +    dtor_score += templated_base_classes * 9;
   1.194 +    // Instantiating a template is an insta-hit.
   1.195 +    dtor_score += templated_non_trivial_member * 10;
   1.196 +    // The fourth normal class member should trigger the warning.
   1.197 +    dtor_score += non_trivial_member * 3;
   1.198 +
   1.199 +    int ctor_score = dtor_score;
   1.200 +    // You should be able to have 9 ints before we warn you.
   1.201 +    ctor_score += trivial_member;
   1.202 +
   1.203 +    if (ctor_score >= 10) {
   1.204 +      if (!record->hasUserDeclaredConstructor()) {
   1.205 +        emitWarning(record_location,
   1.206 +                    "Complex class/struct needs an explicit out-of-line "
   1.207 +                    "constructor.");
   1.208 +      } else {
   1.209 +        // Iterate across all the constructors in this file and yell if we
   1.210 +        // find one that tries to be inline.
   1.211 +        for (CXXRecordDecl::ctor_iterator it = record->ctor_begin();
   1.212 +             it != record->ctor_end(); ++it) {
   1.213 +          if (it->hasInlineBody()) {
   1.214 +            if (it->isCopyConstructor() &&
   1.215 +                !record->hasUserDeclaredCopyConstructor()) {
   1.216 +              emitWarning(record_location,
   1.217 +                          "Complex class/struct needs an explicit out-of-line "
   1.218 +                          "copy constructor.");
   1.219 +            } else {
   1.220 +              emitWarning(it->getInnerLocStart(),
   1.221 +                          "Complex constructor has an inlined body.");
   1.222 +            }
   1.223 +          }
   1.224 +        }
   1.225 +      }
   1.226 +    }
   1.227 +
   1.228 +    // The destructor side is equivalent except that we don't check for
   1.229 +    // trivial members; 20 ints don't need a destructor.
   1.230 +    if (dtor_score >= 10 && !record->hasTrivialDestructor()) {
   1.231 +      if (!record->hasUserDeclaredDestructor()) {
   1.232 +        emitWarning(
   1.233 +            record_location,
   1.234 +            "Complex class/struct needs an explicit out-of-line "
   1.235 +            "destructor.");
   1.236 +      } else if (CXXDestructorDecl* dtor = record->getDestructor()) {
   1.237 +        if (dtor->hasInlineBody()) {
   1.238 +          emitWarning(dtor->getInnerLocStart(),
   1.239 +                      "Complex destructor has an inline body.");
   1.240 +        }
   1.241 +      }
   1.242 +    }
   1.243 +  }
   1.244 +
   1.245 +  void CheckVirtualMethod(const CXXMethodDecl* method,
   1.246 +                          bool warn_on_inline_bodies) {
   1.247 +    if (!method->isVirtual())
   1.248 +      return;
   1.249 +
   1.250 +    if (!method->isVirtualAsWritten()) {
   1.251 +      SourceLocation loc = method->getTypeSpecStartLoc();
   1.252 +      if (isa<CXXDestructorDecl>(method))
   1.253 +        loc = method->getInnerLocStart();
   1.254 +      emitWarning(loc, "Overriding method must have \"virtual\" keyword.");
   1.255 +    }
   1.256 +
   1.257 +    // Virtual methods should not have inline definitions beyond "{}". This
   1.258 +    // only matters for header files.
   1.259 +    if (warn_on_inline_bodies && method->hasBody() &&
   1.260 +        method->hasInlineBody()) {
   1.261 +      if (CompoundStmt* cs = dyn_cast<CompoundStmt>(method->getBody())) {
   1.262 +        if (cs->size()) {
   1.263 +          emitWarning(
   1.264 +              cs->getLBracLoc(),
   1.265 +              "virtual methods with non-empty bodies shouldn't be "
   1.266 +              "declared inline.");
   1.267 +        }
   1.268 +      }
   1.269 +    }
   1.270 +  }
   1.271 +
   1.272 +  bool InTestingNamespace(const Decl* record) {
   1.273 +    return GetNamespace(record).find("testing") != std::string::npos;
   1.274 +  }
   1.275 +
   1.276 +  bool IsMethodInBannedNamespace(const CXXMethodDecl* method) {
   1.277 +    if (InBannedNamespace(method))
   1.278 +      return true;
   1.279 +    for (CXXMethodDecl::method_iterator i = method->begin_overridden_methods();
   1.280 +         i != method->end_overridden_methods();
   1.281 +         ++i) {
   1.282 +      const CXXMethodDecl* overridden = *i;
   1.283 +      if (IsMethodInBannedNamespace(overridden))
   1.284 +        return true;
   1.285 +    }
   1.286 +
   1.287 +    return false;
   1.288 +  }
   1.289 +
   1.290 +  void CheckOverriddenMethod(const CXXMethodDecl* method) {
   1.291 +    if (!method->size_overridden_methods() || method->getAttr<OverrideAttr>())
   1.292 +      return;
   1.293 +
   1.294 +    if (isa<CXXDestructorDecl>(method) || method->isPure())
   1.295 +      return;
   1.296 +
   1.297 +    if (IsMethodInBannedNamespace(method))
   1.298 +      return;
   1.299 +
   1.300 +    SourceLocation loc = method->getTypeSpecStartLoc();
   1.301 +    emitWarning(loc, "Overriding method must be marked with OVERRIDE.");
   1.302 +  }
   1.303 +
   1.304 +  // Makes sure there is a "virtual" keyword on virtual methods.
   1.305 +  //
   1.306 +  // Gmock objects trigger these for each MOCK_BLAH() macro used. So we have a
   1.307 +  // trick to get around that. If a class has member variables whose types are
   1.308 +  // in the "testing" namespace (which is how gmock works behind the scenes),
   1.309 +  // there's a really high chance we won't care about these errors
   1.310 +  void CheckVirtualMethods(SourceLocation record_location,
   1.311 +                           CXXRecordDecl* record,
   1.312 +                           bool warn_on_inline_bodies) {
   1.313 +    for (CXXRecordDecl::field_iterator it = record->field_begin();
   1.314 +         it != record->field_end(); ++it) {
   1.315 +      CXXRecordDecl* record_type =
   1.316 +          it->getTypeSourceInfo()->getTypeLoc().getTypePtr()->
   1.317 +          getAsCXXRecordDecl();
   1.318 +      if (record_type) {
   1.319 +        if (InTestingNamespace(record_type)) {
   1.320 +          return;
   1.321 +        }
   1.322 +      }
   1.323 +    }
   1.324 +
   1.325 +    for (CXXRecordDecl::method_iterator it = record->method_begin();
   1.326 +         it != record->method_end(); ++it) {
   1.327 +      if (it->isCopyAssignmentOperator() || isa<CXXConstructorDecl>(*it)) {
   1.328 +        // Ignore constructors and assignment operators.
   1.329 +      } else if (isa<CXXDestructorDecl>(*it) &&
   1.330 +          !record->hasUserDeclaredDestructor()) {
   1.331 +        // Ignore non-user-declared destructors.
   1.332 +      } else {
   1.333 +        CheckVirtualMethod(*it, warn_on_inline_bodies);
   1.334 +        CheckOverriddenMethod(*it);
   1.335 +      }
   1.336 +    }
   1.337 +  }
   1.338 +
   1.339 +  void CountType(const Type* type,
   1.340 +                 int* trivial_member,
   1.341 +                 int* non_trivial_member,
   1.342 +                 int* templated_non_trivial_member) {
   1.343 +    switch (type->getTypeClass()) {
   1.344 +      case Type::Record: {
   1.345 +        // Simplifying; the whole class isn't trivial if the dtor is, but
   1.346 +        // we use this as a signal about complexity.
   1.347 +        if (TypeHasNonTrivialDtor(type))
   1.348 +          (*trivial_member)++;
   1.349 +        else
   1.350 +          (*non_trivial_member)++;
   1.351 +        break;
   1.352 +      }
   1.353 +      case Type::TemplateSpecialization: {
   1.354 +        TemplateName name =
   1.355 +            dyn_cast<TemplateSpecializationType>(type)->getTemplateName();
   1.356 +        bool whitelisted_template = false;
   1.357 +
   1.358 +        // HACK: I'm at a loss about how to get the syntax checker to get
   1.359 +        // whether a template is exterened or not. For the first pass here,
   1.360 +        // just do retarded string comparisons.
   1.361 +        if (TemplateDecl* decl = name.getAsTemplateDecl()) {
   1.362 +          std::string base_name = decl->getNameAsString();
   1.363 +          if (base_name == "basic_string")
   1.364 +            whitelisted_template = true;
   1.365 +        }
   1.366 +
   1.367 +        if (whitelisted_template)
   1.368 +          (*non_trivial_member)++;
   1.369 +        else
   1.370 +          (*templated_non_trivial_member)++;
   1.371 +        break;
   1.372 +      }
   1.373 +      case Type::Elaborated: {
   1.374 +        CountType(
   1.375 +            dyn_cast<ElaboratedType>(type)->getNamedType().getTypePtr(),
   1.376 +            trivial_member, non_trivial_member, templated_non_trivial_member);
   1.377 +        break;
   1.378 +      }
   1.379 +      case Type::Typedef: {
   1.380 +        while (const TypedefType* TT = dyn_cast<TypedefType>(type)) {
   1.381 +          type = TT->getDecl()->getUnderlyingType().getTypePtr();
   1.382 +        }
   1.383 +        CountType(type, trivial_member, non_trivial_member,
   1.384 +                  templated_non_trivial_member);
   1.385 +        break;
   1.386 +      }
   1.387 +      default: {
   1.388 +        // Stupid assumption: anything we see that isn't the above is one of
   1.389 +        // the 20 integer types.
   1.390 +        (*trivial_member)++;
   1.391 +        break;
   1.392 +      }
   1.393 +    }
   1.394 +  }
   1.395 +};
   1.396 +
   1.397 +class FindBadConstructsAction : public PluginASTAction {
   1.398 + public:
   1.399 +  FindBadConstructsAction()
   1.400 +      : check_refcounted_dtors_(true),
   1.401 +        check_virtuals_in_implementations_(true) {
   1.402 +  }
   1.403 +
   1.404 + protected:
   1.405 +  // Overridden from PluginASTAction:
   1.406 +  virtual ASTConsumer* CreateASTConsumer(CompilerInstance& instance,
   1.407 +                                         llvm::StringRef ref) {
   1.408 +    return new FindBadConstructsConsumer(
   1.409 +        instance, check_refcounted_dtors_, check_virtuals_in_implementations_);
   1.410 +  }
   1.411 +
   1.412 +  virtual bool ParseArgs(const CompilerInstance& instance,
   1.413 +                         const std::vector<std::string>& args) {
   1.414 +    bool parsed = true;
   1.415 +
   1.416 +    for (size_t i = 0; i < args.size() && parsed; ++i) {
   1.417 +      if (args[i] == "skip-refcounted-dtors") {
   1.418 +        check_refcounted_dtors_ = false;
   1.419 +      } else if (args[i] == "skip-virtuals-in-implementations") {
   1.420 +        check_virtuals_in_implementations_ = false;
   1.421 +      } else {
   1.422 +        parsed = false;
   1.423 +        llvm::errs() << "Unknown argument: " << args[i] << "\n";
   1.424 +      }
   1.425 +    }
   1.426 +
   1.427 +    return parsed;
   1.428 +  }
   1.429 +
   1.430 + private:
   1.431 +  bool check_refcounted_dtors_;
   1.432 +  bool check_virtuals_in_implementations_;
   1.433 +};
   1.434 +
   1.435 +}  // namespace
   1.436 +
   1.437 +static FrontendPluginRegistry::Add<FindBadConstructsAction>
   1.438 +X("find-bad-constructs", "Finds bad C++ constructs");

mercurial