xref: /openbsd-src/gnu/llvm/clang/lib/StaticAnalyzer/Checkers/ObjCAutoreleaseWriteChecker.cpp (revision 12c855180aad702bbcca06e0398d774beeafb155)
1e5dd7070Spatrick //===- ObjCAutoreleaseWriteChecker.cpp ----------------------------*- C++ -*-==//
2e5dd7070Spatrick //
3e5dd7070Spatrick // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4e5dd7070Spatrick // See https://llvm.org/LICENSE.txt for license information.
5e5dd7070Spatrick // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6e5dd7070Spatrick //
7e5dd7070Spatrick //===----------------------------------------------------------------------===//
8e5dd7070Spatrick //
9e5dd7070Spatrick // This file defines ObjCAutoreleaseWriteChecker which warns against writes
10e5dd7070Spatrick // into autoreleased out parameters which cause crashes.
11a9ac8606Spatrick // An example of a problematic write is a write to @c error in the example
12e5dd7070Spatrick // below:
13e5dd7070Spatrick //
14e5dd7070Spatrick // - (BOOL) mymethod:(NSError *__autoreleasing *)error list:(NSArray*) list {
15e5dd7070Spatrick //     [list enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
16e5dd7070Spatrick //       NSString *myString = obj;
17e5dd7070Spatrick //       if ([myString isEqualToString:@"error"] && error)
18e5dd7070Spatrick //         *error = [NSError errorWithDomain:@"MyDomain" code:-1];
19e5dd7070Spatrick //     }];
20e5dd7070Spatrick //     return false;
21e5dd7070Spatrick // }
22e5dd7070Spatrick //
23e5dd7070Spatrick // Such code will crash on read from `*error` due to the autorelease pool
24e5dd7070Spatrick // in `enumerateObjectsUsingBlock` implementation freeing the error object
25e5dd7070Spatrick // on exit from the function.
26e5dd7070Spatrick //
27e5dd7070Spatrick //===----------------------------------------------------------------------===//
28e5dd7070Spatrick 
29e5dd7070Spatrick #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
30e5dd7070Spatrick #include "clang/ASTMatchers/ASTMatchFinder.h"
31e5dd7070Spatrick #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
32e5dd7070Spatrick #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
33ec727ea7Spatrick #include "clang/StaticAnalyzer/Core/BugReporter/CommonBugCategories.h"
34e5dd7070Spatrick #include "clang/StaticAnalyzer/Core/Checker.h"
35e5dd7070Spatrick #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h"
36e5dd7070Spatrick #include "llvm/ADT/Twine.h"
37e5dd7070Spatrick 
38e5dd7070Spatrick using namespace clang;
39e5dd7070Spatrick using namespace ento;
40e5dd7070Spatrick using namespace ast_matchers;
41e5dd7070Spatrick 
42e5dd7070Spatrick namespace {
43e5dd7070Spatrick 
44e5dd7070Spatrick const char *ProblematicWriteBind = "problematicwrite";
45e5dd7070Spatrick const char *CapturedBind = "capturedbind";
46e5dd7070Spatrick const char *ParamBind = "parambind";
47e5dd7070Spatrick const char *IsMethodBind = "ismethodbind";
48ec727ea7Spatrick const char *IsARPBind = "isautoreleasepoolbind";
49e5dd7070Spatrick 
50e5dd7070Spatrick class ObjCAutoreleaseWriteChecker : public Checker<check::ASTCodeBody> {
51e5dd7070Spatrick public:
52e5dd7070Spatrick   void checkASTCodeBody(const Decl *D,
53e5dd7070Spatrick                         AnalysisManager &AM,
54e5dd7070Spatrick                         BugReporter &BR) const;
55e5dd7070Spatrick private:
56e5dd7070Spatrick   std::vector<std::string> SelectorsWithAutoreleasingPool = {
57e5dd7070Spatrick       // Common to NSArray,  NSSet, NSOrderedSet
58e5dd7070Spatrick       "enumerateObjectsUsingBlock:",
59e5dd7070Spatrick       "enumerateObjectsWithOptions:usingBlock:",
60e5dd7070Spatrick 
61e5dd7070Spatrick       // Common to NSArray and NSOrderedSet
62e5dd7070Spatrick       "enumerateObjectsAtIndexes:options:usingBlock:",
63e5dd7070Spatrick       "indexOfObjectAtIndexes:options:passingTest:",
64e5dd7070Spatrick       "indexesOfObjectsAtIndexes:options:passingTest:",
65e5dd7070Spatrick       "indexOfObjectPassingTest:",
66e5dd7070Spatrick       "indexOfObjectWithOptions:passingTest:",
67e5dd7070Spatrick       "indexesOfObjectsPassingTest:",
68e5dd7070Spatrick       "indexesOfObjectsWithOptions:passingTest:",
69e5dd7070Spatrick 
70e5dd7070Spatrick       // NSDictionary
71e5dd7070Spatrick       "enumerateKeysAndObjectsUsingBlock:",
72e5dd7070Spatrick       "enumerateKeysAndObjectsWithOptions:usingBlock:",
73e5dd7070Spatrick       "keysOfEntriesPassingTest:",
74e5dd7070Spatrick       "keysOfEntriesWithOptions:passingTest:",
75e5dd7070Spatrick 
76e5dd7070Spatrick       // NSSet
77e5dd7070Spatrick       "objectsPassingTest:",
78e5dd7070Spatrick       "objectsWithOptions:passingTest:",
79e5dd7070Spatrick       "enumerateIndexPathsWithOptions:usingBlock:",
80e5dd7070Spatrick 
81e5dd7070Spatrick       // NSIndexSet
82e5dd7070Spatrick       "enumerateIndexesWithOptions:usingBlock:",
83e5dd7070Spatrick       "enumerateIndexesUsingBlock:",
84e5dd7070Spatrick       "enumerateIndexesInRange:options:usingBlock:",
85e5dd7070Spatrick       "enumerateRangesUsingBlock:",
86e5dd7070Spatrick       "enumerateRangesWithOptions:usingBlock:",
87e5dd7070Spatrick       "enumerateRangesInRange:options:usingBlock:",
88e5dd7070Spatrick       "indexPassingTest:",
89e5dd7070Spatrick       "indexesPassingTest:",
90e5dd7070Spatrick       "indexWithOptions:passingTest:",
91e5dd7070Spatrick       "indexesWithOptions:passingTest:",
92e5dd7070Spatrick       "indexInRange:options:passingTest:",
93e5dd7070Spatrick       "indexesInRange:options:passingTest:"
94e5dd7070Spatrick   };
95e5dd7070Spatrick 
96e5dd7070Spatrick   std::vector<std::string> FunctionsWithAutoreleasingPool = {
97e5dd7070Spatrick       "dispatch_async", "dispatch_group_async", "dispatch_barrier_async"};
98e5dd7070Spatrick };
99e5dd7070Spatrick }
100e5dd7070Spatrick 
101*12c85518Srobert static inline std::vector<llvm::StringRef>
toRefs(const std::vector<std::string> & V)102*12c85518Srobert toRefs(const std::vector<std::string> &V) {
103e5dd7070Spatrick   return std::vector<llvm::StringRef>(V.begin(), V.end());
104e5dd7070Spatrick }
105e5dd7070Spatrick 
106*12c85518Srobert static decltype(auto)
callsNames(const std::vector<std::string> & FunctionNames)107*12c85518Srobert callsNames(const std::vector<std::string> &FunctionNames) {
108e5dd7070Spatrick   return callee(functionDecl(hasAnyName(toRefs(FunctionNames))));
109e5dd7070Spatrick }
110e5dd7070Spatrick 
emitDiagnostics(BoundNodes & Match,const Decl * D,BugReporter & BR,AnalysisManager & AM,const ObjCAutoreleaseWriteChecker * Checker)111e5dd7070Spatrick static void emitDiagnostics(BoundNodes &Match, const Decl *D, BugReporter &BR,
112e5dd7070Spatrick                             AnalysisManager &AM,
113e5dd7070Spatrick                             const ObjCAutoreleaseWriteChecker *Checker) {
114e5dd7070Spatrick   AnalysisDeclContext *ADC = AM.getAnalysisDeclContext(D);
115e5dd7070Spatrick 
116e5dd7070Spatrick   const auto *PVD = Match.getNodeAs<ParmVarDecl>(ParamBind);
117e5dd7070Spatrick   QualType Ty = PVD->getType();
118e5dd7070Spatrick   if (Ty->getPointeeType().getObjCLifetime() != Qualifiers::OCL_Autoreleasing)
119e5dd7070Spatrick     return;
120e5dd7070Spatrick   const char *ActionMsg = "Write to";
121e5dd7070Spatrick   const auto *MarkedStmt = Match.getNodeAs<Expr>(ProblematicWriteBind);
122e5dd7070Spatrick   bool IsCapture = false;
123e5dd7070Spatrick 
124e5dd7070Spatrick   // Prefer to warn on write, but if not available, warn on capture.
125e5dd7070Spatrick   if (!MarkedStmt) {
126e5dd7070Spatrick     MarkedStmt = Match.getNodeAs<Expr>(CapturedBind);
127e5dd7070Spatrick     assert(MarkedStmt);
128e5dd7070Spatrick     ActionMsg = "Capture of";
129e5dd7070Spatrick     IsCapture = true;
130e5dd7070Spatrick   }
131e5dd7070Spatrick 
132e5dd7070Spatrick   SourceRange Range = MarkedStmt->getSourceRange();
133e5dd7070Spatrick   PathDiagnosticLocation Location = PathDiagnosticLocation::createBegin(
134e5dd7070Spatrick       MarkedStmt, BR.getSourceManager(), ADC);
135e5dd7070Spatrick 
136ec727ea7Spatrick   bool IsMethod = Match.getNodeAs<ObjCMethodDecl>(IsMethodBind) != nullptr;
137ec727ea7Spatrick   const char *FunctionDescription = IsMethod ? "method" : "function";
138ec727ea7Spatrick   bool IsARP = Match.getNodeAs<ObjCAutoreleasePoolStmt>(IsARPBind) != nullptr;
139ec727ea7Spatrick 
140ec727ea7Spatrick   llvm::SmallString<128> BugNameBuf;
141ec727ea7Spatrick   llvm::raw_svector_ostream BugName(BugNameBuf);
142ec727ea7Spatrick   BugName << ActionMsg
143ec727ea7Spatrick           << " autoreleasing out parameter inside autorelease pool";
144ec727ea7Spatrick 
145ec727ea7Spatrick   llvm::SmallString<128> BugMessageBuf;
146ec727ea7Spatrick   llvm::raw_svector_ostream BugMessage(BugMessageBuf);
147ec727ea7Spatrick   BugMessage << ActionMsg << " autoreleasing out parameter ";
148ec727ea7Spatrick   if (IsCapture)
149ec727ea7Spatrick     BugMessage << "'" + PVD->getName() + "' ";
150ec727ea7Spatrick 
151ec727ea7Spatrick   BugMessage << "inside ";
152ec727ea7Spatrick   if (IsARP)
153ec727ea7Spatrick     BugMessage << "locally-scoped autorelease pool;";
154ec727ea7Spatrick   else
155ec727ea7Spatrick     BugMessage << "autorelease pool that may exit before "
156ec727ea7Spatrick                << FunctionDescription << " returns;";
157ec727ea7Spatrick 
158ec727ea7Spatrick   BugMessage << " consider writing first to a strong local variable"
159ec727ea7Spatrick                 " declared outside ";
160ec727ea7Spatrick   if (IsARP)
161ec727ea7Spatrick     BugMessage << "of the autorelease pool";
162ec727ea7Spatrick   else
163ec727ea7Spatrick     BugMessage << "of the block";
164ec727ea7Spatrick 
165ec727ea7Spatrick   BR.EmitBasicReport(ADC->getDecl(), Checker, BugName.str(),
166ec727ea7Spatrick                      categories::MemoryRefCount, BugMessage.str(), Location,
167e5dd7070Spatrick                      Range);
168e5dd7070Spatrick }
169e5dd7070Spatrick 
checkASTCodeBody(const Decl * D,AnalysisManager & AM,BugReporter & BR) const170e5dd7070Spatrick void ObjCAutoreleaseWriteChecker::checkASTCodeBody(const Decl *D,
171e5dd7070Spatrick                                                   AnalysisManager &AM,
172e5dd7070Spatrick                                                   BugReporter &BR) const {
173e5dd7070Spatrick 
174e5dd7070Spatrick   auto DoublePointerParamM =
175e5dd7070Spatrick       parmVarDecl(hasType(hasCanonicalType(pointerType(
176e5dd7070Spatrick                       pointee(hasCanonicalType(objcObjectPointerType()))))))
177e5dd7070Spatrick           .bind(ParamBind);
178e5dd7070Spatrick 
179e5dd7070Spatrick   auto ReferencedParamM =
180e5dd7070Spatrick       declRefExpr(to(parmVarDecl(DoublePointerParamM))).bind(CapturedBind);
181e5dd7070Spatrick 
182e5dd7070Spatrick   // Write into a binded object, e.g. *ParamBind = X.
183e5dd7070Spatrick   auto WritesIntoM = binaryOperator(
184e5dd7070Spatrick     hasLHS(unaryOperator(
185e5dd7070Spatrick         hasOperatorName("*"),
186e5dd7070Spatrick         hasUnaryOperand(
187e5dd7070Spatrick           ignoringParenImpCasts(ReferencedParamM))
188e5dd7070Spatrick     )),
189e5dd7070Spatrick     hasOperatorName("=")
190e5dd7070Spatrick   ).bind(ProblematicWriteBind);
191e5dd7070Spatrick 
192e5dd7070Spatrick   auto ArgumentCaptureM = hasAnyArgument(
193e5dd7070Spatrick     ignoringParenImpCasts(ReferencedParamM));
194e5dd7070Spatrick   auto CapturedInParamM = stmt(anyOf(
195e5dd7070Spatrick       callExpr(ArgumentCaptureM),
196e5dd7070Spatrick       objcMessageExpr(ArgumentCaptureM)));
197e5dd7070Spatrick 
198e5dd7070Spatrick   // WritesIntoM happens inside a block passed as an argument.
199e5dd7070Spatrick   auto WritesOrCapturesInBlockM = hasAnyArgument(allOf(
200e5dd7070Spatrick       hasType(hasCanonicalType(blockPointerType())),
201e5dd7070Spatrick       forEachDescendant(
202e5dd7070Spatrick         stmt(anyOf(WritesIntoM, CapturedInParamM))
203e5dd7070Spatrick       )));
204e5dd7070Spatrick 
205e5dd7070Spatrick   auto BlockPassedToMarkedFuncM = stmt(anyOf(
206e5dd7070Spatrick     callExpr(allOf(
207e5dd7070Spatrick       callsNames(FunctionsWithAutoreleasingPool), WritesOrCapturesInBlockM)),
208e5dd7070Spatrick     objcMessageExpr(allOf(
209e5dd7070Spatrick        hasAnySelector(toRefs(SelectorsWithAutoreleasingPool)),
210e5dd7070Spatrick        WritesOrCapturesInBlockM))
211e5dd7070Spatrick   ));
212e5dd7070Spatrick 
213ec727ea7Spatrick   // WritesIntoM happens inside an explicit @autoreleasepool.
214ec727ea7Spatrick   auto WritesOrCapturesInPoolM =
215ec727ea7Spatrick       autoreleasePoolStmt(
216ec727ea7Spatrick           forEachDescendant(stmt(anyOf(WritesIntoM, CapturedInParamM))))
217ec727ea7Spatrick           .bind(IsARPBind);
218ec727ea7Spatrick 
219ec727ea7Spatrick   auto HasParamAndWritesInMarkedFuncM =
220ec727ea7Spatrick       allOf(hasAnyParameter(DoublePointerParamM),
221ec727ea7Spatrick             anyOf(forEachDescendant(BlockPassedToMarkedFuncM),
222ec727ea7Spatrick                   forEachDescendant(WritesOrCapturesInPoolM)));
223e5dd7070Spatrick 
224e5dd7070Spatrick   auto MatcherM = decl(anyOf(
225e5dd7070Spatrick       objcMethodDecl(HasParamAndWritesInMarkedFuncM).bind(IsMethodBind),
226e5dd7070Spatrick       functionDecl(HasParamAndWritesInMarkedFuncM),
227e5dd7070Spatrick       blockDecl(HasParamAndWritesInMarkedFuncM)));
228e5dd7070Spatrick 
229e5dd7070Spatrick   auto Matches = match(MatcherM, *D, AM.getASTContext());
230e5dd7070Spatrick   for (BoundNodes Match : Matches)
231e5dd7070Spatrick     emitDiagnostics(Match, D, BR, AM, this);
232e5dd7070Spatrick }
233e5dd7070Spatrick 
registerAutoreleaseWriteChecker(CheckerManager & Mgr)234e5dd7070Spatrick void ento::registerAutoreleaseWriteChecker(CheckerManager &Mgr) {
235e5dd7070Spatrick   Mgr.registerChecker<ObjCAutoreleaseWriteChecker>();
236e5dd7070Spatrick }
237e5dd7070Spatrick 
shouldRegisterAutoreleaseWriteChecker(const CheckerManager & mgr)238ec727ea7Spatrick bool ento::shouldRegisterAutoreleaseWriteChecker(const CheckerManager &mgr) {
239e5dd7070Spatrick   return true;
240e5dd7070Spatrick }
241