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
toRefs(std::vector<std::string> V)101 static inline std::vector<llvm::StringRef> toRefs(std::vector<std::string> V) {
102 return std::vector<llvm::StringRef>(V.begin(), V.end());
103 }
104
callsNames(std::vector<std::string> FunctionNames)105 static decltype(auto) callsNames(std::vector<std::string> FunctionNames) {
106 return callee(functionDecl(hasAnyName(toRefs(FunctionNames))));
107 }
108
emitDiagnostics(BoundNodes & Match,const Decl * D,BugReporter & BR,AnalysisManager & AM,const ObjCAutoreleaseWriteChecker * Checker)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
134 bool IsMethod = Match.getNodeAs<ObjCMethodDecl>(IsMethodBind) != nullptr;
135 const char *FunctionDescription = IsMethod ? "method" : "function";
136 bool IsARP = Match.getNodeAs<ObjCAutoreleasePoolStmt>(IsARPBind) != nullptr;
137
138 llvm::SmallString<128> BugNameBuf;
139 llvm::raw_svector_ostream BugName(BugNameBuf);
140 BugName << ActionMsg
141 << " autoreleasing out parameter inside autorelease pool";
142
143 llvm::SmallString<128> BugMessageBuf;
144 llvm::raw_svector_ostream BugMessage(BugMessageBuf);
145 BugMessage << ActionMsg << " autoreleasing out parameter ";
146 if (IsCapture)
147 BugMessage << "'" + PVD->getName() + "' ";
148
149 BugMessage << "inside ";
150 if (IsARP)
151 BugMessage << "locally-scoped autorelease pool;";
152 else
153 BugMessage << "autorelease pool that may exit before "
154 << FunctionDescription << " returns;";
155
156 BugMessage << " consider writing first to a strong local variable"
157 " declared outside ";
158 if (IsARP)
159 BugMessage << "of the autorelease pool";
160 else
161 BugMessage << "of the block";
162
163 BR.EmitBasicReport(ADC->getDecl(), Checker, BugName.str(),
164 categories::MemoryRefCount, BugMessage.str(), Location,
165 Range);
166 }
167
checkASTCodeBody(const Decl * D,AnalysisManager & AM,BugReporter & BR) const168 void ObjCAutoreleaseWriteChecker::checkASTCodeBody(const Decl *D,
169 AnalysisManager &AM,
170 BugReporter &BR) const {
171
172 auto DoublePointerParamM =
173 parmVarDecl(hasType(hasCanonicalType(pointerType(
174 pointee(hasCanonicalType(objcObjectPointerType()))))))
175 .bind(ParamBind);
176
177 auto ReferencedParamM =
178 declRefExpr(to(parmVarDecl(DoublePointerParamM))).bind(CapturedBind);
179
180 // Write into a binded object, e.g. *ParamBind = X.
181 auto WritesIntoM = binaryOperator(
182 hasLHS(unaryOperator(
183 hasOperatorName("*"),
184 hasUnaryOperand(
185 ignoringParenImpCasts(ReferencedParamM))
186 )),
187 hasOperatorName("=")
188 ).bind(ProblematicWriteBind);
189
190 auto ArgumentCaptureM = hasAnyArgument(
191 ignoringParenImpCasts(ReferencedParamM));
192 auto CapturedInParamM = stmt(anyOf(
193 callExpr(ArgumentCaptureM),
194 objcMessageExpr(ArgumentCaptureM)));
195
196 // WritesIntoM happens inside a block passed as an argument.
197 auto WritesOrCapturesInBlockM = hasAnyArgument(allOf(
198 hasType(hasCanonicalType(blockPointerType())),
199 forEachDescendant(
200 stmt(anyOf(WritesIntoM, CapturedInParamM))
201 )));
202
203 auto BlockPassedToMarkedFuncM = stmt(anyOf(
204 callExpr(allOf(
205 callsNames(FunctionsWithAutoreleasingPool), WritesOrCapturesInBlockM)),
206 objcMessageExpr(allOf(
207 hasAnySelector(toRefs(SelectorsWithAutoreleasingPool)),
208 WritesOrCapturesInBlockM))
209 ));
210
211 // WritesIntoM happens inside an explicit @autoreleasepool.
212 auto WritesOrCapturesInPoolM =
213 autoreleasePoolStmt(
214 forEachDescendant(stmt(anyOf(WritesIntoM, CapturedInParamM))))
215 .bind(IsARPBind);
216
217 auto HasParamAndWritesInMarkedFuncM =
218 allOf(hasAnyParameter(DoublePointerParamM),
219 anyOf(forEachDescendant(BlockPassedToMarkedFuncM),
220 forEachDescendant(WritesOrCapturesInPoolM)));
221
222 auto MatcherM = decl(anyOf(
223 objcMethodDecl(HasParamAndWritesInMarkedFuncM).bind(IsMethodBind),
224 functionDecl(HasParamAndWritesInMarkedFuncM),
225 blockDecl(HasParamAndWritesInMarkedFuncM)));
226
227 auto Matches = match(MatcherM, *D, AM.getASTContext());
228 for (BoundNodes Match : Matches)
229 emitDiagnostics(Match, D, BR, AM, this);
230 }
231
registerAutoreleaseWriteChecker(CheckerManager & Mgr)232 void ento::registerAutoreleaseWriteChecker(CheckerManager &Mgr) {
233 Mgr.registerChecker<ObjCAutoreleaseWriteChecker>();
234 }
235
shouldRegisterAutoreleaseWriteChecker(const CheckerManager & mgr)236 bool ento::shouldRegisterAutoreleaseWriteChecker(const CheckerManager &mgr) {
237 return true;
238 }
239