Coverage Report

Created: 2019-07-24 05:18

/Users/buildslave/jenkins/workspace/clang-stage2-coverage-R/llvm/tools/clang/lib/StaticAnalyzer/Checkers/ObjCAutoreleaseWriteChecker.cpp
Line
Count
Source
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
37
using namespace clang;
38
using namespace ento;
39
using namespace ast_matchers;
40
41
namespace {
42
43
const char *ProblematicWriteBind = "problematicwrite";
44
const char *CapturedBind = "capturedbind";
45
const char *ParamBind = "parambind";
46
const char *IsMethodBind = "ismethodbind";
47
48
class ObjCAutoreleaseWriteChecker : public Checker<check::ASTCodeBody> {
49
public:
50
  void checkASTCodeBody(const Decl *D,
51
                        AnalysisManager &AM,
52
                        BugReporter &BR) const;
53
private:
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
99
432
static inline std::vector<llvm::StringRef> toRefs(std::vector<std::string> V) {
100
432
  return std::vector<llvm::StringRef>(V.begin(), V.end());
101
432
}
102
103
static auto callsNames(std::vector<std::string> FunctionNames)
104
216
    -> decltype(callee(functionDecl())) {
105
216
  return callee(functionDecl(hasAnyName(toRefs(FunctionNames))));
106
216
}
107
108
static void emitDiagnostics(BoundNodes &Match, const Decl *D, BugReporter &BR,
109
                            AnalysisManager &AM,
110
20
                            const ObjCAutoreleaseWriteChecker *Checker) {
111
20
  AnalysisDeclContext *ADC = AM.getAnalysisDeclContext(D);
112
20
113
20
  const auto *PVD = Match.getNodeAs<ParmVarDecl>(ParamBind);
114
20
  QualType Ty = PVD->getType();
115
20
  if (Ty->getPointeeType().getObjCLifetime() != Qualifiers::OCL_Autoreleasing)
116
2
    return;
117
18
  const char *ActionMsg = "Write to";
118
18
  const auto *MarkedStmt = Match.getNodeAs<Expr>(ProblematicWriteBind);
119
18
  bool IsCapture = false;
120
18
121
18
  // Prefer to warn on write, but if not available, warn on capture.
122
18
  if (!MarkedStmt) {
123
5
    MarkedStmt = Match.getNodeAs<Expr>(CapturedBind);
124
5
    assert(MarkedStmt);
125
5
    ActionMsg = "Capture of";
126
5
    IsCapture = true;
127
5
  }
128
18
129
18
  SourceRange Range = MarkedStmt->getSourceRange();
130
18
  PathDiagnosticLocation Location = PathDiagnosticLocation::createBegin(
131
18
      MarkedStmt, BR.getSourceManager(), ADC);
132
18
  bool IsMethod = Match.getNodeAs<ObjCMethodDecl>(IsMethodBind) != nullptr;
133
18
  const char *Name = IsMethod ? 
"method"5
:
"function"13
;
134
18
135
18
  BR.EmitBasicReport(
136
18
      ADC->getDecl(), Checker,
137
18
      /*Name=*/(llvm::Twine(ActionMsg)
138
18
                + " autoreleasing out parameter inside autorelease pool").str(),
139
18
      /*BugCategory=*/"Memory",
140
18
      (llvm::Twine(ActionMsg) + " autoreleasing out parameter " +
141
18
       (IsCapture ? 
"'" + PVD->getName() + "'" + " "5
:
""13
) + "inside " +
142
18
       "autorelease pool that may exit before " + Name + " returns; consider "
143
18
       "writing first to a strong local variable declared outside of the block")
144
18
          .str(),
145
18
      Location,
146
18
      Range);
147
18
}
148
149
void ObjCAutoreleaseWriteChecker::checkASTCodeBody(const Decl *D,
150
                                                  AnalysisManager &AM,
151
216
                                                  BugReporter &BR) const {
152
216
153
216
  auto DoublePointerParamM =
154
216
      parmVarDecl(hasType(hasCanonicalType(pointerType(
155
216
                      pointee(hasCanonicalType(objcObjectPointerType()))))))
156
216
          .bind(ParamBind);
157
216
158
216
  auto ReferencedParamM =
159
216
      declRefExpr(to(parmVarDecl(DoublePointerParamM))).bind(CapturedBind);
160
216
161
216
  // Write into a binded object, e.g. *ParamBind = X.
162
216
  auto WritesIntoM = binaryOperator(
163
216
    hasLHS(unaryOperator(
164
216
        hasOperatorName("*"),
165
216
        hasUnaryOperand(
166
216
          ignoringParenImpCasts(ReferencedParamM))
167
216
    )),
168
216
    hasOperatorName("=")
169
216
  ).bind(ProblematicWriteBind);
170
216
171
216
  auto ArgumentCaptureM = hasAnyArgument(
172
216
    ignoringParenImpCasts(ReferencedParamM));
173
216
  auto CapturedInParamM = stmt(anyOf(
174
216
      callExpr(ArgumentCaptureM),
175
216
      objcMessageExpr(ArgumentCaptureM)));
176
216
177
216
  // WritesIntoM happens inside a block passed as an argument.
178
216
  auto WritesOrCapturesInBlockM = hasAnyArgument(allOf(
179
216
      hasType(hasCanonicalType(blockPointerType())),
180
216
      forEachDescendant(
181
216
        stmt(anyOf(WritesIntoM, CapturedInParamM))
182
216
      )));
183
216
184
216
  auto BlockPassedToMarkedFuncM = stmt(anyOf(
185
216
    callExpr(allOf(
186
216
      callsNames(FunctionsWithAutoreleasingPool), WritesOrCapturesInBlockM)),
187
216
    objcMessageExpr(allOf(
188
216
       hasAnySelector(toRefs(SelectorsWithAutoreleasingPool)),
189
216
       WritesOrCapturesInBlockM))
190
216
  ));
191
216
192
216
  auto HasParamAndWritesInMarkedFuncM = allOf(
193
216
      hasAnyParameter(DoublePointerParamM),
194
216
      forEachDescendant(BlockPassedToMarkedFuncM));
195
216
196
216
  auto MatcherM = decl(anyOf(
197
216
      objcMethodDecl(HasParamAndWritesInMarkedFuncM).bind(IsMethodBind),
198
216
      functionDecl(HasParamAndWritesInMarkedFuncM),
199
216
      blockDecl(HasParamAndWritesInMarkedFuncM)));
200
216
201
216
  auto Matches = match(MatcherM, *D, AM.getASTContext());
202
216
  for (BoundNodes Match : Matches)
203
20
    emitDiagnostics(Match, D, BR, AM, this);
204
216
}
205
206
25
void ento::registerAutoreleaseWriteChecker(CheckerManager &Mgr) {
207
25
  Mgr.registerChecker<ObjCAutoreleaseWriteChecker>();
208
25
}
209
210
25
bool ento::shouldRegisterAutoreleaseWriteChecker(const LangOptions &LO) {
211
25
  return true;
212
25
}