clang  14.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-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 
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;
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 }
clang::ObjCInterfaceDecl
Represents an ObjC class declaration.
Definition: DeclObjC.h:1151
clang::FunctionDecl::doesThisDeclarationHaveABody
bool doesThisDeclarationHaveABody() const
Returns whether this specific declaration of the function has a body.
Definition: Decl.h:2163
clang::Decl::getASTContext
ASTContext & getASTContext() const LLVM_READONLY
Definition: DeclBase.cpp:414
clang::FunctionDecl::getReturnType
QualType getReturnType() const
Definition: Decl.h:2537
clang::IdentifierTable::get
IdentifierInfo & get(StringRef Name)
Return the identifier token info for the specified named identifier.
Definition: IdentifierTable.h:592
hasFlag
static bool hasFlag(SVal val, ProgramStateRef state)
Definition: NSErrorChecker.cpp:184
setFlag
static void setFlag(ProgramStateRef state, SVal val, CheckerContext &C)
Definition: NSErrorChecker.cpp:192
clang::ento::ProgramStateRef
IntrusiveRefCntPtr< const ProgramState > ProgramStateRef
Definition: ProgramState_Fwd.h:37
clang::QualType
A (possibly-)qualified type.
Definition: Type.h:673
AttributeLangSupport::C
@ C
Definition: SemaDeclAttr.cpp:54
clang::ento::SymbolRef
const SymExpr * SymbolRef
Definition: SymExpr.h:110
llvm::Optional
Definition: LLVM.h:40
clang::StackFrameContext
It represents a stack frame of the call stack (based on CallEvent).
Definition: AnalysisDeclContext.h:295
clang::Type::isVoidType
bool isVoidType() const
Definition: Type.h:6955
clang::tooling::X
static ToolExecutorPluginRegistry::Add< AllTUsToolExecutorPlugin > X("all-TUs", "Runs FrontendActions on all TUs in the compilation database. " "Tool results are stored in memory.")
clang::ObjCObjectPointerType::getInterfaceDecl
ObjCInterfaceDecl * getInterfaceDecl() const
If this pointer points to an Objective @interface type, gets the declaration for that interface.
Definition: Type.h:6124
IsNSError
static bool IsNSError(QualType T, IdentifierInfo *II)
Definition: NSErrorChecker.cpp:291
REGISTER_TRAIT_WITH_PROGRAMSTATE
#define REGISTER_TRAIT_WITH_PROGRAMSTATE(Name, Type)
Declares a program state trait for type Type called Name, and introduce a type named NameTy.
Definition: ProgramStateTrait.h:33
Decl.h
clang::TypedefType
Definition: Type.h:4371
DeclObjC.h
BuiltinCheckerRegistration.h
clang::ento::PathDiagnosticLocation::create
static PathDiagnosticLocation create(const Decl *D, const SourceManager &SM)
Create a location corresponding to the given declaration.
Definition: PathDiagnostic.h:250
CheckerManager.h
clang::ASTContext
Holds long-lived AST nodes (such as types and decls) that can be referred to throughout the semantic ...
Definition: ASTContext.h:212
clang::Type::getAs
const T * getAs() const
Member-template getAs<specific type>'.
Definition: Type.h:7161
parameterTypeFromSVal
static QualType parameterTypeFromSVal(SVal val, CheckerContext &C)
Definition: NSErrorChecker.cpp:198
llvm::SmallString< 128 >
hasReservedReturnType
static bool hasReservedReturnType(const FunctionDecl *D)
Definition: NSErrorChecker.cpp:98
clang::ObjCMethodDecl::getReturnType
QualType getReturnType() const
Definition: DeclObjC.h:332
clang::FunctionDecl::getOverloadedOperator
OverloadedOperatorKind getOverloadedOperator() const
getOverloadedOperator - Which C++ overloaded operator this function represents, if any.
Definition: Decl.cpp:3653
IsCFError
static bool IsCFError(QualType T, IdentifierInfo *II)
Definition: NSErrorChecker.cpp:312
state
and static some checkers Checker The latter are built on top of the former via the Checker and CheckerVisitor and attempts to isolate them from much of the gore of the internal analysis the analyzer is basically a source code simulator that traces out possible paths of execution The state of the and the combination of state and program point is a node in an exploded which has the entry program point and initial state
Definition: README.txt:30
clang::ObjCObjectPointerType
Represents a pointer to an Objective C object.
Definition: Type.h:6072
clang::TypedefType::getDecl
TypedefNameDecl * getDecl() const
Definition: Type.h:4381
BugType.h
clang::NamedDecl::getIdentifier
IdentifierInfo * getIdentifier() const
Get the identifier that names this declaration, if there is one.
Definition: Decl.h:270
bug
and static some checkers Checker The latter are built on top of the former via the Checker and CheckerVisitor and attempts to isolate them from much of the gore of the internal analysis the analyzer is basically a source code simulator that traces out possible paths of execution The state of the and the combination of state and program point is a node in an exploded which has the entry program point and initial and then simulate transitions by analyzing individual expressions The analysis of an expression can cause the state to resulting in a new node in the ExplodedGraph with an updated program point and an updated state A bug is found by hitting a node that satisfies some bug condition(basically a violation of a checking invariant). The analyzer traces out multiple paths by reasoning about branches and then bifurcating the state it can contain cycles as paths loop back onto each other and cache out ProgramState and ExplodedNodes are basically immutable once created Once one creates a you need to create a new one to get a new ProgramState This immutability is key since the ExplodedGraph represents the behavior of the analyzed program from the entry point To represent these we use functional data individual Checkers work by also manipulating the analysis state The analyzer engine talks to them via a visitor interface For the and the checker is asked to check for any preconditions that might not be satisfied The checker can do or it can generate a new ProgramState and ExplodedNode which contains updated checker state If it finds a bug
Definition: README.txt:66
clang::ASTContext::Idents
IdentifierTable & Idents
Definition: ASTContext.h:648
clang::QualType::isNull
bool isNull() const
Return true if this QualType doesn't point to a type yet.
Definition: Type.h:738
clang::IdentifierInfo
One of these records is kept for each identifier that is lexed.
Definition: IdentifierTable.h:84
CheckerContext.h
Checker.h
clang::ObjCMethodDecl
ObjCMethodDecl - Represents an instance or class method declaration.
Definition: DeclObjC.h:139
clang::PointerType
PointerType - C99 6.7.5.1 - Pointer Declarators.
Definition: Type.h:2640
clang::Builtin::ID
ID
Definition: Builtins.h:48
clang
Definition: CalledOnceCheck.h:17
clang::Stmt
Stmt - This represents one statement.
Definition: Stmt.h:69
clang::ObjCMethodDecl::parameters
ArrayRef< ParmVarDecl * > parameters() const
Definition: DeclObjC.h:376
clang::PointerType::getPointeeType
QualType getPointeeType() const
Definition: Type.h:2650
ProgramStateTrait.h
clang::ObjCMethodDecl::isThisDeclarationADefinition
bool isThisDeclarationADefinition() const
Returns whether this specific method is a definition.
Definition: DeclObjC.h:538
clang::FunctionDecl::parameters
ArrayRef< ParmVarDecl * > parameters() const
Definition: Decl.h:2483
clang::FunctionDecl
Represents a function declaration or definition.
Definition: Decl.h:1856
ErrorOutFlag
llvm::ImmutableMap< SymbolRef, unsigned > ErrorOutFlag
Definition: NSErrorChecker.cpp:179