1 //===- ObjCAutoreleaseWriteChecker.cpp ----------------------------*- C++ -*-==//
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
7 //===----------------------------------------------------------------------===//
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
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];
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.
27 //===----------------------------------------------------------------------===//
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"
38 using namespace clang
;
40 using namespace ast_matchers
;
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";
50 class ObjCAutoreleaseWriteChecker
: public Checker
<check::ASTCodeBody
> {
52 void checkASTCodeBody(const Decl
*D
,
54 BugReporter
&BR
) const;
56 std::vector
<std::string
> SelectorsWithAutoreleasingPool
= {
57 // Common to NSArray, NSSet, NSOrderedSet
58 "enumerateObjectsUsingBlock:",
59 "enumerateObjectsWithOptions:usingBlock:",
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:",
71 "enumerateKeysAndObjectsUsingBlock:",
72 "enumerateKeysAndObjectsWithOptions:usingBlock:",
73 "keysOfEntriesPassingTest:",
74 "keysOfEntriesWithOptions:passingTest:",
77 "objectsPassingTest:",
78 "objectsWithOptions:passingTest:",
79 "enumerateIndexPathsWithOptions:usingBlock:",
82 "enumerateIndexesWithOptions:usingBlock:",
83 "enumerateIndexesUsingBlock:",
84 "enumerateIndexesInRange:options:usingBlock:",
85 "enumerateRangesUsingBlock:",
86 "enumerateRangesWithOptions:usingBlock:",
87 "enumerateRangesInRange:options:usingBlock:",
89 "indexesPassingTest:",
90 "indexWithOptions:passingTest:",
91 "indexesWithOptions:passingTest:",
92 "indexInRange:options:passingTest:",
93 "indexesInRange:options:passingTest:"
96 std::vector
<std::string
> FunctionsWithAutoreleasingPool
= {
97 "dispatch_async", "dispatch_group_async", "dispatch_barrier_async"};
101 static inline std::vector
<llvm::StringRef
>
102 toRefs(const std::vector
<std::string
> &V
) {
103 return std::vector
<llvm::StringRef
>(V
.begin(), V
.end());
106 static decltype(auto)
107 callsNames(const std::vector
<std::string
> &FunctionNames
) {
108 return callee(functionDecl(hasAnyName(toRefs(FunctionNames
))));
111 static void emitDiagnostics(BoundNodes
&Match
, const Decl
*D
, BugReporter
&BR
,
113 const ObjCAutoreleaseWriteChecker
*Checker
) {
114 AnalysisDeclContext
*ADC
= AM
.getAnalysisDeclContext(D
);
116 const auto *PVD
= Match
.getNodeAs
<ParmVarDecl
>(ParamBind
);
117 QualType Ty
= PVD
->getType();
118 if (Ty
->getPointeeType().getObjCLifetime() != Qualifiers::OCL_Autoreleasing
)
120 const char *ActionMsg
= "Write to";
121 const auto *MarkedStmt
= Match
.getNodeAs
<Expr
>(ProblematicWriteBind
);
122 bool IsCapture
= false;
124 // Prefer to warn on write, but if not available, warn on capture.
126 MarkedStmt
= Match
.getNodeAs
<Expr
>(CapturedBind
);
128 ActionMsg
= "Capture of";
132 SourceRange Range
= MarkedStmt
->getSourceRange();
133 PathDiagnosticLocation Location
= PathDiagnosticLocation::createBegin(
134 MarkedStmt
, BR
.getSourceManager(), ADC
);
136 bool IsMethod
= Match
.getNodeAs
<ObjCMethodDecl
>(IsMethodBind
) != nullptr;
137 const char *FunctionDescription
= IsMethod
? "method" : "function";
138 bool IsARP
= Match
.getNodeAs
<ObjCAutoreleasePoolStmt
>(IsARPBind
) != nullptr;
140 llvm::SmallString
<128> BugNameBuf
;
141 llvm::raw_svector_ostream
BugName(BugNameBuf
);
143 << " autoreleasing out parameter inside autorelease pool";
145 llvm::SmallString
<128> BugMessageBuf
;
146 llvm::raw_svector_ostream
BugMessage(BugMessageBuf
);
147 BugMessage
<< ActionMsg
<< " autoreleasing out parameter ";
149 BugMessage
<< "'" + PVD
->getName() + "' ";
151 BugMessage
<< "inside ";
153 BugMessage
<< "locally-scoped autorelease pool;";
155 BugMessage
<< "autorelease pool that may exit before "
156 << FunctionDescription
<< " returns;";
158 BugMessage
<< " consider writing first to a strong local variable"
159 " declared outside ";
161 BugMessage
<< "of the autorelease pool";
163 BugMessage
<< "of the block";
165 BR
.EmitBasicReport(ADC
->getDecl(), Checker
, BugName
.str(),
166 categories::MemoryRefCount
, BugMessage
.str(), Location
,
170 void ObjCAutoreleaseWriteChecker::checkASTCodeBody(const Decl
*D
,
172 BugReporter
&BR
) const {
174 auto DoublePointerParamM
=
175 parmVarDecl(hasType(hasCanonicalType(pointerType(
176 pointee(hasCanonicalType(objcObjectPointerType()))))))
179 auto ReferencedParamM
=
180 declRefExpr(to(parmVarDecl(DoublePointerParamM
))).bind(CapturedBind
);
182 // Write into a binded object, e.g. *ParamBind = X.
183 auto WritesIntoM
= binaryOperator(
184 hasLHS(unaryOperator(
185 hasOperatorName("*"),
187 ignoringParenImpCasts(ReferencedParamM
))
190 ).bind(ProblematicWriteBind
);
192 auto ArgumentCaptureM
= hasAnyArgument(
193 ignoringParenImpCasts(ReferencedParamM
));
194 auto CapturedInParamM
= stmt(anyOf(
195 callExpr(ArgumentCaptureM
),
196 objcMessageExpr(ArgumentCaptureM
)));
198 // WritesIntoM happens inside a block passed as an argument.
199 auto WritesOrCapturesInBlockM
= hasAnyArgument(allOf(
200 hasType(hasCanonicalType(blockPointerType())),
202 stmt(anyOf(WritesIntoM
, CapturedInParamM
))
205 auto BlockPassedToMarkedFuncM
= stmt(anyOf(
207 callsNames(FunctionsWithAutoreleasingPool
), WritesOrCapturesInBlockM
)),
208 objcMessageExpr(allOf(
209 hasAnySelector(toRefs(SelectorsWithAutoreleasingPool
)),
210 WritesOrCapturesInBlockM
))
213 // WritesIntoM happens inside an explicit @autoreleasepool.
214 auto WritesOrCapturesInPoolM
=
216 forEachDescendant(stmt(anyOf(WritesIntoM
, CapturedInParamM
))))
219 auto HasParamAndWritesInMarkedFuncM
=
220 allOf(hasAnyParameter(DoublePointerParamM
),
221 anyOf(forEachDescendant(BlockPassedToMarkedFuncM
),
222 forEachDescendant(WritesOrCapturesInPoolM
)));
224 auto MatcherM
= decl(anyOf(
225 objcMethodDecl(HasParamAndWritesInMarkedFuncM
).bind(IsMethodBind
),
226 functionDecl(HasParamAndWritesInMarkedFuncM
),
227 blockDecl(HasParamAndWritesInMarkedFuncM
)));
229 auto Matches
= match(MatcherM
, *D
, AM
.getASTContext());
230 for (BoundNodes Match
: Matches
)
231 emitDiagnostics(Match
, D
, BR
, AM
, this);
234 void ento::registerAutoreleaseWriteChecker(CheckerManager
&Mgr
) {
235 Mgr
.registerChecker
<ObjCAutoreleaseWriteChecker
>();
238 bool ento::shouldRegisterAutoreleaseWriteChecker(const CheckerManager
&mgr
) {