/* 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/. */ /* This file respects the LLVM coding standard described at * http://llvm.org/docs/CodingStandards.html */ #include "clang/AST/ASTConsumer.h" #include "clang/AST/ASTContext.h" #include "clang/AST/RecursiveASTVisitor.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.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 #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 #ifndef HAVE_NEW_ASTMATCHER_NAMES // In clang 3.8, a number of AST matchers were renamed to better match the // respective AST node. We use the new names, and #define them to the old // ones for compatibility with older versions. #define cxxConstructExpr constructExpr #define cxxConstructorDecl constructorDecl #define cxxMethodDecl methodDecl #define cxxNewExpr newExpr #define cxxRecordDecl recordDecl #endif #ifndef HAS_ACCEPTS_IGNORINGPARENIMPCASTS #define hasIgnoringParenImpCasts(x) has(x) #else // Before clang 3.9 "has" would behave like has(ignoringParenImpCasts(x)), // however doing that explicitly would not compile. #define hasIgnoringParenImpCasts(x) has(ignoringParenImpCasts(x)) #endif // Check if the given expression contains an assignment expression. // This can either take the form of a Binary Operator or a // Overloaded Operator Call. bool hasSideEffectAssignment(const Expr *Expression) { if (auto OpCallExpr = dyn_cast_or_null(Expression)) { auto BinOp = OpCallExpr->getOperator(); if (BinOp == OO_Equal || (BinOp >= OO_PlusEqual && BinOp <= OO_PipeEqual)) { return true; } } else if (auto BinOpExpr = dyn_cast_or_null(Expression)) { if (BinOpExpr->isAssignmentOp()) { return true; } } // Recurse to children. for (const Stmt *SubStmt : Expression->children()) { auto ChildExpr = dyn_cast_or_null(SubStmt); if (ChildExpr && hasSideEffectAssignment(ChildExpr)) { return true; } } return false; } namespace { using namespace clang::ast_matchers; class DiagnosticsMatcher { public: DiagnosticsMatcher(); ASTConsumerPtr makeASTConsumer() { return AstMatcher.newASTConsumer(); } private: class ScopeChecker : public MatchFinder::MatchCallback { public: virtual void run(const MatchFinder::MatchResult &Result); }; 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); void emitDiagnostics(SourceLocation Loc, StringRef Name, QualType Type); private: class ThisVisitor : public RecursiveASTVisitor { public: explicit ThisVisitor(RefCountedInsideLambdaChecker& Checker) : Checker(Checker) {} bool VisitCXXThisExpr(CXXThisExpr *This); private: RefCountedInsideLambdaChecker& Checker; }; ASTContext *Context; }; class ExplicitOperatorBoolChecker : public MatchFinder::MatchCallback { public: virtual void run(const MatchFinder::MatchResult &Result); }; class NoDuplicateRefCntMemberChecker : public MatchFinder::MatchCallback { public: virtual void run(const MatchFinder::MatchResult &Result); }; class NeedsNoVTableTypeChecker : public MatchFinder::MatchCallback { public: virtual void run(const MatchFinder::MatchResult &Result); }; class NonMemMovableTemplateArgChecker : public MatchFinder::MatchCallback { public: virtual void run(const MatchFinder::MatchResult &Result); }; class NonMemMovableMemberChecker : public MatchFinder::MatchCallback { public: virtual void run(const MatchFinder::MatchResult &Result); }; class ExplicitImplicitChecker : public MatchFinder::MatchCallback { public: virtual void run(const MatchFinder::MatchResult &Result); }; class NoAutoTypeChecker : public MatchFinder::MatchCallback { public: virtual void run(const MatchFinder::MatchResult &Result); }; class NoExplicitMoveConstructorChecker : public MatchFinder::MatchCallback { public: virtual void run(const MatchFinder::MatchResult &Result); }; class RefCountedCopyConstructorChecker : public MatchFinder::MatchCallback { public: virtual void run(const MatchFinder::MatchResult &Result); }; class AssertAssignmentChecker : public MatchFinder::MatchCallback { public: virtual void run(const MatchFinder::MatchResult &Result); }; class KungFuDeathGripChecker : public MatchFinder::MatchCallback { public: virtual void run(const MatchFinder::MatchResult &Result); }; class SprintfLiteralChecker : public MatchFinder::MatchCallback { public: virtual void run(const MatchFinder::MatchResult &Result); }; class OverrideBaseCallChecker : public MatchFinder::MatchCallback { public: virtual void run(const MatchFinder::MatchResult &Result); private: void evaluateExpression(const Stmt *StmtExpr, std::list &MethodList); void getRequiredBaseMethod(const CXXMethodDecl* Method, std::list& MethodsList); void findBaseMethodCall(const CXXMethodDecl* Method, std::list& MethodsList); bool isRequiredBaseMethod(const CXXMethodDecl *Method); }; /* * This is a companion checker for OverrideBaseCallChecker that rejects * the usage of MOZ_REQUIRED_BASE_METHOD on non-virtual base methods. */ class OverrideBaseCallUsageChecker : public MatchFinder::MatchCallback { public: virtual void run(const MatchFinder::MatchResult &Result); }; class NonParamInsideFunctionDeclChecker : public MatchFinder::MatchCallback { public: virtual void run(const MatchFinder::MatchResult &Result); }; ScopeChecker Scope; ArithmeticArgChecker ArithmeticArg; TrivialCtorDtorChecker TrivialCtorDtor; NaNExprChecker NaNExpr; NoAddRefReleaseOnReturnChecker NoAddRefReleaseOnReturn; RefCountedInsideLambdaChecker RefCountedInsideLambda; ExplicitOperatorBoolChecker ExplicitOperatorBool; NoDuplicateRefCntMemberChecker NoDuplicateRefCntMember; NeedsNoVTableTypeChecker NeedsNoVTableType; NonMemMovableTemplateArgChecker NonMemMovableTemplateArg; NonMemMovableMemberChecker NonMemMovableMember; ExplicitImplicitChecker ExplicitImplicit; NoAutoTypeChecker NoAutoType; NoExplicitMoveConstructorChecker NoExplicitMoveConstructor; RefCountedCopyConstructorChecker RefCountedCopyConstructor; AssertAssignmentChecker AssertAttribution; KungFuDeathGripChecker KungFuDeathGrip; SprintfLiteralChecker SprintfLiteral; OverrideBaseCallChecker OverrideBaseCall; OverrideBaseCallUsageChecker OverrideBaseCallUsage; NonParamInsideFunctionDeclChecker NonParamInsideFunctionDecl; MatchFinder AstMatcher; }; namespace { std::string getDeclarationNamespace(const Decl *Declaration) { const DeclContext *DC = Declaration->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 *Declaration) { std::string Name = getDeclarationNamespace(Declaration); if (Name == "") { return false; } return Name == "std" || // standard C++ lib Name == "__gnu_cxx" || // gnu C++ lib Name == "boost" || // boost Name == "webrtc" || // upstream webrtc Name == "rtc" || // upstream webrtc 'base' package Name.substr(0, 4) == "icu_" || // 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 Name == "Json"; // jsoncpp } bool isInIgnoredNamespaceForImplicitConversion(const Decl *Declaration) { std::string Name = getDeclarationNamespace(Declaration); 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 *Declaration) { SourceLocation Loc = Declaration->getLocation(); const SourceManager &SM = Declaration->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 || Begin->compare_lower(StringRef("icu")) == 0) { return true; } if (Begin->compare_lower(StringRef("chromium")) == 0) { // Ignore security/sandbox/chromium but not ipc/chromium. ++Begin; return Begin != End && Begin->compare_lower(StringRef("sandbox")) == 0; } } return false; } bool isIgnoredPathForImplicitConversion(const Decl *Declaration) { Declaration = Declaration->getCanonicalDecl(); SourceLocation Loc = Declaration->getLocation(); const SourceManager &SM = Declaration->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; } if (Begin->compare_lower(StringRef("chromium")) == 0) { // Ignore security/sandbox/chromium but not ipc/chromium. ++Begin; return Begin != End && Begin->compare_lower(StringRef("sandbox")) == 0; } } return false; } bool isIgnoredPathForSprintfLiteral(const CallExpr *Call, const SourceManager &SM) { SourceLocation Loc = Call->getLocStart(); 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("angle")) == 0 || Begin->compare_lower(StringRef("chromium")) == 0 || Begin->compare_lower(StringRef("crashreporter")) == 0 || Begin->compare_lower(StringRef("google-breakpad")) == 0 || Begin->compare_lower(StringRef("harfbuzz")) == 0 || Begin->compare_lower(StringRef("libstagefright")) == 0 || Begin->compare_lower(StringRef("mtransport")) == 0 || Begin->compare_lower(StringRef("protobuf")) == 0 || Begin->compare_lower(StringRef("skia")) == 0 || // Gtest uses snprintf as GTEST_SNPRINTF_ with sizeof Begin->compare_lower(StringRef("testing")) == 0) { return true; } if (Begin->compare_lower(StringRef("webrtc")) == 0) { // Ignore trunk/webrtc, but not media/webrtc ++Begin; return Begin != End && Begin->compare_lower(StringRef("trunk")) == 0; } } return false; } bool isInterestingDeclForImplicitConversion(const Decl *Declaration) { return !isInIgnoredNamespaceForImplicitConversion(Declaration) && !isIgnoredPathForImplicitConversion(Declaration); } bool isIgnoredExprForMustUse(const Expr *E) { if (const CXXOperatorCallExpr *OpCall = dyn_cast(E)) { switch (OpCall->getOperator()) { case OO_Equal: case OO_PlusEqual: case OO_MinusEqual: case OO_StarEqual: case OO_SlashEqual: case OO_PercentEqual: case OO_CaretEqual: case OO_AmpEqual: case OO_PipeEqual: case OO_LessLessEqual: case OO_GreaterGreaterEqual: return true; default: return false; } } if (const BinaryOperator *Op = dyn_cast(E)) { return Op->isAssignmentOp(); } return false; } template StringRef getNameChecked(const T& D) { return D->getIdentifier() ? D->getName() : ""; } bool typeIsRefPtr(QualType Q) { CXXRecordDecl *D = Q->getAsCXXRecordDecl(); if (!D || !D->getIdentifier()) { return false; } StringRef name = D->getName(); if (name == "RefPtr" || name == "nsCOMPtr") { return true; } return false; } // The method defined in clang for ignoring implicit nodes doesn't work with // some AST trees. To get around this, we define our own implementation of // IgnoreImplicit. const Stmt *IgnoreImplicit(const Stmt *s) { while (true) { if (auto *ewc = dyn_cast(s)) { s = ewc->getSubExpr(); } else if (auto *mte = dyn_cast(s)) { s = mte->GetTemporaryExpr(); } else if (auto *bte = dyn_cast(s)) { s = bte->getSubExpr(); } else if (auto *ice = dyn_cast(s)) { s = ice->getSubExpr(); } else { break; } } return s; } const Expr *IgnoreImplicit(const Expr *e) { return cast(IgnoreImplicit(static_cast(e))); } } class CustomTypeAnnotation { enum ReasonKind { RK_None, RK_Direct, RK_ArrayElement, RK_BaseClass, RK_Field, RK_TemplateInherited, }; struct AnnotationReason { QualType Type; ReasonKind Kind; const FieldDecl *Field; bool valid() const { return Kind != RK_None; } }; typedef DenseMap ReasonCache; const char *Spelling; const char *Pretty; ReasonCache Cache; public: CustomTypeAnnotation(const char *Spelling, const char *Pretty) : Spelling(Spelling), Pretty(Pretty){}; virtual ~CustomTypeAnnotation() {} // Checks if this custom annotation "effectively affects" the given type. bool hasEffectiveAnnotation(QualType T) { return directAnnotationReason(T).valid(); } void dumpAnnotationReason(DiagnosticsEngine &Diag, QualType T, SourceLocation Loc); void reportErrorIfPresent(DiagnosticsEngine &Diag, QualType T, SourceLocation Loc, unsigned ErrorID, unsigned NoteID) { if (hasEffectiveAnnotation(T)) { Diag.Report(Loc, ErrorID) << T; Diag.Report(Loc, NoteID); dumpAnnotationReason(Diag, T, Loc); } } private: bool hasLiteralAnnotation(QualType T) const; AnnotationReason directAnnotationReason(QualType T); AnnotationReason tmplArgAnnotationReason(ArrayRef Args); protected: // Allow subclasses to apply annotations to external code: virtual bool hasFakeAnnotation(const TagDecl *D) const { return false; } }; static CustomTypeAnnotation StackClass = CustomTypeAnnotation("moz_stack_class", "stack"); static CustomTypeAnnotation GlobalClass = CustomTypeAnnotation("moz_global_class", "global"); static CustomTypeAnnotation NonHeapClass = CustomTypeAnnotation("moz_nonheap_class", "non-heap"); static CustomTypeAnnotation HeapClass = CustomTypeAnnotation("moz_heap_class", "heap"); static CustomTypeAnnotation NonTemporaryClass = CustomTypeAnnotation("moz_non_temporary_class", "non-temporary"); static CustomTypeAnnotation MustUse = CustomTypeAnnotation("moz_must_use_type", "must-use"); static CustomTypeAnnotation NonParam = CustomTypeAnnotation("moz_non_param", "non-param"); class MemMoveAnnotation final : public CustomTypeAnnotation { public: MemMoveAnnotation() : CustomTypeAnnotation("moz_non_memmovable", "non-memmove()able") {} virtual ~MemMoveAnnotation() {} protected: bool hasFakeAnnotation(const TagDecl *D) const override { // Annotate everything in ::std, with a few exceptions; see bug // 1201314 for discussion. if (getDeclarationNamespace(D) == "std") { // This doesn't check that it's really ::std::pair and not // ::std::something_else::pair, but should be good enough. StringRef Name = getNameChecked(D); if (Name == "pair" || Name == "atomic" || Name == "__atomic_base") { return false; } return true; } return false; } }; static MemMoveAnnotation NonMemMovable = MemMoveAnnotation(); 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) override { TraverseDecl(Ctx.getTranslationUnitDecl()); } static bool hasCustomAnnotation(const Decl *D, const char *Spelling) { iterator_range> Attrs = D->specific_attrs(); for (AnnotateAttr *Attr : Attrs) { if (Attr->getAnnotation() == Spelling) { return true; } } return false; } void handleUnusedExprResult(const Stmt *Statement) { const Expr *E = dyn_cast_or_null(Statement); if (E) { E = E->IgnoreImplicit(); // Ignore ExprWithCleanup etc. implicit wrappers QualType T = E->getType(); if (MustUse.hasEffectiveAnnotation(T) && !isIgnoredExprForMustUse(E)) { unsigned ErrorID = Diag.getDiagnosticIDs()->getCustomDiagID( DiagnosticIDs::Error, "Unused value of must-use type %0"); Diag.Report(E->getLocStart(), ErrorID) << T; MustUse.dumpAnnotationReason(Diag, T, E->getLocStart()); } } } 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 MustOverrides; 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")) MustOverrides.push_back(*M); } } for (OverridesVector::iterator It = MustOverrides.begin(); It != MustOverrides.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 (getNameChecked(M) == getNameChecked(*It) && !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); } } return true; } bool VisitSwitchCase(SwitchCase *Statement) { handleUnusedExprResult(Statement->getSubStmt()); return true; } bool VisitCompoundStmt(CompoundStmt *Statement) { for (CompoundStmt::body_iterator It = Statement->body_begin(), E = Statement->body_end(); It != E; ++It) { handleUnusedExprResult(*It); } return true; } bool VisitIfStmt(IfStmt *Statement) { handleUnusedExprResult(Statement->getThen()); handleUnusedExprResult(Statement->getElse()); return true; } bool VisitWhileStmt(WhileStmt *Statement) { handleUnusedExprResult(Statement->getBody()); return true; } bool VisitDoStmt(DoStmt *Statement) { handleUnusedExprResult(Statement->getBody()); return true; } bool VisitForStmt(ForStmt *Statement) { handleUnusedExprResult(Statement->getBody()); handleUnusedExprResult(Statement->getInit()); handleUnusedExprResult(Statement->getInc()); return true; } bool VisitBinComma(BinaryOperator *Op) { handleUnusedExprResult(Op->getLHS()); return true; } }; /// 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) { const auto &Name = getNameChecked(Method); 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 clang::ArrayType *ArrTy = T->getAsArrayTypeUnsafe()) T = ArrTy->getElementType(); CXXRecordDecl *Clazz = T->getAsCXXRecordDecl(); return Clazz ? isClassRefCounted(Clazz) : false; } template bool ASTIsInSystemHeader(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); } const FieldDecl *getClassRefCntMember(const CXXRecordDecl *D) { for (RecordDecl::field_iterator Field = D->field_begin(), E = D->field_end(); Field != E; ++Field) { if (getNameChecked(Field) == "mRefCnt") { return *Field; } } return 0; } const FieldDecl *getBaseRefCntMember(QualType T); const FieldDecl *getBaseRefCntMember(const CXXRecordDecl *D) { const FieldDecl *RefCntMember = getClassRefCntMember(D); if (RefCntMember && isClassRefCounted(D)) { return RefCntMember; } for (CXXRecordDecl::base_class_const_iterator Base = D->bases_begin(), E = D->bases_end(); Base != E; ++Base) { RefCntMember = getBaseRefCntMember(Base->getType()); if (RefCntMember) { return RefCntMember; } } return 0; } const FieldDecl *getBaseRefCntMember(QualType T) { while (const clang::ArrayType *ArrTy = T->getAsArrayTypeUnsafe()) T = ArrTy->getElementType(); CXXRecordDecl *Clazz = T->getAsCXXRecordDecl(); return Clazz ? getBaseRefCntMember(Clazz) : 0; } bool typeHasVTable(QualType T) { while (const clang::ArrayType *ArrTy = T->getAsArrayTypeUnsafe()) T = ArrTy->getElementType(); CXXRecordDecl *Offender = T->getAsCXXRecordDecl(); return Offender && Offender->hasDefinition() && Offender->isDynamicClass(); } } namespace clang { namespace ast_matchers { /// 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 ASTIsInSystemHeader(Finder->getASTContext(), Node); } /// This matcher will match a list of files. These files contain /// known NaN-testing expressions which we would like to whitelist. AST_MATCHER(BinaryOperator, isInWhitelistForNaNExpr) { const char* whitelist[] = { "SkScalar.h", "json_writer.cpp" }; SourceLocation Loc = Node.getOperatorLoc(); auto &SourceManager = Finder->getASTContext().getSourceManager(); SmallString<1024> FileName = SourceManager.getFilename(Loc); for (auto itr = std::begin(whitelist); itr != std::end(whitelist); itr++) { if (llvm::sys::path::rbegin(FileName)->equals(*itr)) { return true; } } return false; } /// 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) { const auto &Name = getNameChecked(Method); return Name == "AddRef" || Name == "Release"; } return false; } /// This matcher will select classes which are refcounted. AST_MATCHER(CXXRecordDecl, hasRefCntMember) { return isClassRefCounted(&Node) && getClassRefCntMember(&Node); } AST_MATCHER(QualType, hasVTable) { return typeHasVTable(Node); } AST_MATCHER(CXXRecordDecl, hasNeedsNoVTableTypeAttr) { return MozChecker::hasCustomAnnotation(&Node, "moz_needs_no_vtable_type"); } /// This matcher will select classes which are non-memmovable AST_MATCHER(QualType, isNonMemMovable) { return NonMemMovable.hasEffectiveAnnotation(Node); } /// This matcher will select classes which require a memmovable template arg AST_MATCHER(CXXRecordDecl, needsMemMovableTemplateArg) { return MozChecker::hasCustomAnnotation(&Node, "moz_needs_memmovable_type"); } /// This matcher will select classes which require all members to be memmovable AST_MATCHER(CXXRecordDecl, needsMemMovableMembers) { return MozChecker::hasCustomAnnotation(&Node, "moz_needs_memmovable_members"); } AST_MATCHER(CXXConstructorDecl, isInterestingImplicitCtor) { const CXXConstructorDecl *Declaration = Node.getCanonicalDecl(); return // Skip ignored namespaces and paths !isInIgnoredNamespaceForImplicitCtor(Declaration) && !isIgnoredPathForImplicitCtor(Declaration) && // We only want Converting constructors Declaration->isConvertingConstructor(false) && // We don't want copy of move constructors, as those are allowed to be // implicit !Declaration->isCopyOrMoveConstructor() && // We don't want deleted constructors. !Declaration->isDeleted(); } // We can't call this "isImplicit" since it clashes with an existing matcher in // clang. AST_MATCHER(CXXConstructorDecl, isMarkedImplicit) { return MozChecker::hasCustomAnnotation(&Node, "moz_implicit"); } AST_MATCHER(CXXRecordDecl, isConcreteClass) { return !Node.isAbstract(); } AST_MATCHER(QualType, autoNonAutoableType) { if (const AutoType *T = Node->getContainedAutoType()) { if (const CXXRecordDecl *Rec = T->getAsCXXRecordDecl()) { return MozChecker::hasCustomAnnotation(Rec, "moz_non_autoable"); } } return false; } AST_MATCHER(CXXConstructorDecl, isExplicitMoveConstructor) { return Node.isExplicit() && Node.isMoveConstructor(); } AST_MATCHER(CXXConstructorDecl, isCompilerProvidedCopyConstructor) { return !Node.isUserProvided() && Node.isCopyConstructor(); } AST_MATCHER(CallExpr, isAssertAssignmentTestFunc) { static const std::string AssertName = "MOZ_AssertAssignmentTest"; const FunctionDecl *Method = Node.getDirectCallee(); return Method && Method->getDeclName().isIdentifier() && Method->getName() == AssertName; } AST_MATCHER(CallExpr, isSnprintfLikeFunc) { static const std::string Snprintf = "snprintf"; static const std::string Vsnprintf = "vsnprintf"; const FunctionDecl *Func = Node.getDirectCallee(); if (!Func || isa(Func)) { return false; } StringRef Name = getNameChecked(Func); if (Name != Snprintf && Name != Vsnprintf) { return false; } return !isIgnoredPathForSprintfLiteral(&Node, Finder->getASTContext().getSourceManager()); } AST_MATCHER(CXXRecordDecl, isLambdaDecl) { return Node.isLambda(); } AST_MATCHER(QualType, isRefPtr) { return typeIsRefPtr(Node); } AST_MATCHER(CXXRecordDecl, hasBaseClasses) { const CXXRecordDecl *Decl = Node.getCanonicalDecl(); // Must have definition and should inherit other classes return Decl && Decl->hasDefinition() && Decl->getNumBases(); } AST_MATCHER(CXXMethodDecl, isRequiredBaseMethod) { const CXXMethodDecl *Decl = Node.getCanonicalDecl(); return Decl && MozChecker::hasCustomAnnotation(Decl, "moz_required_base_method"); } AST_MATCHER(CXXMethodDecl, isNonVirtual) { const CXXMethodDecl *Decl = Node.getCanonicalDecl(); return Decl && !Decl->isVirtual(); } } } namespace { void CustomTypeAnnotation::dumpAnnotationReason(DiagnosticsEngine &Diag, QualType T, SourceLocation Loc) { unsigned InheritsID = Diag.getDiagnosticIDs()->getCustomDiagID( DiagnosticIDs::Note, "%1 is a %0 type because it inherits from a %0 type %2"); unsigned MemberID = Diag.getDiagnosticIDs()->getCustomDiagID( DiagnosticIDs::Note, "%1 is a %0 type because member %2 is a %0 type %3"); unsigned ArrayID = Diag.getDiagnosticIDs()->getCustomDiagID( DiagnosticIDs::Note, "%1 is a %0 type because it is an array of %0 type %2"); unsigned TemplID = Diag.getDiagnosticIDs()->getCustomDiagID( DiagnosticIDs::Note, "%1 is a %0 type because it has a template argument %0 type %2"); AnnotationReason Reason = directAnnotationReason(T); for (;;) { switch (Reason.Kind) { case RK_ArrayElement: Diag.Report(Loc, ArrayID) << Pretty << T << Reason.Type; break; case RK_BaseClass: { const CXXRecordDecl *Declaration = T->getAsCXXRecordDecl(); assert(Declaration && "This type should be a C++ class"); Diag.Report(Declaration->getLocation(), InheritsID) << Pretty << T << Reason.Type; break; } case RK_Field: Diag.Report(Reason.Field->getLocation(), MemberID) << Pretty << T << Reason.Field << Reason.Type; break; case RK_TemplateInherited: { const CXXRecordDecl *Declaration = T->getAsCXXRecordDecl(); assert(Declaration && "This type should be a C++ class"); Diag.Report(Declaration->getLocation(), TemplID) << Pretty << T << Reason.Type; break; } default: // FIXME (bug 1203263): note the original annotation. return; } T = Reason.Type; Reason = directAnnotationReason(T); } } bool CustomTypeAnnotation::hasLiteralAnnotation(QualType T) const { #if CLANG_VERSION_FULL >= 306 if (const TagDecl *D = T->getAsTagDecl()) { #else if (const CXXRecordDecl *D = T->getAsCXXRecordDecl()) { #endif return hasFakeAnnotation(D) || MozChecker::hasCustomAnnotation(D, Spelling); } return false; } CustomTypeAnnotation::AnnotationReason CustomTypeAnnotation::directAnnotationReason(QualType T) { if (hasLiteralAnnotation(T)) { AnnotationReason Reason = {T, RK_Direct, nullptr}; return Reason; } // Check if we have a cached answer void *Key = T.getAsOpaquePtr(); ReasonCache::iterator Cached = Cache.find(T.getAsOpaquePtr()); if (Cached != Cache.end()) { return Cached->second; } // Check if we have a type which we can recurse into if (const clang::ArrayType *Array = T->getAsArrayTypeUnsafe()) { if (hasEffectiveAnnotation(Array->getElementType())) { AnnotationReason Reason = {Array->getElementType(), RK_ArrayElement, nullptr}; Cache[Key] = Reason; return Reason; } } // Recurse into Base classes if (const CXXRecordDecl *Declaration = T->getAsCXXRecordDecl()) { if (Declaration->hasDefinition()) { Declaration = Declaration->getDefinition(); for (const CXXBaseSpecifier &Base : Declaration->bases()) { if (hasEffectiveAnnotation(Base.getType())) { AnnotationReason Reason = {Base.getType(), RK_BaseClass, nullptr}; Cache[Key] = Reason; return Reason; } } // Recurse into members for (const FieldDecl *Field : Declaration->fields()) { if (hasEffectiveAnnotation(Field->getType())) { AnnotationReason Reason = {Field->getType(), RK_Field, Field}; Cache[Key] = Reason; return Reason; } } // Recurse into template arguments if the annotation // MOZ_INHERIT_TYPE_ANNOTATIONS_FROM_TEMPLATE_ARGS is present if (MozChecker::hasCustomAnnotation( Declaration, "moz_inherit_type_annotations_from_template_args")) { const ClassTemplateSpecializationDecl *Spec = dyn_cast(Declaration); if (Spec) { const TemplateArgumentList &Args = Spec->getTemplateArgs(); AnnotationReason Reason = tmplArgAnnotationReason(Args.asArray()); if (Reason.Kind != RK_None) { Cache[Key] = Reason; return Reason; } } } } } AnnotationReason Reason = {QualType(), RK_None, nullptr}; Cache[Key] = Reason; return Reason; } CustomTypeAnnotation::AnnotationReason CustomTypeAnnotation::tmplArgAnnotationReason(ArrayRef Args) { for (const TemplateArgument &Arg : Args) { if (Arg.getKind() == TemplateArgument::Type) { QualType Type = Arg.getAsType(); if (hasEffectiveAnnotation(Type)) { AnnotationReason Reason = {Type, RK_TemplateInherited, nullptr}; return Reason; } } else if (Arg.getKind() == TemplateArgument::Pack) { AnnotationReason Reason = tmplArgAnnotationReason(Arg.getPackAsArray()); if (Reason.Kind != RK_None) { return Reason; } } } AnnotationReason Reason = {QualType(), RK_None, nullptr}; return Reason; } bool isPlacementNew(const CXXNewExpr *Expression) { // Regular new expressions aren't placement new if (Expression->getNumPlacementArgs() == 0) return false; const FunctionDecl *Declaration = Expression->getOperatorNew(); if (Declaration && MozChecker::hasCustomAnnotation(Declaration, "moz_heap_allocator")) { return false; } return true; } DiagnosticsMatcher::DiagnosticsMatcher() { AstMatcher.addMatcher(varDecl().bind("node"), &Scope); AstMatcher.addMatcher(cxxNewExpr().bind("node"), &Scope); AstMatcher.addMatcher(materializeTemporaryExpr().bind("node"), &Scope); AstMatcher.addMatcher( callExpr(callee(functionDecl(heapAllocator()))).bind("node"), &Scope); AstMatcher.addMatcher(parmVarDecl().bind("parm_vardecl"), &Scope); 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"), &ArithmeticArg); AstMatcher.addMatcher( cxxConstructExpr( 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"), &ArithmeticArg); AstMatcher.addMatcher(cxxRecordDecl(hasTrivialCtorDtor()).bind("node"), &TrivialCtorDtor); AstMatcher.addMatcher( binaryOperator( allOf(binaryEqualityOperator(), hasLHS(hasIgnoringParenImpCasts( declRefExpr(hasType(qualType((isFloat())))).bind("lhs"))), hasRHS(hasIgnoringParenImpCasts( declRefExpr(hasType(qualType((isFloat())))).bind("rhs"))), unless(anyOf(isInSystemHeader(), isInWhitelistForNaNExpr())))) .bind("node"), &NaNExpr); // First, look for direct parents of the MemberExpr. AstMatcher.addMatcher( callExpr( callee(functionDecl(hasNoAddRefReleaseOnReturnAttr()).bind("func")), hasParent(memberExpr(isAddRefOrRelease(), hasParent(callExpr())) .bind("member"))) .bind("node"), &NoAddRefReleaseOnReturn); // Then, look for MemberExpr that need to be casted to the right type using // an intermediary CastExpr before we get to the CallExpr. AstMatcher.addMatcher( callExpr( callee(functionDecl(hasNoAddRefReleaseOnReturnAttr()).bind("func")), hasParent(castExpr( hasParent(memberExpr(isAddRefOrRelease(), hasParent(callExpr())) .bind("member"))))) .bind("node"), &NoAddRefReleaseOnReturn); // We want to reject any code which captures a pointer to an object of a // refcounted type, and then lets that value escape. As a primitive analysis, // we reject any occurances of the lambda as a template parameter to a class // (which could allow it to escape), as well as any presence of such a lambda // in a return value (either from lambdas, or in c++14, auto functions). // // We check these lambdas' capture lists for raw pointers to refcounted types. AstMatcher.addMatcher( functionDecl(returns(recordType(hasDeclaration(cxxRecordDecl( isLambdaDecl()).bind("decl"))))), &RefCountedInsideLambda); AstMatcher.addMatcher(lambdaExpr().bind("lambdaExpr"), &RefCountedInsideLambda); AstMatcher.addMatcher( classTemplateSpecializationDecl(hasAnyTemplateArgument(refersToType( recordType(hasDeclaration(cxxRecordDecl( isLambdaDecl()).bind("decl")))))), &RefCountedInsideLambda); // 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( cxxMethodDecl(anyOf(hasName("operator bool"), hasName("operator _Bool"))) .bind("node"), &ExplicitOperatorBool); AstMatcher.addMatcher(cxxRecordDecl().bind("decl"), &NoDuplicateRefCntMember); AstMatcher.addMatcher( classTemplateSpecializationDecl( allOf(hasAnyTemplateArgument(refersToType(hasVTable())), hasNeedsNoVTableTypeAttr())) .bind("node"), &NeedsNoVTableType); // Handle non-mem-movable template specializations AstMatcher.addMatcher( classTemplateSpecializationDecl( allOf(needsMemMovableTemplateArg(), hasAnyTemplateArgument(refersToType(isNonMemMovable())))) .bind("specialization"), &NonMemMovableTemplateArg); // Handle non-mem-movable members AstMatcher.addMatcher( cxxRecordDecl(needsMemMovableMembers()) .bind("decl"), &NonMemMovableMember); AstMatcher.addMatcher(cxxConstructorDecl(isInterestingImplicitCtor(), ofClass(allOf(isConcreteClass(), decl().bind("class"))), unless(isMarkedImplicit())) .bind("ctor"), &ExplicitImplicit); AstMatcher.addMatcher(varDecl(hasType(autoNonAutoableType())).bind("node"), &NoAutoType); AstMatcher.addMatcher( cxxConstructorDecl(isExplicitMoveConstructor()).bind("node"), &NoExplicitMoveConstructor); AstMatcher.addMatcher( cxxConstructExpr( hasDeclaration(cxxConstructorDecl(isCompilerProvidedCopyConstructor(), ofClass(hasRefCntMember())))) .bind("node"), &RefCountedCopyConstructor); AstMatcher.addMatcher( callExpr(isAssertAssignmentTestFunc()).bind("funcCall"), &AssertAttribution); AstMatcher.addMatcher(varDecl(hasType(isRefPtr())).bind("decl"), &KungFuDeathGrip); AstMatcher.addMatcher( callExpr(isSnprintfLikeFunc(), allOf(hasArgument(0, ignoringParenImpCasts(declRefExpr().bind("buffer"))), anyOf(hasArgument(1, sizeOfExpr(hasIgnoringParenImpCasts(declRefExpr().bind("size")))), hasArgument(1, integerLiteral().bind("immediate")), hasArgument(1, declRefExpr(to(varDecl(hasType(isConstQualified()), hasInitializer(integerLiteral().bind("constant"))))))))) .bind("funcCall"), &SprintfLiteral ); AstMatcher.addMatcher(cxxRecordDecl(hasBaseClasses()).bind("class"), &OverrideBaseCall); AstMatcher.addMatcher( cxxMethodDecl(isNonVirtual(), isRequiredBaseMethod()).bind("method"), &OverrideBaseCallUsage); AstMatcher.addMatcher( functionDecl(anyOf(allOf(isDefinition(), hasAncestor(classTemplateSpecializationDecl() .bind("spec"))), isDefinition())) .bind("func"), &NonParamInsideFunctionDecl); AstMatcher.addMatcher( lambdaExpr().bind("lambda"), &NonParamInsideFunctionDecl); } // These enum variants determine whether an allocation has occured in the code. enum AllocationVariety { AV_None, AV_Global, AV_Automatic, AV_Temporary, AV_Heap, }; // XXX Currently the Decl* in the AutomaticTemporaryMap is unused, but it // probably will be used at some point in the future, in order to produce better // error messages. typedef DenseMap AutomaticTemporaryMap; AutomaticTemporaryMap AutomaticTemporaries; void DiagnosticsMatcher::ScopeChecker::run( const MatchFinder::MatchResult &Result) { DiagnosticsEngine &Diag = Result.Context->getDiagnostics(); // There are a variety of different reasons why something could be allocated AllocationVariety Variety = AV_None; SourceLocation Loc; QualType T; if (const ParmVarDecl *D = Result.Nodes.getNodeAs("parm_vardecl")) { if (D->hasUnparsedDefaultArg() || D->hasUninstantiatedDefaultArg()) { return; } if (const Expr *Default = D->getDefaultArg()) { if (const MaterializeTemporaryExpr *E = dyn_cast(Default)) { // We have just found a ParmVarDecl which has, as its default argument, // a MaterializeTemporaryExpr. We mark that MaterializeTemporaryExpr as // automatic, by adding it to the AutomaticTemporaryMap. // Reporting on this type will occur when the MaterializeTemporaryExpr // is matched against. AutomaticTemporaries[E] = D; } } return; } // Determine the type of allocation which we detected if (const VarDecl *D = Result.Nodes.getNodeAs("node")) { if (D->hasGlobalStorage()) { Variety = AV_Global; } else { Variety = AV_Automatic; } T = D->getType(); Loc = D->getLocStart(); } else if (const CXXNewExpr *E = Result.Nodes.getNodeAs("node")) { // New allocates things on the heap. // We don't consider placement new to do anything, as it doesn't actually // allocate the storage, and thus gives us no useful information. if (!isPlacementNew(E)) { Variety = AV_Heap; T = E->getAllocatedType(); Loc = E->getLocStart(); } } else if (const MaterializeTemporaryExpr *E = Result.Nodes.getNodeAs("node")) { // Temporaries can actually have varying storage durations, due to temporary // lifetime extension. We consider the allocation variety of this temporary // to be the same as the allocation variety of its lifetime. // XXX We maybe should mark these lifetimes as being due to a temporary // which has had its lifetime extended, to improve the error messages. switch (E->getStorageDuration()) { case SD_FullExpression: { // Check if this temporary is allocated as a default argument! // if it is, we want to pretend that it is automatic. AutomaticTemporaryMap::iterator AutomaticTemporary = AutomaticTemporaries.find(E); if (AutomaticTemporary != AutomaticTemporaries.end()) { Variety = AV_Automatic; } else { Variety = AV_Temporary; } } break; case SD_Automatic: Variety = AV_Automatic; break; case SD_Thread: case SD_Static: Variety = AV_Global; break; case SD_Dynamic: assert(false && "I don't think that this ever should occur..."); Variety = AV_Heap; break; } T = E->getType().getUnqualifiedType(); Loc = E->getLocStart(); } else if (const CallExpr *E = Result.Nodes.getNodeAs("node")) { T = E->getType()->getPointeeType(); if (!T.isNull()) { // This will always allocate on the heap, as the heapAllocator() check // was made in the matcher Variety = AV_Heap; Loc = E->getLocStart(); } } // Error messages for incorrect allocations. 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 HeapID = Diag.getDiagnosticIDs()->getCustomDiagID( DiagnosticIDs::Error, "variable of type %0 only valid on the heap"); unsigned NonHeapID = Diag.getDiagnosticIDs()->getCustomDiagID( DiagnosticIDs::Error, "variable of type %0 is not valid on the heap"); unsigned NonTemporaryID = Diag.getDiagnosticIDs()->getCustomDiagID( DiagnosticIDs::Error, "variable of type %0 is not valid in a temporary"); unsigned StackNoteID = Diag.getDiagnosticIDs()->getCustomDiagID( DiagnosticIDs::Note, "value incorrectly allocated in an automatic variable"); unsigned GlobalNoteID = Diag.getDiagnosticIDs()->getCustomDiagID( DiagnosticIDs::Note, "value incorrectly allocated in a global variable"); unsigned HeapNoteID = Diag.getDiagnosticIDs()->getCustomDiagID( DiagnosticIDs::Note, "value incorrectly allocated on the heap"); unsigned TemporaryNoteID = Diag.getDiagnosticIDs()->getCustomDiagID( DiagnosticIDs::Note, "value incorrectly allocated in a temporary"); // Report errors depending on the annotations on the input types. switch (Variety) { case AV_None: return; case AV_Global: StackClass.reportErrorIfPresent(Diag, T, Loc, StackID, GlobalNoteID); HeapClass.reportErrorIfPresent(Diag, T, Loc, HeapID, GlobalNoteID); break; case AV_Automatic: GlobalClass.reportErrorIfPresent(Diag, T, Loc, GlobalID, StackNoteID); HeapClass.reportErrorIfPresent(Diag, T, Loc, HeapID, StackNoteID); break; case AV_Temporary: GlobalClass.reportErrorIfPresent(Diag, T, Loc, GlobalID, TemporaryNoteID); HeapClass.reportErrorIfPresent(Diag, T, Loc, HeapID, TemporaryNoteID); NonTemporaryClass.reportErrorIfPresent(Diag, T, Loc, NonTemporaryID, TemporaryNoteID); break; case AV_Heap: GlobalClass.reportErrorIfPresent(Diag, T, Loc, GlobalID, HeapNoteID); StackClass.reportErrorIfPresent(Diag, T, Loc, StackID, HeapNoteID); NonHeapClass.reportErrorIfPresent(Diag, T, Loc, NonHeapID, HeapNoteID); break; } } 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 *Expression = Result.Nodes.getNodeAs("node"); if (const CallExpr *Call = Result.Nodes.getNodeAs("call")) { Diag.Report(Expression->getLocStart(), ErrorID) << Call->getDirectCallee(); } else if (const CXXConstructExpr *Ctr = Result.Nodes.getNodeAs("call")) { Diag.Report(Expression->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"); // We need to accept non-constexpr trivial constructors as well. This occurs // when a struct contains pod members, which will not be initialized. As // constexpr values are initialized, the constructor is non-constexpr. bool BadCtor = !(Node->hasConstexprDefaultConstructor() || 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 *Expression = Result.Nodes.getNodeAs( "node"); const DeclRefExpr *LHS = Result.Nodes.getNodeAs("lhs"); const DeclRefExpr *RHS = Result.Nodes.getNodeAs("rhs"); const ImplicitCastExpr *LHSExpr = dyn_cast( Expression->getLHS()); const ImplicitCastExpr *RHSExpr = dyn_cast( Expression->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(Expression->getLocStart(), ErrorID); Diag.Report(Expression->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) { Context = Result.Context; static DenseSet CheckedDecls; const CXXRecordDecl *Lambda = Result.Nodes.getNodeAs("decl"); if (const LambdaExpr *OuterLambda = Result.Nodes.getNodeAs("lambdaExpr")) { const CXXMethodDecl *OpCall = OuterLambda->getCallOperator(); QualType ReturnTy = OpCall->getReturnType(); if (const CXXRecordDecl *Record = ReturnTy->getAsCXXRecordDecl()) { Lambda = Record; } } if (!Lambda || !Lambda->isLambda()) { return; } // Don't report errors on the same declarations more than once. if (CheckedDecls.count(Lambda)) { return; } CheckedDecls.insert(Lambda); bool StrongRefToThisCaptured = false; for (const LambdaCapture& Capture : Lambda->captures()) { // Check if any of the captures are ByRef. If they are, we have nothing to // report, as it's OK to capture raw pointers to refcounted objects so long as // the Lambda doesn't escape the current scope, which is required by ByRef // captures already. if (Capture.getCaptureKind() == LCK_ByRef) { return; } // Check if this capture is byvalue, and captures a strong reference to this. // XXX: Do we want to make sure that this type which we are capturing is a "Smart Pointer" somehow? if (!StrongRefToThisCaptured && Capture.capturesVariable() && Capture.getCaptureKind() == LCK_ByCopy) { const VarDecl *Var = Capture.getCapturedVar(); if (Var->hasInit()) { const Stmt *Init = Var->getInit(); // Ignore single argument constructors, and trivial nodes. while (true) { auto NewInit = IgnoreImplicit(Init); if (auto ConstructExpr = dyn_cast(NewInit)) { if (ConstructExpr->getNumArgs() == 1) { NewInit = ConstructExpr->getArg(0); } } if (Init == NewInit) { break; } Init = NewInit; } if (isa(Init)) { StrongRefToThisCaptured = true; } } } } // Now we can go through and produce errors for any captured variables or this pointers. for (const LambdaCapture& Capture : Lambda->captures()) { if (Capture.capturesVariable()) { QualType Pointee = Capture.getCapturedVar()->getType()->getPointeeType(); if (!Pointee.isNull() && isClassRefCounted(Pointee)) { emitDiagnostics(Capture.getLocation(), Capture.getCapturedVar()->getName(), Pointee); return; } } // The situation with captures of `this` is more complex. All captures of // `this` look the same-ish (they are LCK_This). We want to complain about // captures of `this` where `this` is a refcounted type, and the capture is // actually used in the body of the lambda (if the capture isn't used, then // we don't care, because it's only being captured in order to give access // to private methods). // // In addition, we don't complain about this, even if it is used, if it was // captured implicitly when the LambdaCaptureDefault was LCD_ByRef, as that // expresses the intent that the lambda won't leave the enclosing scope. bool ImplicitByRefDefaultedCapture = Capture.isImplicit() && Lambda->getLambdaCaptureDefault() == LCD_ByRef; if (Capture.capturesThis() && !ImplicitByRefDefaultedCapture && !StrongRefToThisCaptured) { ThisVisitor V(*this); bool NotAborted = V.TraverseDecl(const_cast(Lambda->getLambdaCallOperator())); if (!NotAborted) { return; } } } } void DiagnosticsMatcher::RefCountedInsideLambdaChecker::emitDiagnostics( SourceLocation Loc, StringRef Name, QualType Type) { DiagnosticsEngine& Diag = Context->getDiagnostics(); unsigned ErrorID = Diag.getDiagnosticIDs()->getCustomDiagID( DiagnosticIDs::Error, "Refcounted variable '%0' of type %1 cannot be captured by a lambda"); unsigned NoteID = Diag.getDiagnosticIDs()->getCustomDiagID( DiagnosticIDs::Note, "Please consider using a smart pointer"); Diag.Report(Loc, ErrorID) << Name << Type; Diag.Report(Loc, NoteID); } bool DiagnosticsMatcher::RefCountedInsideLambdaChecker::ThisVisitor::VisitCXXThisExpr(CXXThisExpr *This) { QualType Pointee = This->getType()->getPointeeType(); if (!Pointee.isNull() && isClassRefCounted(Pointee)) { Checker.emitDiagnostics(This->getLocStart(), "this", Pointee); return false; } return true; } 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") && !ASTIsInSystemHeader(Method->getASTContext(), *Method) && isInterestingDeclForImplicitConversion(Method)) { Diag.Report(Method->getLocStart(), ErrorID) << Clazz; Diag.Report(Method->getLocStart(), NoteID) << "'operator bool'"; } } void DiagnosticsMatcher::NoDuplicateRefCntMemberChecker::run( const MatchFinder::MatchResult &Result) { DiagnosticsEngine &Diag = Result.Context->getDiagnostics(); const CXXRecordDecl *D = Result.Nodes.getNodeAs("decl"); const FieldDecl *RefCntMember = getClassRefCntMember(D); const FieldDecl *FoundRefCntBase = nullptr; if (!D->hasDefinition()) return; D = D->getDefinition(); // If we don't have an mRefCnt member, and we have less than 2 superclasses, // we don't have to run this loop, as neither case will ever apply. if (!RefCntMember && D->getNumBases() < 2) { return; } // Check every superclass for whether it has a base with a refcnt member, and // warn for those which do for (auto &Base : D->bases()) { // Determine if this base class has an mRefCnt member const FieldDecl *BaseRefCntMember = getBaseRefCntMember(Base.getType()); if (BaseRefCntMember) { if (RefCntMember) { // We have an mRefCnt, and superclass has an mRefCnt unsigned Error = Diag.getDiagnosticIDs()->getCustomDiagID( DiagnosticIDs::Error, "Refcounted record %0 has multiple mRefCnt members"); unsigned Note1 = Diag.getDiagnosticIDs()->getCustomDiagID( DiagnosticIDs::Note, "Superclass %0 also has an mRefCnt member"); unsigned Note2 = Diag.getDiagnosticIDs()->getCustomDiagID( DiagnosticIDs::Note, "Consider using the _INHERITED macros for AddRef and Release here"); Diag.Report(D->getLocStart(), Error) << D; Diag.Report(BaseRefCntMember->getLocStart(), Note1) << BaseRefCntMember->getParent(); Diag.Report(RefCntMember->getLocStart(), Note2); } if (FoundRefCntBase) { unsigned Error = Diag.getDiagnosticIDs()->getCustomDiagID( DiagnosticIDs::Error, "Refcounted record %0 has multiple superclasses with mRefCnt members"); unsigned Note = Diag.getDiagnosticIDs()->getCustomDiagID( DiagnosticIDs::Note, "Superclass %0 has an mRefCnt member"); // superclass has mRefCnt, and another superclass also has an mRefCnt Diag.Report(D->getLocStart(), Error) << D; Diag.Report(BaseRefCntMember->getLocStart(), Note) << BaseRefCntMember->getParent(); Diag.Report(FoundRefCntBase->getLocStart(), Note) << FoundRefCntBase->getParent(); } // Record that we've found a base with a mRefCnt member FoundRefCntBase = BaseRefCntMember; } } } void DiagnosticsMatcher::NeedsNoVTableTypeChecker::run( const MatchFinder::MatchResult &Result) { DiagnosticsEngine &Diag = Result.Context->getDiagnostics(); unsigned ErrorID = Diag.getDiagnosticIDs()->getCustomDiagID( DiagnosticIDs::Error, "%0 cannot be instantiated because %1 has a VTable"); unsigned NoteID = Diag.getDiagnosticIDs()->getCustomDiagID( DiagnosticIDs::Note, "bad instantiation of %0 requested here"); const ClassTemplateSpecializationDecl *Specialization = Result.Nodes.getNodeAs("node"); // Get the offending template argument QualType Offender; const TemplateArgumentList &Args = Specialization->getTemplateInstantiationArgs(); for (unsigned i = 0; i < Args.size(); ++i) { Offender = Args[i].getAsType(); if (typeHasVTable(Offender)) { break; } } Diag.Report(Specialization->getLocStart(), ErrorID) << Specialization << Offender; Diag.Report(Specialization->getPointOfInstantiation(), NoteID) << Specialization; } void DiagnosticsMatcher::NonMemMovableTemplateArgChecker::run( const MatchFinder::MatchResult &Result) { DiagnosticsEngine &Diag = Result.Context->getDiagnostics(); unsigned ErrorID = Diag.getDiagnosticIDs()->getCustomDiagID( DiagnosticIDs::Error, "Cannot instantiate %0 with non-memmovable template argument %1"); unsigned Note1ID = Diag.getDiagnosticIDs()->getCustomDiagID( DiagnosticIDs::Note, "instantiation of %0 requested here"); // Get the specialization const ClassTemplateSpecializationDecl *Specialization = Result.Nodes.getNodeAs("specialization"); SourceLocation RequestLoc = Specialization->getPointOfInstantiation(); // Report an error for every template argument which is non-memmovable const TemplateArgumentList &Args = Specialization->getTemplateInstantiationArgs(); for (unsigned i = 0; i < Args.size(); ++i) { QualType ArgType = Args[i].getAsType(); if (NonMemMovable.hasEffectiveAnnotation(ArgType)) { Diag.Report(Specialization->getLocation(), ErrorID) << Specialization << ArgType; // XXX It would be really nice if we could get the instantiation stack // information // from Sema such that we could print a full template instantiation stack, // however, // it seems as though that information is thrown out by the time we get // here so we // can only report one level of template specialization (which in many // cases won't // be useful) Diag.Report(RequestLoc, Note1ID) << Specialization; NonMemMovable.dumpAnnotationReason(Diag, ArgType, RequestLoc); } } } void DiagnosticsMatcher::NonMemMovableMemberChecker::run( const MatchFinder::MatchResult &Result) { DiagnosticsEngine &Diag = Result.Context->getDiagnostics(); unsigned ErrorID = Diag.getDiagnosticIDs()->getCustomDiagID( DiagnosticIDs::Error, "class %0 cannot have non-memmovable member %1 of type %2"); // Get the specialization const CXXRecordDecl* Declaration = Result.Nodes.getNodeAs("decl"); // Report an error for every member which is non-memmovable for (const FieldDecl *Field : Declaration->fields()) { QualType Type = Field->getType(); if (NonMemMovable.hasEffectiveAnnotation(Type)) { Diag.Report(Field->getLocation(), ErrorID) << Declaration << Field << Type; NonMemMovable.dumpAnnotationReason(Diag, Type, Declaration->getLocation()); } } } void DiagnosticsMatcher::ExplicitImplicitChecker::run( const MatchFinder::MatchResult &Result) { DiagnosticsEngine &Diag = Result.Context->getDiagnostics(); unsigned ErrorID = 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"); // We've already checked everything in the matcher, so we just have to report // the error. const CXXConstructorDecl *Ctor = Result.Nodes.getNodeAs("ctor"); const CXXRecordDecl *Declaration = Result.Nodes.getNodeAs("class"); Diag.Report(Ctor->getLocation(), ErrorID) << Declaration->getDeclName(); Diag.Report(Ctor->getLocation(), NoteID); } void DiagnosticsMatcher::NoAutoTypeChecker::run( const MatchFinder::MatchResult &Result) { DiagnosticsEngine &Diag = Result.Context->getDiagnostics(); unsigned ErrorID = Diag.getDiagnosticIDs()->getCustomDiagID( DiagnosticIDs::Error, "Cannot use auto to declare a variable of type %0"); unsigned NoteID = Diag.getDiagnosticIDs()->getCustomDiagID( DiagnosticIDs::Note, "Please write out this type explicitly"); const VarDecl *D = Result.Nodes.getNodeAs("node"); Diag.Report(D->getLocation(), ErrorID) << D->getType(); Diag.Report(D->getLocation(), NoteID); } void DiagnosticsMatcher::NoExplicitMoveConstructorChecker::run( const MatchFinder::MatchResult &Result) { DiagnosticsEngine &Diag = Result.Context->getDiagnostics(); unsigned ErrorID = Diag.getDiagnosticIDs()->getCustomDiagID( DiagnosticIDs::Error, "Move constructors may not be marked explicit"); // Everything we needed to know was checked in the matcher - we just report // the error here const CXXConstructorDecl *D = Result.Nodes.getNodeAs("node"); Diag.Report(D->getLocation(), ErrorID); } void DiagnosticsMatcher::RefCountedCopyConstructorChecker::run( const MatchFinder::MatchResult &Result) { DiagnosticsEngine &Diag = Result.Context->getDiagnostics(); unsigned ErrorID = Diag.getDiagnosticIDs()->getCustomDiagID( DiagnosticIDs::Error, "Invalid use of compiler-provided copy constructor " "on refcounted type"); unsigned NoteID = Diag.getDiagnosticIDs()->getCustomDiagID( DiagnosticIDs::Note, "The default copy constructor also copies the " "default mRefCnt property, leading to reference " "count imbalance issues. Please provide your own " "copy constructor which only copies the fields which " "need to be copied"); // Everything we needed to know was checked in the matcher - we just report // the error here const CXXConstructExpr *E = Result.Nodes.getNodeAs("node"); Diag.Report(E->getLocation(), ErrorID); Diag.Report(E->getLocation(), NoteID); } void DiagnosticsMatcher::AssertAssignmentChecker::run( const MatchFinder::MatchResult &Result) { DiagnosticsEngine &Diag = Result.Context->getDiagnostics(); unsigned AssignInsteadOfComp = Diag.getDiagnosticIDs()->getCustomDiagID( DiagnosticIDs::Error, "Forbidden assignment in assert expression"); const CallExpr *FuncCall = Result.Nodes.getNodeAs("funcCall"); if (FuncCall && hasSideEffectAssignment(FuncCall)) { Diag.Report(FuncCall->getLocStart(), AssignInsteadOfComp); } } void DiagnosticsMatcher::KungFuDeathGripChecker::run( const MatchFinder::MatchResult &Result) { DiagnosticsEngine &Diag = Result.Context->getDiagnostics(); unsigned ErrorID = Diag.getDiagnosticIDs()->getCustomDiagID( DiagnosticIDs::Error, "Unused \"kungFuDeathGrip\" %0 objects constructed from %1 are prohibited"); unsigned NoteID = Diag.getDiagnosticIDs()->getCustomDiagID( DiagnosticIDs::Note, "Please switch all accesses to this %0 to go through '%1', or explicitly pass '%1' to `mozilla::Unused`"); const VarDecl *D = Result.Nodes.getNodeAs("decl"); if (D->isReferenced() || !D->hasLocalStorage() || !D->hasInit()) { return; } // Not interested in parameters. if (isa(D) || isa(D)) { return; } const Expr *E = IgnoreImplicit(D->getInit()); const CXXConstructExpr *CE = dyn_cast(E); if (CE && CE->getNumArgs() == 0) { // We don't report an error when we construct and don't use a nsCOMPtr / // nsRefPtr with no arguments. We don't report it because the error is not // related to the current check. In the future it may be reported through a // more generic mechanism. return; } // We don't want to look at the single argument conversion constructors // which are inbetween the declaration and the actual object which we are // assigning into the nsCOMPtr/RefPtr. To do this, we repeatedly // IgnoreImplicit, then look at the expression. If it is one of these // conversion constructors, we ignore it and continue to dig. while ((CE = dyn_cast(E)) && CE->getNumArgs() == 1) { E = IgnoreImplicit(CE->getArg(0)); } // We allow taking a kungFuDeathGrip of `this` because it cannot change // beneath us, so calling directly through `this` is OK. This is the same // for local variable declarations. // // We also don't complain about unused RefPtrs which are constructed from // the return value of a new expression, as these are required in order to // immediately destroy the value created (which was presumably created for // its side effects), and are not used as a death grip. if (isa(E) || isa(E) || isa(E)) { return; } // These types are assigned into nsCOMPtr and RefPtr for their side effects, // and not as a kungFuDeathGrip. We don't want to consider RefPtr and nsCOMPtr // types which are initialized with these types as errors. const TagDecl *TD = E->getType()->getAsTagDecl(); if (TD && TD->getIdentifier()) { static const char *IgnoreTypes[] = { "already_AddRefed", "nsGetServiceByCID", "nsGetServiceByCIDWithError", "nsGetServiceByContractID", "nsGetServiceByContractIDWithError", "nsCreateInstanceByCID", "nsCreateInstanceByContractID", "nsCreateInstanceFromFactory", }; for (uint32_t i = 0; i < sizeof(IgnoreTypes) / sizeof(IgnoreTypes[0]); ++i) { if (TD->getName() == IgnoreTypes[i]) { return; } } } // Report the error const char *ErrThing; const char *NoteThing; if (isa(E)) { ErrThing = "members"; NoteThing = "member"; } else { ErrThing = "temporary values"; NoteThing = "value"; } // We cannot provide the note if we don't have an initializer Diag.Report(D->getLocStart(), ErrorID) << D->getType() << ErrThing; Diag.Report(E->getLocStart(), NoteID) << NoteThing << getNameChecked(D); } void DiagnosticsMatcher::SprintfLiteralChecker::run( const MatchFinder::MatchResult &Result) { if (!Result.Context->getLangOpts().CPlusPlus) { // SprintfLiteral 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, "Use %1 instead of %0 when writing into a character array."); unsigned NoteID = Diag.getDiagnosticIDs()->getCustomDiagID( DiagnosticIDs::Note, "This will prevent passing in the wrong size to %0 accidentally."); const CallExpr *D = Result.Nodes.getNodeAs("funcCall"); StringRef Name = D->getDirectCallee()->getName(); const char *Replacement; if (Name == "snprintf") { Replacement = "SprintfLiteral"; } else { assert(Name == "vsnprintf"); Replacement = "VsprintfLiteral"; } const DeclRefExpr *Buffer = Result.Nodes.getNodeAs("buffer"); const DeclRefExpr *Size = Result.Nodes.getNodeAs("size"); if (Size) { // Match calls like snprintf(x, sizeof(x), ...). if (Buffer->getFoundDecl() != Size->getFoundDecl()) { return; } Diag.Report(D->getLocStart(), ErrorID) << Name << Replacement; Diag.Report(D->getLocStart(), NoteID) << Name; return; } const QualType QType = Buffer->getType(); const ConstantArrayType *Type = dyn_cast(QType.getTypePtrOrNull()); if (Type) { // Match calls like snprintf(x, 100, ...), where x is int[100]; const IntegerLiteral *Literal = Result.Nodes.getNodeAs("immediate"); if (!Literal) { // Match calls like: const int y = 100; snprintf(x, y, ...); Literal = Result.Nodes.getNodeAs("constant"); } if (Type->getSize().ule(Literal->getValue())) { Diag.Report(D->getLocStart(), ErrorID) << Name << Replacement; Diag.Report(D->getLocStart(), NoteID) << Name; } } } bool DiagnosticsMatcher::OverrideBaseCallChecker::isRequiredBaseMethod( const CXXMethodDecl *Method) { return MozChecker::hasCustomAnnotation(Method, "moz_required_base_method"); } void DiagnosticsMatcher::OverrideBaseCallChecker::evaluateExpression( const Stmt *StmtExpr, std::list &MethodList) { // Continue while we have methods in our list if (!MethodList.size()) { return; } if (auto MemberFuncCall = dyn_cast(StmtExpr)) { if (auto Method = dyn_cast( MemberFuncCall->getDirectCallee())) { findBaseMethodCall(Method, MethodList); } } for (auto S : StmtExpr->children()) { if (S) { evaluateExpression(S, MethodList); } } } void DiagnosticsMatcher::OverrideBaseCallChecker::getRequiredBaseMethod( const CXXMethodDecl *Method, std::list& MethodsList) { if (isRequiredBaseMethod(Method)) { MethodsList.push_back(Method); } else { // Loop through all it's base methods. for (auto BaseMethod = Method->begin_overridden_methods(); BaseMethod != Method->end_overridden_methods(); BaseMethod++) { getRequiredBaseMethod(*BaseMethod, MethodsList); } } } void DiagnosticsMatcher::OverrideBaseCallChecker::findBaseMethodCall( const CXXMethodDecl* Method, std::list& MethodsList) { MethodsList.remove(Method); // Loop also through all it's base methods; for (auto BaseMethod = Method->begin_overridden_methods(); BaseMethod != Method->end_overridden_methods(); BaseMethod++) { findBaseMethodCall(*BaseMethod, MethodsList); } } void DiagnosticsMatcher::OverrideBaseCallChecker::run( const MatchFinder::MatchResult &Result) { DiagnosticsEngine &Diag = Result.Context->getDiagnostics(); unsigned OverrideBaseCallCheckID = Diag.getDiagnosticIDs()->getCustomDiagID( DiagnosticIDs::Error, "Method %0 must be called in all overrides, but is not called in " "this override defined for class %1"); const CXXRecordDecl *Decl = Result.Nodes.getNodeAs("class"); // Loop through the methods and look for the ones that are overridden. for (auto Method : Decl->methods()) { // If this method doesn't override other methods or it doesn't have a body, // continue to the next declaration. if (!Method->size_overridden_methods() || !Method->hasBody()) { continue; } // Preferred the usage of list instead of vector in order to avoid // calling erase-remove when deleting items std::list MethodsList; // For each overridden method push it to a list if it meets our // criteria for (auto BaseMethod = Method->begin_overridden_methods(); BaseMethod != Method->end_overridden_methods(); BaseMethod++) { getRequiredBaseMethod(*BaseMethod, MethodsList); } // If no method has been found then no annotation was used // so checking is not needed if (!MethodsList.size()) { continue; } // Loop through the body of our method and search for calls to // base methods evaluateExpression(Method->getBody(), MethodsList); // If list is not empty pop up errors for (auto BaseMethod : MethodsList) { Diag.Report(Method->getLocation(), OverrideBaseCallCheckID) << BaseMethod->getQualifiedNameAsString() << Decl->getName(); } } } void DiagnosticsMatcher::OverrideBaseCallUsageChecker::run( const MatchFinder::MatchResult &Result) { DiagnosticsEngine &Diag = Result.Context->getDiagnostics(); unsigned ErrorID = Diag.getDiagnosticIDs()->getCustomDiagID( DiagnosticIDs::Error, "MOZ_REQUIRED_BASE_METHOD can be used only on virtual methods"); const CXXMethodDecl *Method = Result.Nodes.getNodeAs("method"); Diag.Report(Method->getLocation(), ErrorID); } void DiagnosticsMatcher::NonParamInsideFunctionDeclChecker::run( const MatchFinder::MatchResult &Result) { static DenseSet CheckedFunctionDecls; const FunctionDecl *func = Result.Nodes.getNodeAs("func"); if (!func) { const LambdaExpr *lambda = Result.Nodes.getNodeAs("lambda"); if (lambda) { func = lambda->getCallOperator(); } } if (!func) { return; } if (func->isDeleted()) { return; } // Don't report errors on the same declarations more than once. if (CheckedFunctionDecls.count(func)) { return; } CheckedFunctionDecls.insert(func); const ClassTemplateSpecializationDecl *Spec = Result.Nodes.getNodeAs("spec"); DiagnosticsEngine &Diag = Result.Context->getDiagnostics(); unsigned ErrorID = Diag.getDiagnosticIDs()->getCustomDiagID( DiagnosticIDs::Error, "Type %0 must not be used as parameter"); unsigned NoteID = Diag.getDiagnosticIDs()->getCustomDiagID( DiagnosticIDs::Note, "Please consider passing a const reference instead"); unsigned SpecNoteID = Diag.getDiagnosticIDs()->getCustomDiagID( DiagnosticIDs::Note, "The bad argument was passed to %0 here"); for (ParmVarDecl *p : func->parameters()) { QualType T = p->getType().withoutLocalFastQualifiers(); if (NonParam.hasEffectiveAnnotation(T)) { Diag.Report(p->getLocation(), ErrorID) << T; Diag.Report(p->getLocation(), NoteID); if (Spec) { Diag.Report(Spec->getPointOfInstantiation(), SpecNoteID) << Spec->getSpecializedTemplate(); } } } } class MozCheckAction : public PluginASTAction { public: ASTConsumerPtr CreateASTConsumer(CompilerInstance &CI, StringRef FileName) override { #if CLANG_VERSION_FULL >= 306 std::unique_ptr Checker(llvm::make_unique(CI)); ASTConsumerPtr Other(Checker->getOtherConsumer()); std::vector Consumers; Consumers.push_back(std::move(Checker)); Consumers.push_back(std::move(Other)); return llvm::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"); // Export the registry on Windows. #ifdef LLVM_EXPORT_REGISTRY LLVM_EXPORT_REGISTRY(FrontendPluginRegistry) #endif