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 {@code 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/Checker.h"
34 #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h"
35 #include "llvm/ADT/Twine.h"
37 using namespace clang;
39 using namespace ast_matchers;
43 const char *ProblematicWriteBind = "problematicwrite";
44 const char *CapturedBind = "capturedbind";
45 const char *ParamBind = "parambind";
46 const char *IsMethodBind = "ismethodbind";
48 class ObjCAutoreleaseWriteChecker : public Checker<check::ASTCodeBody> {
50 void checkASTCodeBody(const Decl *D,
52 BugReporter &BR) const;
54 std::vector<std::string> SelectorsWithAutoreleasingPool = {
55 // Common to NSArray, NSSet, NSOrderedSet
56 "enumerateObjectsUsingBlock:",
57 "enumerateObjectsWithOptions:usingBlock:",
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:",
69 "enumerateKeysAndObjectsUsingBlock:",
70 "enumerateKeysAndObjectsWithOptions:usingBlock:",
71 "keysOfEntriesPassingTest:",
72 "keysOfEntriesWithOptions:passingTest:",
75 "objectsPassingTest:",
76 "objectsWithOptions:passingTest:",
77 "enumerateIndexPathsWithOptions:usingBlock:",
80 "enumerateIndexesWithOptions:usingBlock:",
81 "enumerateIndexesUsingBlock:",
82 "enumerateIndexesInRange:options:usingBlock:",
83 "enumerateRangesUsingBlock:",
84 "enumerateRangesWithOptions:usingBlock:",
85 "enumerateRangesInRange:options:usingBlock:",
87 "indexesPassingTest:",
88 "indexWithOptions:passingTest:",
89 "indexesWithOptions:passingTest:",
90 "indexInRange:options:passingTest:",
91 "indexesInRange:options:passingTest:"
94 std::vector<std::string> FunctionsWithAutoreleasingPool = {
95 "dispatch_async", "dispatch_group_async", "dispatch_barrier_async"};
99 static inline std::vector<llvm::StringRef> toRefs(std::vector<std::string> V) {
100 return std::vector<llvm::StringRef>(V.begin(), V.end());
103 static auto callsNames(std::vector<std::string> FunctionNames)
104 -> decltype(callee(functionDecl())) {
105 return callee(functionDecl(hasAnyName(toRefs(FunctionNames))));
108 static void emitDiagnostics(BoundNodes &Match, const Decl *D, BugReporter &BR,
110 const ObjCAutoreleaseWriteChecker *Checker) {
111 AnalysisDeclContext *ADC = AM.getAnalysisDeclContext(D);
113 const auto *PVD = Match.getNodeAs<ParmVarDecl>(ParamBind);
114 QualType Ty = PVD->getType();
115 if (Ty->getPointeeType().getObjCLifetime() != Qualifiers::OCL_Autoreleasing)
117 const char *ActionMsg = "Write to";
118 const auto *MarkedStmt = Match.getNodeAs<Expr>(ProblematicWriteBind);
119 bool IsCapture = false;
121 // Prefer to warn on write, but if not available, warn on capture.
123 MarkedStmt = Match.getNodeAs<Expr>(CapturedBind);
125 ActionMsg = "Capture of";
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";
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")
149 void ObjCAutoreleaseWriteChecker::checkASTCodeBody(const Decl *D,
151 BugReporter &BR) const {
153 auto DoublePointerParamM =
154 parmVarDecl(hasType(hasCanonicalType(pointerType(
155 pointee(hasCanonicalType(objcObjectPointerType()))))))
158 auto ReferencedParamM =
159 declRefExpr(to(parmVarDecl(DoublePointerParamM))).bind(CapturedBind);
161 // Write into a binded object, e.g. *ParamBind = X.
162 auto WritesIntoM = binaryOperator(
163 hasLHS(unaryOperator(
164 hasOperatorName("*"),
166 ignoringParenImpCasts(ReferencedParamM))
169 ).bind(ProblematicWriteBind);
171 auto ArgumentCaptureM = hasAnyArgument(
172 ignoringParenImpCasts(ReferencedParamM));
173 auto CapturedInParamM = stmt(anyOf(
174 callExpr(ArgumentCaptureM),
175 objcMessageExpr(ArgumentCaptureM)));
177 // WritesIntoM happens inside a block passed as an argument.
178 auto WritesOrCapturesInBlockM = hasAnyArgument(allOf(
179 hasType(hasCanonicalType(blockPointerType())),
181 stmt(anyOf(WritesIntoM, CapturedInParamM))
184 auto BlockPassedToMarkedFuncM = stmt(anyOf(
186 callsNames(FunctionsWithAutoreleasingPool), WritesOrCapturesInBlockM)),
187 objcMessageExpr(allOf(
188 hasAnySelector(toRefs(SelectorsWithAutoreleasingPool)),
189 WritesOrCapturesInBlockM))
192 auto HasParamAndWritesInMarkedFuncM = allOf(
193 hasAnyParameter(DoublePointerParamM),
194 forEachDescendant(BlockPassedToMarkedFuncM));
196 auto MatcherM = decl(anyOf(
197 objcMethodDecl(HasParamAndWritesInMarkedFuncM).bind(IsMethodBind),
198 functionDecl(HasParamAndWritesInMarkedFuncM),
199 blockDecl(HasParamAndWritesInMarkedFuncM)));
201 auto Matches = match(MatcherM, *D, AM.getASTContext());
202 for (BoundNodes Match : Matches)
203 emitDiagnostics(Match, D, BR, AM, this);
206 void ento::registerAutoreleaseWriteChecker(CheckerManager &Mgr) {
207 Mgr.registerChecker<ObjCAutoreleaseWriteChecker>();
210 bool ento::shouldRegisterAutoreleaseWriteChecker(const LangOptions &LO) {