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