/* 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/. */ #ifndef CustomMatchers_h__ #define CustomMatchers_h__ #include "MemMoveAnnotation.h" #include "Utils.h" #if CLANG_VERSION_FULL >= 1300 // Starting with clang-13 Expr::isRValue has been renamed to Expr::isPRValue #define isRValue isPRValue #endif namespace clang { namespace ast_matchers { /// This matcher will match any function declaration that is declared as a heap /// allocator. AST_MATCHER(FunctionDecl, heapAllocator) { return hasCustomAttribute(&Node); } /// This matcher will match any declaration that is marked as not accepting /// arithmetic expressions in its arguments. AST_MATCHER(Decl, noArithmeticExprInArgs) { return hasCustomAttribute(&Node); } /// This matcher will match any C++ class that is marked as having a trivial /// constructor and destructor. AST_MATCHER(CXXRecordDecl, hasTrivialCtorDtor) { return hasCustomAttribute(&Node); } /// This matcher will match any C++ class that is marked as having a trivial /// destructor. AST_MATCHER(CXXRecordDecl, hasTrivialDtor) { return hasCustomAttribute(&Node); } AST_MATCHER(CXXConstructExpr, allowsTemporary) { return hasCustomAttribute(Node.getConstructor()); } /// This matcher will match lvalue-ref-qualified methods. AST_MATCHER(CXXMethodDecl, isLValueRefQualified) { return Node.getRefQualifier() == RQ_LValue; } /// This matcher will match rvalue-ref-qualified methods. AST_MATCHER(CXXMethodDecl, isRValueRefQualified) { return Node.getRefQualifier() == RQ_RValue; } AST_POLYMORPHIC_MATCHER(isFirstParty, AST_POLYMORPHIC_SUPPORTED_TYPES(Decl, Stmt)) { return !inThirdPartyPath(&Node, &Finder->getASTContext()) && !ASTIsInSystemHeader(Finder->getASTContext(), Node); } AST_MATCHER(DeclaratorDecl, isNotSpiderMonkey) { // Detect SpiderMonkey path. Not as strict as isFirstParty, but this is // expected to disappear soon by getting a common style guide between DOM and // SpiderMonkey. std::string Path = Node.getBeginLoc().printToString( Finder->getASTContext().getSourceManager()); return Path.find("js") == std::string::npos && Path.find("xpc") == std::string::npos && Path.find("XPC") == std::string::npos; } /// This matcher will match temporary expressions. /// We need this matcher for compatibility with clang 3.* (clang 4 and above /// insert a MaterializeTemporaryExpr everywhere). AST_MATCHER(Expr, isTemporary) { return Node.isRValue() || Node.isXValue() || isa(&Node); } /// This matcher will match any method declaration that is marked as returning /// a pointer deleted by the destructor of the class. AST_MATCHER(CXXMethodDecl, noDanglingOnTemporaries) { return hasCustomAttribute(&Node); } /// 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 hasCustomAttribute(&Node); } /// This matcher will match any function declaration that is marked as being /// allowed to run script. AST_MATCHER(FunctionDecl, hasCanRunScriptAnnotation) { return hasCustomAttribute(&Node); } /// 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 the unary dereference operator AST_MATCHER(UnaryOperator, unaryDereferenceOperator) { UnaryOperatorKind OpCode = Node.getOpcode(); return OpCode == UO_Deref; } /// 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 comma operator. AST_MATCHER(BinaryOperator, binaryCommaOperator) { BinaryOperatorKind OpCode = Node.getOpcode(); return OpCode == BO_Comma; } /// 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_POLYMORPHIC_MATCHER(isInSystemHeader, AST_POLYMORPHIC_SUPPORTED_TYPES(Decl, Stmt)) { return ASTIsInSystemHeader(Finder->getASTContext(), Node); } /// This matcher will match a file "gtest-port.h". The file contains /// known fopen usages that are OK. AST_MATCHER(CallExpr, isInWhitelistForFopenUsage) { static const char Whitelist[] = "gtest-port.h"; SourceLocation Loc = Node.getBeginLoc(); StringRef FileName = getFilename(Finder->getASTContext().getSourceManager(), Loc); return llvm::sys::path::rbegin(FileName)->equals(Whitelist); } /// 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", "State.cpp"}; SourceLocation Loc = Node.getOperatorLoc(); StringRef FileName = getFilename(Finder->getASTContext().getSourceManager(), Loc); for (auto itr = std::begin(whitelist); itr != std::end(whitelist); itr++) { if (llvm::sys::path::rbegin(FileName)->equals(*itr)) { return true; } } return false; } AST_MATCHER(CallExpr, isInWhiteListForPrincipalGetUri) { const auto Whitelist = {"nsIPrincipal.h", "BasePrincipal.cpp", "ContentPrincipal.cpp"}; SourceLocation Loc = Node.getBeginLoc(); StringRef Filename = getFilename(Finder->getASTContext().getSourceManager(), Loc); for (auto Exclusion : Whitelist) { if (Filename.find(Exclusion) != std::string::npos) { return true; } } return false; } /// This matcher will match a list of files which contain NS_NewNamedThread /// code or names of existing threads that we would like to ignore. AST_MATCHER(CallExpr, isInAllowlistForThreads) { // Get the source location of the call. SourceLocation Loc = Node.getRParenLoc(); StringRef FileName = getFilename(Finder->getASTContext().getSourceManager(), Loc); const auto rbegin = [](StringRef s) { return llvm::sys::path::rbegin(s); }; const auto rend = [](StringRef s) { return llvm::sys::path::rend(s); }; // Files in the allowlist are (definitionally) explicitly permitted to create // new threads. for (auto thread_file : allow_thread_files) { // All the provided path-elements must match. const bool match = [&] { auto it1 = rbegin(FileName), it2 = rbegin(thread_file), end1 = rend(FileName), end2 = rend(thread_file); for (; it2 != end2; ++it1, ++it2) { if (it1 == end1 || !it1->equals(*it2)) { return false; } } return true; }(); if (match) { return true; } } // Check the first arg (the name of the thread). const StringLiteral *nameArg = dyn_cast(Node.getArg(0)->IgnoreImplicit()); if (nameArg) { const StringRef name = nameArg->getString(); for (auto thread_name : allow_thread_names) { if (name.equals(thread_name)) { 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 AND have an mRefCnt /// member. AST_MATCHER(CXXRecordDecl, hasRefCntMember) { return isClassRefCounted(&Node) && getClassRefCntMember(&Node); } /// This matcher will select classes which are refcounted. AST_MATCHER(CXXRecordDecl, isRefCounted) { return isClassRefCounted(&Node); } AST_MATCHER(QualType, hasVTable) { return typeHasVTable(Node); } AST_MATCHER(CXXRecordDecl, hasNeedsNoVTableTypeAttr) { return hasCustomAttribute(&Node); } /// 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 hasCustomAttribute(&Node); } /// This matcher will select classes which require all members to be memmovable AST_MATCHER(CXXRecordDecl, needsMemMovableMembers) { return hasCustomAttribute(&Node); } AST_MATCHER(CXXConstructorDecl, isInterestingImplicitCtor) { const CXXConstructorDecl *Declaration = Node.getCanonicalDecl(); return // Skip constructors in system headers !ASTIsInSystemHeader(Declaration->getASTContext(), *Declaration) && // Skip ignored namespaces and paths !isInIgnoredNamespaceForImplicitCtor(Declaration) && !inThirdPartyPath(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 inheriting constructors, since using declarations can't // have attributes !Declaration->isInheritingConstructor() && // We don't want deleted constructors. !Declaration->isDeleted(); } AST_MATCHER_P(Expr, ignoreTrivials, internal::Matcher, InnerMatcher) { return InnerMatcher.matches(*IgnoreTrivials(&Node), Finder, Builder); } // Takes two matchers: the first one is a condition; the second is a matcher to // be applied once we are done unwrapping trivials. While the condition does // not match and we're looking at a trivial, will keep unwrapping the trivial // and trying again. Once the condition matches, we will go ahead and unwrap all // trivials and apply the inner matcher to the result. // // The expected use here is if we want to condition a match on some typecheck // but apply the match to only non-trivials, because there are trivials (e.g. // casts) that can change types. AST_MATCHER_P2(Expr, ignoreTrivialsConditional, internal::Matcher, Condition, internal::Matcher, InnerMatcher) { const Expr *node = &Node; while (true) { if (Condition.matches(*node, Finder, Builder)) { return InnerMatcher.matches(*IgnoreTrivials(node), Finder, Builder); } const Expr *newNode = MaybeSkipOneTrivial(node); if (newNode == node) { return false; } node = newNode; } } // We can't call this "isImplicit" since it clashes with an existing matcher in // clang. AST_MATCHER(CXXConstructorDecl, isMarkedImplicit) { return hasCustomAttribute(&Node); } AST_MATCHER(CXXRecordDecl, isConcreteClass) { return !Node.isAbstract(); } AST_MATCHER(QualType, autoNonAutoableType) { if (const AutoType *T = Node->getContainedAutoType()) { if (const CXXRecordDecl *Rec = T->getAsCXXRecordDecl()) { return hasCustomAttribute(Rec); } } 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 !inThirdPartyPath(Node.getBeginLoc(), Finder->getASTContext().getSourceManager()) && !isIgnoredPathForSprintfLiteral( &Node, Finder->getASTContext().getSourceManager()); } AST_MATCHER(CXXRecordDecl, isLambdaDecl) { return Node.isLambda(); } AST_MATCHER(QualType, isRefPtr) { return typeIsRefPtr(Node); } AST_MATCHER(QualType, isSmartPtrToRefCounted) { auto *D = getNonTemplateSpecializedCXXRecordDecl(Node); if (!D) { return false; } D = D->getCanonicalDecl(); return D && hasCustomAttribute(D); } AST_MATCHER(ClassTemplateSpecializationDecl, isSmartPtrToRefCountedDecl) { auto *D = dyn_cast_or_null( Node.getSpecializedTemplate()->getTemplatedDecl()); if (!D) { return false; } D = D->getCanonicalDecl(); return D && hasCustomAttribute(D); } 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 && hasCustomAttribute(Decl); } AST_MATCHER(CXXMethodDecl, isNonVirtual) { const CXXMethodDecl *Decl = Node.getCanonicalDecl(); return Decl && !Decl->isVirtual(); } AST_MATCHER(FunctionDecl, isMozMustReturnFromCaller) { const FunctionDecl *Decl = Node.getCanonicalDecl(); return Decl && hasCustomAttribute(Decl); } AST_MATCHER(FunctionDecl, isMozTemporaryLifetimeBound) { const FunctionDecl *Decl = Node.getCanonicalDecl(); return Decl && hasCustomAttribute(Decl); } /// This matcher will select default args which have nullptr as the value. AST_MATCHER(CXXDefaultArgExpr, isNullDefaultArg) { const Expr *Expr = Node.getExpr(); return Expr && Expr->isNullPointerConstant(Finder->getASTContext(), Expr::NPC_NeverValueDependent); } AST_MATCHER(UsingDirectiveDecl, isUsingNamespaceMozillaJava) { const NamespaceDecl *Namespace = Node.getNominatedNamespace(); const std::string &FQName = Namespace->getQualifiedNameAsString(); static const char NAMESPACE[] = "mozilla::java"; static const char PREFIX[] = "mozilla::java::"; // We match both the `mozilla::java` namespace itself as well as any other // namespaces contained within the `mozilla::java` namespace. return !FQName.compare(NAMESPACE) || !FQName.compare(0, sizeof(PREFIX) - 1, PREFIX); } AST_MATCHER(MemberExpr, hasKnownLiveAnnotation) { ValueDecl *Member = Node.getMemberDecl(); FieldDecl *Field = dyn_cast(Member); return Field && hasCustomAttribute(Field); } #define GENERATE_JSTYPEDEF_PAIR(templateName) \ {templateName "Function", templateName ""}, \ {templateName "Id", templateName ""}, \ {templateName "Object", templateName ""}, \ {templateName "Script", templateName ""}, \ {templateName "String", templateName ""}, \ {templateName "Symbol", templateName ""}, \ {templateName "BigInt", templateName ""}, \ {templateName "Value", templateName ""}, \ {templateName "ValueVector", templateName "Vector"}, \ {templateName "ObjectVector", templateName "Vector"}, { \ templateName "IdVector", templateName "Vector" \ } static const char *const JSHandleRootedTypedefMap[][2] = { GENERATE_JSTYPEDEF_PAIR("JS::Handle"), GENERATE_JSTYPEDEF_PAIR("JS::MutableHandle"), GENERATE_JSTYPEDEF_PAIR("JS::Rooted"), // Technically there is no PersistentRootedValueVector, and that's okay GENERATE_JSTYPEDEF_PAIR("JS::PersistentRooted"), }; AST_MATCHER(DeclaratorDecl, isUsingJSHandleRootedTypedef) { QualType Type = Node.getType(); std::string TypeName = Type.getAsString(); for (auto &pair : JSHandleRootedTypedefMap) { if (!TypeName.compare(pair[0])) { return true; } } return false; } } // namespace ast_matchers } // namespace clang #undef isRValue #endif