1 //===- ObjCAutoreleaseWriteChecker.cpp ----------------------------*- C++ -*-==// 2 // 3 // The LLVM Compiler Infrastructure 4 // 5 // This file is distributed under the University of Illinois Open Source 6 // License. See LICENSE.TXT for details. 7 // 8 //===----------------------------------------------------------------------===// 9 // 10 // This file defines ObjCAutoreleaseWriteChecker which warns against writes 11 // into autoreleased out parameters which are likely to cause crashes. 12 // An example of a problematic write is a write to {@code error} in the example 13 // below: 14 // 15 // - (BOOL) mymethod:(NSError *__autoreleasing *)error list:(NSArray*) list { 16 // [list enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { 17 // NSString *myString = obj; 18 // if ([myString isEqualToString:@"error"] && error) 19 // *error = [NSError errorWithDomain:@"MyDomain" code:-1]; 20 // }]; 21 // return false; 22 // } 23 // 24 // Such code is very likely to crash due to the other queue autorelease pool 25 // begin able to free the error object. 26 // 27 //===----------------------------------------------------------------------===// 28 29 #include "ClangSACheckers.h" 30 #include "clang/ASTMatchers/ASTMatchFinder.h" 31 #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" 32 #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" 33 #include "clang/StaticAnalyzer/Core/Checker.h" 34 #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" 35 #include "llvm/ADT/Twine.h" 36 37 using namespace clang; 38 using namespace ento; 39 using namespace ast_matchers; 40 41 namespace { 42 43 const char *ProblematicWriteBind = "problematicwrite"; 44 const char *ParamBind = "parambind"; 45 const char *MethodBind = "methodbind"; 46 47 class ObjCAutoreleaseWriteChecker : public Checker<check::ASTCodeBody> { 48 public: 49 void checkASTCodeBody(const Decl *D, 50 AnalysisManager &AM, 51 BugReporter &BR) const; 52 private: 53 std::vector<std::string> SelectorsWithAutoreleasingPool = { 54 // Common to NSArray, NSSet, NSOrderedSet 55 "enumerateObjectsUsingBlock:", 56 "enumerateObjectsWithOptions:usingBlock:", 57 58 // Common to NSArray and NSOrderedSet 59 "enumerateObjectsAtIndexes:options:usingBlock:", 60 "indexOfObjectAtIndexes:options:passingTest:", 61 "indexesOfObjectsAtIndexes:options:passingTest:", 62 "indexOfObjectPassingTest:", 63 "indexOfObjectWithOptions:passingTest:", 64 "indexesOfObjectsPassingTest:", 65 "indexesOfObjectsWithOptions:passingTest:", 66 67 // NSDictionary 68 "enumerateKeysAndObjectsUsingBlock:", 69 "enumerateKeysAndObjectsWithOptions:usingBlock:", 70 "keysOfEntriesPassingTest:", 71 "keysOfEntriesWithOptions:passingTest:", 72 73 // NSSet 74 "objectsPassingTest:", 75 "objectsWithOptions:passingTest:", 76 "enumerateIndexPathsWithOptions:usingBlock:", 77 78 // NSIndexSet 79 "enumerateIndexesWithOptions:usingBlock:", 80 "enumerateIndexesUsingBlock:", 81 "enumerateIndexesInRange:options:usingBlock:", 82 "enumerateRangesUsingBlock:", 83 "enumerateRangesWithOptions:usingBlock:", 84 "enumerateRangesInRange:options:usingBlock:", 85 "indexPassingTest:", 86 "indexesPassingTest:", 87 "indexWithOptions:passingTest:", 88 "indexesWithOptions:passingTest:", 89 "indexInRange:options:passingTest:", 90 "indexesInRange:options:passingTest:" 91 }; 92 93 std::vector<std::string> FunctionsWithAutoreleasingPool = { 94 "dispatch_async", "dispatch_group_async", "dispatch_barrier_async"}; 95 }; 96 } 97 98 static inline std::vector<llvm::StringRef> toRefs(std::vector<std::string> V) { 99 return std::vector<llvm::StringRef>(V.begin(), V.end()); 100 } 101 102 static auto callsNames(std::vector<std::string> FunctionNames) 103 -> decltype(callee(functionDecl())) { 104 return callee(functionDecl(hasAnyName(toRefs(FunctionNames)))); 105 } 106 107 static void emitDiagnostics(BoundNodes &Match, const Decl *D, BugReporter &BR, 108 AnalysisManager &AM, 109 const ObjCAutoreleaseWriteChecker *Checker) { 110 AnalysisDeclContext *ADC = AM.getAnalysisDeclContext(D); 111 112 const auto *PVD = Match.getNodeAs<ParmVarDecl>(ParamBind); 113 assert(PVD); 114 QualType Ty = PVD->getType(); 115 if (Ty->getPointeeType().getObjCLifetime() != Qualifiers::OCL_Autoreleasing) 116 return; 117 const auto *SW = Match.getNodeAs<Expr>(ProblematicWriteBind); 118 bool IsMethod = Match.getNodeAs<ObjCMethodDecl>(MethodBind) != nullptr; 119 const char *Name = IsMethod ? "method" : "function"; 120 assert(SW); 121 BR.EmitBasicReport( 122 ADC->getDecl(), Checker, 123 /*Name=*/"Write to autoreleasing out parameter inside autorelease pool", 124 /*Category=*/"Memory", 125 (llvm::Twine("Write to autoreleasing out parameter inside ") + 126 "autorelease pool that may exit before " + Name + " returns; consider " 127 "writing first to a strong local variable declared outside of the block") 128 .str(), 129 PathDiagnosticLocation::createBegin(SW, BR.getSourceManager(), ADC), 130 SW->getSourceRange()); 131 } 132 133 void ObjCAutoreleaseWriteChecker::checkASTCodeBody(const Decl *D, 134 AnalysisManager &AM, 135 BugReporter &BR) const { 136 137 // Write into a binded object, e.g. *ParamBind = X. 138 auto WritesIntoM = binaryOperator( 139 hasLHS(unaryOperator( 140 hasOperatorName("*"), 141 hasUnaryOperand( 142 ignoringParenImpCasts( 143 declRefExpr(to(parmVarDecl(equalsBoundNode(ParamBind)))))) 144 )), 145 hasOperatorName("=") 146 ).bind(ProblematicWriteBind); 147 148 // WritesIntoM happens inside a block passed as an argument. 149 auto WritesInBlockM = hasAnyArgument(allOf( 150 hasType(hasCanonicalType(blockPointerType())), 151 forEachDescendant(WritesIntoM) 152 )); 153 154 auto CallsAsyncM = stmt(anyOf( 155 callExpr(allOf( 156 callsNames(FunctionsWithAutoreleasingPool), WritesInBlockM)), 157 objcMessageExpr(allOf( 158 hasAnySelector(toRefs(SelectorsWithAutoreleasingPool)), 159 WritesInBlockM)) 160 )); 161 162 auto DoublePointerParamM = 163 parmVarDecl(hasType(pointerType( 164 pointee(hasCanonicalType(objcObjectPointerType()))))) 165 .bind(ParamBind); 166 167 auto HasParamAndWritesAsyncM = allOf( 168 hasAnyParameter(DoublePointerParamM), 169 forEachDescendant(CallsAsyncM)); 170 171 auto MatcherM = decl(anyOf( 172 objcMethodDecl(HasParamAndWritesAsyncM).bind(MethodBind), 173 functionDecl(HasParamAndWritesAsyncM))); 174 175 auto Matches = match(MatcherM, *D, AM.getASTContext()); 176 for (BoundNodes Match : Matches) 177 emitDiagnostics(Match, D, BR, AM, this); 178 } 179 180 void ento::registerAutoreleaseWriteChecker(CheckerManager &Mgr) { 181 Mgr.registerChecker<ObjCAutoreleaseWriteChecker>(); 182 } 183