michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: #include "clang/AST/ASTConsumer.h" michael@0: #include "clang/AST/ASTContext.h" michael@0: #include "clang/AST/RecursiveASTVisitor.h" michael@0: #include "clang/ASTMatchers/ASTMatchers.h" michael@0: #include "clang/ASTMatchers/ASTMatchFinder.h" michael@0: #include "clang/Basic/Version.h" michael@0: #include "clang/Frontend/CompilerInstance.h" michael@0: #include "clang/Frontend/FrontendPluginRegistry.h" michael@0: #include "clang/Frontend/MultiplexConsumer.h" michael@0: #include "clang/Sema/Sema.h" michael@0: #include "llvm/ADT/DenseMap.h" michael@0: michael@0: #define CLANG_VERSION_FULL (CLANG_VERSION_MAJOR * 100 + CLANG_VERSION_MINOR) michael@0: michael@0: using namespace llvm; michael@0: using namespace clang; michael@0: michael@0: namespace { michael@0: michael@0: using namespace clang::ast_matchers; michael@0: class DiagnosticsMatcher { michael@0: public: michael@0: DiagnosticsMatcher(); michael@0: michael@0: ASTConsumer *makeASTConsumer() { michael@0: return astMatcher.newASTConsumer(); michael@0: } michael@0: michael@0: private: michael@0: class StackClassChecker : public MatchFinder::MatchCallback { michael@0: public: michael@0: virtual void run(const MatchFinder::MatchResult &Result); michael@0: void noteInferred(QualType T, DiagnosticsEngine &Diag); michael@0: }; michael@0: michael@0: class NonHeapClassChecker : public MatchFinder::MatchCallback { michael@0: public: michael@0: virtual void run(const MatchFinder::MatchResult &Result); michael@0: void noteInferred(QualType T, DiagnosticsEngine &Diag); michael@0: }; michael@0: michael@0: StackClassChecker stackClassChecker; michael@0: NonHeapClassChecker nonheapClassChecker; michael@0: MatchFinder astMatcher; michael@0: }; michael@0: michael@0: class MozChecker : public ASTConsumer, public RecursiveASTVisitor { michael@0: DiagnosticsEngine &Diag; michael@0: const CompilerInstance &CI; michael@0: DiagnosticsMatcher matcher; michael@0: public: michael@0: MozChecker(const CompilerInstance &CI) : Diag(CI.getDiagnostics()), CI(CI) {} michael@0: michael@0: ASTConsumer *getOtherConsumer() { michael@0: return matcher.makeASTConsumer(); michael@0: } michael@0: michael@0: virtual void HandleTranslationUnit(ASTContext &ctx) { michael@0: TraverseDecl(ctx.getTranslationUnitDecl()); michael@0: } michael@0: michael@0: static bool hasCustomAnnotation(const Decl *d, const char *spelling) { michael@0: AnnotateAttr *attr = d->getAttr(); michael@0: if (!attr) michael@0: return false; michael@0: michael@0: return attr->getAnnotation() == spelling; michael@0: } michael@0: michael@0: bool VisitCXXRecordDecl(CXXRecordDecl *d) { michael@0: // We need definitions, not declarations michael@0: if (!d->isThisDeclarationADefinition()) return true; michael@0: michael@0: // Look through all of our immediate bases to find methods that need to be michael@0: // overridden michael@0: typedef std::vector OverridesVector; michael@0: OverridesVector must_overrides; michael@0: for (CXXRecordDecl::base_class_iterator base = d->bases_begin(), michael@0: e = d->bases_end(); base != e; ++base) { michael@0: // The base is either a class (CXXRecordDecl) or it's a templated class... michael@0: CXXRecordDecl *parent = base->getType() michael@0: .getDesugaredType(d->getASTContext())->getAsCXXRecordDecl(); michael@0: // The parent might not be resolved to a type yet. In this case, we can't michael@0: // do any checking here. For complete correctness, we should visit michael@0: // template instantiations, but this case is likely to be rare, so we will michael@0: // ignore it until it becomes important. michael@0: if (!parent) { michael@0: continue; michael@0: } michael@0: parent = parent->getDefinition(); michael@0: for (CXXRecordDecl::method_iterator M = parent->method_begin(); michael@0: M != parent->method_end(); ++M) { michael@0: if (hasCustomAnnotation(*M, "moz_must_override")) michael@0: must_overrides.push_back(*M); michael@0: } michael@0: } michael@0: michael@0: for (OverridesVector::iterator it = must_overrides.begin(); michael@0: it != must_overrides.end(); ++it) { michael@0: bool overridden = false; michael@0: for (CXXRecordDecl::method_iterator M = d->method_begin(); michael@0: !overridden && M != d->method_end(); ++M) { michael@0: // The way that Clang checks if a method M overrides its parent method michael@0: // is if the method has the same name but would not overload. michael@0: if (M->getName() == (*it)->getName() && michael@0: !CI.getSema().IsOverload(*M, (*it), false)) michael@0: overridden = true; michael@0: } michael@0: if (!overridden) { michael@0: unsigned overrideID = Diag.getDiagnosticIDs()->getCustomDiagID( michael@0: DiagnosticIDs::Error, "%0 must override %1"); michael@0: unsigned overrideNote = Diag.getDiagnosticIDs()->getCustomDiagID( michael@0: DiagnosticIDs::Note, "function to override is here"); michael@0: Diag.Report(d->getLocation(), overrideID) << d->getDeclName() << michael@0: (*it)->getDeclName(); michael@0: Diag.Report((*it)->getLocation(), overrideNote); michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: }; michael@0: michael@0: /** michael@0: * Where classes may be allocated. Regular classes can be allocated anywhere, michael@0: * non-heap classes on the stack or as static variables, and stack classes only michael@0: * on the stack. Note that stack classes subsumes non-heap classes. michael@0: */ michael@0: enum ClassAllocationNature { michael@0: RegularClass = 0, michael@0: NonHeapClass = 1, michael@0: StackClass = 2 michael@0: }; michael@0: michael@0: /// A cached data of whether classes are stack classes, non-heap classes, or michael@0: /// neither. michael@0: DenseMap > inferredAllocCauses; michael@0: michael@0: ClassAllocationNature getClassAttrs(QualType T); michael@0: michael@0: ClassAllocationNature getClassAttrs(CXXRecordDecl *D) { michael@0: // Normalize so that D points to the definition if it exists. If it doesn't, michael@0: // then we can't allocate it anyways. michael@0: if (!D->hasDefinition()) michael@0: return RegularClass; michael@0: D = D->getDefinition(); michael@0: // Base class: anyone with this annotation is obviously a stack class michael@0: if (MozChecker::hasCustomAnnotation(D, "moz_stack_class")) michael@0: return StackClass; michael@0: michael@0: // See if we cached the result. michael@0: DenseMap >::iterator it = michael@0: inferredAllocCauses.find(D); michael@0: if (it != inferredAllocCauses.end()) { michael@0: return it->second.second; michael@0: } michael@0: michael@0: // Continue looking, we might be a stack class yet. Even if we're a nonheap michael@0: // class, it might be possible that we've inferred to be a stack class. michael@0: ClassAllocationNature type = RegularClass; michael@0: if (MozChecker::hasCustomAnnotation(D, "moz_nonheap_class")) { michael@0: type = NonHeapClass; michael@0: } michael@0: inferredAllocCauses.insert(std::make_pair(D, michael@0: std::make_pair((const Decl *)0, type))); michael@0: michael@0: // Look through all base cases to figure out if the parent is a stack class or michael@0: // a non-heap class. Since we might later infer to also be a stack class, keep michael@0: // going. michael@0: for (CXXRecordDecl::base_class_iterator base = D->bases_begin(), michael@0: e = D->bases_end(); base != e; ++base) { michael@0: ClassAllocationNature super = getClassAttrs(base->getType()); michael@0: if (super == StackClass) { michael@0: inferredAllocCauses[D] = std::make_pair( michael@0: base->getType()->getAsCXXRecordDecl(), StackClass); michael@0: return StackClass; michael@0: } else if (super == NonHeapClass) { michael@0: inferredAllocCauses[D] = std::make_pair( michael@0: base->getType()->getAsCXXRecordDecl(), NonHeapClass); michael@0: type = NonHeapClass; michael@0: } michael@0: } michael@0: michael@0: // Maybe it has a member which is a stack class. michael@0: for (RecordDecl::field_iterator field = D->field_begin(), e = D->field_end(); michael@0: field != e; ++field) { michael@0: ClassAllocationNature fieldType = getClassAttrs(field->getType()); michael@0: if (fieldType == StackClass) { michael@0: inferredAllocCauses[D] = std::make_pair(*field, StackClass); michael@0: return StackClass; michael@0: } else if (fieldType == NonHeapClass) { michael@0: inferredAllocCauses[D] = std::make_pair(*field, NonHeapClass); michael@0: type = NonHeapClass; michael@0: } michael@0: } michael@0: michael@0: return type; michael@0: } michael@0: michael@0: ClassAllocationNature getClassAttrs(QualType T) { michael@0: while (const ArrayType *arrTy = T->getAsArrayTypeUnsafe()) michael@0: T = arrTy->getElementType(); michael@0: CXXRecordDecl *clazz = T->getAsCXXRecordDecl(); michael@0: return clazz ? getClassAttrs(clazz) : RegularClass; michael@0: } michael@0: michael@0: } michael@0: michael@0: namespace clang { michael@0: namespace ast_matchers { michael@0: michael@0: /// This matcher will match any class with the stack class assertion or an michael@0: /// array of such classes. michael@0: AST_MATCHER(QualType, stackClassAggregate) { michael@0: return getClassAttrs(Node) == StackClass; michael@0: } michael@0: michael@0: /// This matcher will match any class with the stack class assertion or an michael@0: /// array of such classes. michael@0: AST_MATCHER(QualType, nonheapClassAggregate) { michael@0: return getClassAttrs(Node) == NonHeapClass; michael@0: } michael@0: michael@0: /// This matcher will match any function declaration that is declared as a heap michael@0: /// allocator. michael@0: AST_MATCHER(FunctionDecl, heapAllocator) { michael@0: return MozChecker::hasCustomAnnotation(&Node, "moz_heap_allocator"); michael@0: } michael@0: } michael@0: } michael@0: michael@0: namespace { michael@0: michael@0: bool isPlacementNew(const CXXNewExpr *expr) { michael@0: // Regular new expressions aren't placement new michael@0: if (expr->getNumPlacementArgs() == 0) michael@0: return false; michael@0: if (MozChecker::hasCustomAnnotation(expr->getOperatorNew(), michael@0: "moz_heap_allocator")) michael@0: return false; michael@0: return true; michael@0: } michael@0: michael@0: DiagnosticsMatcher::DiagnosticsMatcher() { michael@0: // Stack class assertion: non-local variables of a stack class are forbidden michael@0: // (non-localness checked in the callback) michael@0: astMatcher.addMatcher(varDecl(hasType(stackClassAggregate())).bind("node"), michael@0: &stackClassChecker); michael@0: // Stack class assertion: new stack class is forbidden (unless placement new) michael@0: astMatcher.addMatcher(newExpr(hasType(pointerType( michael@0: pointee(stackClassAggregate()) michael@0: ))).bind("node"), &stackClassChecker); michael@0: // Non-heap class assertion: new non-heap class is forbidden (unless placement michael@0: // new) michael@0: astMatcher.addMatcher(newExpr(hasType(pointerType( michael@0: pointee(nonheapClassAggregate()) michael@0: ))).bind("node"), &nonheapClassChecker); michael@0: michael@0: // Any heap allocation function that returns a non-heap or a stack class is michael@0: // definitely doing something wrong michael@0: astMatcher.addMatcher(callExpr(callee(functionDecl(allOf(heapAllocator(), michael@0: returns(pointerType(pointee(nonheapClassAggregate()))))))).bind("node"), michael@0: &nonheapClassChecker); michael@0: astMatcher.addMatcher(callExpr(callee(functionDecl(allOf(heapAllocator(), michael@0: returns(pointerType(pointee(stackClassAggregate()))))))).bind("node"), michael@0: &stackClassChecker); michael@0: } michael@0: michael@0: void DiagnosticsMatcher::StackClassChecker::run( michael@0: const MatchFinder::MatchResult &Result) { michael@0: DiagnosticsEngine &Diag = Result.Context->getDiagnostics(); michael@0: unsigned stackID = Diag.getDiagnosticIDs()->getCustomDiagID( michael@0: DiagnosticIDs::Error, "variable of type %0 only valid on the stack"); michael@0: if (const VarDecl *d = Result.Nodes.getNodeAs("node")) { michael@0: // Ignore the match if it's a local variable. michael@0: if (d->hasLocalStorage()) michael@0: return; michael@0: michael@0: Diag.Report(d->getLocation(), stackID) << d->getType(); michael@0: noteInferred(d->getType(), Diag); michael@0: } else if (const CXXNewExpr *expr = michael@0: Result.Nodes.getNodeAs("node")) { michael@0: // If it's placement new, then this match doesn't count. michael@0: if (isPlacementNew(expr)) michael@0: return; michael@0: Diag.Report(expr->getStartLoc(), stackID) << expr->getAllocatedType(); michael@0: noteInferred(expr->getAllocatedType(), Diag); michael@0: } else if (const CallExpr *expr = michael@0: Result.Nodes.getNodeAs("node")) { michael@0: QualType badType = expr->getCallReturnType()->getPointeeType(); michael@0: Diag.Report(expr->getLocStart(), stackID) << badType; michael@0: noteInferred(badType, Diag); michael@0: } michael@0: } michael@0: michael@0: void DiagnosticsMatcher::StackClassChecker::noteInferred(QualType T, michael@0: DiagnosticsEngine &Diag) { michael@0: unsigned inheritsID = Diag.getDiagnosticIDs()->getCustomDiagID( michael@0: DiagnosticIDs::Note, michael@0: "%0 is a stack class because it inherits from a stack class %1"); michael@0: unsigned memberID = Diag.getDiagnosticIDs()->getCustomDiagID( michael@0: DiagnosticIDs::Note, michael@0: "%0 is a stack class because member %1 is a stack class %2"); michael@0: michael@0: // Find the CXXRecordDecl that is the stack class of interest michael@0: while (const ArrayType *arrTy = T->getAsArrayTypeUnsafe()) michael@0: T = arrTy->getElementType(); michael@0: CXXRecordDecl *clazz = T->getAsCXXRecordDecl(); michael@0: michael@0: // Direct result, we're done. michael@0: if (MozChecker::hasCustomAnnotation(clazz, "moz_stack_class")) michael@0: return; michael@0: michael@0: const Decl *cause = inferredAllocCauses[clazz].first; michael@0: if (const CXXRecordDecl *CRD = dyn_cast(cause)) { michael@0: Diag.Report(clazz->getLocation(), inheritsID) << T << CRD->getDeclName(); michael@0: } else if (const FieldDecl *FD = dyn_cast(cause)) { michael@0: Diag.Report(FD->getLocation(), memberID) << T << FD << FD->getType(); michael@0: } michael@0: michael@0: // Recursively follow this back. michael@0: noteInferred(cast(cause)->getType(), Diag); michael@0: } michael@0: michael@0: void DiagnosticsMatcher::NonHeapClassChecker::run( michael@0: const MatchFinder::MatchResult &Result) { michael@0: DiagnosticsEngine &Diag = Result.Context->getDiagnostics(); michael@0: unsigned stackID = Diag.getDiagnosticIDs()->getCustomDiagID( michael@0: DiagnosticIDs::Error, "variable of type %0 is not valid on the heap"); michael@0: if (const CXXNewExpr *expr = Result.Nodes.getNodeAs("node")) { michael@0: // If it's placement new, then this match doesn't count. michael@0: if (isPlacementNew(expr)) michael@0: return; michael@0: Diag.Report(expr->getStartLoc(), stackID) << expr->getAllocatedType(); michael@0: noteInferred(expr->getAllocatedType(), Diag); michael@0: } else if (const CallExpr *expr = Result.Nodes.getNodeAs("node")) { michael@0: QualType badType = expr->getCallReturnType()->getPointeeType(); michael@0: Diag.Report(expr->getLocStart(), stackID) << badType; michael@0: noteInferred(badType, Diag); michael@0: } michael@0: } michael@0: michael@0: void DiagnosticsMatcher::NonHeapClassChecker::noteInferred(QualType T, michael@0: DiagnosticsEngine &Diag) { michael@0: unsigned inheritsID = Diag.getDiagnosticIDs()->getCustomDiagID( michael@0: DiagnosticIDs::Note, michael@0: "%0 is a non-heap class because it inherits from a non-heap class %1"); michael@0: unsigned memberID = Diag.getDiagnosticIDs()->getCustomDiagID( michael@0: DiagnosticIDs::Note, michael@0: "%0 is a non-heap class because member %1 is a non-heap class %2"); michael@0: michael@0: // Find the CXXRecordDecl that is the stack class of interest michael@0: while (const ArrayType *arrTy = T->getAsArrayTypeUnsafe()) michael@0: T = arrTy->getElementType(); michael@0: CXXRecordDecl *clazz = T->getAsCXXRecordDecl(); michael@0: michael@0: // Direct result, we're done. michael@0: if (MozChecker::hasCustomAnnotation(clazz, "moz_nonheap_class")) michael@0: return; michael@0: michael@0: const Decl *cause = inferredAllocCauses[clazz].first; michael@0: if (const CXXRecordDecl *CRD = dyn_cast(cause)) { michael@0: Diag.Report(clazz->getLocation(), inheritsID) << T << CRD->getDeclName(); michael@0: } else if (const FieldDecl *FD = dyn_cast(cause)) { michael@0: Diag.Report(FD->getLocation(), memberID) << T << FD << FD->getType(); michael@0: } michael@0: michael@0: // Recursively follow this back. michael@0: noteInferred(cast(cause)->getType(), Diag); michael@0: } michael@0: michael@0: class MozCheckAction : public PluginASTAction { michael@0: public: michael@0: ASTConsumer *CreateASTConsumer(CompilerInstance &CI, StringRef fileName) { michael@0: MozChecker *checker = new MozChecker(CI); michael@0: michael@0: ASTConsumer *consumers[] = { checker, checker->getOtherConsumer() }; michael@0: return new MultiplexConsumer(consumers); michael@0: } michael@0: michael@0: bool ParseArgs(const CompilerInstance &CI, michael@0: const std::vector &args) { michael@0: return true; michael@0: } michael@0: }; michael@0: } michael@0: michael@0: static FrontendPluginRegistry::Add michael@0: X("moz-check", "check moz action");