xref: /freebsd/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCAutoreleaseWriteChecker.cpp (revision 6966ac055c3b7a39266fb982493330df7a097997)
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 {@code 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/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 *CapturedBind = "capturedbind";
45 const char *ParamBind = "parambind";
46 const char *IsMethodBind = "ismethodbind";
47 
48 class ObjCAutoreleaseWriteChecker : public Checker<check::ASTCodeBody> {
49 public:
50   void checkASTCodeBody(const Decl *D,
51                         AnalysisManager &AM,
52                         BugReporter &BR) const;
53 private:
54   std::vector<std::string> SelectorsWithAutoreleasingPool = {
55       // Common to NSArray,  NSSet, NSOrderedSet
56       "enumerateObjectsUsingBlock:",
57       "enumerateObjectsWithOptions:usingBlock:",
58 
59       // Common to NSArray and NSOrderedSet
60       "enumerateObjectsAtIndexes:options:usingBlock:",
61       "indexOfObjectAtIndexes:options:passingTest:",
62       "indexesOfObjectsAtIndexes:options:passingTest:",
63       "indexOfObjectPassingTest:",
64       "indexOfObjectWithOptions:passingTest:",
65       "indexesOfObjectsPassingTest:",
66       "indexesOfObjectsWithOptions:passingTest:",
67 
68       // NSDictionary
69       "enumerateKeysAndObjectsUsingBlock:",
70       "enumerateKeysAndObjectsWithOptions:usingBlock:",
71       "keysOfEntriesPassingTest:",
72       "keysOfEntriesWithOptions:passingTest:",
73 
74       // NSSet
75       "objectsPassingTest:",
76       "objectsWithOptions:passingTest:",
77       "enumerateIndexPathsWithOptions:usingBlock:",
78 
79       // NSIndexSet
80       "enumerateIndexesWithOptions:usingBlock:",
81       "enumerateIndexesUsingBlock:",
82       "enumerateIndexesInRange:options:usingBlock:",
83       "enumerateRangesUsingBlock:",
84       "enumerateRangesWithOptions:usingBlock:",
85       "enumerateRangesInRange:options:usingBlock:",
86       "indexPassingTest:",
87       "indexesPassingTest:",
88       "indexWithOptions:passingTest:",
89       "indexesWithOptions:passingTest:",
90       "indexInRange:options:passingTest:",
91       "indexesInRange:options:passingTest:"
92   };
93 
94   std::vector<std::string> FunctionsWithAutoreleasingPool = {
95       "dispatch_async", "dispatch_group_async", "dispatch_barrier_async"};
96 };
97 }
98 
99 static inline std::vector<llvm::StringRef> toRefs(std::vector<std::string> V) {
100   return std::vector<llvm::StringRef>(V.begin(), V.end());
101 }
102 
103 static auto callsNames(std::vector<std::string> FunctionNames)
104     -> decltype(callee(functionDecl())) {
105   return callee(functionDecl(hasAnyName(toRefs(FunctionNames))));
106 }
107 
108 static void emitDiagnostics(BoundNodes &Match, const Decl *D, BugReporter &BR,
109                             AnalysisManager &AM,
110                             const ObjCAutoreleaseWriteChecker *Checker) {
111   AnalysisDeclContext *ADC = AM.getAnalysisDeclContext(D);
112 
113   const auto *PVD = Match.getNodeAs<ParmVarDecl>(ParamBind);
114   QualType Ty = PVD->getType();
115   if (Ty->getPointeeType().getObjCLifetime() != Qualifiers::OCL_Autoreleasing)
116     return;
117   const char *ActionMsg = "Write to";
118   const auto *MarkedStmt = Match.getNodeAs<Expr>(ProblematicWriteBind);
119   bool IsCapture = false;
120 
121   // Prefer to warn on write, but if not available, warn on capture.
122   if (!MarkedStmt) {
123     MarkedStmt = Match.getNodeAs<Expr>(CapturedBind);
124     assert(MarkedStmt);
125     ActionMsg = "Capture of";
126     IsCapture = true;
127   }
128 
129   SourceRange Range = MarkedStmt->getSourceRange();
130   PathDiagnosticLocation Location = PathDiagnosticLocation::createBegin(
131       MarkedStmt, BR.getSourceManager(), ADC);
132   bool IsMethod = Match.getNodeAs<ObjCMethodDecl>(IsMethodBind) != nullptr;
133   const char *Name = IsMethod ? "method" : "function";
134 
135   BR.EmitBasicReport(
136       ADC->getDecl(), Checker,
137       /*Name=*/(llvm::Twine(ActionMsg)
138                 + " autoreleasing out parameter inside autorelease pool").str(),
139       /*BugCategory=*/"Memory",
140       (llvm::Twine(ActionMsg) + " autoreleasing out parameter " +
141        (IsCapture ? "'" + PVD->getName() + "'" + " " : "") + "inside " +
142        "autorelease pool that may exit before " + Name + " returns; consider "
143        "writing first to a strong local variable declared outside of the block")
144           .str(),
145       Location,
146       Range);
147 }
148 
149 void ObjCAutoreleaseWriteChecker::checkASTCodeBody(const Decl *D,
150                                                   AnalysisManager &AM,
151                                                   BugReporter &BR) const {
152 
153   auto DoublePointerParamM =
154       parmVarDecl(hasType(hasCanonicalType(pointerType(
155                       pointee(hasCanonicalType(objcObjectPointerType()))))))
156           .bind(ParamBind);
157 
158   auto ReferencedParamM =
159       declRefExpr(to(parmVarDecl(DoublePointerParamM))).bind(CapturedBind);
160 
161   // Write into a binded object, e.g. *ParamBind = X.
162   auto WritesIntoM = binaryOperator(
163     hasLHS(unaryOperator(
164         hasOperatorName("*"),
165         hasUnaryOperand(
166           ignoringParenImpCasts(ReferencedParamM))
167     )),
168     hasOperatorName("=")
169   ).bind(ProblematicWriteBind);
170 
171   auto ArgumentCaptureM = hasAnyArgument(
172     ignoringParenImpCasts(ReferencedParamM));
173   auto CapturedInParamM = stmt(anyOf(
174       callExpr(ArgumentCaptureM),
175       objcMessageExpr(ArgumentCaptureM)));
176 
177   // WritesIntoM happens inside a block passed as an argument.
178   auto WritesOrCapturesInBlockM = hasAnyArgument(allOf(
179       hasType(hasCanonicalType(blockPointerType())),
180       forEachDescendant(
181         stmt(anyOf(WritesIntoM, CapturedInParamM))
182       )));
183 
184   auto BlockPassedToMarkedFuncM = stmt(anyOf(
185     callExpr(allOf(
186       callsNames(FunctionsWithAutoreleasingPool), WritesOrCapturesInBlockM)),
187     objcMessageExpr(allOf(
188        hasAnySelector(toRefs(SelectorsWithAutoreleasingPool)),
189        WritesOrCapturesInBlockM))
190   ));
191 
192   auto HasParamAndWritesInMarkedFuncM = allOf(
193       hasAnyParameter(DoublePointerParamM),
194       forEachDescendant(BlockPassedToMarkedFuncM));
195 
196   auto MatcherM = decl(anyOf(
197       objcMethodDecl(HasParamAndWritesInMarkedFuncM).bind(IsMethodBind),
198       functionDecl(HasParamAndWritesInMarkedFuncM),
199       blockDecl(HasParamAndWritesInMarkedFuncM)));
200 
201   auto Matches = match(MatcherM, *D, AM.getASTContext());
202   for (BoundNodes Match : Matches)
203     emitDiagnostics(Match, D, BR, AM, this);
204 }
205 
206 void ento::registerAutoreleaseWriteChecker(CheckerManager &Mgr) {
207   Mgr.registerChecker<ObjCAutoreleaseWriteChecker>();
208 }
209 
210 bool ento::shouldRegisterAutoreleaseWriteChecker(const LangOptions &LO) {
211   return true;
212 }
213