| /* 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 <memory> |
| |
| #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<ASTConsumer> 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: |
| 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); |
| }; |
| |
| 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 NonMemMovableChecker : 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); |
| }; |
| |
| ScopeChecker scopeChecker; |
| ArithmeticArgChecker arithmeticArgChecker; |
| TrivialCtorDtorChecker trivialCtorDtorChecker; |
| NaNExprChecker nanExprChecker; |
| NoAddRefReleaseOnReturnChecker noAddRefReleaseOnReturnChecker; |
| RefCountedInsideLambdaChecker refCountedInsideLambdaChecker; |
| ExplicitOperatorBoolChecker explicitOperatorBoolChecker; |
| NoDuplicateRefCntMemberChecker noDuplicateRefCntMemberChecker; |
| NeedsNoVTableTypeChecker needsNoVTableTypeChecker; |
| NonMemMovableChecker nonMemMovableChecker; |
| ExplicitImplicitChecker explicitImplicitChecker; |
| NoAutoTypeChecker noAutoTypeChecker; |
| NoExplicitMoveConstructorChecker noExplicitMoveConstructorChecker; |
| RefCountedCopyConstructorChecker refCountedCopyConstructorChecker; |
| MatchFinder astMatcher; |
| }; |
| |
| namespace { |
| |
| std::string getDeclarationNamespace(const Decl *decl) { |
| const DeclContext *DC = |
| decl->getDeclContext()->getEnclosingNamespaceContext(); |
| const NamespaceDecl *ND = dyn_cast<NamespaceDecl>(DC); |
| if (!ND) { |
| return ""; |
| } |
| |
| while (const DeclContext *ParentDC = ND->getParent()) { |
| if (!isa<NamespaceDecl>(ParentDC)) { |
| break; |
| } |
| ND = cast<NamespaceDecl>(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 == "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 |
| } |
| |
| 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) { |
| 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; |
| } |
| 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 *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 isInterestingDeclForImplicitConversion(const Decl *decl) { |
| return !isInIgnoredNamespaceForImplicitConversion(decl) && |
| !isIgnoredPathForImplicitConversion(decl); |
| } |
| |
| bool isIgnoredExprForMustUse(const Expr *E) { |
| if (const CXXOperatorCallExpr *OpCall = dyn_cast<CXXOperatorCallExpr>(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<BinaryOperator>(E)) { |
| return Op->isAssignmentOp(); |
| } |
| |
| return false; |
| } |
| } |
| |
| 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<void *, AnnotationReason> 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); |
| |
| 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", "must-use"); |
| |
| 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 = D->getName(); |
| if (Name == "pair" || Name == "atomic" || Name == "__atomic_base") { |
| return false; |
| } |
| return true; |
| } |
| return false; |
| } |
| }; |
| |
| static MemMoveAnnotation NonMemMovable = MemMoveAnnotation(); |
| |
| class MozChecker : public ASTConsumer, public RecursiveASTVisitor<MozChecker> { |
| 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) { |
| iterator_range<specific_attr_iterator<AnnotateAttr>> Attrs = |
| D->specific_attrs<AnnotateAttr>(); |
| |
| for (AnnotateAttr *Attr : Attrs) { |
| if (Attr->getAnnotation() == Spelling) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| void HandleUnusedExprResult(const Stmt *stmt) { |
| const Expr *E = dyn_cast_or_null<Expr>(stmt); |
| if (E) { |
| 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<CXXMethodDecl *> 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); |
| } |
| } |
| |
| return true; |
| } |
| |
| bool VisitSwitchCase(SwitchCase *stmt) { |
| HandleUnusedExprResult(stmt->getSubStmt()); |
| return true; |
| } |
| bool VisitCompoundStmt(CompoundStmt *stmt) { |
| for (CompoundStmt::body_iterator it = stmt->body_begin(), |
| e = stmt->body_end(); |
| it != e; ++it) { |
| HandleUnusedExprResult(*it); |
| } |
| return true; |
| } |
| bool VisitIfStmt(IfStmt *Stmt) { |
| HandleUnusedExprResult(Stmt->getThen()); |
| HandleUnusedExprResult(Stmt->getElse()); |
| return true; |
| } |
| bool VisitWhileStmt(WhileStmt *Stmt) { |
| HandleUnusedExprResult(Stmt->getBody()); |
| return true; |
| } |
| bool VisitDoStmt(DoStmt *Stmt) { |
| HandleUnusedExprResult(Stmt->getBody()); |
| return true; |
| } |
| bool VisitForStmt(ForStmt *Stmt) { |
| HandleUnusedExprResult(Stmt->getBody()); |
| HandleUnusedExprResult(Stmt->getInit()); |
| HandleUnusedExprResult(Stmt->getInc()); |
| return true; |
| } |
| bool VisitBinComma(BinaryOperator *Op) { |
| HandleUnusedExprResult(Op->getLHS()); |
| return true; |
| } |
| }; |
| |
| /// A cached data of whether classes are refcounted or not. |
| typedef DenseMap<const CXXRecordDecl *, std::pair<const Decl *, bool>> |
| 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 = method->getName(); |
| 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) : false; |
| } |
| |
| template <class T> 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); |
| } |
| |
| const FieldDecl *getClassRefCntMember(const CXXRecordDecl *D) { |
| for (RecordDecl::field_iterator field = D->field_begin(), e = D->field_end(); |
| field != e; ++field) { |
| if (field->getName() == "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 ArrayType *arrTy = T->getAsArrayTypeUnsafe()) |
| T = arrTy->getElementType(); |
| CXXRecordDecl *clazz = T->getAsCXXRecordDecl(); |
| return clazz ? getBaseRefCntMember(clazz) : 0; |
| } |
| |
| bool typeHasVTable(QualType T) { |
| while (const 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 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<CXXMethodDecl>(Member); |
| if (Method) { |
| const auto &Name = Method->getName(); |
| 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, needsMemMovable) { |
| return MozChecker::hasCustomAnnotation(&Node, "moz_needs_memmovable_type"); |
| } |
| |
| AST_MATCHER(CXXConstructorDecl, isInterestingImplicitCtor) { |
| const CXXConstructorDecl *decl = Node.getCanonicalDecl(); |
| return |
| // Skip ignored namespaces and paths |
| !isInIgnoredNamespaceForImplicitCtor(decl) && |
| !isIgnoredPathForImplicitCtor(decl) && |
| // We only want Converting constructors |
| decl->isConvertingConstructor(false) && |
| // We don't want copy of move constructors, as those are allowed to be |
| // implicit |
| !decl->isCopyOrMoveConstructor() && |
| // We don't want deleted constructors. |
| !decl->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(); |
| } |
| } |
| } |
| |
| 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 *Decl = T->getAsCXXRecordDecl(); |
| assert(Decl && "This type should be a C++ class"); |
| |
| Diag.Report(Decl->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 *Decl = T->getAsCXXRecordDecl(); |
| assert(Decl && "This type should be a C++ class"); |
| |
| Diag.Report(Decl->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 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 *Decl = T->getAsCXXRecordDecl()) { |
| if (Decl->hasDefinition()) { |
| Decl = Decl->getDefinition(); |
| |
| for (const CXXBaseSpecifier &Base : Decl->bases()) { |
| if (hasEffectiveAnnotation(Base.getType())) { |
| AnnotationReason Reason = {Base.getType(), RK_BaseClass, nullptr}; |
| Cache[Key] = Reason; |
| return Reason; |
| } |
| } |
| |
| // Recurse into members |
| for (const FieldDecl *Field : Decl->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( |
| Decl, "moz_inherit_type_annotations_from_template_args")) { |
| const ClassTemplateSpecializationDecl *Spec = |
| dyn_cast<ClassTemplateSpecializationDecl>(Decl); |
| if (Spec) { |
| const TemplateArgumentList &Args = Spec->getTemplateArgs(); |
| |
| for (const TemplateArgument &Arg : Args.asArray()) { |
| if (Arg.getKind() == TemplateArgument::Type) { |
| QualType Type = Arg.getAsType(); |
| |
| if (hasEffectiveAnnotation(Type)) { |
| AnnotationReason Reason = {Type, RK_TemplateInherited, nullptr}; |
| Cache[Key] = Reason; |
| return Reason; |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| AnnotationReason Reason = {QualType(), RK_None, nullptr}; |
| Cache[Key] = Reason; |
| return Reason; |
| } |
| |
| bool isPlacementNew(const CXXNewExpr *Expr) { |
| // Regular new expressions aren't placement new |
| if (Expr->getNumPlacementArgs() == 0) |
| return false; |
| const FunctionDecl *Decl = Expr->getOperatorNew(); |
| if (Decl && MozChecker::hasCustomAnnotation(Decl, "moz_heap_allocator")) { |
| return false; |
| } |
| return true; |
| } |
| |
| DiagnosticsMatcher::DiagnosticsMatcher() { |
| astMatcher.addMatcher(varDecl().bind("node"), &scopeChecker); |
| astMatcher.addMatcher(newExpr().bind("node"), &scopeChecker); |
| astMatcher.addMatcher(materializeTemporaryExpr().bind("node"), &scopeChecker); |
| astMatcher.addMatcher( |
| callExpr(callee(functionDecl(heapAllocator()))).bind("node"), |
| &scopeChecker); |
| astMatcher.addMatcher(parmVarDecl().bind("parm_vardecl"), &scopeChecker); |
| |
| 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); |
| |
| // 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"), |
| &noAddRefReleaseOnReturnChecker); |
| // 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"), |
| &noAddRefReleaseOnReturnChecker); |
| |
| // Match declrefs with type "pointer to object of ref-counted type" inside a |
| // lambda, where the declaration they reference is not inside the lambda. |
| // This excludes arguments and local variables, leaving only captured |
| // variables. |
| astMatcher.addMatcher(lambdaExpr().bind("lambda"), &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); |
| |
| astMatcher.addMatcher( |
| recordDecl(allOf(decl().bind("decl"), hasRefCntMember())), |
| &noDuplicateRefCntMemberChecker); |
| |
| astMatcher.addMatcher( |
| classTemplateSpecializationDecl( |
| allOf(hasAnyTemplateArgument(refersToType(hasVTable())), |
| hasNeedsNoVTableTypeAttr())) |
| .bind("node"), |
| &needsNoVTableTypeChecker); |
| |
| // Handle non-mem-movable template specializations |
| astMatcher.addMatcher( |
| classTemplateSpecializationDecl( |
| allOf(needsMemMovable(), |
| hasAnyTemplateArgument(refersToType(isNonMemMovable())))) |
| .bind("specialization"), |
| &nonMemMovableChecker); |
| |
| astMatcher.addMatcher( |
| constructorDecl(isInterestingImplicitCtor(), |
| ofClass(allOf(isConcreteClass(), decl().bind("class"))), |
| unless(isMarkedImplicit())) |
| .bind("ctor"), |
| &explicitImplicitChecker); |
| |
| astMatcher.addMatcher(varDecl(hasType(autoNonAutoableType())).bind("node"), |
| &noAutoTypeChecker); |
| |
| astMatcher.addMatcher(constructorDecl(isExplicitMoveConstructor()).bind("node"), |
| &noExplicitMoveConstructorChecker); |
| |
| astMatcher.addMatcher(constructExpr(hasDeclaration( |
| constructorDecl( |
| isCompilerProvidedCopyConstructor(), |
| ofClass(hasRefCntMember())))).bind("node"), |
| &refCountedCopyConstructorChecker); |
| } |
| |
| // 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<const MaterializeTemporaryExpr *, const Decl *> 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<ParmVarDecl>("parm_vardecl")) { |
| if (const Expr *Default = D->getDefaultArg()) { |
| if (const MaterializeTemporaryExpr *E = dyn_cast<MaterializeTemporaryExpr>(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<VarDecl>("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<CXXNewExpr>("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<MaterializeTemporaryExpr>("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<CallExpr>("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 *expr = Result.Nodes.getNodeAs<Expr>("node"); |
| if (const CallExpr *call = Result.Nodes.getNodeAs<CallExpr>("call")) { |
| Diag.Report(expr->getLocStart(), errorID) << call->getDirectCallee(); |
| } else if (const CXXConstructExpr *ctr = |
| Result.Nodes.getNodeAs<CXXConstructExpr>("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<CXXRecordDecl>("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<BinaryOperator>("node"); |
| const DeclRefExpr *lhs = Result.Nodes.getNodeAs<DeclRefExpr>("lhs"); |
| const DeclRefExpr *rhs = Result.Nodes.getNodeAs<DeclRefExpr>("rhs"); |
| const ImplicitCastExpr *lhsExpr = dyn_cast<ImplicitCastExpr>(expr->getLHS()); |
| const ImplicitCastExpr *rhsExpr = dyn_cast<ImplicitCastExpr>(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<Stmt>("node"); |
| const FunctionDecl *func = Result.Nodes.getNodeAs<FunctionDecl>("func"); |
| const MemberExpr *member = Result.Nodes.getNodeAs<MemberExpr>("member"); |
| const CXXMethodDecl *method = |
| dyn_cast<CXXMethodDecl>(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 captured by a lambda"); |
| unsigned noteID = Diag.getDiagnosticIDs()->getCustomDiagID( |
| DiagnosticIDs::Note, "Please consider using a smart pointer"); |
| const LambdaExpr *Lambda = Result.Nodes.getNodeAs<LambdaExpr>("lambda"); |
| |
| for (const LambdaCapture Capture : Lambda->captures()) { |
| if (Capture.capturesVariable()) { |
| QualType Pointee = Capture.getCapturedVar()->getType()->getPointeeType(); |
| |
| if (!Pointee.isNull() && isClassRefCounted(Pointee)) { |
| Diag.Report(Capture.getLocation(), errorID) |
| << Capture.getCapturedVar() << Pointee; |
| Diag.Report(Capture.getLocation(), 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<CXXConversionDecl>("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'"; |
| } |
| } |
| |
| void DiagnosticsMatcher::NoDuplicateRefCntMemberChecker::run( |
| const MatchFinder::MatchResult &Result) { |
| DiagnosticsEngine &Diag = Result.Context->getDiagnostics(); |
| unsigned warningID = Diag.getDiagnosticIDs()->getCustomDiagID( |
| DiagnosticIDs::Error, |
| "Refcounted record %0 has multiple mRefCnt members"); |
| unsigned note1ID = Diag.getDiagnosticIDs()->getCustomDiagID( |
| DiagnosticIDs::Note, "Superclass %0 also has an mRefCnt member"); |
| unsigned note2ID = Diag.getDiagnosticIDs()->getCustomDiagID( |
| DiagnosticIDs::Note, |
| "Consider using the _INHERITED macros for AddRef and Release here"); |
| |
| const CXXRecordDecl *decl = Result.Nodes.getNodeAs<CXXRecordDecl>("decl"); |
| const FieldDecl *refCntMember = getClassRefCntMember(decl); |
| assert(refCntMember && |
| "The matcher checked to make sure we have a refCntMember"); |
| |
| // Check every superclass for whether it has a base with a refcnt member, and |
| // warn for those which do |
| for (CXXRecordDecl::base_class_const_iterator base = decl->bases_begin(), |
| e = decl->bases_end(); |
| base != e; ++base) { |
| const FieldDecl *baseRefCntMember = getBaseRefCntMember(base->getType()); |
| if (baseRefCntMember) { |
| Diag.Report(decl->getLocStart(), warningID) << decl; |
| Diag.Report(baseRefCntMember->getLocStart(), note1ID) |
| << baseRefCntMember->getParent(); |
| Diag.Report(refCntMember->getLocStart(), note2ID); |
| } |
| } |
| } |
| |
| 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<ClassTemplateSpecializationDecl>("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::NonMemMovableChecker::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<ClassTemplateSpecializationDecl>("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(args[i].getAsType())) { |
| 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::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<CXXConstructorDecl>("ctor"); |
| const CXXRecordDecl *Decl = Result.Nodes.getNodeAs<CXXRecordDecl>("class"); |
| |
| Diag.Report(Ctor->getLocation(), ErrorID) << Decl->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<VarDecl>("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<CXXConstructorDecl>("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<CXXConstructExpr>("node"); |
| |
| Diag.Report(E->getLocation(), ErrorID); |
| Diag.Report(E->getLocation(), NoteID); |
| } |
| |
| class MozCheckAction : public PluginASTAction { |
| public: |
| ASTConsumerPtr CreateASTConsumer(CompilerInstance &CI, |
| StringRef fileName) override { |
| #if CLANG_VERSION_FULL >= 306 |
| std::unique_ptr<MozChecker> checker(llvm::make_unique<MozChecker>(CI)); |
| ASTConsumerPtr other(checker->getOtherConsumer()); |
| |
| std::vector<ASTConsumerPtr> consumers; |
| consumers.push_back(std::move(checker)); |
| consumers.push_back(std::move(other)); |
| return llvm::make_unique<MultiplexConsumer>(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<std::string> &args) override { |
| return true; |
| } |
| }; |
| } |
| |
| static FrontendPluginRegistry::Add<MozCheckAction> X("moz-check", |
| "check moz action"); |