Coverage Report

Created: 2022-01-22 13:19

/Users/buildslave/jenkins/workspace/coverage/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCAutoreleaseWriteChecker.cpp
Line
Count
Source (jump to first uncovered line)
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 @c 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/BugReporter/CommonBugCategories.h"
34
#include "clang/StaticAnalyzer/Core/Checker.h"
35
#include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h"
36
#include "llvm/ADT/Twine.h"
37
38
using namespace clang;
39
using namespace ento;
40
using namespace ast_matchers;
41
42
namespace {
43
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";
49
50
class ObjCAutoreleaseWriteChecker : public Checker<check::ASTCodeBody> {
51
public:
52
  void checkASTCodeBody(const Decl *D,
53
                        AnalysisManager &AM,
54
                        BugReporter &BR) const;
55
private:
56
  std::vector<std::string> SelectorsWithAutoreleasingPool = {
57
      // Common to NSArray,  NSSet, NSOrderedSet
58
      "enumerateObjectsUsingBlock:",
59
      "enumerateObjectsWithOptions:usingBlock:",
60
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:",
69
70
      // NSDictionary
71
      "enumerateKeysAndObjectsUsingBlock:",
72
      "enumerateKeysAndObjectsWithOptions:usingBlock:",
73
      "keysOfEntriesPassingTest:",
74
      "keysOfEntriesWithOptions:passingTest:",
75
76
      // NSSet
77
      "objectsPassingTest:",
78
      "objectsWithOptions:passingTest:",
79
      "enumerateIndexPathsWithOptions:usingBlock:",
80
81
      // NSIndexSet
82
      "enumerateIndexesWithOptions:usingBlock:",
83
      "enumerateIndexesUsingBlock:",
84
      "enumerateIndexesInRange:options:usingBlock:",
85
      "enumerateRangesUsingBlock:",
86
      "enumerateRangesWithOptions:usingBlock:",
87
      "enumerateRangesInRange:options:usingBlock:",
88
      "indexPassingTest:",
89
      "indexesPassingTest:",
90
      "indexWithOptions:passingTest:",
91
      "indexesWithOptions:passingTest:",
92
      "indexInRange:options:passingTest:",
93
      "indexesInRange:options:passingTest:"
94
  };
95
96
  std::vector<std::string> FunctionsWithAutoreleasingPool = {
97
      "dispatch_async", "dispatch_group_async", "dispatch_barrier_async"};
98
};
99
}
100
101
534
static inline std::vector<llvm::StringRef> toRefs(std::vector<std::string> V) {
102
534
  return std::vector<llvm::StringRef>(V.begin(), V.end());
103
534
}
104
105
267
static decltype(auto) callsNames(std::vector<std::string> FunctionNames) {
106
267
  return callee(functionDecl(hasAnyName(toRefs(FunctionNames))));
107
267
}
108
109
static void emitDiagnostics(BoundNodes &Match, const Decl *D, BugReporter &BR,
110
                            AnalysisManager &AM,
111
26
                            const ObjCAutoreleaseWriteChecker *Checker) {
112
26
  AnalysisDeclContext *ADC = AM.getAnalysisDeclContext(D);
113
114
26
  const auto *PVD = Match.getNodeAs<ParmVarDecl>(ParamBind);
115
26
  QualType Ty = PVD->getType();
116
26
  if (Ty->getPointeeType().getObjCLifetime() != Qualifiers::OCL_Autoreleasing)
117
3
    return;
118
23
  const char *ActionMsg = "Write to";
119
23
  const auto *MarkedStmt = Match.getNodeAs<Expr>(ProblematicWriteBind);
120
23
  bool IsCapture = false;
121
122
  // Prefer to warn on write, but if not available, warn on capture.
123
23
  if (!MarkedStmt) {
124
5
    MarkedStmt = Match.getNodeAs<Expr>(CapturedBind);
125
5
    assert(MarkedStmt);
126
0
    ActionMsg = "Capture of";
127
5
    IsCapture = true;
128
5
  }
129
130
0
  SourceRange Range = MarkedStmt->getSourceRange();
131
23
  PathDiagnosticLocation Location = PathDiagnosticLocation::createBegin(
132
23
      MarkedStmt, BR.getSourceManager(), ADC);
133
134
23
  bool IsMethod = Match.getNodeAs<ObjCMethodDecl>(IsMethodBind) != nullptr;
135
23
  const char *FunctionDescription = IsMethod ? 
"method"9
:
"function"14
;
136
23
  bool IsARP = Match.getNodeAs<ObjCAutoreleasePoolStmt>(IsARPBind) != nullptr;
137
138
23
  llvm::SmallString<128> BugNameBuf;
139
23
  llvm::raw_svector_ostream BugName(BugNameBuf);
140
23
  BugName << ActionMsg
141
23
          << " autoreleasing out parameter inside autorelease pool";
142
143
23
  llvm::SmallString<128> BugMessageBuf;
144
23
  llvm::raw_svector_ostream BugMessage(BugMessageBuf);
145
23
  BugMessage << ActionMsg << " autoreleasing out parameter ";
146
23
  if (IsCapture)
147
5
    BugMessage << "'" + PVD->getName() + "' ";
148
149
23
  BugMessage << "inside ";
150
23
  if (IsARP)
151
5
    BugMessage << "locally-scoped autorelease pool;";
152
18
  else
153
18
    BugMessage << "autorelease pool that may exit before "
154
18
               << FunctionDescription << " returns;";
155
156
23
  BugMessage << " consider writing first to a strong local variable"
157
23
                " declared outside ";
158
23
  if (IsARP)
159
5
    BugMessage << "of the autorelease pool";
160
18
  else
161
18
    BugMessage << "of the block";
162
163
23
  BR.EmitBasicReport(ADC->getDecl(), Checker, BugName.str(),
164
23
                     categories::MemoryRefCount, BugMessage.str(), Location,
165
23
                     Range);
166
23
}
167
168
void ObjCAutoreleaseWriteChecker::checkASTCodeBody(const Decl *D,
169
                                                  AnalysisManager &AM,
170
267
                                                  BugReporter &BR) const {
171
172
267
  auto DoublePointerParamM =
173
267
      parmVarDecl(hasType(hasCanonicalType(pointerType(
174
267
                      pointee(hasCanonicalType(objcObjectPointerType()))))))
175
267
          .bind(ParamBind);
176
177
267
  auto ReferencedParamM =
178
267
      declRefExpr(to(parmVarDecl(DoublePointerParamM))).bind(CapturedBind);
179
180
  // Write into a binded object, e.g. *ParamBind = X.
181
267
  auto WritesIntoM = binaryOperator(
182
267
    hasLHS(unaryOperator(
183
267
        hasOperatorName("*"),
184
267
        hasUnaryOperand(
185
267
          ignoringParenImpCasts(ReferencedParamM))
186
267
    )),
187
267
    hasOperatorName("=")
188
267
  ).bind(ProblematicWriteBind);
189
190
267
  auto ArgumentCaptureM = hasAnyArgument(
191
267
    ignoringParenImpCasts(ReferencedParamM));
192
267
  auto CapturedInParamM = stmt(anyOf(
193
267
      callExpr(ArgumentCaptureM),
194
267
      objcMessageExpr(ArgumentCaptureM)));
195
196
  // WritesIntoM happens inside a block passed as an argument.
197
267
  auto WritesOrCapturesInBlockM = hasAnyArgument(allOf(
198
267
      hasType(hasCanonicalType(blockPointerType())),
199
267
      forEachDescendant(
200
267
        stmt(anyOf(WritesIntoM, CapturedInParamM))
201
267
      )));
202
203
267
  auto BlockPassedToMarkedFuncM = stmt(anyOf(
204
267
    callExpr(allOf(
205
267
      callsNames(FunctionsWithAutoreleasingPool), WritesOrCapturesInBlockM)),
206
267
    objcMessageExpr(allOf(
207
267
       hasAnySelector(toRefs(SelectorsWithAutoreleasingPool)),
208
267
       WritesOrCapturesInBlockM))
209
267
  ));
210
211
  // WritesIntoM happens inside an explicit @autoreleasepool.
212
267
  auto WritesOrCapturesInPoolM =
213
267
      autoreleasePoolStmt(
214
267
          forEachDescendant(stmt(anyOf(WritesIntoM, CapturedInParamM))))
215
267
          .bind(IsARPBind);
216
217
267
  auto HasParamAndWritesInMarkedFuncM =
218
267
      allOf(hasAnyParameter(DoublePointerParamM),
219
267
            anyOf(forEachDescendant(BlockPassedToMarkedFuncM),
220
267
                  forEachDescendant(WritesOrCapturesInPoolM)));
221
222
267
  auto MatcherM = decl(anyOf(
223
267
      objcMethodDecl(HasParamAndWritesInMarkedFuncM).bind(IsMethodBind),
224
267
      functionDecl(HasParamAndWritesInMarkedFuncM),
225
267
      blockDecl(HasParamAndWritesInMarkedFuncM)));
226
227
267
  auto Matches = match(MatcherM, *D, AM.getASTContext());
228
267
  for (BoundNodes Match : Matches)
229
26
    emitDiagnostics(Match, D, BR, AM, this);
230
267
}
231
232
48
void ento::registerAutoreleaseWriteChecker(CheckerManager &Mgr) {
233
48
  Mgr.registerChecker<ObjCAutoreleaseWriteChecker>();
234
48
}
235
236
96
bool ento::shouldRegisterAutoreleaseWriteChecker(const CheckerManager &mgr) {
237
96
  return true;
238
96
}