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 "enumerateObjectsUsingBlock:", 55 "enumerateKeysAndObjectsUsingBlock:", 56 "enumerateKeysAndObjectsWithOptions:usingBlock:", 57 "enumerateObjectsWithOptions:usingBlock:", 58 "enumerateObjectsAtIndexes:options:usingBlock:", 59 "enumerateIndexesWithOptions:usingBlock:", 60 "enumerateIndexesUsingBlock:", 61 "enumerateIndexesInRange:options:usingBlock:", 62 "enumerateRangesUsingBlock:", 63 "enumerateRangesWithOptions:usingBlock:", 64 "enumerateRangesInRange:options:usingBlock:" 65 "objectWithOptions:passingTest:", 66 }; 67 68 std::vector<std::string> FunctionsWithAutoreleasingPool = { 69 "dispatch_async", "dispatch_group_async", "dispatch_barrier_async"}; 70 }; 71 } 72 73 static inline std::vector<llvm::StringRef> toRefs(std::vector<std::string> V) { 74 return std::vector<llvm::StringRef>(V.begin(), V.end()); 75 } 76 77 static auto callsNames(std::vector<std::string> FunctionNames) 78 -> decltype(callee(functionDecl())) { 79 return callee(functionDecl(hasAnyName(toRefs(FunctionNames)))); 80 } 81 82 static void emitDiagnostics(BoundNodes &Match, const Decl *D, BugReporter &BR, 83 AnalysisManager &AM, 84 const ObjCAutoreleaseWriteChecker *Checker) { 85 AnalysisDeclContext *ADC = AM.getAnalysisDeclContext(D); 86 87 const auto *PVD = Match.getNodeAs<ParmVarDecl>(ParamBind); 88 assert(PVD); 89 QualType Ty = PVD->getType(); 90 if (Ty->getPointeeType().getObjCLifetime() != Qualifiers::OCL_Autoreleasing) 91 return; 92 const auto *SW = Match.getNodeAs<Expr>(ProblematicWriteBind); 93 bool IsMethod = Match.getNodeAs<ObjCMethodDecl>(MethodBind) != nullptr; 94 const char *Name = IsMethod ? "method" : "function"; 95 assert(SW); 96 BR.EmitBasicReport( 97 ADC->getDecl(), Checker, 98 /*Name=*/"Writing into auto-releasing variable from a different queue", 99 /*Category=*/"Memory", 100 (llvm::Twine("Writing into an auto-releasing out parameter inside ") + 101 "autorelease pool that may exit before " + Name + " returns; consider " 102 "writing first to a strong local variable declared outside of the block") 103 .str(), 104 PathDiagnosticLocation::createBegin(SW, BR.getSourceManager(), ADC), 105 SW->getSourceRange()); 106 } 107 108 void ObjCAutoreleaseWriteChecker::checkASTCodeBody(const Decl *D, 109 AnalysisManager &AM, 110 BugReporter &BR) const { 111 112 // Write into a binded object, e.g. *ParamBind = X. 113 auto WritesIntoM = binaryOperator( 114 hasLHS(unaryOperator( 115 hasOperatorName("*"), 116 hasUnaryOperand( 117 ignoringParenImpCasts( 118 declRefExpr(to(parmVarDecl(equalsBoundNode(ParamBind)))))) 119 )), 120 hasOperatorName("=") 121 ).bind(ProblematicWriteBind); 122 123 // WritesIntoM happens inside a block passed as an argument. 124 auto WritesInBlockM = hasAnyArgument(allOf( 125 hasType(hasCanonicalType(blockPointerType())), 126 forEachDescendant(WritesIntoM) 127 )); 128 129 auto CallsAsyncM = stmt(anyOf( 130 callExpr(allOf( 131 callsNames(FunctionsWithAutoreleasingPool), WritesInBlockM)), 132 objcMessageExpr(allOf( 133 hasAnySelector(toRefs(SelectorsWithAutoreleasingPool)), 134 WritesInBlockM)) 135 )); 136 137 auto DoublePointerParamM = 138 parmVarDecl(hasType(pointerType( 139 pointee(hasCanonicalType(objcObjectPointerType()))))) 140 .bind(ParamBind); 141 142 auto HasParamAndWritesAsyncM = allOf( 143 hasAnyParameter(DoublePointerParamM), 144 forEachDescendant(CallsAsyncM)); 145 146 auto MatcherM = decl(anyOf( 147 objcMethodDecl(HasParamAndWritesAsyncM).bind(MethodBind), 148 functionDecl(HasParamAndWritesAsyncM))); 149 150 auto Matches = match(MatcherM, *D, AM.getASTContext()); 151 for (BoundNodes Match : Matches) 152 emitDiagnostics(Match, D, BR, AM, this); 153 } 154 155 void ento::registerAutoreleaseWriteChecker(CheckerManager &Mgr) { 156 Mgr.registerChecker<ObjCAutoreleaseWriteChecker>(); 157 } 158