1 //===- ObjCAutoreleaseWriteChecker.cpp ----------------------------*- 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 defines ObjCAutoreleaseWriteChecker which warns against writes 10 // into autoreleased out parameters which cause crashes. 11 // An example of a problematic write is a write to @c error in the example 12 // below: 13 // 14 // - (BOOL) mymethod:(NSError *__autoreleasing *)error list:(NSArray*) list { 15 // [list enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { 16 // NSString *myString = obj; 17 // if ([myString isEqualToString:@"error"] && error) 18 // *error = [NSError errorWithDomain:@"MyDomain" code:-1]; 19 // }]; 20 // return false; 21 // } 22 // 23 // Such code will crash on read from `*error` due to the autorelease pool 24 // in `enumerateObjectsUsingBlock` implementation freeing the error object 25 // on exit from the function. 26 // 27 //===----------------------------------------------------------------------===// 28 29 #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.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/BugReporter/CommonBugCategories.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 const char *IsARPBind = "isautoreleasepoolbind"; 49 50 class ObjCAutoreleaseWriteChecker : public Checker<check::ASTCodeBody> { 51 public: 52 void checkASTCodeBody(const Decl *D, 53 AnalysisManager &AM, 54 BugReporter &BR) const; 55 private: 56 std::vector<std::string> SelectorsWithAutoreleasingPool = { 57 // Common to NSArray, NSSet, NSOrderedSet 58 "enumerateObjectsUsingBlock:", 59 "enumerateObjectsWithOptions:usingBlock:", 60 61 // Common to NSArray and NSOrderedSet 62 "enumerateObjectsAtIndexes:options:usingBlock:", 63 "indexOfObjectAtIndexes:options:passingTest:", 64 "indexesOfObjectsAtIndexes:options:passingTest:", 65 "indexOfObjectPassingTest:", 66 "indexOfObjectWithOptions:passingTest:", 67 "indexesOfObjectsPassingTest:", 68 "indexesOfObjectsWithOptions:passingTest:", 69 70 // NSDictionary 71 "enumerateKeysAndObjectsUsingBlock:", 72 "enumerateKeysAndObjectsWithOptions:usingBlock:", 73 "keysOfEntriesPassingTest:", 74 "keysOfEntriesWithOptions:passingTest:", 75 76 // NSSet 77 "objectsPassingTest:", 78 "objectsWithOptions:passingTest:", 79 "enumerateIndexPathsWithOptions:usingBlock:", 80 81 // NSIndexSet 82 "enumerateIndexesWithOptions:usingBlock:", 83 "enumerateIndexesUsingBlock:", 84 "enumerateIndexesInRange:options:usingBlock:", 85 "enumerateRangesUsingBlock:", 86 "enumerateRangesWithOptions:usingBlock:", 87 "enumerateRangesInRange:options:usingBlock:", 88 "indexPassingTest:", 89 "indexesPassingTest:", 90 "indexWithOptions:passingTest:", 91 "indexesWithOptions:passingTest:", 92 "indexInRange:options:passingTest:", 93 "indexesInRange:options:passingTest:" 94 }; 95 96 std::vector<std::string> FunctionsWithAutoreleasingPool = { 97 "dispatch_async", "dispatch_group_async", "dispatch_barrier_async"}; 98 }; 99 } 100 101 static inline std::vector<llvm::StringRef> 102 toRefs(const std::vector<std::string> &V) { 103 return std::vector<llvm::StringRef>(V.begin(), V.end()); 104 } 105 106 static decltype(auto) 107 callsNames(const std::vector<std::string> &FunctionNames) { 108 return callee(functionDecl(hasAnyName(toRefs(FunctionNames)))); 109 } 110 111 static void emitDiagnostics(BoundNodes &Match, const Decl *D, BugReporter &BR, 112 AnalysisManager &AM, 113 const ObjCAutoreleaseWriteChecker *Checker) { 114 AnalysisDeclContext *ADC = AM.getAnalysisDeclContext(D); 115 116 const auto *PVD = Match.getNodeAs<ParmVarDecl>(ParamBind); 117 QualType Ty = PVD->getType(); 118 if (Ty->getPointeeType().getObjCLifetime() != Qualifiers::OCL_Autoreleasing) 119 return; 120 const char *ActionMsg = "Write to"; 121 const auto *MarkedStmt = Match.getNodeAs<Expr>(ProblematicWriteBind); 122 bool IsCapture = false; 123 124 // Prefer to warn on write, but if not available, warn on capture. 125 if (!MarkedStmt) { 126 MarkedStmt = Match.getNodeAs<Expr>(CapturedBind); 127 assert(MarkedStmt); 128 ActionMsg = "Capture of"; 129 IsCapture = true; 130 } 131 132 SourceRange Range = MarkedStmt->getSourceRange(); 133 PathDiagnosticLocation Location = PathDiagnosticLocation::createBegin( 134 MarkedStmt, BR.getSourceManager(), ADC); 135 136 bool IsMethod = Match.getNodeAs<ObjCMethodDecl>(IsMethodBind) != nullptr; 137 const char *FunctionDescription = IsMethod ? "method" : "function"; 138 bool IsARP = Match.getNodeAs<ObjCAutoreleasePoolStmt>(IsARPBind) != nullptr; 139 140 llvm::SmallString<128> BugNameBuf; 141 llvm::raw_svector_ostream BugName(BugNameBuf); 142 BugName << ActionMsg 143 << " autoreleasing out parameter inside autorelease pool"; 144 145 llvm::SmallString<128> BugMessageBuf; 146 llvm::raw_svector_ostream BugMessage(BugMessageBuf); 147 BugMessage << ActionMsg << " autoreleasing out parameter "; 148 if (IsCapture) 149 BugMessage << "'" + PVD->getName() + "' "; 150 151 BugMessage << "inside "; 152 if (IsARP) 153 BugMessage << "locally-scoped autorelease pool;"; 154 else 155 BugMessage << "autorelease pool that may exit before " 156 << FunctionDescription << " returns;"; 157 158 BugMessage << " consider writing first to a strong local variable" 159 " declared outside "; 160 if (IsARP) 161 BugMessage << "of the autorelease pool"; 162 else 163 BugMessage << "of the block"; 164 165 BR.EmitBasicReport(ADC->getDecl(), Checker, BugName.str(), 166 categories::MemoryRefCount, BugMessage.str(), Location, 167 Range); 168 } 169 170 void ObjCAutoreleaseWriteChecker::checkASTCodeBody(const Decl *D, 171 AnalysisManager &AM, 172 BugReporter &BR) const { 173 174 auto DoublePointerParamM = 175 parmVarDecl(hasType(hasCanonicalType(pointerType( 176 pointee(hasCanonicalType(objcObjectPointerType())))))) 177 .bind(ParamBind); 178 179 auto ReferencedParamM = 180 declRefExpr(to(parmVarDecl(DoublePointerParamM))).bind(CapturedBind); 181 182 // Write into a binded object, e.g. *ParamBind = X. 183 auto WritesIntoM = binaryOperator( 184 hasLHS(unaryOperator( 185 hasOperatorName("*"), 186 hasUnaryOperand( 187 ignoringParenImpCasts(ReferencedParamM)) 188 )), 189 hasOperatorName("=") 190 ).bind(ProblematicWriteBind); 191 192 auto ArgumentCaptureM = hasAnyArgument( 193 ignoringParenImpCasts(ReferencedParamM)); 194 auto CapturedInParamM = stmt(anyOf( 195 callExpr(ArgumentCaptureM), 196 objcMessageExpr(ArgumentCaptureM))); 197 198 // WritesIntoM happens inside a block passed as an argument. 199 auto WritesOrCapturesInBlockM = hasAnyArgument(allOf( 200 hasType(hasCanonicalType(blockPointerType())), 201 forEachDescendant( 202 stmt(anyOf(WritesIntoM, CapturedInParamM)) 203 ))); 204 205 auto BlockPassedToMarkedFuncM = stmt(anyOf( 206 callExpr(allOf( 207 callsNames(FunctionsWithAutoreleasingPool), WritesOrCapturesInBlockM)), 208 objcMessageExpr(allOf( 209 hasAnySelector(toRefs(SelectorsWithAutoreleasingPool)), 210 WritesOrCapturesInBlockM)) 211 )); 212 213 // WritesIntoM happens inside an explicit @autoreleasepool. 214 auto WritesOrCapturesInPoolM = 215 autoreleasePoolStmt( 216 forEachDescendant(stmt(anyOf(WritesIntoM, CapturedInParamM)))) 217 .bind(IsARPBind); 218 219 auto HasParamAndWritesInMarkedFuncM = 220 allOf(hasAnyParameter(DoublePointerParamM), 221 anyOf(forEachDescendant(BlockPassedToMarkedFuncM), 222 forEachDescendant(WritesOrCapturesInPoolM))); 223 224 auto MatcherM = decl(anyOf( 225 objcMethodDecl(HasParamAndWritesInMarkedFuncM).bind(IsMethodBind), 226 functionDecl(HasParamAndWritesInMarkedFuncM), 227 blockDecl(HasParamAndWritesInMarkedFuncM))); 228 229 auto Matches = match(MatcherM, *D, AM.getASTContext()); 230 for (BoundNodes Match : Matches) 231 emitDiagnostics(Match, D, BR, AM, this); 232 } 233 234 void ento::registerAutoreleaseWriteChecker(CheckerManager &Mgr) { 235 Mgr.registerChecker<ObjCAutoreleaseWriteChecker>(); 236 } 237 238 bool ento::shouldRegisterAutoreleaseWriteChecker(const CheckerManager &mgr) { 239 return true; 240 } 241