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