1 //=- NSErrorChecker.cpp - Coding conventions for uses of NSError -*- C++ -*-==//
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
7 //===----------------------------------------------------------------------===//
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.
13 // File under feature request PR 2600.
15 //===----------------------------------------------------------------------===//
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"
29 using namespace clang
;
32 static bool IsNSError(QualType T
, IdentifierInfo
*II
);
33 static bool IsCFError(QualType T
, IdentifierInfo
*II
);
35 //===----------------------------------------------------------------------===//
36 // NSErrorMethodChecker
37 //===----------------------------------------------------------------------===//
40 class NSErrorMethodChecker
41 : public Checker
< check::ASTDecl
<ObjCMethodDecl
> > {
42 mutable IdentifierInfo
*II
= nullptr;
45 NSErrorMethodChecker() = default;
47 void checkASTDecl(const ObjCMethodDecl
*D
,
48 AnalysisManager
&mgr
, BugReporter
&BR
) const;
52 void NSErrorMethodChecker::checkASTDecl(const ObjCMethodDecl
*D
,
54 BugReporter
&BR
) const {
55 if (!D
->isThisDeclarationADefinition())
57 if (!D
->getReturnType()->isVoidType())
61 II
= &D
->getASTContext().Idents
.get("NSError");
63 bool hasNSError
= false;
64 for (const auto *I
: D
->parameters()) {
65 if (IsNSError(I
->getType(), II
)) {
72 const char *err
= "Method accepting NSError** "
73 "should have a non-void return value to indicate whether or not an "
75 PathDiagnosticLocation L
=
76 PathDiagnosticLocation::create(D
, BR
.getSourceManager());
77 BR
.EmitBasicReport(D
, this, "Bad return type when passing NSError**",
78 "Coding conventions (Apple)", err
, L
);
82 //===----------------------------------------------------------------------===//
83 // CFErrorFunctionChecker
84 //===----------------------------------------------------------------------===//
87 class CFErrorFunctionChecker
88 : public Checker
< check::ASTDecl
<FunctionDecl
> > {
89 mutable IdentifierInfo
*II
;
92 CFErrorFunctionChecker() : II(nullptr) {}
94 void checkASTDecl(const FunctionDecl
*D
,
95 AnalysisManager
&mgr
, BugReporter
&BR
) const;
99 static bool hasReservedReturnType(const FunctionDecl
*D
) {
100 if (isa
<CXXConstructorDecl
>(D
))
103 // operators delete and delete[] are required to have 'void' return type
104 auto OperatorKind
= D
->getOverloadedOperator();
105 return OperatorKind
== OO_Delete
|| OperatorKind
== OO_Array_Delete
;
108 void CFErrorFunctionChecker::checkASTDecl(const FunctionDecl
*D
,
109 AnalysisManager
&mgr
,
110 BugReporter
&BR
) const {
111 if (!D
->doesThisDeclarationHaveABody())
113 if (!D
->getReturnType()->isVoidType())
115 if (hasReservedReturnType(D
))
119 II
= &D
->getASTContext().Idents
.get("CFErrorRef");
121 bool hasCFError
= false;
122 for (auto *I
: D
->parameters()) {
123 if (IsCFError(I
->getType(), II
)) {
130 const char *err
= "Function accepting CFErrorRef* "
131 "should have a non-void return value to indicate whether or not an "
133 PathDiagnosticLocation L
=
134 PathDiagnosticLocation::create(D
, BR
.getSourceManager());
135 BR
.EmitBasicReport(D
, this, "Bad return type when passing CFErrorRef*",
136 "Coding conventions (Apple)", err
, L
);
140 //===----------------------------------------------------------------------===//
141 // NSOrCFErrorDerefChecker
142 //===----------------------------------------------------------------------===//
146 class NSErrorDerefBug
: public BugType
{
148 NSErrorDerefBug(const CheckerNameRef Checker
)
149 : BugType(Checker
, "NSError** null dereference",
150 "Coding conventions (Apple)") {}
153 class CFErrorDerefBug
: public BugType
{
155 CFErrorDerefBug(const CheckerNameRef Checker
)
156 : BugType(Checker
, "CFErrorRef* null dereference",
157 "Coding conventions (Apple)") {}
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
;
170 bool ShouldCheckNSError
= false, ShouldCheckCFError
= false;
171 CheckerNameRef NSErrorName
, CFErrorName
;
172 NSOrCFErrorDerefChecker() : NSErrorII(nullptr), CFErrorII(nullptr) {}
174 void checkLocation(SVal loc
, bool isLoad
, const Stmt
*S
,
175 CheckerContext
&C
) const;
176 void checkEvent(ImplicitNullDerefEvent event
) const;
180 typedef llvm::ImmutableMap
<SymbolRef
, unsigned> ErrorOutFlag
;
181 REGISTER_TRAIT_WITH_PROGRAMSTATE(NSErrorOut
, ErrorOutFlag
)
182 REGISTER_TRAIT_WITH_PROGRAMSTATE(CFErrorOut
, ErrorOutFlag
)
184 template <typename T
>
185 static bool hasFlag(SVal val
, ProgramStateRef state
) {
186 if (SymbolRef sym
= val
.getAsSymbol())
187 if (const unsigned *attachedFlags
= state
->get
<T
>(sym
))
188 return *attachedFlags
;
192 template <typename T
>
193 static void setFlag(ProgramStateRef state
, SVal val
, CheckerContext
&C
) {
194 // We tag the symbol that the SVal wraps.
195 if (SymbolRef sym
= val
.getAsSymbol())
196 C
.addTransition(state
->set
<T
>(sym
, true));
199 static QualType
parameterTypeFromSVal(SVal val
, CheckerContext
&C
) {
200 const StackFrameContext
* SFC
= C
.getStackFrame();
201 if (std::optional
<loc::MemRegionVal
> X
= val
.getAs
<loc::MemRegionVal
>()) {
202 const MemRegion
* R
= X
->getRegion();
203 if (const VarRegion
*VR
= R
->getAs
<VarRegion
>())
204 if (const StackArgumentsSpaceRegion
*
205 stackReg
= dyn_cast
<StackArgumentsSpaceRegion
>(VR
->getMemorySpace()))
206 if (stackReg
->getStackFrame() == SFC
)
207 return VR
->getValueType();
213 void NSOrCFErrorDerefChecker::checkLocation(SVal loc
, bool isLoad
,
215 CheckerContext
&C
) const {
218 if (loc
.isUndef() || !isa
<Loc
>(loc
))
221 ASTContext
&Ctx
= C
.getASTContext();
222 ProgramStateRef state
= C
.getState();
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
230 QualType parmT
= parameterTypeFromSVal(loc
, C
);
235 NSErrorII
= &Ctx
.Idents
.get("NSError");
237 CFErrorII
= &Ctx
.Idents
.get("CFErrorRef");
239 if (ShouldCheckNSError
&& IsNSError(parmT
, NSErrorII
)) {
240 setFlag
<NSErrorOut
>(state
, state
->getSVal(loc
.castAs
<Loc
>()), C
);
244 if (ShouldCheckCFError
&& IsCFError(parmT
, CFErrorII
)) {
245 setFlag
<CFErrorOut
>(state
, state
->getSVal(loc
.castAs
<Loc
>()), C
);
250 void NSOrCFErrorDerefChecker::checkEvent(ImplicitNullDerefEvent event
) const {
254 SVal loc
= event
.Location
;
255 ProgramStateRef state
= event
.SinkNode
->getState();
256 BugReporter
&BR
= *event
.BR
;
258 bool isNSError
= hasFlag
<NSErrorOut
>(loc
, state
);
259 bool isCFError
= false;
261 isCFError
= hasFlag
<CFErrorOut
>(loc
, state
);
263 if (!(isNSError
|| isCFError
))
266 // Storing to possible null NSError/CFErrorRef out parameter.
267 SmallString
<128> Buf
;
268 llvm::raw_svector_ostream
os(Buf
);
270 os
<< "Potential null dereference. According to coding standards ";
272 ? "in 'Creating and Returning NSError Objects' the parameter"
273 : "documented in CoreFoundation/CFError.h the parameter");
275 os
<< " may be null";
277 BugType
*bug
= nullptr;
280 NSBT
.reset(new NSErrorDerefBug(NSErrorName
));
285 CFBT
.reset(new CFErrorDerefBug(CFErrorName
));
289 std::make_unique
<PathSensitiveBugReport
>(*bug
, os
.str(), event
.SinkNode
));
292 static bool IsNSError(QualType T
, IdentifierInfo
*II
) {
294 const PointerType
* PPT
= T
->getAs
<PointerType
>();
298 const ObjCObjectPointerType
* PT
=
299 PPT
->getPointeeType()->getAs
<ObjCObjectPointerType
>();
304 const ObjCInterfaceDecl
*ID
= PT
->getInterfaceDecl();
306 // FIXME: Can ID ever be NULL?
308 return II
== ID
->getIdentifier();
313 static bool IsCFError(QualType T
, IdentifierInfo
*II
) {
314 const PointerType
* PPT
= T
->getAs
<PointerType
>();
315 if (!PPT
) return false;
317 const TypedefType
* TT
= PPT
->getPointeeType()->getAs
<TypedefType
>();
318 if (!TT
) return false;
320 return TT
->getDecl()->getIdentifier() == II
;
323 void ento::registerNSOrCFErrorDerefChecker(CheckerManager
&mgr
) {
324 mgr
.registerChecker
<NSOrCFErrorDerefChecker
>();
327 bool ento::shouldRegisterNSOrCFErrorDerefChecker(const CheckerManager
&mgr
) {
331 void ento::registerNSErrorChecker(CheckerManager
&mgr
) {
332 mgr
.registerChecker
<NSErrorMethodChecker
>();
333 NSOrCFErrorDerefChecker
*checker
= mgr
.getChecker
<NSOrCFErrorDerefChecker
>();
334 checker
->ShouldCheckNSError
= true;
335 checker
->NSErrorName
= mgr
.getCurrentCheckerName();
338 bool ento::shouldRegisterNSErrorChecker(const CheckerManager
&mgr
) {
342 void ento::registerCFErrorChecker(CheckerManager
&mgr
) {
343 mgr
.registerChecker
<CFErrorFunctionChecker
>();
344 NSOrCFErrorDerefChecker
*checker
= mgr
.getChecker
<NSOrCFErrorDerefChecker
>();
345 checker
->ShouldCheckCFError
= true;
346 checker
->CFErrorName
= mgr
.getCurrentCheckerName();
349 bool ento::shouldRegisterCFErrorChecker(const CheckerManager
&mgr
) {