Coverage Report

Created: 2023-09-12 09:32

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