ObjCAutoreleaseWriteChecker.cpp revision 1.1.1.1
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
37using namespace clang;
38using namespace ento;
39using namespace ast_matchers;
40
41namespace {
42
43const char *ProblematicWriteBind = "problematicwrite";
44const char *CapturedBind = "capturedbind";
45const char *ParamBind = "parambind";
46const char *IsMethodBind = "ismethodbind";
47
48class ObjCAutoreleaseWriteChecker : public Checker<check::ASTCodeBody> {
49public:
50  void checkASTCodeBody(const Decl *D,
51                        AnalysisManager &AM,
52                        BugReporter &BR) const;
53private:
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
99static inline std::vector<llvm::StringRef> toRefs(std::vector<std::string> V) {
100  return std::vector<llvm::StringRef>(V.begin(), V.end());
101}
102
103static auto callsNames(std::vector<std::string> FunctionNames)
104    -> decltype(callee(functionDecl())) {
105  return callee(functionDecl(hasAnyName(toRefs(FunctionNames))));
106}
107
108static 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
149void 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
206void ento::registerAutoreleaseWriteChecker(CheckerManager &Mgr) {
207  Mgr.registerChecker<ObjCAutoreleaseWriteChecker>();
208}
209
210bool ento::shouldRegisterAutoreleaseWriteChecker(const LangOptions &LO) {
211  return true;
212}
213