| 1 | //===- CocoaConventions.h - Special handling of Cocoa conventions -*- C++ -*--// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | // |
| 9 | // This file implements cocoa naming convention analysis. |
| 10 | // |
| 11 | //===----------------------------------------------------------------------===// |
| 12 | |
| 13 | #include "clang/Analysis/DomainSpecific/CocoaConventions.h" |
| 14 | #include "clang/AST/Decl.h" |
| 15 | #include "clang/AST/DeclObjC.h" |
| 16 | #include "clang/AST/Type.h" |
| 17 | #include "clang/Basic/CharInfo.h" |
| 18 | #include "llvm/Support/ErrorHandling.h" |
| 19 | |
| 20 | using namespace clang; |
| 21 | using namespace ento; |
| 22 | |
| 23 | bool cocoa::isRefType(QualType RetTy, StringRef Prefix, |
| 24 | StringRef Name) { |
| 25 | // Recursively walk the typedef stack, allowing typedefs of reference types. |
| 26 | while (const TypedefType *TD = RetTy->getAs<TypedefType>()) { |
| 27 | StringRef TDName = TD->getDecl()->getIdentifier()->getName(); |
| 28 | if (TDName.starts_with(Prefix) && TDName.ends_with(Suffix: "Ref" )) |
| 29 | return true; |
| 30 | // XPC unfortunately uses CF-style function names, but aren't CF types. |
| 31 | if (TDName.starts_with(Prefix: "xpc_" )) |
| 32 | return false; |
| 33 | RetTy = TD->getDecl()->getUnderlyingType(); |
| 34 | } |
| 35 | |
| 36 | if (Name.empty()) |
| 37 | return false; |
| 38 | |
| 39 | // Is the type void*? |
| 40 | const PointerType* PT = RetTy->castAs<PointerType>(); |
| 41 | if (!PT || !PT->getPointeeType().getUnqualifiedType()->isVoidType()) |
| 42 | return false; |
| 43 | |
| 44 | // Does the name start with the prefix? |
| 45 | return Name.starts_with(Prefix); |
| 46 | } |
| 47 | |
| 48 | /// Returns true when the passed-in type is a CF-style reference-counted |
| 49 | /// type from the DiskArbitration framework. |
| 50 | static bool isDiskArbitrationAPIRefType(QualType T) { |
| 51 | return cocoa::isRefType(RetTy: T, Prefix: "DADisk" ) || |
| 52 | cocoa::isRefType(RetTy: T, Prefix: "DADissenter" ) || |
| 53 | cocoa::isRefType(RetTy: T, Prefix: "DASessionRef" ); |
| 54 | } |
| 55 | |
| 56 | bool coreFoundation::isCFObjectRef(QualType T) { |
| 57 | return cocoa::isRefType(RetTy: T, Prefix: "CF" ) || // Core Foundation. |
| 58 | cocoa::isRefType(RetTy: T, Prefix: "CG" ) || // Core Graphics. |
| 59 | cocoa::isRefType(RetTy: T, Prefix: "CM" ) || // Core Media. |
| 60 | isDiskArbitrationAPIRefType(T); |
| 61 | } |
| 62 | |
| 63 | |
| 64 | bool cocoa::isCocoaObjectRef(QualType Ty) { |
| 65 | if (!Ty->isObjCObjectPointerType()) |
| 66 | return false; |
| 67 | |
| 68 | const ObjCObjectPointerType *PT = Ty->getAs<ObjCObjectPointerType>(); |
| 69 | |
| 70 | // Can be true for objects with the 'NSObject' attribute. |
| 71 | if (!PT) |
| 72 | return true; |
| 73 | |
| 74 | // We assume that id<..>, id, Class, and Class<..> all represent tracked |
| 75 | // objects. |
| 76 | if (PT->isObjCIdType() || PT->isObjCQualifiedIdType() || |
| 77 | PT->isObjCClassType() || PT->isObjCQualifiedClassType()) |
| 78 | return true; |
| 79 | |
| 80 | // Does the interface subclass NSObject? |
| 81 | // FIXME: We can memoize here if this gets too expensive. |
| 82 | const ObjCInterfaceDecl *ID = PT->getInterfaceDecl(); |
| 83 | |
| 84 | // Assume that anything declared with a forward declaration and no |
| 85 | // @interface subclasses NSObject. |
| 86 | if (!ID->hasDefinition()) |
| 87 | return true; |
| 88 | |
| 89 | for ( ; ID ; ID = ID->getSuperClass()) |
| 90 | if (ID->getIdentifier()->getName() == "NSObject" ) |
| 91 | return true; |
| 92 | |
| 93 | return false; |
| 94 | } |
| 95 | |
| 96 | bool coreFoundation::followsCreateRule(const FunctionDecl *fn) { |
| 97 | // For now, *just* base this on the function name, not on anything else. |
| 98 | |
| 99 | const IdentifierInfo *ident = fn->getIdentifier(); |
| 100 | if (!ident) return false; |
| 101 | StringRef functionName = ident->getName(); |
| 102 | |
| 103 | StringRef::iterator it = functionName.begin(); |
| 104 | StringRef::iterator start = it; |
| 105 | StringRef::iterator endI = functionName.end(); |
| 106 | |
| 107 | while (true) { |
| 108 | // Scan for the start of 'create' or 'copy'. |
| 109 | for ( ; it != endI ; ++it) { |
| 110 | // Search for the first character. It can either be 'C' or 'c'. |
| 111 | char ch = *it; |
| 112 | if (ch == 'C' || ch == 'c') { |
| 113 | // Make sure this isn't something like 'recreate' or 'Scopy'. |
| 114 | if (ch == 'c' && it != start && isLetter(c: *(it - 1))) |
| 115 | continue; |
| 116 | |
| 117 | ++it; |
| 118 | break; |
| 119 | } |
| 120 | } |
| 121 | |
| 122 | // Did we hit the end of the string? If so, we didn't find a match. |
| 123 | if (it == endI) |
| 124 | return false; |
| 125 | |
| 126 | // Scan for *lowercase* 'reate' or 'opy', followed by no lowercase |
| 127 | // character. |
| 128 | StringRef suffix = functionName.substr(Start: it - start); |
| 129 | if (suffix.starts_with(Prefix: "reate" )) { |
| 130 | it += 5; |
| 131 | } else if (suffix.starts_with(Prefix: "opy" )) { |
| 132 | it += 3; |
| 133 | } else { |
| 134 | // Keep scanning. |
| 135 | continue; |
| 136 | } |
| 137 | |
| 138 | if (it == endI || !isLowercase(c: *it)) |
| 139 | return true; |
| 140 | |
| 141 | // If we matched a lowercase character, it isn't the end of the |
| 142 | // word. Keep scanning. |
| 143 | } |
| 144 | } |
| 145 | |