1 //===- ObjCAutoreleaseWriteChecker.cpp ----------------------------*- C++ -*-==//
3 // The LLVM Compiler Infrastructure
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
8 //===----------------------------------------------------------------------===//
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
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];
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.
28 //===----------------------------------------------------------------------===//
30 #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.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"
38 using namespace clang;
40 using namespace ast_matchers;
44 const char *ProblematicWriteBind = "problematicwrite";
45 const char *CapturedBind = "capturedbind";
46 const char *ParamBind = "parambind";
47 const char *IsMethodBind = "ismethodbind";
49 class ObjCAutoreleaseWriteChecker : public Checker<check::ASTCodeBody> {
51 void checkASTCodeBody(const Decl *D,
53 BugReporter &BR) const;
55 std::vector<std::string> SelectorsWithAutoreleasingPool = {
56 // Common to NSArray, NSSet, NSOrderedSet
57 "enumerateObjectsUsingBlock:",
58 "enumerateObjectsWithOptions:usingBlock:",
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:",
70 "enumerateKeysAndObjectsUsingBlock:",
71 "enumerateKeysAndObjectsWithOptions:usingBlock:",
72 "keysOfEntriesPassingTest:",
73 "keysOfEntriesWithOptions:passingTest:",
76 "objectsPassingTest:",
77 "objectsWithOptions:passingTest:",
78 "enumerateIndexPathsWithOptions:usingBlock:",
81 "enumerateIndexesWithOptions:usingBlock:",
82 "enumerateIndexesUsingBlock:",
83 "enumerateIndexesInRange:options:usingBlock:",
84 "enumerateRangesUsingBlock:",
85 "enumerateRangesWithOptions:usingBlock:",
86 "enumerateRangesInRange:options:usingBlock:",
88 "indexesPassingTest:",
89 "indexWithOptions:passingTest:",
90 "indexesWithOptions:passingTest:",
91 "indexInRange:options:passingTest:",
92 "indexesInRange:options:passingTest:"
95 std::vector<std::string> FunctionsWithAutoreleasingPool = {
96 "dispatch_async", "dispatch_group_async", "dispatch_barrier_async"};
100 static inline std::vector<llvm::StringRef> toRefs(std::vector<std::string> V) {
101 return std::vector<llvm::StringRef>(V.begin(), V.end());
104 static auto callsNames(std::vector<std::string> FunctionNames)
105 -> decltype(callee(functionDecl())) {
106 return callee(functionDecl(hasAnyName(toRefs(FunctionNames))));
109 static void emitDiagnostics(BoundNodes &Match, const Decl *D, BugReporter &BR,
111 const ObjCAutoreleaseWriteChecker *Checker) {
112 AnalysisDeclContext *ADC = AM.getAnalysisDeclContext(D);
114 const auto *PVD = Match.getNodeAs<ParmVarDecl>(ParamBind);
115 QualType Ty = PVD->getType();
116 if (Ty->getPointeeType().getObjCLifetime() != Qualifiers::OCL_Autoreleasing)
118 const char *ActionMsg = "Write to";
119 const auto *MarkedStmt = Match.getNodeAs<Expr>(ProblematicWriteBind);
120 bool IsCapture = false;
122 // Prefer to warn on write, but if not available, warn on capture.
124 MarkedStmt = Match.getNodeAs<Expr>(CapturedBind);
126 ActionMsg = "Capture of";
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";
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")
150 void ObjCAutoreleaseWriteChecker::checkASTCodeBody(const Decl *D,
152 BugReporter &BR) const {
154 auto DoublePointerParamM =
155 parmVarDecl(hasType(hasCanonicalType(pointerType(
156 pointee(hasCanonicalType(objcObjectPointerType()))))))
159 auto ReferencedParamM =
160 declRefExpr(to(parmVarDecl(DoublePointerParamM))).bind(CapturedBind);
162 // Write into a binded object, e.g. *ParamBind = X.
163 auto WritesIntoM = binaryOperator(
164 hasLHS(unaryOperator(
165 hasOperatorName("*"),
167 ignoringParenImpCasts(ReferencedParamM))
170 ).bind(ProblematicWriteBind);
172 auto ArgumentCaptureM = hasAnyArgument(
173 ignoringParenImpCasts(ReferencedParamM));
174 auto CapturedInParamM = stmt(anyOf(
175 callExpr(ArgumentCaptureM),
176 objcMessageExpr(ArgumentCaptureM)));
178 // WritesIntoM happens inside a block passed as an argument.
179 auto WritesOrCapturesInBlockM = hasAnyArgument(allOf(
180 hasType(hasCanonicalType(blockPointerType())),
182 stmt(anyOf(WritesIntoM, CapturedInParamM))
185 auto BlockPassedToMarkedFuncM = stmt(anyOf(
187 callsNames(FunctionsWithAutoreleasingPool), WritesOrCapturesInBlockM)),
188 objcMessageExpr(allOf(
189 hasAnySelector(toRefs(SelectorsWithAutoreleasingPool)),
190 WritesOrCapturesInBlockM))
193 auto HasParamAndWritesInMarkedFuncM = allOf(
194 hasAnyParameter(DoublePointerParamM),
195 forEachDescendant(BlockPassedToMarkedFuncM));
197 auto MatcherM = decl(anyOf(
198 objcMethodDecl(HasParamAndWritesInMarkedFuncM).bind(IsMethodBind),
199 functionDecl(HasParamAndWritesInMarkedFuncM),
200 blockDecl(HasParamAndWritesInMarkedFuncM)));
202 auto Matches = match(MatcherM, *D, AM.getASTContext());
203 for (BoundNodes Match : Matches)
204 emitDiagnostics(Match, D, BR, AM, this);
207 void ento::registerAutoreleaseWriteChecker(CheckerManager &Mgr) {
208 Mgr.registerChecker<ObjCAutoreleaseWriteChecker>();