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