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