/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "clang/AST/ASTConsumer.h" #include "clang/AST/ASTContext.h" #include "clang/AST/RecursiveASTVisitor.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/Basic/Version.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/FrontendPluginRegistry.h" #include "clang/Frontend/MultiplexConsumer.h" #include "clang/Sema/Sema.h" #include "llvm/ADT/DenseMap.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/Path.h" #include #define CLANG_VERSION_FULL (CLANG_VERSION_MAJOR * 100 + CLANG_VERSION_MINOR) using namespace llvm; using namespace clang; #if CLANG_VERSION_FULL >= 306 typedef std::unique_ptr ASTConsumerPtr; #else typedef ASTConsumer *ASTConsumerPtr; #endif namespace { using namespace clang::ast_matchers; class DiagnosticsMatcher { public: DiagnosticsMatcher(); ASTConsumerPtr makeASTConsumer() { return astMatcher.newASTConsumer(); } private: class ScopeChecker : public MatchFinder::MatchCallback { public: enum Scope { eLocal, eGlobal }; ScopeChecker(Scope scope_) : scope(scope_) {} virtual void run(const MatchFinder::MatchResult &Result); void noteInferred(QualType T, DiagnosticsEngine &Diag); private: Scope scope; }; class NonHeapClassChecker : public MatchFinder::MatchCallback { public: virtual void run(const MatchFinder::MatchResult &Result); void noteInferred(QualType T, DiagnosticsEngine &Diag); }; class ArithmeticArgChecker : public MatchFinder::MatchCallback { public: virtual void run(const MatchFinder::MatchResult &Result); }; class TrivialCtorDtorChecker : public MatchFinder::MatchCallback { public: virtual void run(const MatchFinder::MatchResult &Result); }; class NaNExprChecker : public MatchFinder::MatchCallback { public: virtual void run(const MatchFinder::MatchResult &Result); }; class NoAddRefReleaseOnReturnChecker : public MatchFinder::MatchCallback { public: virtual void run(const MatchFinder::MatchResult &Result); }; class RefCountedInsideLambdaChecker : public MatchFinder::MatchCallback { public: virtual void run(const MatchFinder::MatchResult &Result); }; class ExplicitOperatorBoolChecker : public MatchFinder::MatchCallback { public: virtual void run(const MatchFinder::MatchResult &Result); }; ScopeChecker stackClassChecker; ScopeChecker globalClassChecker; NonHeapClassChecker nonheapClassChecker; ArithmeticArgChecker arithmeticArgChecker; TrivialCtorDtorChecker trivialCtorDtorChecker; NaNExprChecker nanExprChecker; NoAddRefReleaseOnReturnChecker noAddRefReleaseOnReturnChecker; RefCountedInsideLambdaChecker refCountedInsideLambdaChecker; ExplicitOperatorBoolChecker explicitOperatorBoolChecker; MatchFinder astMatcher; }; namespace { std::string getDeclarationNamespace(const Decl *decl) { const DeclContext *DC = decl->getDeclContext()->getEnclosingNamespaceContext(); const NamespaceDecl *ND = dyn_cast(DC); if (!ND) { return ""; } while (const DeclContext *ParentDC = ND->getParent()) { if (!isa(ParentDC)) { break; } ND = cast(ParentDC); } const auto& name = ND->getName(); return name; } bool isInIgnoredNamespaceForImplicitCtor(const Decl *decl) { std::string name = getDeclarationNamespace(decl); if (name == "") { return false; } return name == "std" || // standard C++ lib name == "__gnu_cxx" || // gnu C++ lib name == "boost" || // boost name == "webrtc" || // upstream webrtc name == "icu_52" || // icu name == "google" || // protobuf name == "google_breakpad" || // breakpad name == "soundtouch" || // libsoundtouch name == "stagefright" || // libstagefright name == "MacFileUtilities" || // MacFileUtilities name == "dwarf2reader" || // dwarf2reader name == "arm_ex_to_module" || // arm_ex_to_module name == "testing"; // gtest } bool isInIgnoredNamespaceForImplicitConversion(const Decl *decl) { std::string name = getDeclarationNamespace(decl); if (name == "") { return false; } return name == "std" || // standard C++ lib name == "__gnu_cxx" || // gnu C++ lib name == "google_breakpad" || // breakpad name == "testing"; // gtest } bool isIgnoredPathForImplicitCtor(const Decl *decl) { decl = decl->getCanonicalDecl(); SourceLocation Loc = decl->getLocation(); const SourceManager &SM = decl->getASTContext().getSourceManager(); SmallString<1024> FileName = SM.getFilename(Loc); llvm::sys::fs::make_absolute(FileName); llvm::sys::path::reverse_iterator begin = llvm::sys::path::rbegin(FileName), end = llvm::sys::path::rend(FileName); for (; begin != end; ++begin) { if (begin->compare_lower(StringRef("skia")) == 0 || begin->compare_lower(StringRef("angle")) == 0 || begin->compare_lower(StringRef("harfbuzz")) == 0 || begin->compare_lower(StringRef("hunspell")) == 0 || begin->compare_lower(StringRef("scoped_ptr.h")) == 0 || begin->compare_lower(StringRef("graphite2")) == 0) { return true; } } return false; } bool isIgnoredPathForImplicitConversion(const Decl *decl) { decl = decl->getCanonicalDecl(); SourceLocation Loc = decl->getLocation(); const SourceManager &SM = decl->getASTContext().getSourceManager(); SmallString<1024> FileName = SM.getFilename(Loc); llvm::sys::fs::make_absolute(FileName); llvm::sys::path::reverse_iterator begin = llvm::sys::path::rbegin(FileName), end = llvm::sys::path::rend(FileName); for (; begin != end; ++begin) { if (begin->compare_lower(StringRef("graphite2")) == 0) { return true; } } return false; } bool isInterestingDeclForImplicitCtor(const Decl *decl) { return !isInIgnoredNamespaceForImplicitCtor(decl) && !isIgnoredPathForImplicitCtor(decl); } bool isInterestingDeclForImplicitConversion(const Decl *decl) { return !isInIgnoredNamespaceForImplicitConversion(decl) && !isIgnoredPathForImplicitConversion(decl); } } class MozChecker : public ASTConsumer, public RecursiveASTVisitor { DiagnosticsEngine &Diag; const CompilerInstance &CI; DiagnosticsMatcher matcher; public: MozChecker(const CompilerInstance &CI) : Diag(CI.getDiagnostics()), CI(CI) {} ASTConsumerPtr getOtherConsumer() { return matcher.makeASTConsumer(); } virtual void HandleTranslationUnit(ASTContext &ctx) { TraverseDecl(ctx.getTranslationUnitDecl()); } static bool hasCustomAnnotation(const Decl *d, const char *spelling) { AnnotateAttr *attr = d->getAttr(); if (!attr) return false; return attr->getAnnotation() == spelling; } bool VisitCXXRecordDecl(CXXRecordDecl *d) { // We need definitions, not declarations if (!d->isThisDeclarationADefinition()) return true; // Look through all of our immediate bases to find methods that need to be // overridden typedef std::vector OverridesVector; OverridesVector must_overrides; for (CXXRecordDecl::base_class_iterator base = d->bases_begin(), e = d->bases_end(); base != e; ++base) { // The base is either a class (CXXRecordDecl) or it's a templated class... CXXRecordDecl *parent = base->getType() .getDesugaredType(d->getASTContext())->getAsCXXRecordDecl(); // The parent might not be resolved to a type yet. In this case, we can't // do any checking here. For complete correctness, we should visit // template instantiations, but this case is likely to be rare, so we will // ignore it until it becomes important. if (!parent) { continue; } parent = parent->getDefinition(); for (CXXRecordDecl::method_iterator M = parent->method_begin(); M != parent->method_end(); ++M) { if (hasCustomAnnotation(*M, "moz_must_override")) must_overrides.push_back(*M); } } for (OverridesVector::iterator it = must_overrides.begin(); it != must_overrides.end(); ++it) { bool overridden = false; for (CXXRecordDecl::method_iterator M = d->method_begin(); !overridden && M != d->method_end(); ++M) { // The way that Clang checks if a method M overrides its parent method // is if the method has the same name but would not overload. if (M->getName() == (*it)->getName() && !CI.getSema().IsOverload(*M, (*it), false)) { overridden = true; break; } } if (!overridden) { unsigned overrideID = Diag.getDiagnosticIDs()->getCustomDiagID( DiagnosticIDs::Error, "%0 must override %1"); unsigned overrideNote = Diag.getDiagnosticIDs()->getCustomDiagID( DiagnosticIDs::Note, "function to override is here"); Diag.Report(d->getLocation(), overrideID) << d->getDeclName() << (*it)->getDeclName(); Diag.Report((*it)->getLocation(), overrideNote); } } if (!d->isAbstract() && isInterestingDeclForImplicitCtor(d)) { for (CXXRecordDecl::ctor_iterator ctor = d->ctor_begin(), e = d->ctor_end(); ctor != e; ++ctor) { // Ignore non-converting ctors if (!ctor->isConvertingConstructor(false)) { continue; } // Ignore copy or move constructors if (ctor->isCopyOrMoveConstructor()) { continue; } // Ignore deleted constructors if (ctor->isDeleted()) { continue; } // Ignore whitelisted constructors if (MozChecker::hasCustomAnnotation(*ctor, "moz_implicit")) { continue; } unsigned ctorID = Diag.getDiagnosticIDs()->getCustomDiagID( DiagnosticIDs::Error, "bad implicit conversion constructor for %0"); unsigned noteID = Diag.getDiagnosticIDs()->getCustomDiagID( DiagnosticIDs::Note, "consider adding the explicit keyword to the constructor"); Diag.Report(ctor->getLocation(), ctorID) << d->getDeclName(); Diag.Report(ctor->getLocation(), noteID); } } return true; } }; /** * Where classes may be allocated. Regular classes can be allocated anywhere, * non-heap classes on the stack or as static variables, and stack classes only * on the stack. Note that stack classes subsumes non-heap classes. */ enum ClassAllocationNature { RegularClass = 0, NonHeapClass = 1, StackClass = 2, GlobalClass = 3 }; /// A cached data of whether classes are stack classes, non-heap classes, or /// neither. DenseMap > inferredAllocCauses; ClassAllocationNature getClassAttrs(QualType T); ClassAllocationNature getClassAttrs(CXXRecordDecl *D) { // Normalize so that D points to the definition if it exists. If it doesn't, // then we can't allocate it anyways. if (!D->hasDefinition()) return RegularClass; D = D->getDefinition(); // Base class: anyone with this annotation is obviously a stack class if (MozChecker::hasCustomAnnotation(D, "moz_stack_class")) return StackClass; // Base class: anyone with this annotation is obviously a global class if (MozChecker::hasCustomAnnotation(D, "moz_global_class")) return GlobalClass; // See if we cached the result. DenseMap >::iterator it = inferredAllocCauses.find(D); if (it != inferredAllocCauses.end()) { return it->second.second; } // Continue looking, we might be a stack class yet. Even if we're a nonheap // class, it might be possible that we've inferred to be a stack class. ClassAllocationNature type = RegularClass; if (MozChecker::hasCustomAnnotation(D, "moz_nonheap_class")) { type = NonHeapClass; } inferredAllocCauses.insert(std::make_pair(D, std::make_pair((const Decl *)0, type))); // Look through all base cases to figure out if the parent is a stack class or // a non-heap class. Since we might later infer to also be a stack class, keep // going. for (CXXRecordDecl::base_class_iterator base = D->bases_begin(), e = D->bases_end(); base != e; ++base) { ClassAllocationNature super = getClassAttrs(base->getType()); if (super == StackClass) { inferredAllocCauses[D] = std::make_pair( base->getType()->getAsCXXRecordDecl(), StackClass); return StackClass; } else if (super == GlobalClass) { inferredAllocCauses[D] = std::make_pair( base->getType()->getAsCXXRecordDecl(), GlobalClass); return GlobalClass; } else if (super == NonHeapClass) { inferredAllocCauses[D] = std::make_pair( base->getType()->getAsCXXRecordDecl(), NonHeapClass); type = NonHeapClass; } } // Maybe it has a member which is a stack class. for (RecordDecl::field_iterator field = D->field_begin(), e = D->field_end(); field != e; ++field) { ClassAllocationNature fieldType = getClassAttrs(field->getType()); if (fieldType == StackClass) { inferredAllocCauses[D] = std::make_pair(*field, StackClass); return StackClass; } else if (fieldType == GlobalClass) { inferredAllocCauses[D] = std::make_pair(*field, GlobalClass); return GlobalClass; } else if (fieldType == NonHeapClass) { inferredAllocCauses[D] = std::make_pair(*field, NonHeapClass); type = NonHeapClass; } } return type; } ClassAllocationNature getClassAttrs(QualType T) { while (const ArrayType *arrTy = T->getAsArrayTypeUnsafe()) T = arrTy->getElementType(); CXXRecordDecl *clazz = T->getAsCXXRecordDecl(); return clazz ? getClassAttrs(clazz) : RegularClass; } /// A cached data of whether classes are refcounted or not. typedef DenseMap > RefCountedMap; RefCountedMap refCountedClasses; bool classHasAddRefRelease(const CXXRecordDecl *D) { const RefCountedMap::iterator& it = refCountedClasses.find(D); if (it != refCountedClasses.end()) { return it->second.second; } bool seenAddRef = false; bool seenRelease = false; for (CXXRecordDecl::method_iterator method = D->method_begin(); method != D->method_end(); ++method) { std::string name = method->getNameAsString(); if (name == "AddRef") { seenAddRef = true; } else if (name == "Release") { seenRelease = true; } } refCountedClasses[D] = std::make_pair(D, seenAddRef && seenRelease); return seenAddRef && seenRelease; } bool isClassRefCounted(QualType T); bool isClassRefCounted(const CXXRecordDecl *D) { // Normalize so that D points to the definition if it exists. if (!D->hasDefinition()) return false; D = D->getDefinition(); // Base class: anyone with AddRef/Release is obviously a refcounted class. if (classHasAddRefRelease(D)) return true; // Look through all base cases to figure out if the parent is a refcounted class. for (CXXRecordDecl::base_class_const_iterator base = D->bases_begin(); base != D->bases_end(); ++base) { bool super = isClassRefCounted(base->getType()); if (super) { return true; } } return false; } bool isClassRefCounted(QualType T) { while (const ArrayType *arrTy = T->getAsArrayTypeUnsafe()) T = arrTy->getElementType(); CXXRecordDecl *clazz = T->getAsCXXRecordDecl(); return clazz ? isClassRefCounted(clazz) : RegularClass; } template bool IsInSystemHeader(const ASTContext &AC, const T &D) { auto &SourceManager = AC.getSourceManager(); auto ExpansionLoc = SourceManager.getExpansionLoc(D.getLocStart()); if (ExpansionLoc.isInvalid()) { return false; } return SourceManager.isInSystemHeader(ExpansionLoc); } } namespace clang { namespace ast_matchers { /// This matcher will match any class with the stack class assertion or an /// array of such classes. AST_MATCHER(QualType, stackClassAggregate) { return getClassAttrs(Node) == StackClass; } /// This matcher will match any class with the global class assertion or an /// array of such classes. AST_MATCHER(QualType, globalClassAggregate) { return getClassAttrs(Node) == GlobalClass; } /// This matcher will match any class with the stack class assertion or an /// array of such classes. AST_MATCHER(QualType, nonheapClassAggregate) { return getClassAttrs(Node) == NonHeapClass; } /// This matcher will match any function declaration that is declared as a heap /// allocator. AST_MATCHER(FunctionDecl, heapAllocator) { return MozChecker::hasCustomAnnotation(&Node, "moz_heap_allocator"); } /// This matcher will match any declaration that is marked as not accepting /// arithmetic expressions in its arguments. AST_MATCHER(Decl, noArithmeticExprInArgs) { return MozChecker::hasCustomAnnotation(&Node, "moz_no_arith_expr_in_arg"); } /// This matcher will match any C++ class that is marked as having a trivial /// constructor and destructor. AST_MATCHER(CXXRecordDecl, hasTrivialCtorDtor) { return MozChecker::hasCustomAnnotation(&Node, "moz_trivial_ctor_dtor"); } /// This matcher will match any function declaration that is marked to prohibit /// calling AddRef or Release on its return value. AST_MATCHER(FunctionDecl, hasNoAddRefReleaseOnReturnAttr) { return MozChecker::hasCustomAnnotation(&Node, "moz_no_addref_release_on_return"); } /// This matcher will match all arithmetic binary operators. AST_MATCHER(BinaryOperator, binaryArithmeticOperator) { BinaryOperatorKind opcode = Node.getOpcode(); return opcode == BO_Mul || opcode == BO_Div || opcode == BO_Rem || opcode == BO_Add || opcode == BO_Sub || opcode == BO_Shl || opcode == BO_Shr || opcode == BO_And || opcode == BO_Xor || opcode == BO_Or || opcode == BO_MulAssign || opcode == BO_DivAssign || opcode == BO_RemAssign || opcode == BO_AddAssign || opcode == BO_SubAssign || opcode == BO_ShlAssign || opcode == BO_ShrAssign || opcode == BO_AndAssign || opcode == BO_XorAssign || opcode == BO_OrAssign; } /// This matcher will match all arithmetic unary operators. AST_MATCHER(UnaryOperator, unaryArithmeticOperator) { UnaryOperatorKind opcode = Node.getOpcode(); return opcode == UO_PostInc || opcode == UO_PostDec || opcode == UO_PreInc || opcode == UO_PreDec || opcode == UO_Plus || opcode == UO_Minus || opcode == UO_Not; } /// This matcher will match == and != binary operators. AST_MATCHER(BinaryOperator, binaryEqualityOperator) { BinaryOperatorKind opcode = Node.getOpcode(); return opcode == BO_EQ || opcode == BO_NE; } /// This matcher will match floating point types. AST_MATCHER(QualType, isFloat) { return Node->isRealFloatingType(); } /// This matcher will match locations in system headers. This is adopted from /// isExpansionInSystemHeader in newer clangs, but modified in order to work /// with old clangs that we use on infra. AST_MATCHER(BinaryOperator, isInSystemHeader) { return IsInSystemHeader(Finder->getASTContext(), Node); } /// This matcher will match locations in SkScalar.h. This header contains a /// known NaN-testing expression which we would like to whitelist. AST_MATCHER(BinaryOperator, isInSkScalarDotH) { SourceLocation Loc = Node.getOperatorLoc(); auto &SourceManager = Finder->getASTContext().getSourceManager(); SmallString<1024> FileName = SourceManager.getFilename(Loc); return llvm::sys::path::rbegin(FileName)->equals("SkScalar.h"); } /// This matcher will match all accesses to AddRef or Release methods. AST_MATCHER(MemberExpr, isAddRefOrRelease) { ValueDecl *Member = Node.getMemberDecl(); CXXMethodDecl *Method = dyn_cast(Member); if (Method) { std::string Name = Method->getNameAsString(); return Name == "AddRef" || Name == "Release"; } return false; } /// This matcher will select classes which are refcounted. AST_MATCHER(QualType, isRefCounted) { return isClassRefCounted(Node); } } } namespace { bool isPlacementNew(const CXXNewExpr *expr) { // Regular new expressions aren't placement new if (expr->getNumPlacementArgs() == 0) return false; if (MozChecker::hasCustomAnnotation(expr->getOperatorNew(), "moz_heap_allocator")) return false; return true; } DiagnosticsMatcher::DiagnosticsMatcher() : stackClassChecker(ScopeChecker::eLocal), globalClassChecker(ScopeChecker::eGlobal) { // Stack class assertion: non-local variables of a stack class are forbidden // (non-localness checked in the callback) astMatcher.addMatcher(varDecl(hasType(stackClassAggregate())).bind("node"), &stackClassChecker); // Stack class assertion: new stack class is forbidden (unless placement new) astMatcher.addMatcher(newExpr(hasType(pointerType( pointee(stackClassAggregate()) ))).bind("node"), &stackClassChecker); // Global class assertion: non-global variables of a global class are forbidden // (globalness checked in the callback) astMatcher.addMatcher(varDecl(hasType(globalClassAggregate())).bind("node"), &globalClassChecker); // Global class assertion: new global class is forbidden astMatcher.addMatcher(newExpr(hasType(pointerType( pointee(globalClassAggregate()) ))).bind("node"), &globalClassChecker); // Non-heap class assertion: new non-heap class is forbidden (unless placement // new) astMatcher.addMatcher(newExpr(hasType(pointerType( pointee(nonheapClassAggregate()) ))).bind("node"), &nonheapClassChecker); // Any heap allocation function that returns a non-heap or a stack class or // a global class is definitely doing something wrong astMatcher.addMatcher(callExpr(callee(functionDecl(allOf(heapAllocator(), returns(pointerType(pointee(nonheapClassAggregate()))))))).bind("node"), &nonheapClassChecker); astMatcher.addMatcher(callExpr(callee(functionDecl(allOf(heapAllocator(), returns(pointerType(pointee(stackClassAggregate()))))))).bind("node"), &stackClassChecker); astMatcher.addMatcher(callExpr(callee(functionDecl(allOf(heapAllocator(), returns(pointerType(pointee(globalClassAggregate()))))))).bind("node"), &globalClassChecker); astMatcher.addMatcher(callExpr(allOf(hasDeclaration(noArithmeticExprInArgs()), anyOf( hasDescendant(binaryOperator(allOf(binaryArithmeticOperator(), hasLHS(hasDescendant(declRefExpr())), hasRHS(hasDescendant(declRefExpr())) )).bind("node")), hasDescendant(unaryOperator(allOf(unaryArithmeticOperator(), hasUnaryOperand(allOf(hasType(builtinType()), anyOf(hasDescendant(declRefExpr()), declRefExpr()))) )).bind("node")) ) )).bind("call"), &arithmeticArgChecker); astMatcher.addMatcher(constructExpr(allOf(hasDeclaration(noArithmeticExprInArgs()), anyOf( hasDescendant(binaryOperator(allOf(binaryArithmeticOperator(), hasLHS(hasDescendant(declRefExpr())), hasRHS(hasDescendant(declRefExpr())) )).bind("node")), hasDescendant(unaryOperator(allOf(unaryArithmeticOperator(), hasUnaryOperand(allOf(hasType(builtinType()), anyOf(hasDescendant(declRefExpr()), declRefExpr()))) )).bind("node")) ) )).bind("call"), &arithmeticArgChecker); astMatcher.addMatcher(recordDecl(hasTrivialCtorDtor()).bind("node"), &trivialCtorDtorChecker); astMatcher.addMatcher(binaryOperator(allOf(binaryEqualityOperator(), hasLHS(has(declRefExpr(hasType(qualType((isFloat())))).bind("lhs"))), hasRHS(has(declRefExpr(hasType(qualType((isFloat())))).bind("rhs"))), unless(anyOf(isInSystemHeader(), isInSkScalarDotH())) )).bind("node"), &nanExprChecker); astMatcher.addMatcher(callExpr(callee(functionDecl(hasNoAddRefReleaseOnReturnAttr()).bind("func")), hasParent(memberExpr(isAddRefOrRelease(), hasParent(callExpr())).bind("member") )).bind("node"), &noAddRefReleaseOnReturnChecker); astMatcher.addMatcher(lambdaExpr( hasDescendant(declRefExpr(hasType(pointerType(pointee(isRefCounted())))).bind("node")) ), &refCountedInsideLambdaChecker); // Older clang versions such as the ones used on the infra recognize these // conversions as 'operator _Bool', but newer clang versions recognize these // as 'operator bool'. astMatcher.addMatcher(methodDecl(anyOf(hasName("operator bool"), hasName("operator _Bool"))).bind("node"), &explicitOperatorBoolChecker); } void DiagnosticsMatcher::ScopeChecker::run( const MatchFinder::MatchResult &Result) { DiagnosticsEngine &Diag = Result.Context->getDiagnostics(); unsigned stackID = Diag.getDiagnosticIDs()->getCustomDiagID( DiagnosticIDs::Error, "variable of type %0 only valid on the stack"); unsigned globalID = Diag.getDiagnosticIDs()->getCustomDiagID( DiagnosticIDs::Error, "variable of type %0 only valid as global"); unsigned errorID = (scope == eGlobal) ? globalID : stackID; if (const VarDecl *d = Result.Nodes.getNodeAs("node")) { if (scope == eLocal) { // Ignore the match if it's a local variable. if (d->hasLocalStorage()) return; } else if (scope == eGlobal) { // Ignore the match if it's a global variable or a static member of a // class. The latter is technically not in the global scope, but for the // use case of classes that intend to avoid introducing static // initializers that is fine. if (d->hasGlobalStorage() && !d->isStaticLocal()) return; } Diag.Report(d->getLocation(), errorID) << d->getType(); noteInferred(d->getType(), Diag); } else if (const CXXNewExpr *expr = Result.Nodes.getNodeAs("node")) { // If it's placement new, then this match doesn't count. if (scope == eLocal && isPlacementNew(expr)) return; Diag.Report(expr->getStartLoc(), errorID) << expr->getAllocatedType(); noteInferred(expr->getAllocatedType(), Diag); } else if (const CallExpr *expr = Result.Nodes.getNodeAs("node")) { QualType badType = expr->getCallReturnType()->getPointeeType(); Diag.Report(expr->getLocStart(), errorID) << badType; noteInferred(badType, Diag); } } void DiagnosticsMatcher::ScopeChecker::noteInferred(QualType T, DiagnosticsEngine &Diag) { unsigned inheritsID = Diag.getDiagnosticIDs()->getCustomDiagID( DiagnosticIDs::Note, "%0 is a %2 class because it inherits from a %2 class %1"); unsigned memberID = Diag.getDiagnosticIDs()->getCustomDiagID( DiagnosticIDs::Note, "%0 is a %3 class because member %1 is a %3 class %2"); const char* attribute = (scope == eGlobal) ? "moz_global_class" : "moz_stack_class"; const char* type = (scope == eGlobal) ? "global" : "stack"; // Find the CXXRecordDecl that is the local/global class of interest while (const ArrayType *arrTy = T->getAsArrayTypeUnsafe()) T = arrTy->getElementType(); CXXRecordDecl *clazz = T->getAsCXXRecordDecl(); // Direct result, we're done. if (MozChecker::hasCustomAnnotation(clazz, attribute)) return; const Decl *cause = inferredAllocCauses[clazz].first; if (const CXXRecordDecl *CRD = dyn_cast(cause)) { Diag.Report(clazz->getLocation(), inheritsID) << T << CRD->getDeclName() << type; } else if (const FieldDecl *FD = dyn_cast(cause)) { Diag.Report(FD->getLocation(), memberID) << T << FD << FD->getType() << type; } // Recursively follow this back. noteInferred(cast(cause)->getType(), Diag); } void DiagnosticsMatcher::NonHeapClassChecker::run( const MatchFinder::MatchResult &Result) { DiagnosticsEngine &Diag = Result.Context->getDiagnostics(); unsigned stackID = Diag.getDiagnosticIDs()->getCustomDiagID( DiagnosticIDs::Error, "variable of type %0 is not valid on the heap"); if (const CXXNewExpr *expr = Result.Nodes.getNodeAs("node")) { // If it's placement new, then this match doesn't count. if (isPlacementNew(expr)) return; Diag.Report(expr->getStartLoc(), stackID) << expr->getAllocatedType(); noteInferred(expr->getAllocatedType(), Diag); } else if (const CallExpr *expr = Result.Nodes.getNodeAs("node")) { QualType badType = expr->getCallReturnType()->getPointeeType(); Diag.Report(expr->getLocStart(), stackID) << badType; noteInferred(badType, Diag); } } void DiagnosticsMatcher::NonHeapClassChecker::noteInferred(QualType T, DiagnosticsEngine &Diag) { unsigned inheritsID = Diag.getDiagnosticIDs()->getCustomDiagID( DiagnosticIDs::Note, "%0 is a non-heap class because it inherits from a non-heap class %1"); unsigned memberID = Diag.getDiagnosticIDs()->getCustomDiagID( DiagnosticIDs::Note, "%0 is a non-heap class because member %1 is a non-heap class %2"); // Find the CXXRecordDecl that is the stack class of interest while (const ArrayType *arrTy = T->getAsArrayTypeUnsafe()) T = arrTy->getElementType(); CXXRecordDecl *clazz = T->getAsCXXRecordDecl(); // Direct result, we're done. if (MozChecker::hasCustomAnnotation(clazz, "moz_nonheap_class")) return; const Decl *cause = inferredAllocCauses[clazz].first; if (const CXXRecordDecl *CRD = dyn_cast(cause)) { Diag.Report(clazz->getLocation(), inheritsID) << T << CRD->getDeclName(); } else if (const FieldDecl *FD = dyn_cast(cause)) { Diag.Report(FD->getLocation(), memberID) << T << FD << FD->getType(); } // Recursively follow this back. noteInferred(cast(cause)->getType(), Diag); } void DiagnosticsMatcher::ArithmeticArgChecker::run( const MatchFinder::MatchResult &Result) { DiagnosticsEngine &Diag = Result.Context->getDiagnostics(); unsigned errorID = Diag.getDiagnosticIDs()->getCustomDiagID( DiagnosticIDs::Error, "cannot pass an arithmetic expression of built-in types to %0"); const Expr *expr = Result.Nodes.getNodeAs("node"); if (const CallExpr *call = Result.Nodes.getNodeAs("call")) { Diag.Report(expr->getLocStart(), errorID) << call->getDirectCallee(); } else if (const CXXConstructExpr *ctr = Result.Nodes.getNodeAs("call")) { Diag.Report(expr->getLocStart(), errorID) << ctr->getConstructor(); } } void DiagnosticsMatcher::TrivialCtorDtorChecker::run( const MatchFinder::MatchResult &Result) { DiagnosticsEngine &Diag = Result.Context->getDiagnostics(); unsigned errorID = Diag.getDiagnosticIDs()->getCustomDiagID( DiagnosticIDs::Error, "class %0 must have trivial constructors and destructors"); const CXXRecordDecl *node = Result.Nodes.getNodeAs("node"); bool badCtor = !node->hasTrivialDefaultConstructor(); bool badDtor = !node->hasTrivialDestructor(); if (badCtor || badDtor) Diag.Report(node->getLocStart(), errorID) << node; } void DiagnosticsMatcher::NaNExprChecker::run( const MatchFinder::MatchResult &Result) { if (!Result.Context->getLangOpts().CPlusPlus) { // mozilla::IsNaN is not usable in C, so there is no point in issuing these warnings. return; } DiagnosticsEngine &Diag = Result.Context->getDiagnostics(); unsigned errorID = Diag.getDiagnosticIDs()->getCustomDiagID( DiagnosticIDs::Error, "comparing a floating point value to itself for NaN checking can lead to incorrect results"); unsigned noteID = Diag.getDiagnosticIDs()->getCustomDiagID( DiagnosticIDs::Note, "consider using mozilla::IsNaN instead"); const BinaryOperator *expr = Result.Nodes.getNodeAs("node"); const DeclRefExpr *lhs = Result.Nodes.getNodeAs("lhs"); const DeclRefExpr *rhs = Result.Nodes.getNodeAs("rhs"); const ImplicitCastExpr *lhsExpr = dyn_cast(expr->getLHS()); const ImplicitCastExpr *rhsExpr = dyn_cast(expr->getRHS()); // The AST subtree that we are looking for will look like this: // -BinaryOperator ==/!= // |-ImplicitCastExpr LValueToRValue // | |-DeclRefExpr // |-ImplicitCastExpr LValueToRValue // |-DeclRefExpr // The check below ensures that we are dealing with the correct AST subtree shape, and // also that both of the found DeclRefExpr's point to the same declaration. if (lhs->getFoundDecl() == rhs->getFoundDecl() && lhsExpr && rhsExpr && std::distance(lhsExpr->child_begin(), lhsExpr->child_end()) == 1 && std::distance(rhsExpr->child_begin(), rhsExpr->child_end()) == 1 && *lhsExpr->child_begin() == lhs && *rhsExpr->child_begin() == rhs) { Diag.Report(expr->getLocStart(), errorID); Diag.Report(expr->getLocStart(), noteID); } } void DiagnosticsMatcher::NoAddRefReleaseOnReturnChecker::run( const MatchFinder::MatchResult &Result) { DiagnosticsEngine &Diag = Result.Context->getDiagnostics(); unsigned errorID = Diag.getDiagnosticIDs()->getCustomDiagID( DiagnosticIDs::Error, "%1 cannot be called on the return value of %0"); const Stmt *node = Result.Nodes.getNodeAs("node"); const FunctionDecl *func = Result.Nodes.getNodeAs("func"); const MemberExpr *member = Result.Nodes.getNodeAs("member"); const CXXMethodDecl *method = dyn_cast(member->getMemberDecl()); Diag.Report(node->getLocStart(), errorID) << func << method; } void DiagnosticsMatcher::RefCountedInsideLambdaChecker::run( const MatchFinder::MatchResult &Result) { DiagnosticsEngine &Diag = Result.Context->getDiagnostics(); unsigned errorID = Diag.getDiagnosticIDs()->getCustomDiagID( DiagnosticIDs::Error, "Refcounted variable %0 of type %1 cannot be used inside a lambda"); unsigned noteID = Diag.getDiagnosticIDs()->getCustomDiagID( DiagnosticIDs::Note, "Please consider using a smart pointer"); const DeclRefExpr *node = Result.Nodes.getNodeAs("node"); Diag.Report(node->getLocStart(), errorID) << node->getFoundDecl() << node->getType()->getPointeeType(); Diag.Report(node->getLocStart(), noteID); } void DiagnosticsMatcher::ExplicitOperatorBoolChecker::run( const MatchFinder::MatchResult &Result) { DiagnosticsEngine &Diag = Result.Context->getDiagnostics(); unsigned errorID = Diag.getDiagnosticIDs()->getCustomDiagID( DiagnosticIDs::Error, "bad implicit conversion operator for %0"); unsigned noteID = Diag.getDiagnosticIDs()->getCustomDiagID( DiagnosticIDs::Note, "consider adding the explicit keyword to %0"); const CXXConversionDecl *method = Result.Nodes.getNodeAs("node"); const CXXRecordDecl *clazz = method->getParent(); if (!method->isExplicitSpecified() && !MozChecker::hasCustomAnnotation(method, "moz_implicit") && !IsInSystemHeader(method->getASTContext(), *method) && isInterestingDeclForImplicitConversion(method)) { Diag.Report(method->getLocStart(), errorID) << clazz; Diag.Report(method->getLocStart(), noteID) << "'operator bool'"; } } class MozCheckAction : public PluginASTAction { public: ASTConsumerPtr CreateASTConsumer(CompilerInstance &CI, StringRef fileName) override { #if CLANG_VERSION_FULL >= 306 std::unique_ptr checker(make_unique(CI)); std::vector> consumers; consumers.push_back(std::move(checker)); consumers.push_back(checker->getOtherConsumer()); return make_unique(std::move(consumers)); #else MozChecker *checker = new MozChecker(CI); ASTConsumer *consumers[] = { checker, checker->getOtherConsumer() }; return new MultiplexConsumer(consumers); #endif } bool ParseArgs(const CompilerInstance &CI, const std::vector &args) override { return true; } }; } static FrontendPluginRegistry::Add X("moz-check", "check moz action");