clang  11.0.0git
NSErrorChecker.cpp
Go to the documentation of this file.
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 
18 #include "clang/AST/Decl.h"
19 #include "clang/AST/DeclObjC.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  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  BugReporter &BR) const {
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 
85 namespace {
86 class CFErrorFunctionChecker
87  : public Checker< check::ASTDecl<FunctionDecl> > {
88  mutable IdentifierInfo *II;
89 
90 public:
91  CFErrorFunctionChecker() : II(nullptr) {}
92 
93  void checkASTDecl(const FunctionDecl *D,
94  AnalysisManager &mgr, BugReporter &BR) const;
95 };
96 }
97 
98 static 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 
107 void CFErrorFunctionChecker::checkASTDecl(const FunctionDecl *D,
108  AnalysisManager &mgr,
109  BugReporter &BR) const {
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 
143 namespace {
144 
145 class NSErrorDerefBug : public BugType {
146 public:
147  NSErrorDerefBug(const CheckerNameRef Checker)
148  : BugType(Checker, "NSError** null dereference",
149  "Coding conventions (Apple)") {}
150 };
151 
152 class CFErrorDerefBug : public BugType {
153 public:
154  CFErrorDerefBug(const CheckerNameRef Checker)
155  : BugType(Checker, "CFErrorRef* null dereference",
156  "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  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 static 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 
191 template <typename T>
192 static 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 
198 static 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 
212 void 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 
249 void 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 
291 static 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 =
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 
312 static 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 
322 void ento::registerNSOrCFErrorDerefChecker(CheckerManager &mgr) {
323  mgr.registerChecker<NSOrCFErrorDerefChecker>();
324 }
325 
326 bool ento::shouldRegisterNSOrCFErrorDerefChecker(const CheckerManager &mgr) {
327  return true;
328 }
329 
330 void 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 
337 bool ento::shouldRegisterNSErrorChecker(const CheckerManager &mgr) {
338  return true;
339 }
340 
341 void 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 
348 bool ento::shouldRegisterCFErrorChecker(const CheckerManager &mgr) {
349  return true;
350 }
Represents a function declaration or definition.
Definition: Decl.h:1783
bool isThisDeclarationADefinition() const
Returns whether this specific method is a definition.
Definition: DeclObjC.h:528
PointerType - C99 6.7.5.1 - Pointer Declarators.
Definition: Type.h:2645
QualType getPointeeType() const
Definition: Type.h:2655
A (possibly-)qualified type.
Definition: Type.h:655
Stmt - This represents one statement.
Definition: Stmt.h:68
IntrusiveRefCntPtr< const ProgramState > ProgramStateRef
static bool IsCFError(QualType T, IdentifierInfo *II)
QualType getReturnType() const
Definition: Decl.h:2456
const T * getAs() const
Member-template getAs<specific type>&#39;.
Definition: Type.h:7153
ObjCMethodDecl - Represents an instance or class method declaration.
Definition: DeclObjC.h:139
IdentifierInfo * getIdentifier() const
Get the identifier that names this declaration, if there is one.
Definition: Decl.h:244
One of these records is kept for each identifier that is lexed.
const SymExpr * SymbolRef
Definition: SymExpr.h:110
Holds long-lived AST nodes (such as types and decls) that can be referred to throughout the semantic ...
Definition: ASTContext.h:174
i32 captured_struct **param SharedsTy A type which contains references the shared variables *param Shareds Context with the list of shared variables from the p *TaskFunction *param Data Additional data for task generation like final * state
IdentifierTable & Idents
Definition: ASTContext.h:584
ArrayRef< ParmVarDecl * > parameters() const
Definition: Decl.h:2402
static PathDiagnosticLocation create(const Decl *D, const SourceManager &SM)
Create a location corresponding to the given declaration.
llvm::ImmutableMap< SymbolRef, unsigned > ErrorOutFlag
Represents an ObjC class declaration.
Definition: DeclObjC.h:1163
QualType getReturnType() const
Definition: DeclObjC.h:325
OverloadedOperatorKind getOverloadedOperator() const
getOverloadedOperator - Which C++ overloaded operator this function represents, if any...
Definition: Decl.cpp:3515
static void setFlag(ProgramStateRef state, SVal val, CheckerContext &C)
bool isNull() const
Return true if this QualType doesn&#39;t point to a type yet.
Definition: Type.h:720
#define REGISTER_TRAIT_WITH_PROGRAMSTATE(Name, Type)
Declares a program state trait for type Type called Name, and introduce a type named NameTy...
IdentifierInfo & get(StringRef Name)
Return the identifier token info for the specified named identifier.
ASTContext & getASTContext() const LLVM_READONLY
Definition: DeclBase.cpp:414
static bool hasFlag(SVal val, ProgramStateRef state)
Dataflow Directional Tag Classes.
Represents a pointer to an Objective C object.
Definition: Type.h:6050
ObjCInterfaceDecl * getInterfaceDecl() const
If this pointer points to an Objective @interface type, gets the declaration for that interface...
Definition: Type.h:6102
const StackFrameContext * getStackFrame() const
It represents a stack frame of the call stack (based on CallEvent).
TypedefNameDecl * getDecl() const
Definition: Type.h:4385
X
Add a minimal nested name specifier fixit hint to allow lookup of a tag name from an outer enclosing ...
Definition: SemaDecl.cpp:15145
bool isVoidType() const
Definition: Type.h:6933
bool doesThisDeclarationHaveABody() const
Returns whether this specific declaration of the function has a body.
Definition: Decl.h:2078
static bool IsNSError(QualType T, IdentifierInfo *II)
static QualType parameterTypeFromSVal(SVal val, CheckerContext &C)
static bool hasReservedReturnType(const FunctionDecl *D)
ArrayRef< ParmVarDecl * > parameters() const
Definition: DeclObjC.h:369