Thu, 15 Jan 2015 15:59:08 +0100
Implement a real Private Browsing Mode condition by changing the API/ABI;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.
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"
16 #define CLANG_VERSION_FULL (CLANG_VERSION_MAJOR * 100 + CLANG_VERSION_MINOR)
18 using namespace llvm;
19 using namespace clang;
21 namespace {
23 using namespace clang::ast_matchers;
24 class DiagnosticsMatcher {
25 public:
26 DiagnosticsMatcher();
28 ASTConsumer *makeASTConsumer() {
29 return astMatcher.newASTConsumer();
30 }
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 };
39 class NonHeapClassChecker : public MatchFinder::MatchCallback {
40 public:
41 virtual void run(const MatchFinder::MatchResult &Result);
42 void noteInferred(QualType T, DiagnosticsEngine &Diag);
43 };
45 StackClassChecker stackClassChecker;
46 NonHeapClassChecker nonheapClassChecker;
47 MatchFinder astMatcher;
48 };
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) {}
57 ASTConsumer *getOtherConsumer() {
58 return matcher.makeASTConsumer();
59 }
61 virtual void HandleTranslationUnit(ASTContext &ctx) {
62 TraverseDecl(ctx.getTranslationUnitDecl());
63 }
65 static bool hasCustomAnnotation(const Decl *d, const char *spelling) {
66 AnnotateAttr *attr = d->getAttr<AnnotateAttr>();
67 if (!attr)
68 return false;
70 return attr->getAnnotation() == spelling;
71 }
73 bool VisitCXXRecordDecl(CXXRecordDecl *d) {
74 // We need definitions, not declarations
75 if (!d->isThisDeclarationADefinition()) return true;
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 }
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 };
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 };
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;
142 ClassAllocationNature getClassAttrs(QualType T);
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;
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 }
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)));
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 }
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 }
201 return type;
202 }
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 }
211 }
213 namespace clang {
214 namespace ast_matchers {
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 }
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 }
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 }
236 namespace {
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 }
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);
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 }
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;
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 }
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");
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();
314 // Direct result, we're done.
315 if (MozChecker::hasCustomAnnotation(clazz, "moz_stack_class"))
316 return;
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 }
325 // Recursively follow this back.
326 noteInferred(cast<ValueDecl>(cause)->getType(), Diag);
327 }
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 }
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");
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();
361 // Direct result, we're done.
362 if (MozChecker::hasCustomAnnotation(clazz, "moz_nonheap_class"))
363 return;
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 }
372 // Recursively follow this back.
373 noteInferred(cast<ValueDecl>(cause)->getType(), Diag);
374 }
376 class MozCheckAction : public PluginASTAction {
377 public:
378 ASTConsumer *CreateASTConsumer(CompilerInstance &CI, StringRef fileName) {
379 MozChecker *checker = new MozChecker(CI);
381 ASTConsumer *consumers[] = { checker, checker->getOtherConsumer() };
382 return new MultiplexConsumer(consumers);
383 }
385 bool ParseArgs(const CompilerInstance &CI,
386 const std::vector<std::string> &args) {
387 return true;
388 }
389 };
390 }
392 static FrontendPluginRegistry::Add<MozCheckAction>
393 X("moz-check", "check moz action");