Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright (c) 2011 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. | |
| 4 | |
| 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 // - Virtual methods with nonempty implementations in their headers. | |
| 12 // | |
| 13 // Things that are still TODO: | |
| 14 // - Deriving from base::RefCounted and friends should mandate non-public | |
| 15 // destructors. | |
| 16 | |
| 17 #include "clang/Frontend/FrontendPluginRegistry.h" | |
| 18 #include "clang/AST/ASTConsumer.h" | |
| 19 #include "clang/AST/AST.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" | |
| 24 | |
| 25 #include "ChromeClassTester.h" | |
| 26 | |
| 27 using namespace clang; | |
| 28 | |
| 29 namespace { | |
| 30 | |
| 31 bool TypeHasNonTrivialDtor(const Type* type) { | |
| 32 if (const CXXRecordDecl* cxx_r = type->getCXXRecordDeclForPointerType()) | |
| 33 return cxx_r->hasTrivialDestructor(); | |
| 34 | |
| 35 return false; | |
| 36 } | |
| 37 | |
| 38 // Searches for constructs that we know we don't want in the Chromium code base. | |
| 39 class FindBadConstructsConsumer : public ChromeClassTester { | |
| 40 public: | |
| 41 FindBadConstructsConsumer(CompilerInstance& instance) | |
| 42 : ChromeClassTester(instance) {} | |
| 43 | |
| 44 virtual void CheckChromeClass(const SourceLocation& record_location, | |
| 45 CXXRecordDecl* record) { | |
| 46 CheckCtorDtorWeight(record_location, record); | |
| 47 CheckVirtualMethods(record_location, record); | |
| 48 } | |
| 49 | |
| 50 // Prints errors if the constructor/destructor weight it too heavy. | |
| 51 void CheckCtorDtorWeight(const SourceLocation& record_location, | |
| 52 CXXRecordDecl* record) { | |
| 53 // We don't handle anonymous structs. If this record doesn't have a | |
| 54 // name, it's of the form: | |
| 55 // | |
| 56 // struct { | |
| 57 // ... | |
| 58 // } name_; | |
| 59 if (record->getIdentifier() == NULL) | |
| 60 return; | |
| 61 | |
| 62 // Count the number of templated base classes as a feature of whether the | |
| 63 // destructor can be inlined. | |
| 64 int templated_base_classes = 0; | |
| 65 for (CXXRecordDecl::base_class_const_iterator it = record->bases_begin(); | |
| 66 it != record->bases_end(); ++it) { | |
| 67 if (it->getTypeSourceInfo()->getTypeLoc().getTypeLocClass() == | |
| 68 TypeLoc::TemplateSpecialization) { | |
| 69 ++templated_base_classes; | |
| 70 } | |
| 71 } | |
| 72 | |
| 73 // Count the number of trivial and non-trivial member variables. | |
| 74 int trivial_member = 0; | |
| 75 int non_trivial_member = 0; | |
| 76 int templated_non_trivial_member = 0; | |
| 77 for (RecordDecl::field_iterator it = record->field_begin(); | |
| 78 it != record->field_end(); ++it) { | |
| 79 CountType(it->getType().getTypePtr(), | |
| 80 &trivial_member, | |
| 81 &non_trivial_member, | |
| 82 &templated_non_trivial_member); | |
| 83 } | |
| 84 | |
| 85 // Check to see if we need to ban inlined/synthesized constructors. Note | |
| 86 // that the cutoffs here are kind of arbitrary. Scores over 10 break. | |
| 87 int dtor_score = 0; | |
| 88 // Deriving from a templated base class shouldn't be enough to trigger | |
| 89 // the ctor warning, but if you do *anything* else, it should. TODO: This | |
|
Nico
2011/02/02 23:13:06
TODO(erg)?
| |
| 90 // is motivated by templated base classes that don't have any data | |
| 91 // members. Somehow detect when templated base classes have data members | |
| 92 // and treat them differently. | |
| 93 dtor_score += (templated_base_classes * 9); | |
|
Nico
2011/02/02 23:13:06
no parens? also below
| |
| 94 // Instantiating a template is an insta-hit. | |
| 95 dtor_score += (templated_non_trivial_member * 10); | |
| 96 // The fourth normal class member should trigger the warning. | |
| 97 dtor_score += (non_trivial_member * 3); | |
| 98 | |
| 99 int ctor_score = dtor_score; | |
| 100 // You should be able to have 9 ints before we warn you. | |
| 101 ctor_score += trivial_member; | |
| 102 | |
| 103 if (ctor_score >= 10) { | |
| 104 if (!record->hasUserDeclaredConstructor()) { | |
| 105 emitWarning(record_location, | |
| 106 "Complex class/struct needs a declared constructor."); | |
| 107 } else { | |
| 108 // Iterate across all the constructors in this file and yell if we | |
| 109 // find one that tries to be inline. | |
| 110 for (CXXRecordDecl::ctor_iterator it = record->ctor_begin(); | |
| 111 it != record->ctor_end(); ++it) { | |
| 112 if (it->hasInlineBody()) { | |
| 113 emitWarning(it->getInnerLocStart(), | |
| 114 "Complex constructor has an inlined body."); | |
| 115 } | |
| 116 } | |
| 117 } | |
| 118 } | |
| 119 | |
| 120 // The destructor side is equivalent except that we don't check for | |
| 121 // trivial members; 20 ints don't need a destructor. | |
| 122 if (dtor_score >= 10 && !record->hasTrivialDestructor()) { | |
| 123 if (!record->hasUserDeclaredDestructor()) { | |
| 124 emitWarning(record_location, | |
| 125 "Complex class/struct needs a declared destructor."); | |
| 126 } else if (CXXDestructorDecl* dtor = record->getDestructor()) { | |
| 127 if (dtor->hasInlineBody()) { | |
| 128 emitWarning(dtor->getInnerLocStart(), | |
| 129 "Complex destructor has an inline body."); | |
| 130 } | |
| 131 } | |
| 132 } | |
| 133 } | |
| 134 | |
| 135 // Makes sure there is a "virtual" keyword on virtual methods and that there | |
| 136 // are no inline function bodies on them (but "{}" is allowed). | |
| 137 void CheckVirtualMethods(const SourceLocation& record_location, | |
| 138 CXXRecordDecl* record) { | |
| 139 for (CXXRecordDecl::method_iterator it = record->method_begin(); | |
| 140 it != record->method_end(); ++it) { | |
| 141 if (it->isCopyAssignmentOperator() || | |
| 142 dyn_cast<CXXConstructorDecl>(*it)) { | |
| 143 // Ignore constructors and assignment operators. | |
| 144 } else if (dyn_cast<CXXDestructorDecl>(*it)) { | |
| 145 // TODO: I'd love to handle this case, but | |
| 146 // CXXDestructorDecl::isImplicitlyDefined() asserts if I call it | |
| 147 // here, and against my better judgment, I don't want to *always* | |
| 148 // disallow implicit destructors. | |
| 149 } else { | |
| 150 if (it->isVirtual() && !it->isVirtualAsWritten()) { | |
| 151 emitWarning(it->getTypeSpecStartLoc(), | |
| 152 "Overridden method must have \"virtual\" keyword."); | |
| 153 } | |
| 154 | |
| 155 // Virtual methods should not have inline definitions beyond "{}". | |
| 156 if (it->isVirtual() && it->hasBody() && it->hasInlineBody()) { | |
| 157 if (CompoundStmt* cs = dyn_cast<CompoundStmt>(it->getBody())) { | |
| 158 if (cs->size()) { | |
| 159 emitWarning( | |
| 160 cs->getLBracLoc(), | |
| 161 "virtual methods with non-empty bodies shouldn't be " | |
| 162 "declared inline."); | |
| 163 } | |
| 164 } | |
| 165 } | |
| 166 } | |
| 167 } | |
| 168 } | |
| 169 | |
| 170 void CountType(const Type* type, | |
| 171 int* trivial_member, | |
| 172 int* non_trivial_member, | |
| 173 int* templated_non_trivial_member) { | |
| 174 switch (type->getTypeClass()) { | |
| 175 case Type::Record: { | |
| 176 // Simplifying; the whole class isn't trivial if the dtor is, but | |
| 177 // we use this as a signal about complexity. | |
| 178 if (TypeHasNonTrivialDtor(type)) | |
| 179 (*trivial_member)++; | |
| 180 else | |
| 181 (*non_trivial_member)++; | |
| 182 break; | |
| 183 } | |
| 184 case Type::TemplateSpecialization: { | |
| 185 TemplateName name = | |
| 186 dyn_cast<TemplateSpecializationType>(type)->getTemplateName(); | |
| 187 bool whitelisted_template = false; | |
| 188 | |
| 189 // HACK: I'm at a loss about how to get the syntax checker to get | |
| 190 // whether a template is exterened or not. For the first pass here, | |
| 191 // just do retarded string comparisons. | |
| 192 if (TemplateDecl* decl = name.getAsTemplateDecl()) { | |
| 193 std::string base_name = decl->getNameAsString(); | |
| 194 if (base_name == "basic_string") | |
| 195 whitelisted_template = true; | |
| 196 } | |
| 197 | |
| 198 if (whitelisted_template) | |
| 199 (*non_trivial_member)++; | |
| 200 else | |
| 201 (*templated_non_trivial_member)++; | |
| 202 break; | |
| 203 } | |
| 204 case Type::Elaborated: { | |
| 205 CountType( | |
| 206 dyn_cast<ElaboratedType>(type)->getNamedType().getTypePtr(), | |
| 207 trivial_member, non_trivial_member, templated_non_trivial_member); | |
| 208 break; | |
| 209 } | |
| 210 case Type::Typedef: { | |
| 211 while (const TypedefType *TT = dyn_cast<TypedefType>(type)) { | |
| 212 type = TT->getDecl()->getUnderlyingType().getTypePtr(); | |
| 213 } | |
| 214 CountType(type, trivial_member, non_trivial_member, | |
| 215 templated_non_trivial_member); | |
| 216 break; | |
| 217 } | |
| 218 default: { | |
| 219 // Stupid assumption: anything we see that isn't the above is one of | |
| 220 // the 20 integer types. | |
| 221 (*trivial_member)++; | |
| 222 break; | |
| 223 } | |
| 224 } | |
| 225 } | |
| 226 }; | |
| 227 | |
| 228 class FindBadConstructsAction : public PluginASTAction { | |
| 229 protected: | |
| 230 ASTConsumer* CreateASTConsumer(CompilerInstance &CI, llvm::StringRef ref) { | |
| 231 return new FindBadConstructsConsumer(CI); | |
| 232 } | |
| 233 | |
| 234 bool ParseArgs(const CompilerInstance &CI, | |
| 235 const std::vector<std::string>& args) { | |
| 236 // We don't take any additional arguments here. | |
| 237 return true; | |
| 238 } | |
| 239 }; | |
| 240 | |
| 241 } // namespace | |
| 242 | |
| 243 static FrontendPluginRegistry::Add<FindBadConstructsAction> | |
| 244 X("find-bad-constructs", "Finds bad C++ constructs"); | |
| OLD | NEW |