CustomMatchers.h (18983B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 #ifndef CustomMatchers_h__ 6 #define CustomMatchers_h__ 7 8 #include "MemMoveAnnotation.h" 9 #include "Utils.h" 10 11 #if CLANG_VERSION_FULL >= 1300 12 // Starting with clang-13 Expr::isRValue has been renamed to Expr::isPRValue 13 #define isRValue isPRValue 14 #endif 15 16 namespace clang { 17 namespace ast_matchers { 18 19 /// This matcher will match any function declaration that is declared as a heap 20 /// allocator. 21 AST_MATCHER(VarDecl, hasMozGlobalType) { 22 if(auto * TD = Node.getType().getTypePtr()->getAsTagDecl()) { 23 if(hasCustomAttribute<moz_global_class>(TD)) 24 return true; 25 } 26 return false; 27 } 28 29 /// Match any variable declared with the MOZ_RUNINIT qualifier. 30 AST_MATCHER(VarDecl, isMozGlobal) { 31 return hasCustomAttribute<moz_global_var>(&Node); 32 } 33 34 /// Match any variable declared with the MOZ_GLOBINIT qualifier. 35 AST_MATCHER(VarDecl, isMozGenerated) { 36 return hasCustomAttribute<moz_generated>(&Node); 37 } 38 39 /// Match any variable declared with the constinit qualifier. 40 AST_MATCHER(VarDecl, hasConstInitAttr) { 41 return Node.hasAttr<ConstInitAttr>(); 42 } 43 44 /// This matcher will match any function declaration that is declared as a heap 45 /// allocator. 46 AST_MATCHER(FunctionDecl, heapAllocator) { 47 return hasCustomAttribute<moz_heap_allocator>(&Node); 48 } 49 50 /// This matcher will match any declaration that is marked as not accepting 51 /// arithmetic expressions in its arguments. 52 AST_MATCHER(Decl, noArithmeticExprInArgs) { 53 return hasCustomAttribute<moz_no_arith_expr_in_arg>(&Node); 54 } 55 56 /// This matcher will match any C++ class that is marked as having a trivial 57 /// constructor and destructor. 58 AST_MATCHER(CXXRecordDecl, hasTrivialCtorDtor) { 59 return hasCustomAttribute<moz_trivial_ctor_dtor>(&Node); 60 } 61 62 /// This matcher will match any C++ class that is marked as having a trivial 63 /// destructor. 64 AST_MATCHER(CXXRecordDecl, hasTrivialDtor) { 65 return hasCustomAttribute<moz_trivial_dtor>(&Node); 66 } 67 68 AST_MATCHER(CXXConstructExpr, allowsTemporary) { 69 return hasCustomAttribute<moz_allow_temporary>(Node.getConstructor()); 70 } 71 72 /// This matcher will match lvalue-ref-qualified methods. 73 AST_MATCHER(CXXMethodDecl, isLValueRefQualified) { 74 return Node.getRefQualifier() == RQ_LValue; 75 } 76 77 /// This matcher will match rvalue-ref-qualified methods. 78 AST_MATCHER(CXXMethodDecl, isRValueRefQualified) { 79 return Node.getRefQualifier() == RQ_RValue; 80 } 81 82 AST_POLYMORPHIC_MATCHER(isFirstParty, 83 AST_POLYMORPHIC_SUPPORTED_TYPES(Decl, Stmt)) { 84 return !inThirdPartyPath(&Node, &Finder->getASTContext()) && 85 !ASTIsInSystemHeader(Finder->getASTContext(), Node); 86 } 87 88 AST_MATCHER(DeclaratorDecl, isNotSpiderMonkey) { 89 // Detect SpiderMonkey path. Not as strict as isFirstParty, but this is 90 // expected to disappear soon by getting a common style guide between DOM and 91 // SpiderMonkey. 92 std::string Path = Node.getBeginLoc().printToString( 93 Finder->getASTContext().getSourceManager()); 94 return Path.find("js") == std::string::npos && 95 Path.find("xpc") == std::string::npos && 96 Path.find("XPC") == std::string::npos; 97 } 98 99 /// This matcher will match any declaration with an initializer that's 100 /// considered as constant by the compiler. 101 AST_MATCHER(VarDecl, hasConstantInitializer) { 102 if(Node.hasInit()) 103 return Node.getInit()->isConstantInitializer( 104 Finder->getASTContext(), 105 Node.getType()->isReferenceType()); 106 else 107 return Node.getType().isTrivialType(Finder->getASTContext()); 108 } 109 110 /// This matcher will match temporary expressions. 111 /// We need this matcher for compatibility with clang 3.* (clang 4 and above 112 /// insert a MaterializeTemporaryExpr everywhere). 113 AST_MATCHER(Expr, isTemporary) { 114 return Node.isRValue() || Node.isXValue() || 115 isa<MaterializeTemporaryExpr>(&Node); 116 } 117 118 /// This matcher will match any method declaration that is marked as returning 119 /// a pointer deleted by the destructor of the class. 120 AST_MATCHER(CXXMethodDecl, noDanglingOnTemporaries) { 121 return hasCustomAttribute<moz_no_dangling_on_temporaries>(&Node); 122 } 123 124 /// This matcher will match any function declaration that is marked to prohibit 125 /// calling AddRef or Release on its return value. 126 AST_MATCHER(FunctionDecl, hasNoAddRefReleaseOnReturnAttr) { 127 return hasCustomAttribute<moz_no_addref_release_on_return>(&Node); 128 } 129 130 /// This matcher will match any function declaration that is marked as being 131 /// allowed to run script. 132 AST_MATCHER(FunctionDecl, hasCanRunScriptAnnotation) { 133 return hasCustomAttribute<moz_can_run_script>(&Node); 134 } 135 136 /// This matcher will match all arithmetic binary operators. 137 AST_MATCHER(BinaryOperator, binaryArithmeticOperator) { 138 BinaryOperatorKind OpCode = Node.getOpcode(); 139 return OpCode == BO_Mul || OpCode == BO_Div || OpCode == BO_Rem || 140 OpCode == BO_Add || OpCode == BO_Sub || OpCode == BO_Shl || 141 OpCode == BO_Shr || OpCode == BO_And || OpCode == BO_Xor || 142 OpCode == BO_Or || OpCode == BO_MulAssign || OpCode == BO_DivAssign || 143 OpCode == BO_RemAssign || OpCode == BO_AddAssign || 144 OpCode == BO_SubAssign || OpCode == BO_ShlAssign || 145 OpCode == BO_ShrAssign || OpCode == BO_AndAssign || 146 OpCode == BO_XorAssign || OpCode == BO_OrAssign; 147 } 148 149 /// This matcher will match all arithmetic unary operators. 150 AST_MATCHER(UnaryOperator, unaryArithmeticOperator) { 151 UnaryOperatorKind OpCode = Node.getOpcode(); 152 return OpCode == UO_PostInc || OpCode == UO_PostDec || OpCode == UO_PreInc || 153 OpCode == UO_PreDec || OpCode == UO_Plus || OpCode == UO_Minus || 154 OpCode == UO_Not; 155 } 156 157 /// This matcher will match the unary dereference operator 158 AST_MATCHER(UnaryOperator, unaryDereferenceOperator) { 159 UnaryOperatorKind OpCode = Node.getOpcode(); 160 return OpCode == UO_Deref; 161 } 162 163 /// This matcher will match == and != binary operators. 164 AST_MATCHER(BinaryOperator, binaryEqualityOperator) { 165 BinaryOperatorKind OpCode = Node.getOpcode(); 166 return OpCode == BO_EQ || OpCode == BO_NE; 167 } 168 169 /// This matcher will match comma operator. 170 AST_MATCHER(BinaryOperator, binaryCommaOperator) { 171 BinaryOperatorKind OpCode = Node.getOpcode(); 172 return OpCode == BO_Comma; 173 } 174 175 /// This matcher will match floating point types. 176 AST_MATCHER(QualType, isFloat) { return Node->isRealFloatingType(); } 177 178 /// This matcher will match locations in system headers. This is adopted from 179 /// isExpansionInSystemHeader in newer clangs, but modified in order to work 180 /// with old clangs that we use on infra. 181 AST_POLYMORPHIC_MATCHER(isInSystemHeader, 182 AST_POLYMORPHIC_SUPPORTED_TYPES(Decl, Stmt)) { 183 return ASTIsInSystemHeader(Finder->getASTContext(), Node); 184 } 185 186 /// This matcher will match a file "gtest-port.h". The file contains 187 /// known fopen usages that are OK. 188 AST_MATCHER(CallExpr, isInWhitelistForFopenUsage) { 189 static const char Whitelist[] = "gtest-port.h"; 190 SourceLocation Loc = Node.getBeginLoc(); 191 StringRef FileName = 192 getFilename(Finder->getASTContext().getSourceManager(), Loc); 193 194 return *llvm::sys::path::rbegin(FileName) == Whitelist; 195 } 196 197 /// This matcher will match a list of files. These files contain 198 /// known NaN-testing expressions which we would like to whitelist. 199 AST_MATCHER(BinaryOperator, isInWhitelistForNaNExpr) { 200 const char *whitelist[] = {"SkScalar.h", "json_writer.cpp", "State.cpp"}; 201 202 SourceLocation Loc = Node.getOperatorLoc(); 203 StringRef FileName = 204 getFilename(Finder->getASTContext().getSourceManager(), Loc); 205 for (auto itr = std::begin(whitelist); itr != std::end(whitelist); itr++) { 206 if (*llvm::sys::path::rbegin(FileName) == *itr) { 207 return true; 208 } 209 } 210 211 return false; 212 } 213 214 AST_MATCHER(CallExpr, isInWhiteListForPrincipalGetUri) { 215 const auto Whitelist = {"nsIPrincipal.h", "BasePrincipal.cpp", 216 "ContentPrincipal.cpp"}; 217 SourceLocation Loc = Node.getBeginLoc(); 218 StringRef Filename = 219 getFilename(Finder->getASTContext().getSourceManager(), Loc); 220 221 for (auto Exclusion : Whitelist) { 222 if (Filename.find(Exclusion) != std::string::npos) { 223 return true; 224 } 225 } 226 return false; 227 } 228 229 /// This matcher will match a list of files which contain NS_NewNamedThread 230 /// code or names of existing threads that we would like to ignore. 231 AST_MATCHER(CallExpr, isInAllowlistForThreads) { 232 233 // Get the source location of the call. 234 SourceLocation Loc = Node.getRParenLoc(); 235 StringRef FileName = 236 getFilename(Finder->getASTContext().getSourceManager(), Loc); 237 238 const auto rbegin = [](StringRef s) { return llvm::sys::path::rbegin(s); }; 239 const auto rend = [](StringRef s) { return llvm::sys::path::rend(s); }; 240 241 // Files in the allowlist are (definitionally) explicitly permitted to create 242 // new threads. 243 for (auto thread_file : allow_thread_files) { 244 // All the provided path-elements must match. 245 const bool match = [&] { 246 auto it1 = rbegin(FileName), it2 = rbegin(thread_file), 247 end1 = rend(FileName), end2 = rend(thread_file); 248 for (; it2 != end2; ++it1, ++it2) { 249 if (it1 == end1 || *it1 != *it2) { 250 return false; 251 } 252 } 253 return true; 254 }(); 255 if (match) { 256 return true; 257 } 258 } 259 260 // Check the first arg (the name of the thread). 261 const StringLiteral *nameArg = 262 dyn_cast<StringLiteral>(Node.getArg(0)->IgnoreImplicit()); 263 if (nameArg) { 264 const StringRef name = nameArg->getString(); 265 for (auto thread_name : allow_thread_names) { 266 if (name == thread_name) { 267 return true; 268 } 269 } 270 } 271 272 return false; 273 } 274 275 /// This matcher will match all accesses to AddRef or Release methods. 276 AST_MATCHER(MemberExpr, isAddRefOrRelease) { 277 ValueDecl *Member = Node.getMemberDecl(); 278 CXXMethodDecl *Method = dyn_cast<CXXMethodDecl>(Member); 279 if (Method) { 280 const auto &Name = getNameChecked(Method); 281 return Name == "AddRef" || Name == "Release"; 282 } 283 return false; 284 } 285 286 /// This matcher will select classes which are refcounted AND have an mRefCnt 287 /// member. 288 AST_MATCHER(CXXRecordDecl, hasRefCntMember) { 289 return isClassRefCounted(&Node) && getClassRefCntMember(&Node); 290 } 291 292 /// This matcher will select classes which are refcounted. 293 AST_MATCHER(CXXRecordDecl, isRefCounted) { return isClassRefCounted(&Node); } 294 295 AST_MATCHER(QualType, hasVTable) { return typeHasVTable(Node); } 296 297 AST_MATCHER(CXXRecordDecl, hasNeedsNoVTableTypeAttr) { 298 return hasCustomAttribute<moz_needs_no_vtable_type>(&Node); 299 } 300 301 /// This matcher will select classes which are non-memmovable 302 AST_MATCHER(QualType, isNonMemMovable) { 303 return NonMemMovable.hasEffectiveAnnotation(Node); 304 } 305 306 /// This matcher will select classes which require a memmovable template arg 307 AST_MATCHER(CXXRecordDecl, needsMemMovableTemplateArg) { 308 return hasCustomAttribute<moz_needs_memmovable_type>(&Node); 309 } 310 311 /// This matcher will select classes which require all members to be memmovable 312 AST_MATCHER(CXXRecordDecl, needsMemMovableMembers) { 313 return hasCustomAttribute<moz_needs_memmovable_members>(&Node); 314 } 315 316 AST_MATCHER(CXXConstructorDecl, isInterestingImplicitCtor) { 317 const CXXConstructorDecl *Declaration = Node.getCanonicalDecl(); 318 return 319 // Skip constructors in system headers 320 !ASTIsInSystemHeader(Declaration->getASTContext(), *Declaration) && 321 // Skip ignored namespaces and paths 322 !isInIgnoredNamespaceForImplicitCtor(Declaration) && 323 !inThirdPartyPath(Declaration) && 324 // We only want Converting constructors 325 Declaration->isConvertingConstructor(false) && 326 // We don't want copy of move constructors, as those are allowed to be 327 // implicit 328 !Declaration->isCopyOrMoveConstructor() && 329 // We don't want inheriting constructors, since using declarations can't 330 // have attributes 331 !Declaration->isInheritingConstructor() && 332 // We don't want deleted constructors. 333 !Declaration->isDeleted(); 334 } 335 336 AST_MATCHER_P(Expr, ignoreTrivials, internal::Matcher<Expr>, InnerMatcher) { 337 return InnerMatcher.matches(*IgnoreTrivials(&Node), Finder, Builder); 338 } 339 340 // Takes two matchers: the first one is a condition; the second is a matcher to 341 // be applied once we are done unwrapping trivials. While the condition does 342 // not match and we're looking at a trivial, will keep unwrapping the trivial 343 // and trying again. Once the condition matches, we will go ahead and unwrap all 344 // trivials and apply the inner matcher to the result. 345 // 346 // The expected use here is if we want to condition a match on some typecheck 347 // but apply the match to only non-trivials, because there are trivials (e.g. 348 // casts) that can change types. 349 AST_MATCHER_P2(Expr, ignoreTrivialsConditional, internal::Matcher<Expr>, 350 Condition, internal::Matcher<Expr>, InnerMatcher) { 351 const Expr *node = &Node; 352 while (true) { 353 if (Condition.matches(*node, Finder, Builder)) { 354 return InnerMatcher.matches(*IgnoreTrivials(node), Finder, Builder); 355 } 356 const Expr *newNode = MaybeSkipOneTrivial(node); 357 if (newNode == node) { 358 return false; 359 } 360 node = newNode; 361 } 362 } 363 364 // We can't call this "isImplicit" since it clashes with an existing matcher in 365 // clang. 366 AST_MATCHER(CXXConstructorDecl, isMarkedImplicit) { 367 return hasCustomAttribute<moz_implicit>(&Node); 368 } 369 370 AST_MATCHER(CXXRecordDecl, isConcreteClass) { return !Node.isAbstract(); } 371 372 AST_MATCHER(QualType, autoNonAutoableType) { 373 if (const AutoType *T = Node->getContainedAutoType()) { 374 if (const CXXRecordDecl *Rec = T->getAsCXXRecordDecl()) { 375 return hasCustomAttribute<moz_non_autoable>(Rec); 376 } 377 } 378 return false; 379 } 380 381 AST_MATCHER(CXXConstructorDecl, isExplicitMoveConstructor) { 382 return Node.isExplicit() && Node.isMoveConstructor(); 383 } 384 385 AST_MATCHER(CXXConstructorDecl, isCompilerProvidedCopyConstructor) { 386 return !Node.isUserProvided() && Node.isCopyConstructor(); 387 } 388 389 AST_MATCHER(CallExpr, isAssertAssignmentTestFunc) { 390 static const std::string AssertName = "MOZ_AssertAssignmentTest"; 391 const FunctionDecl *Method = Node.getDirectCallee(); 392 393 return Method && Method->getDeclName().isIdentifier() && 394 Method->getName() == AssertName; 395 } 396 397 AST_MATCHER(CallExpr, isSnprintfLikeFunc) { 398 static const std::string Snprintf = "snprintf"; 399 static const std::string Vsnprintf = "vsnprintf"; 400 const FunctionDecl *Func = Node.getDirectCallee(); 401 402 if (!Func || isa<CXXMethodDecl>(Func)) { 403 return false; 404 } 405 406 StringRef Name = getNameChecked(Func); 407 if (Name != Snprintf && Name != Vsnprintf) { 408 return false; 409 } 410 411 return !inThirdPartyPath(Node.getBeginLoc(), 412 Finder->getASTContext().getSourceManager()) && 413 !isIgnoredPathForSprintfLiteral( 414 &Node, Finder->getASTContext().getSourceManager()); 415 } 416 417 AST_MATCHER(CXXRecordDecl, isLambdaDecl) { return Node.isLambda(); } 418 419 AST_MATCHER(QualType, isRefPtr) { return typeIsRefPtr(Node); } 420 421 AST_MATCHER(QualType, isSmartPtrToRefCounted) { 422 auto *D = getNonTemplateSpecializedCXXRecordDecl(Node); 423 if (!D) { 424 return false; 425 } 426 427 D = D->getCanonicalDecl(); 428 429 return D && hasCustomAttribute<moz_is_smartptr_to_refcounted>(D); 430 } 431 432 AST_MATCHER(ClassTemplateSpecializationDecl, isSmartPtrToRefCountedDecl) { 433 auto *D = dyn_cast_or_null<CXXRecordDecl>( 434 Node.getSpecializedTemplate()->getTemplatedDecl()); 435 if (!D) { 436 return false; 437 } 438 439 D = D->getCanonicalDecl(); 440 441 return D && hasCustomAttribute<moz_is_smartptr_to_refcounted>(D); 442 } 443 444 AST_MATCHER(CXXRecordDecl, hasBaseClasses) { 445 const CXXRecordDecl *Decl = Node.getCanonicalDecl(); 446 447 // Must have definition and should inherit other classes 448 return Decl && Decl->hasDefinition() && Decl->getNumBases(); 449 } 450 451 AST_MATCHER(CXXMethodDecl, isRequiredBaseMethod) { 452 const CXXMethodDecl *Decl = Node.getCanonicalDecl(); 453 return Decl && hasCustomAttribute<moz_required_base_method>(Decl); 454 } 455 456 AST_MATCHER(CXXMethodDecl, isNonVirtual) { 457 const CXXMethodDecl *Decl = Node.getCanonicalDecl(); 458 return Decl && !Decl->isVirtual(); 459 } 460 461 AST_MATCHER(FunctionDecl, isMozMustReturnFromCaller) { 462 const FunctionDecl *Decl = Node.getCanonicalDecl(); 463 return Decl && 464 hasCustomAttribute<moz_must_return_from_caller_if_this_is_arg>(Decl); 465 } 466 467 /// This matcher will select default args which have nullptr as the value. 468 AST_MATCHER(CXXDefaultArgExpr, isNullDefaultArg) { 469 const Expr *Expr = Node.getExpr(); 470 return Expr && Expr->isNullPointerConstant(Finder->getASTContext(), 471 Expr::NPC_NeverValueDependent); 472 } 473 474 AST_MATCHER(UsingDirectiveDecl, isUsingNamespaceMozillaJava) { 475 const NamespaceDecl *Namespace = Node.getNominatedNamespace(); 476 const std::string &FQName = Namespace->getQualifiedNameAsString(); 477 478 static const char NAMESPACE[] = "mozilla::java"; 479 static const char PREFIX[] = "mozilla::java::"; 480 481 // We match both the `mozilla::java` namespace itself as well as any other 482 // namespaces contained within the `mozilla::java` namespace. 483 return !FQName.compare(NAMESPACE) || 484 !FQName.compare(0, sizeof(PREFIX) - 1, PREFIX); 485 } 486 487 AST_MATCHER(MemberExpr, hasKnownLiveAnnotation) { 488 ValueDecl *Member = Node.getMemberDecl(); 489 FieldDecl *Field = dyn_cast<FieldDecl>(Member); 490 return Field && hasCustomAttribute<moz_known_live>(Field); 491 } 492 493 #define GENERATE_JSTYPEDEF_PAIR(templateName) \ 494 {templateName "Function", templateName "<JSFunction*>"}, \ 495 {templateName "Id", templateName "<JS::PropertyKey>"}, \ 496 {templateName "Object", templateName "<JSObject*>"}, \ 497 {templateName "Script", templateName "<JSScript*>"}, \ 498 {templateName "String", templateName "<JSString*>"}, \ 499 {templateName "Symbol", templateName "<JS::Symbol*>"}, \ 500 {templateName "BigInt", templateName "<JS::BigInt*>"}, \ 501 {templateName "Value", templateName "<JS::Value>"}, \ 502 {templateName "ValueVector", templateName "Vector<JS::Value>"}, \ 503 {templateName "ObjectVector", templateName "Vector<JSObject*>"}, { \ 504 templateName "IdVector", templateName "Vector<JS::PropertyKey>" \ 505 } 506 507 static const char *const JSHandleRootedTypedefMap[][2] = { 508 GENERATE_JSTYPEDEF_PAIR("JS::Handle"), 509 GENERATE_JSTYPEDEF_PAIR("JS::MutableHandle"), 510 GENERATE_JSTYPEDEF_PAIR("JS::Rooted"), 511 // Technically there is no PersistentRootedValueVector, and that's okay 512 GENERATE_JSTYPEDEF_PAIR("JS::PersistentRooted"), 513 }; 514 515 AST_MATCHER(DeclaratorDecl, isUsingJSHandleRootedTypedef) { 516 QualType Type = Node.getType(); 517 std::string TypeName = Type.getAsString(); 518 for (auto &pair : JSHandleRootedTypedefMap) { 519 if (!TypeName.compare(pair[0])) { 520 return true; 521 } 522 } 523 return false; 524 } 525 526 } // namespace ast_matchers 527 } // namespace clang 528 529 #undef isRValue 530 #endif