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>
toRefs(const std::vector<std::string> & V)102 toRefs(const std::vector<std::string> &V) {
103   return std::vector<llvm::StringRef>(V.begin(), V.end());
104 }
105 
106 static decltype(auto)
callsNames(const std::vector<std::string> & FunctionNames)107 callsNames(const std::vector<std::string> &FunctionNames) {
108   return callee(functionDecl(hasAnyName(toRefs(FunctionNames))));
109 }
110 
emitDiagnostics(BoundNodes & Match,const Decl * D,BugReporter & BR,AnalysisManager & AM,const ObjCAutoreleaseWriteChecker * Checker)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 
checkASTCodeBody(const Decl * D,AnalysisManager & AM,BugReporter & BR) const170 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 
registerAutoreleaseWriteChecker(CheckerManager & Mgr)234 void ento::registerAutoreleaseWriteChecker(CheckerManager &Mgr) {
235   Mgr.registerChecker<ObjCAutoreleaseWriteChecker>();
236 }
237 
shouldRegisterAutoreleaseWriteChecker(const CheckerManager & mgr)238 bool ento::shouldRegisterAutoreleaseWriteChecker(const CheckerManager &mgr) {
239   return true;
240 }
241