Coverage Report

Created: 2022-01-18 06:27

/Users/buildslave/jenkins/workspace/coverage/llvm-project/clang/lib/StaticAnalyzer/Checkers/NSErrorChecker.cpp
Line
Count
Source (jump to first uncovered line)
1
//=- NSErrorChecker.cpp - Coding conventions for uses of NSError -*- 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 a CheckNSError, a flow-insensitive check
10
//  that determines if an Objective-C class interface correctly returns
11
//  a non-void return type.
12
//
13
//  File under feature request PR 2600.
14
//
15
//===----------------------------------------------------------------------===//
16
17
#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
18
#include "clang/AST/Decl.h"
19
#include "clang/AST/DeclObjC.h"
20
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
21
#include "clang/StaticAnalyzer/Core/Checker.h"
22
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
23
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
24
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
25
#include "llvm/ADT/SmallString.h"
26
#include "llvm/Support/raw_ostream.h"
27
28
using namespace clang;
29
using namespace ento;
30
31
static bool IsNSError(QualType T, IdentifierInfo *II);
32
static bool IsCFError(QualType T, IdentifierInfo *II);
33
34
//===----------------------------------------------------------------------===//
35
// NSErrorMethodChecker
36
//===----------------------------------------------------------------------===//
37
38
namespace {
39
class NSErrorMethodChecker
40
    : public Checker< check::ASTDecl<ObjCMethodDecl> > {
41
  mutable IdentifierInfo *II;
42
43
public:
44
47
  NSErrorMethodChecker() : II(nullptr) {}
45
46
  void checkASTDecl(const ObjCMethodDecl *D,
47
                    AnalysisManager &mgr, BugReporter &BR) const;
48
};
49
}
50
51
void NSErrorMethodChecker::checkASTDecl(const ObjCMethodDecl *D,
52
                                        AnalysisManager &mgr,
53
99
                                        BugReporter &BR) const {
54
99
  if (!D->isThisDeclarationADefinition())
55
72
    return;
56
27
  if (!D->getReturnType()->isVoidType())
57
17
    return;
58
59
10
  if (!II)
60
6
    II = &D->getASTContext().Idents.get("NSError");
61
62
10
  bool hasNSError = false;
63
10
  for (const auto *I : D->parameters())  {
64
5
    if (IsNSError(I->getType(), II)) {
65
2
      hasNSError = true;
66
2
      break;
67
2
    }
68
5
  }
69
70
10
  if (hasNSError) {
71
2
    const char *err = "Method accepting NSError** "
72
2
        "should have a non-void return value to indicate whether or not an "
73
2
        "error occurred";
74
2
    PathDiagnosticLocation L =
75
2
      PathDiagnosticLocation::create(D, BR.getSourceManager());
76
2
    BR.EmitBasicReport(D, this, "Bad return type when passing NSError**",
77
2
                       "Coding conventions (Apple)", err, L);
78
2
  }
79
10
}
80
81
//===----------------------------------------------------------------------===//
82
// CFErrorFunctionChecker
83
//===----------------------------------------------------------------------===//
84
85
namespace {
86
class CFErrorFunctionChecker
87
    : public Checker< check::ASTDecl<FunctionDecl> > {
88
  mutable IdentifierInfo *II;
89
90
public:
91
48
  CFErrorFunctionChecker() : II(nullptr) {}
92
93
  void checkASTDecl(const FunctionDecl *D,
94
                    AnalysisManager &mgr, BugReporter &BR) const;
95
};
96
}
97
98
146
static bool hasReservedReturnType(const FunctionDecl *D) {
99
146
  if (isa<CXXConstructorDecl>(D))
100
7
    return true;
101
102
  // operators delete and delete[] are required to have 'void' return type
103
139
  auto OperatorKind = D->getOverloadedOperator();
104
139
  return OperatorKind == OO_Delete || 
OperatorKind == OO_Array_Delete137
;
105
146
}
106
107
void CFErrorFunctionChecker::checkASTDecl(const FunctionDecl *D,
108
                                        AnalysisManager &mgr,
109
384
                                        BugReporter &BR) const {
110
384
  if (!D->doesThisDeclarationHaveABody())
111
160
    return;
112
224
  if (!D->getReturnType()->isVoidType())
113
78
    return;
114
146
  if (hasReservedReturnType(D))
115
10
    return;
116
117
136
  if (!II)
118
32
    II = &D->getASTContext().Idents.get("CFErrorRef");
119
120
136
  bool hasCFError = false;
121
136
  for (auto I : D->parameters())  {
122
59
    if (IsCFError(I->getType(), II)) {
123
3
      hasCFError = true;
124
3
      break;
125
3
    }
126
59
  }
127
128
136
  if (hasCFError) {
129
3
    const char *err = "Function accepting CFErrorRef* "
130
3
        "should have a non-void return value to indicate whether or not an "
131
3
        "error occurred";
132
3
    PathDiagnosticLocation L =
133
3
      PathDiagnosticLocation::create(D, BR.getSourceManager());
134
3
    BR.EmitBasicReport(D, this, "Bad return type when passing CFErrorRef*",
135
3
                       "Coding conventions (Apple)", err, L);
136
3
  }
137
136
}
138
139
//===----------------------------------------------------------------------===//
140
// NSOrCFErrorDerefChecker
141
//===----------------------------------------------------------------------===//
142
143
namespace {
144
145
class NSErrorDerefBug : public BugType {
146
public:
147
  NSErrorDerefBug(const CheckerNameRef Checker)
148
      : BugType(Checker, "NSError** null dereference",
149
2
                "Coding conventions (Apple)") {}
150
};
151
152
class CFErrorDerefBug : public BugType {
153
public:
154
  CFErrorDerefBug(const CheckerNameRef Checker)
155
      : BugType(Checker, "CFErrorRef* null dereference",
156
2
                "Coding conventions (Apple)") {}
157
};
158
159
}
160
161
namespace {
162
class NSOrCFErrorDerefChecker
163
    : public Checker< check::Location,
164
                        check::Event<ImplicitNullDerefEvent> > {
165
  mutable IdentifierInfo *NSErrorII, *CFErrorII;
166
  mutable std::unique_ptr<NSErrorDerefBug> NSBT;
167
  mutable std::unique_ptr<CFErrorDerefBug> CFBT;
168
public:
169
  DefaultBool ShouldCheckNSError, ShouldCheckCFError;
170
  CheckerNameRef NSErrorName, CFErrorName;
171
48
  NSOrCFErrorDerefChecker() : NSErrorII(nullptr), CFErrorII(nullptr) {}
172
173
  void checkLocation(SVal loc, bool isLoad, const Stmt *S,
174
                     CheckerContext &C) const;
175
  void checkEvent(ImplicitNullDerefEvent event) const;
176
};
177
}
178
179
typedef llvm::ImmutableMap<SymbolRef, unsigned> ErrorOutFlag;
180
REGISTER_TRAIT_WITH_PROGRAMSTATE(NSErrorOut, ErrorOutFlag)
181
REGISTER_TRAIT_WITH_PROGRAMSTATE(CFErrorOut, ErrorOutFlag)
182
183
template <typename T>
184
14
static bool hasFlag(SVal val, ProgramStateRef state) {
185
14
  if (SymbolRef sym = val.getAsSymbol())
186
14
    if (const unsigned *attachedFlags = state->get<T>(sym))
187
5
      return *attachedFlags;
188
9
  return false;
189
14
}
NSErrorChecker.cpp:bool hasFlag<(anonymous namespace)::NSErrorOut>(clang::ento::SVal, llvm::IntrusiveRefCntPtr<clang::ento::ProgramState const>)
Line
Count
Source
184
8
static bool hasFlag(SVal val, ProgramStateRef state) {
185
8
  if (SymbolRef sym = val.getAsSymbol())
186
8
    if (const unsigned *attachedFlags = state->get<T>(sym))
187
2
      return *attachedFlags;
188
6
  return false;
189
8
}
NSErrorChecker.cpp:bool hasFlag<(anonymous namespace)::CFErrorOut>(clang::ento::SVal, llvm::IntrusiveRefCntPtr<clang::ento::ProgramState const>)
Line
Count
Source
184
6
static bool hasFlag(SVal val, ProgramStateRef state) {
185
6
  if (SymbolRef sym = val.getAsSymbol())
186
6
    if (const unsigned *attachedFlags = state->get<T>(sym))
187
3
      return *attachedFlags;
188
3
  return false;
189
6
}
190
191
template <typename T>
192
17
static void setFlag(ProgramStateRef state, SVal val, CheckerContext &C) {
193
  // We tag the symbol that the SVal wraps.
194
17
  if (SymbolRef sym = val.getAsSymbol())
195
17
    C.addTransition(state->set<T>(sym, true));
196
17
}
NSErrorChecker.cpp:void setFlag<(anonymous namespace)::NSErrorOut>(llvm::IntrusiveRefCntPtr<clang::ento::ProgramState const>, clang::ento::SVal, clang::ento::CheckerContext&)
Line
Count
Source
192
6
static void setFlag(ProgramStateRef state, SVal val, CheckerContext &C) {
193
  // We tag the symbol that the SVal wraps.
194
6
  if (SymbolRef sym = val.getAsSymbol())
195
6
    C.addTransition(state->set<T>(sym, true));
196
6
}
NSErrorChecker.cpp:void setFlag<(anonymous namespace)::CFErrorOut>(llvm::IntrusiveRefCntPtr<clang::ento::ProgramState const>, clang::ento::SVal, clang::ento::CheckerContext&)
Line
Count
Source
192
11
static void setFlag(ProgramStateRef state, SVal val, CheckerContext &C) {
193
  // We tag the symbol that the SVal wraps.
194
11
  if (SymbolRef sym = val.getAsSymbol())
195
11
    C.addTransition(state->set<T>(sym, true));
196
11
}
197
198
488
static QualType parameterTypeFromSVal(SVal val, CheckerContext &C) {
199
488
  const StackFrameContext * SFC = C.getStackFrame();
200
488
  if (Optional<loc::MemRegionVal> X = val.getAs<loc::MemRegionVal>()) {
201
488
    const MemRegion* R = X->getRegion();
202
488
    if (const VarRegion *VR = R->getAs<VarRegion>())
203
449
      if (const StackArgumentsSpaceRegion *
204
449
          stackReg = dyn_cast<StackArgumentsSpaceRegion>(VR->getMemorySpace()))
205
198
        if (stackReg->getStackFrame() == SFC)
206
198
          return VR->getValueType();
207
488
  }
208
209
290
  return QualType();
210
488
}
211
212
void NSOrCFErrorDerefChecker::checkLocation(SVal loc, bool isLoad,
213
                                            const Stmt *S,
214
550
                                            CheckerContext &C) const {
215
550
  if (!isLoad)
216
62
    return;
217
488
  if (loc.isUndef() || !loc.getAs<Loc>())
218
0
    return;
219
220
488
  ASTContext &Ctx = C.getASTContext();
221
488
  ProgramStateRef state = C.getState();
222
223
  // If we are loading from NSError**/CFErrorRef* parameter, mark the resulting
224
  // SVal so that we can later check it when handling the
225
  // ImplicitNullDerefEvent event.
226
  // FIXME: Cumbersome! Maybe add hook at construction of SVals at start of
227
  // function ?
228
229
488
  QualType parmT = parameterTypeFromSVal(loc, C);
230
488
  if (parmT.isNull())
231
290
    return;
232
233
198
  if (!NSErrorII)
234
29
    NSErrorII = &Ctx.Idents.get("NSError");
235
198
  if (!CFErrorII)
236
29
    CFErrorII = &Ctx.Idents.get("CFErrorRef");
237
238
198
  if (ShouldCheckNSError && IsNSError(parmT, NSErrorII)) {
239
6
    setFlag<NSErrorOut>(state, state->getSVal(loc.castAs<Loc>()), C);
240
6
    return;
241
6
  }
242
243
192
  if (ShouldCheckCFError && IsCFError(parmT, CFErrorII)) {
244
11
    setFlag<CFErrorOut>(state, state->getSVal(loc.castAs<Loc>()), C);
245
11
    return;
246
11
  }
247
192
}
248
249
11
void NSOrCFErrorDerefChecker::checkEvent(ImplicitNullDerefEvent event) const {
250
11
  if (event.IsLoad)
251
3
    return;
252
253
8
  SVal loc = event.Location;
254
8
  ProgramStateRef state = event.SinkNode->getState();
255
8
  BugReporter &BR = *event.BR;
256
257
8
  bool isNSError = hasFlag<NSErrorOut>(loc, state);
258
8
  bool isCFError = false;
259
8
  if (!isNSError)
260
6
    isCFError = hasFlag<CFErrorOut>(loc, state);
261
262
8
  if (!(isNSError || 
isCFError6
))
263
3
    return;
264
265
  // Storing to possible null NSError/CFErrorRef out parameter.
266
5
  SmallString<128> Buf;
267
5
  llvm::raw_svector_ostream os(Buf);
268
269
5
  os << "Potential null dereference.  According to coding standards ";
270
5
  os << (isNSError
271
5
         ? 
"in 'Creating and Returning NSError Objects' the parameter"2
272
5
         : 
"documented in CoreFoundation/CFError.h the parameter"3
);
273
274
5
  os  << " may be null";
275
276
5
  BugType *bug = nullptr;
277
5
  if (isNSError) {
278
2
    if (!NSBT)
279
2
      NSBT.reset(new NSErrorDerefBug(NSErrorName));
280
2
    bug = NSBT.get();
281
2
  }
282
3
  else {
283
3
    if (!CFBT)
284
2
      CFBT.reset(new CFErrorDerefBug(CFErrorName));
285
3
    bug = CFBT.get();
286
3
  }
287
5
  BR.emitReport(
288
5
      std::make_unique<PathSensitiveBugReport>(*bug, os.str(), event.SinkNode));
289
5
}
290
291
203
static bool IsNSError(QualType T, IdentifierInfo *II) {
292
293
203
  const PointerType* PPT = T->getAs<PointerType>();
294
203
  if (!PPT)
295
84
    return false;
296
297
119
  const ObjCObjectPointerType* PT =
298
119
    PPT->getPointeeType()->getAs<ObjCObjectPointerType>();
299
300
119
  if (!PT)
301
105
    return false;
302
303
14
  const ObjCInterfaceDecl *ID = PT->getInterfaceDecl();
304
305
  // FIXME: Can ID ever be NULL?
306
14
  if (ID)
307
14
    return II == ID->getIdentifier();
308
309
0
  return false;
310
14
}
311
312
251
static bool IsCFError(QualType T, IdentifierInfo *II) {
313
251
  const PointerType* PPT = T->getAs<PointerType>();
314
251
  if (!PPT) 
return false97
;
315
316
154
  const TypedefType* TT = PPT->getPointeeType()->getAs<TypedefType>();
317
154
  if (!TT) 
return false130
;
318
319
24
  return TT->getDecl()->getIdentifier() == II;
320
154
}
321
322
48
void ento::registerNSOrCFErrorDerefChecker(CheckerManager &mgr) {
323
48
  mgr.registerChecker<NSOrCFErrorDerefChecker>();
324
48
}
325
326
282
bool ento::shouldRegisterNSOrCFErrorDerefChecker(const CheckerManager &mgr) {
327
282
  return true;
328
282
}
329
330
47
void ento::registerNSErrorChecker(CheckerManager &mgr) {
331
47
  mgr.registerChecker<NSErrorMethodChecker>();
332
47
  NSOrCFErrorDerefChecker *checker = mgr.getChecker<NSOrCFErrorDerefChecker>();
333
47
  checker->ShouldCheckNSError = true;
334
47
  checker->NSErrorName = mgr.getCurrentCheckerName();
335
47
}
336
337
94
bool ento::shouldRegisterNSErrorChecker(const CheckerManager &mgr) {
338
94
  return true;
339
94
}
340
341
48
void ento::registerCFErrorChecker(CheckerManager &mgr) {
342
48
  mgr.registerChecker<CFErrorFunctionChecker>();
343
48
  NSOrCFErrorDerefChecker *checker = mgr.getChecker<NSOrCFErrorDerefChecker>();
344
48
  checker->ShouldCheckCFError = true;
345
48
  checker->CFErrorName = mgr.getCurrentCheckerName();
346
48
}
347
348
96
bool ento::shouldRegisterCFErrorChecker(const CheckerManager &mgr) {
349
96
  return true;
350
96
}