Coverage Report

Created: 2023-09-30 09:22

/Users/buildslave/jenkins/workspace/coverage/llvm-project/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 @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
static inline std::vector<llvm::StringRef>
102
604
toRefs(const std::vector<std::string> &V) {
103
604
  return std::vector<llvm::StringRef>(V.begin(), V.end());
104
604
}
105
106
static decltype(auto)
107
302
callsNames(const std::vector<std::string> &FunctionNames) {
108
302
  return callee(functionDecl(hasAnyName(toRefs(FunctionNames))));
109
302
}
110
111
static void emitDiagnostics(BoundNodes &Match, const Decl *D, BugReporter &BR,
112
                            AnalysisManager &AM,
113
26
                            const ObjCAutoreleaseWriteChecker *Checker) {
114
26
  AnalysisDeclContext *ADC = AM.getAnalysisDeclContext(D);
115
116
26
  const auto *PVD = Match.getNodeAs<ParmVarDecl>(ParamBind);
117
26
  QualType Ty = PVD->getType();
118
26
  if (Ty->getPointeeType().getObjCLifetime() != Qualifiers::OCL_Autoreleasing)
119
3
    return;
120
23
  const char *ActionMsg = "Write to";
121
23
  const auto *MarkedStmt = Match.getNodeAs<Expr>(ProblematicWriteBind);
122
23
  bool IsCapture = false;
123
124
  // Prefer to warn on write, but if not available, warn on capture.
125
23
  if (!MarkedStmt) {
126
5
    MarkedStmt = Match.getNodeAs<Expr>(CapturedBind);
127
5
    assert(MarkedStmt);
128
5
    ActionMsg = "Capture of";
129
5
    IsCapture = true;
130
5
  }
131
132
23
  SourceRange Range = MarkedStmt->getSourceRange();
133
23
  PathDiagnosticLocation Location = PathDiagnosticLocation::createBegin(
134
23
      MarkedStmt, BR.getSourceManager(), ADC);
135
136
23
  bool IsMethod = Match.getNodeAs<ObjCMethodDecl>(IsMethodBind) != nullptr;
137
23
  const char *FunctionDescription = IsMethod ? 
"method"9
:
"function"14
;
138
23
  bool IsARP = Match.getNodeAs<ObjCAutoreleasePoolStmt>(IsARPBind) != nullptr;
139
140
23
  llvm::SmallString<128> BugNameBuf;
141
23
  llvm::raw_svector_ostream BugName(BugNameBuf);
142
23
  BugName << ActionMsg
143
23
          << " autoreleasing out parameter inside autorelease pool";
144
145
23
  llvm::SmallString<128> BugMessageBuf;
146
23
  llvm::raw_svector_ostream BugMessage(BugMessageBuf);
147
23
  BugMessage << ActionMsg << " autoreleasing out parameter ";
148
23
  if (IsCapture)
149
5
    BugMessage << "'" + PVD->getName() + "' ";
150
151
23
  BugMessage << "inside ";
152
23
  if (IsARP)
153
5
    BugMessage << "locally-scoped autorelease pool;";
154
18
  else
155
18
    BugMessage << "autorelease pool that may exit before "
156
18
               << FunctionDescription << " returns;";
157
158
23
  BugMessage << " consider writing first to a strong local variable"
159
23
                " declared outside ";
160
23
  if (IsARP)
161
5
    BugMessage << "of the autorelease pool";
162
18
  else
163
18
    BugMessage << "of the block";
164
165
23
  BR.EmitBasicReport(ADC->getDecl(), Checker, BugName.str(),
166
23
                     categories::MemoryRefCount, BugMessage.str(), Location,
167
23
                     Range);
168
23
}
169
170
void ObjCAutoreleaseWriteChecker::checkASTCodeBody(const Decl *D,
171
                                                  AnalysisManager &AM,
172
302
                                                  BugReporter &BR) const {
173
174
302
  auto DoublePointerParamM =
175
302
      parmVarDecl(hasType(hasCanonicalType(pointerType(
176
302
                      pointee(hasCanonicalType(objcObjectPointerType()))))))
177
302
          .bind(ParamBind);
178
179
302
  auto ReferencedParamM =
180
302
      declRefExpr(to(parmVarDecl(DoublePointerParamM))).bind(CapturedBind);
181
182
  // Write into a binded object, e.g. *ParamBind = X.
183
302
  auto WritesIntoM = binaryOperator(
184
302
    hasLHS(unaryOperator(
185
302
        hasOperatorName("*"),
186
302
        hasUnaryOperand(
187
302
          ignoringParenImpCasts(ReferencedParamM))
188
302
    )),
189
302
    hasOperatorName("=")
190
302
  ).bind(ProblematicWriteBind);
191
192
302
  auto ArgumentCaptureM = hasAnyArgument(
193
302
    ignoringParenImpCasts(ReferencedParamM));
194
302
  auto CapturedInParamM = stmt(anyOf(
195
302
      callExpr(ArgumentCaptureM),
196
302
      objcMessageExpr(ArgumentCaptureM)));
197
198
  // WritesIntoM happens inside a block passed as an argument.
199
302
  auto WritesOrCapturesInBlockM = hasAnyArgument(allOf(
200
302
      hasType(hasCanonicalType(blockPointerType())),
201
302
      forEachDescendant(
202
302
        stmt(anyOf(WritesIntoM, CapturedInParamM))
203
302
      )));
204
205
302
  auto BlockPassedToMarkedFuncM = stmt(anyOf(
206
302
    callExpr(allOf(
207
302
      callsNames(FunctionsWithAutoreleasingPool), WritesOrCapturesInBlockM)),
208
302
    objcMessageExpr(allOf(
209
302
       hasAnySelector(toRefs(SelectorsWithAutoreleasingPool)),
210
302
       WritesOrCapturesInBlockM))
211
302
  ));
212
213
  // WritesIntoM happens inside an explicit @autoreleasepool.
214
302
  auto WritesOrCapturesInPoolM =
215
302
      autoreleasePoolStmt(
216
302
          forEachDescendant(stmt(anyOf(WritesIntoM, CapturedInParamM))))
217
302
          .bind(IsARPBind);
218
219
302
  auto HasParamAndWritesInMarkedFuncM =
220
302
      allOf(hasAnyParameter(DoublePointerParamM),
221
302
            anyOf(forEachDescendant(BlockPassedToMarkedFuncM),
222
302
                  forEachDescendant(WritesOrCapturesInPoolM)));
223
224
302
  auto MatcherM = decl(anyOf(
225
302
      objcMethodDecl(HasParamAndWritesInMarkedFuncM).bind(IsMethodBind),
226
302
      functionDecl(HasParamAndWritesInMarkedFuncM),
227
302
      blockDecl(HasParamAndWritesInMarkedFuncM)));
228
229
302
  auto Matches = match(MatcherM, *D, AM.getASTContext());
230
302
  for (BoundNodes Match : Matches)
231
26
    emitDiagnostics(Match, D, BR, AM, this);
232
302
}
233
234
49
void ento::registerAutoreleaseWriteChecker(CheckerManager &Mgr) {
235
49
  Mgr.registerChecker<ObjCAutoreleaseWriteChecker>();
236
49
}
237
238
98
bool ento::shouldRegisterAutoreleaseWriteChecker(const CheckerManager &mgr) {
239
98
  return true;
240
98
}