|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 #include "clang/AST/ASTConsumer.h" |
|
5 #include "clang/AST/ASTContext.h" |
|
6 #include "clang/AST/RecursiveASTVisitor.h" |
|
7 #include "clang/ASTMatchers/ASTMatchers.h" |
|
8 #include "clang/ASTMatchers/ASTMatchFinder.h" |
|
9 #include "clang/Basic/Version.h" |
|
10 #include "clang/Frontend/CompilerInstance.h" |
|
11 #include "clang/Frontend/FrontendPluginRegistry.h" |
|
12 #include "clang/Frontend/MultiplexConsumer.h" |
|
13 #include "clang/Sema/Sema.h" |
|
14 #include "llvm/ADT/DenseMap.h" |
|
15 |
|
16 #define CLANG_VERSION_FULL (CLANG_VERSION_MAJOR * 100 + CLANG_VERSION_MINOR) |
|
17 |
|
18 using namespace llvm; |
|
19 using namespace clang; |
|
20 |
|
21 namespace { |
|
22 |
|
23 using namespace clang::ast_matchers; |
|
24 class DiagnosticsMatcher { |
|
25 public: |
|
26 DiagnosticsMatcher(); |
|
27 |
|
28 ASTConsumer *makeASTConsumer() { |
|
29 return astMatcher.newASTConsumer(); |
|
30 } |
|
31 |
|
32 private: |
|
33 class StackClassChecker : public MatchFinder::MatchCallback { |
|
34 public: |
|
35 virtual void run(const MatchFinder::MatchResult &Result); |
|
36 void noteInferred(QualType T, DiagnosticsEngine &Diag); |
|
37 }; |
|
38 |
|
39 class NonHeapClassChecker : public MatchFinder::MatchCallback { |
|
40 public: |
|
41 virtual void run(const MatchFinder::MatchResult &Result); |
|
42 void noteInferred(QualType T, DiagnosticsEngine &Diag); |
|
43 }; |
|
44 |
|
45 StackClassChecker stackClassChecker; |
|
46 NonHeapClassChecker nonheapClassChecker; |
|
47 MatchFinder astMatcher; |
|
48 }; |
|
49 |
|
50 class MozChecker : public ASTConsumer, public RecursiveASTVisitor<MozChecker> { |
|
51 DiagnosticsEngine &Diag; |
|
52 const CompilerInstance &CI; |
|
53 DiagnosticsMatcher matcher; |
|
54 public: |
|
55 MozChecker(const CompilerInstance &CI) : Diag(CI.getDiagnostics()), CI(CI) {} |
|
56 |
|
57 ASTConsumer *getOtherConsumer() { |
|
58 return matcher.makeASTConsumer(); |
|
59 } |
|
60 |
|
61 virtual void HandleTranslationUnit(ASTContext &ctx) { |
|
62 TraverseDecl(ctx.getTranslationUnitDecl()); |
|
63 } |
|
64 |
|
65 static bool hasCustomAnnotation(const Decl *d, const char *spelling) { |
|
66 AnnotateAttr *attr = d->getAttr<AnnotateAttr>(); |
|
67 if (!attr) |
|
68 return false; |
|
69 |
|
70 return attr->getAnnotation() == spelling; |
|
71 } |
|
72 |
|
73 bool VisitCXXRecordDecl(CXXRecordDecl *d) { |
|
74 // We need definitions, not declarations |
|
75 if (!d->isThisDeclarationADefinition()) return true; |
|
76 |
|
77 // Look through all of our immediate bases to find methods that need to be |
|
78 // overridden |
|
79 typedef std::vector<CXXMethodDecl *> OverridesVector; |
|
80 OverridesVector must_overrides; |
|
81 for (CXXRecordDecl::base_class_iterator base = d->bases_begin(), |
|
82 e = d->bases_end(); base != e; ++base) { |
|
83 // The base is either a class (CXXRecordDecl) or it's a templated class... |
|
84 CXXRecordDecl *parent = base->getType() |
|
85 .getDesugaredType(d->getASTContext())->getAsCXXRecordDecl(); |
|
86 // The parent might not be resolved to a type yet. In this case, we can't |
|
87 // do any checking here. For complete correctness, we should visit |
|
88 // template instantiations, but this case is likely to be rare, so we will |
|
89 // ignore it until it becomes important. |
|
90 if (!parent) { |
|
91 continue; |
|
92 } |
|
93 parent = parent->getDefinition(); |
|
94 for (CXXRecordDecl::method_iterator M = parent->method_begin(); |
|
95 M != parent->method_end(); ++M) { |
|
96 if (hasCustomAnnotation(*M, "moz_must_override")) |
|
97 must_overrides.push_back(*M); |
|
98 } |
|
99 } |
|
100 |
|
101 for (OverridesVector::iterator it = must_overrides.begin(); |
|
102 it != must_overrides.end(); ++it) { |
|
103 bool overridden = false; |
|
104 for (CXXRecordDecl::method_iterator M = d->method_begin(); |
|
105 !overridden && M != d->method_end(); ++M) { |
|
106 // The way that Clang checks if a method M overrides its parent method |
|
107 // is if the method has the same name but would not overload. |
|
108 if (M->getName() == (*it)->getName() && |
|
109 !CI.getSema().IsOverload(*M, (*it), false)) |
|
110 overridden = true; |
|
111 } |
|
112 if (!overridden) { |
|
113 unsigned overrideID = Diag.getDiagnosticIDs()->getCustomDiagID( |
|
114 DiagnosticIDs::Error, "%0 must override %1"); |
|
115 unsigned overrideNote = Diag.getDiagnosticIDs()->getCustomDiagID( |
|
116 DiagnosticIDs::Note, "function to override is here"); |
|
117 Diag.Report(d->getLocation(), overrideID) << d->getDeclName() << |
|
118 (*it)->getDeclName(); |
|
119 Diag.Report((*it)->getLocation(), overrideNote); |
|
120 } |
|
121 } |
|
122 return true; |
|
123 } |
|
124 }; |
|
125 |
|
126 /** |
|
127 * Where classes may be allocated. Regular classes can be allocated anywhere, |
|
128 * non-heap classes on the stack or as static variables, and stack classes only |
|
129 * on the stack. Note that stack classes subsumes non-heap classes. |
|
130 */ |
|
131 enum ClassAllocationNature { |
|
132 RegularClass = 0, |
|
133 NonHeapClass = 1, |
|
134 StackClass = 2 |
|
135 }; |
|
136 |
|
137 /// A cached data of whether classes are stack classes, non-heap classes, or |
|
138 /// neither. |
|
139 DenseMap<const CXXRecordDecl *, |
|
140 std::pair<const Decl *, ClassAllocationNature> > inferredAllocCauses; |
|
141 |
|
142 ClassAllocationNature getClassAttrs(QualType T); |
|
143 |
|
144 ClassAllocationNature getClassAttrs(CXXRecordDecl *D) { |
|
145 // Normalize so that D points to the definition if it exists. If it doesn't, |
|
146 // then we can't allocate it anyways. |
|
147 if (!D->hasDefinition()) |
|
148 return RegularClass; |
|
149 D = D->getDefinition(); |
|
150 // Base class: anyone with this annotation is obviously a stack class |
|
151 if (MozChecker::hasCustomAnnotation(D, "moz_stack_class")) |
|
152 return StackClass; |
|
153 |
|
154 // See if we cached the result. |
|
155 DenseMap<const CXXRecordDecl *, |
|
156 std::pair<const Decl *, ClassAllocationNature> >::iterator it = |
|
157 inferredAllocCauses.find(D); |
|
158 if (it != inferredAllocCauses.end()) { |
|
159 return it->second.second; |
|
160 } |
|
161 |
|
162 // Continue looking, we might be a stack class yet. Even if we're a nonheap |
|
163 // class, it might be possible that we've inferred to be a stack class. |
|
164 ClassAllocationNature type = RegularClass; |
|
165 if (MozChecker::hasCustomAnnotation(D, "moz_nonheap_class")) { |
|
166 type = NonHeapClass; |
|
167 } |
|
168 inferredAllocCauses.insert(std::make_pair(D, |
|
169 std::make_pair((const Decl *)0, type))); |
|
170 |
|
171 // Look through all base cases to figure out if the parent is a stack class or |
|
172 // a non-heap class. Since we might later infer to also be a stack class, keep |
|
173 // going. |
|
174 for (CXXRecordDecl::base_class_iterator base = D->bases_begin(), |
|
175 e = D->bases_end(); base != e; ++base) { |
|
176 ClassAllocationNature super = getClassAttrs(base->getType()); |
|
177 if (super == StackClass) { |
|
178 inferredAllocCauses[D] = std::make_pair( |
|
179 base->getType()->getAsCXXRecordDecl(), StackClass); |
|
180 return StackClass; |
|
181 } else if (super == NonHeapClass) { |
|
182 inferredAllocCauses[D] = std::make_pair( |
|
183 base->getType()->getAsCXXRecordDecl(), NonHeapClass); |
|
184 type = NonHeapClass; |
|
185 } |
|
186 } |
|
187 |
|
188 // Maybe it has a member which is a stack class. |
|
189 for (RecordDecl::field_iterator field = D->field_begin(), e = D->field_end(); |
|
190 field != e; ++field) { |
|
191 ClassAllocationNature fieldType = getClassAttrs(field->getType()); |
|
192 if (fieldType == StackClass) { |
|
193 inferredAllocCauses[D] = std::make_pair(*field, StackClass); |
|
194 return StackClass; |
|
195 } else if (fieldType == NonHeapClass) { |
|
196 inferredAllocCauses[D] = std::make_pair(*field, NonHeapClass); |
|
197 type = NonHeapClass; |
|
198 } |
|
199 } |
|
200 |
|
201 return type; |
|
202 } |
|
203 |
|
204 ClassAllocationNature getClassAttrs(QualType T) { |
|
205 while (const ArrayType *arrTy = T->getAsArrayTypeUnsafe()) |
|
206 T = arrTy->getElementType(); |
|
207 CXXRecordDecl *clazz = T->getAsCXXRecordDecl(); |
|
208 return clazz ? getClassAttrs(clazz) : RegularClass; |
|
209 } |
|
210 |
|
211 } |
|
212 |
|
213 namespace clang { |
|
214 namespace ast_matchers { |
|
215 |
|
216 /// This matcher will match any class with the stack class assertion or an |
|
217 /// array of such classes. |
|
218 AST_MATCHER(QualType, stackClassAggregate) { |
|
219 return getClassAttrs(Node) == StackClass; |
|
220 } |
|
221 |
|
222 /// This matcher will match any class with the stack class assertion or an |
|
223 /// array of such classes. |
|
224 AST_MATCHER(QualType, nonheapClassAggregate) { |
|
225 return getClassAttrs(Node) == NonHeapClass; |
|
226 } |
|
227 |
|
228 /// This matcher will match any function declaration that is declared as a heap |
|
229 /// allocator. |
|
230 AST_MATCHER(FunctionDecl, heapAllocator) { |
|
231 return MozChecker::hasCustomAnnotation(&Node, "moz_heap_allocator"); |
|
232 } |
|
233 } |
|
234 } |
|
235 |
|
236 namespace { |
|
237 |
|
238 bool isPlacementNew(const CXXNewExpr *expr) { |
|
239 // Regular new expressions aren't placement new |
|
240 if (expr->getNumPlacementArgs() == 0) |
|
241 return false; |
|
242 if (MozChecker::hasCustomAnnotation(expr->getOperatorNew(), |
|
243 "moz_heap_allocator")) |
|
244 return false; |
|
245 return true; |
|
246 } |
|
247 |
|
248 DiagnosticsMatcher::DiagnosticsMatcher() { |
|
249 // Stack class assertion: non-local variables of a stack class are forbidden |
|
250 // (non-localness checked in the callback) |
|
251 astMatcher.addMatcher(varDecl(hasType(stackClassAggregate())).bind("node"), |
|
252 &stackClassChecker); |
|
253 // Stack class assertion: new stack class is forbidden (unless placement new) |
|
254 astMatcher.addMatcher(newExpr(hasType(pointerType( |
|
255 pointee(stackClassAggregate()) |
|
256 ))).bind("node"), &stackClassChecker); |
|
257 // Non-heap class assertion: new non-heap class is forbidden (unless placement |
|
258 // new) |
|
259 astMatcher.addMatcher(newExpr(hasType(pointerType( |
|
260 pointee(nonheapClassAggregate()) |
|
261 ))).bind("node"), &nonheapClassChecker); |
|
262 |
|
263 // Any heap allocation function that returns a non-heap or a stack class is |
|
264 // definitely doing something wrong |
|
265 astMatcher.addMatcher(callExpr(callee(functionDecl(allOf(heapAllocator(), |
|
266 returns(pointerType(pointee(nonheapClassAggregate()))))))).bind("node"), |
|
267 &nonheapClassChecker); |
|
268 astMatcher.addMatcher(callExpr(callee(functionDecl(allOf(heapAllocator(), |
|
269 returns(pointerType(pointee(stackClassAggregate()))))))).bind("node"), |
|
270 &stackClassChecker); |
|
271 } |
|
272 |
|
273 void DiagnosticsMatcher::StackClassChecker::run( |
|
274 const MatchFinder::MatchResult &Result) { |
|
275 DiagnosticsEngine &Diag = Result.Context->getDiagnostics(); |
|
276 unsigned stackID = Diag.getDiagnosticIDs()->getCustomDiagID( |
|
277 DiagnosticIDs::Error, "variable of type %0 only valid on the stack"); |
|
278 if (const VarDecl *d = Result.Nodes.getNodeAs<VarDecl>("node")) { |
|
279 // Ignore the match if it's a local variable. |
|
280 if (d->hasLocalStorage()) |
|
281 return; |
|
282 |
|
283 Diag.Report(d->getLocation(), stackID) << d->getType(); |
|
284 noteInferred(d->getType(), Diag); |
|
285 } else if (const CXXNewExpr *expr = |
|
286 Result.Nodes.getNodeAs<CXXNewExpr>("node")) { |
|
287 // If it's placement new, then this match doesn't count. |
|
288 if (isPlacementNew(expr)) |
|
289 return; |
|
290 Diag.Report(expr->getStartLoc(), stackID) << expr->getAllocatedType(); |
|
291 noteInferred(expr->getAllocatedType(), Diag); |
|
292 } else if (const CallExpr *expr = |
|
293 Result.Nodes.getNodeAs<CallExpr>("node")) { |
|
294 QualType badType = expr->getCallReturnType()->getPointeeType(); |
|
295 Diag.Report(expr->getLocStart(), stackID) << badType; |
|
296 noteInferred(badType, Diag); |
|
297 } |
|
298 } |
|
299 |
|
300 void DiagnosticsMatcher::StackClassChecker::noteInferred(QualType T, |
|
301 DiagnosticsEngine &Diag) { |
|
302 unsigned inheritsID = Diag.getDiagnosticIDs()->getCustomDiagID( |
|
303 DiagnosticIDs::Note, |
|
304 "%0 is a stack class because it inherits from a stack class %1"); |
|
305 unsigned memberID = Diag.getDiagnosticIDs()->getCustomDiagID( |
|
306 DiagnosticIDs::Note, |
|
307 "%0 is a stack class because member %1 is a stack class %2"); |
|
308 |
|
309 // Find the CXXRecordDecl that is the stack class of interest |
|
310 while (const ArrayType *arrTy = T->getAsArrayTypeUnsafe()) |
|
311 T = arrTy->getElementType(); |
|
312 CXXRecordDecl *clazz = T->getAsCXXRecordDecl(); |
|
313 |
|
314 // Direct result, we're done. |
|
315 if (MozChecker::hasCustomAnnotation(clazz, "moz_stack_class")) |
|
316 return; |
|
317 |
|
318 const Decl *cause = inferredAllocCauses[clazz].first; |
|
319 if (const CXXRecordDecl *CRD = dyn_cast<CXXRecordDecl>(cause)) { |
|
320 Diag.Report(clazz->getLocation(), inheritsID) << T << CRD->getDeclName(); |
|
321 } else if (const FieldDecl *FD = dyn_cast<FieldDecl>(cause)) { |
|
322 Diag.Report(FD->getLocation(), memberID) << T << FD << FD->getType(); |
|
323 } |
|
324 |
|
325 // Recursively follow this back. |
|
326 noteInferred(cast<ValueDecl>(cause)->getType(), Diag); |
|
327 } |
|
328 |
|
329 void DiagnosticsMatcher::NonHeapClassChecker::run( |
|
330 const MatchFinder::MatchResult &Result) { |
|
331 DiagnosticsEngine &Diag = Result.Context->getDiagnostics(); |
|
332 unsigned stackID = Diag.getDiagnosticIDs()->getCustomDiagID( |
|
333 DiagnosticIDs::Error, "variable of type %0 is not valid on the heap"); |
|
334 if (const CXXNewExpr *expr = Result.Nodes.getNodeAs<CXXNewExpr>("node")) { |
|
335 // If it's placement new, then this match doesn't count. |
|
336 if (isPlacementNew(expr)) |
|
337 return; |
|
338 Diag.Report(expr->getStartLoc(), stackID) << expr->getAllocatedType(); |
|
339 noteInferred(expr->getAllocatedType(), Diag); |
|
340 } else if (const CallExpr *expr = Result.Nodes.getNodeAs<CallExpr>("node")) { |
|
341 QualType badType = expr->getCallReturnType()->getPointeeType(); |
|
342 Diag.Report(expr->getLocStart(), stackID) << badType; |
|
343 noteInferred(badType, Diag); |
|
344 } |
|
345 } |
|
346 |
|
347 void DiagnosticsMatcher::NonHeapClassChecker::noteInferred(QualType T, |
|
348 DiagnosticsEngine &Diag) { |
|
349 unsigned inheritsID = Diag.getDiagnosticIDs()->getCustomDiagID( |
|
350 DiagnosticIDs::Note, |
|
351 "%0 is a non-heap class because it inherits from a non-heap class %1"); |
|
352 unsigned memberID = Diag.getDiagnosticIDs()->getCustomDiagID( |
|
353 DiagnosticIDs::Note, |
|
354 "%0 is a non-heap class because member %1 is a non-heap class %2"); |
|
355 |
|
356 // Find the CXXRecordDecl that is the stack class of interest |
|
357 while (const ArrayType *arrTy = T->getAsArrayTypeUnsafe()) |
|
358 T = arrTy->getElementType(); |
|
359 CXXRecordDecl *clazz = T->getAsCXXRecordDecl(); |
|
360 |
|
361 // Direct result, we're done. |
|
362 if (MozChecker::hasCustomAnnotation(clazz, "moz_nonheap_class")) |
|
363 return; |
|
364 |
|
365 const Decl *cause = inferredAllocCauses[clazz].first; |
|
366 if (const CXXRecordDecl *CRD = dyn_cast<CXXRecordDecl>(cause)) { |
|
367 Diag.Report(clazz->getLocation(), inheritsID) << T << CRD->getDeclName(); |
|
368 } else if (const FieldDecl *FD = dyn_cast<FieldDecl>(cause)) { |
|
369 Diag.Report(FD->getLocation(), memberID) << T << FD << FD->getType(); |
|
370 } |
|
371 |
|
372 // Recursively follow this back. |
|
373 noteInferred(cast<ValueDecl>(cause)->getType(), Diag); |
|
374 } |
|
375 |
|
376 class MozCheckAction : public PluginASTAction { |
|
377 public: |
|
378 ASTConsumer *CreateASTConsumer(CompilerInstance &CI, StringRef fileName) { |
|
379 MozChecker *checker = new MozChecker(CI); |
|
380 |
|
381 ASTConsumer *consumers[] = { checker, checker->getOtherConsumer() }; |
|
382 return new MultiplexConsumer(consumers); |
|
383 } |
|
384 |
|
385 bool ParseArgs(const CompilerInstance &CI, |
|
386 const std::vector<std::string> &args) { |
|
387 return true; |
|
388 } |
|
389 }; |
|
390 } |
|
391 |
|
392 static FrontendPluginRegistry::Add<MozCheckAction> |
|
393 X("moz-check", "check moz action"); |