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 |