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 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 will crash on read from `*error` due to the autorelease pool 25 // in `enumerateObjectsUsingBlock` implementation freeing the error object 26 // on exit from the function. 27 // 28 //===----------------------------------------------------------------------===// 29 30 #include "ClangSACheckers.h" 31 #include "clang/ASTMatchers/ASTMatchFinder.h" 32 #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" 33 #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" 34 #include "clang/StaticAnalyzer/Core/Checker.h" 35 #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" 36 #include "llvm/ADT/Twine.h" 37 38 using namespace clang; 39 using namespace ento; 40 using namespace ast_matchers; 41 42 namespace { 43 44 const char *ProblematicWriteBind = "problematicwrite"; 45 const char *CapturedBind = "capturedbind"; 46 const char *ParamBind = "parambind"; 47 const char *IsMethodBind = "ismethodbind"; 48 49 class ObjCAutoreleaseWriteChecker : public Checker<check::ASTCodeBody> { 50 public: 51 void checkASTCodeBody(const Decl *D, 52 AnalysisManager &AM, 53 BugReporter &BR) const; 54 private: 55 std::vector<std::string> SelectorsWithAutoreleasingPool = { 56 // Common to NSArray, NSSet, NSOrderedSet 57 "enumerateObjectsUsingBlock:", 58 "enumerateObjectsWithOptions:usingBlock:", 59 60 // Common to NSArray and NSOrderedSet 61 "enumerateObjectsAtIndexes:options:usingBlock:", 62 "indexOfObjectAtIndexes:options:passingTest:", 63 "indexesOfObjectsAtIndexes:options:passingTest:", 64 "indexOfObjectPassingTest:", 65 "indexOfObjectWithOptions:passingTest:", 66 "indexesOfObjectsPassingTest:", 67 "indexesOfObjectsWithOptions:passingTest:", 68 69 // NSDictionary 70 "enumerateKeysAndObjectsUsingBlock:", 71 "enumerateKeysAndObjectsWithOptions:usingBlock:", 72 "keysOfEntriesPassingTest:", 73 "keysOfEntriesWithOptions:passingTest:", 74 75 // NSSet 76 "objectsPassingTest:", 77 "objectsWithOptions:passingTest:", 78 "enumerateIndexPathsWithOptions:usingBlock:", 79 80 // NSIndexSet 81 "enumerateIndexesWithOptions:usingBlock:", 82 "enumerateIndexesUsingBlock:", 83 "enumerateIndexesInRange:options:usingBlock:", 84 "enumerateRangesUsingBlock:", 85 "enumerateRangesWithOptions:usingBlock:", 86 "enumerateRangesInRange:options:usingBlock:", 87 "indexPassingTest:", 88 "indexesPassingTest:", 89 "indexWithOptions:passingTest:", 90 "indexesWithOptions:passingTest:", 91 "indexInRange:options:passingTest:", 92 "indexesInRange:options:passingTest:" 93 }; 94 95 std::vector<std::string> FunctionsWithAutoreleasingPool = { 96 "dispatch_async", "dispatch_group_async", "dispatch_barrier_async"}; 97 }; 98 } 99 100 static inline std::vector<llvm::StringRef> toRefs(std::vector<std::string> V) { 101 return std::vector<llvm::StringRef>(V.begin(), V.end()); 102 } 103 104 static auto callsNames(std::vector<std::string> FunctionNames) 105 -> decltype(callee(functionDecl())) { 106 return callee(functionDecl(hasAnyName(toRefs(FunctionNames)))); 107 } 108 109 static void emitDiagnostics(BoundNodes &Match, const Decl *D, BugReporter &BR, 110 AnalysisManager &AM, 111 const ObjCAutoreleaseWriteChecker *Checker) { 112 AnalysisDeclContext *ADC = AM.getAnalysisDeclContext(D); 113 114 const auto *PVD = Match.getNodeAs<ParmVarDecl>(ParamBind); 115 QualType Ty = PVD->getType(); 116 if (Ty->getPointeeType().getObjCLifetime() != Qualifiers::OCL_Autoreleasing) 117 return; 118 const char *ActionMsg = "Write to"; 119 const auto *MarkedStmt = Match.getNodeAs<Expr>(ProblematicWriteBind); 120 bool IsCapture = false; 121 122 // Prefer to warn on write, but if not available, warn on capture. 123 if (!MarkedStmt) { 124 MarkedStmt = Match.getNodeAs<Expr>(CapturedBind); 125 assert(MarkedStmt); 126 ActionMsg = "Capture of"; 127 IsCapture = true; 128 } 129 130 SourceRange Range = MarkedStmt->getSourceRange(); 131 PathDiagnosticLocation Location = PathDiagnosticLocation::createBegin( 132 MarkedStmt, BR.getSourceManager(), ADC); 133 bool IsMethod = Match.getNodeAs<ObjCMethodDecl>(IsMethodBind) != nullptr; 134 const char *Name = IsMethod ? "method" : "function"; 135 136 BR.EmitBasicReport( 137 ADC->getDecl(), Checker, 138 /*Name=*/(llvm::Twine(ActionMsg) 139 + " autoreleasing out parameter inside autorelease pool").str(), 140 /*Category=*/"Memory", 141 (llvm::Twine(ActionMsg) + " autoreleasing out parameter " + 142 (IsCapture ? "'" + PVD->getName() + "'" + " " : "") + "inside " + 143 "autorelease pool that may exit before " + Name + " returns; consider " 144 "writing first to a strong local variable declared outside of the block") 145 .str(), 146 Location, 147 Range); 148 } 149 150 void ObjCAutoreleaseWriteChecker::checkASTCodeBody(const Decl *D, 151 AnalysisManager &AM, 152 BugReporter &BR) const { 153 154 auto DoublePointerParamM = 155 parmVarDecl(hasType(hasCanonicalType(pointerType( 156 pointee(hasCanonicalType(objcObjectPointerType())))))) 157 .bind(ParamBind); 158 159 auto ReferencedParamM = 160 declRefExpr(to(parmVarDecl(DoublePointerParamM))).bind(CapturedBind); 161 162 // Write into a binded object, e.g. *ParamBind = X. 163 auto WritesIntoM = binaryOperator( 164 hasLHS(unaryOperator( 165 hasOperatorName("*"), 166 hasUnaryOperand( 167 ignoringParenImpCasts(ReferencedParamM)) 168 )), 169 hasOperatorName("=") 170 ).bind(ProblematicWriteBind); 171 172 auto ArgumentCaptureM = hasAnyArgument( 173 ignoringParenImpCasts(ReferencedParamM)); 174 auto CapturedInParamM = stmt(anyOf( 175 callExpr(ArgumentCaptureM), 176 objcMessageExpr(ArgumentCaptureM))); 177 178 // WritesIntoM happens inside a block passed as an argument. 179 auto WritesOrCapturesInBlockM = hasAnyArgument(allOf( 180 hasType(hasCanonicalType(blockPointerType())), 181 forEachDescendant( 182 stmt(anyOf(WritesIntoM, CapturedInParamM)) 183 ))); 184 185 auto BlockPassedToMarkedFuncM = stmt(anyOf( 186 callExpr(allOf( 187 callsNames(FunctionsWithAutoreleasingPool), WritesOrCapturesInBlockM)), 188 objcMessageExpr(allOf( 189 hasAnySelector(toRefs(SelectorsWithAutoreleasingPool)), 190 WritesOrCapturesInBlockM)) 191 )); 192 193 auto HasParamAndWritesInMarkedFuncM = allOf( 194 hasAnyParameter(DoublePointerParamM), 195 forEachDescendant(BlockPassedToMarkedFuncM)); 196 197 auto MatcherM = decl(anyOf( 198 objcMethodDecl(HasParamAndWritesInMarkedFuncM).bind(IsMethodBind), 199 functionDecl(HasParamAndWritesInMarkedFuncM), 200 blockDecl(HasParamAndWritesInMarkedFuncM))); 201 202 auto Matches = match(MatcherM, *D, AM.getASTContext()); 203 for (BoundNodes Match : Matches) 204 emitDiagnostics(Match, D, BR, AM, this); 205 } 206 207 void ento::registerAutoreleaseWriteChecker(CheckerManager &Mgr) { 208 Mgr.registerChecker<ObjCAutoreleaseWriteChecker>(); 209 } 210