clang 22.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#include <optional>
28
29using namespace clang;
30using namespace ento;
31
32static bool IsNSError(QualType T, IdentifierInfo *II);
33static bool IsCFError(QualType T, IdentifierInfo *II);
34
35//===----------------------------------------------------------------------===//
36// NSErrorMethodChecker
37//===----------------------------------------------------------------------===//
38
39namespace {
40class NSErrorMethodChecker
41 : public Checker< check::ASTDecl<ObjCMethodDecl> > {
42 mutable IdentifierInfo *II = nullptr;
43
44public:
45 NSErrorMethodChecker() = default;
46
47 void checkASTDecl(const ObjCMethodDecl *D,
48 AnalysisManager &mgr, BugReporter &BR) const;
49};
50}
51
52void NSErrorMethodChecker::checkASTDecl(const ObjCMethodDecl *D,
53 AnalysisManager &mgr,
54 BugReporter &BR) const {
55 if (!D->isThisDeclarationADefinition())
56 return;
57 if (!D->getReturnType()->isVoidType())
58 return;
59
60 if (!II)
61 II = &D->getASTContext().Idents.get("NSError");
62
63 bool hasNSError = false;
64 for (const auto *I : D->parameters()) {
65 if (IsNSError(I->getType(), II)) {
66 hasNSError = true;
67 break;
68 }
69 }
70
71 if (hasNSError) {
72 const char *err = "Method accepting NSError** "
73 "should have a non-void return value to indicate whether or not an "
74 "error occurred";
77 BR.EmitBasicReport(D, this, "Bad return type when passing NSError**",
78 "Coding conventions (Apple)", err, L);
79 }
80}
81
82//===----------------------------------------------------------------------===//
83// CFErrorFunctionChecker
84//===----------------------------------------------------------------------===//
85
86namespace {
87class CFErrorFunctionChecker
88 : public Checker< check::ASTDecl<FunctionDecl> > {
89 mutable IdentifierInfo *II;
90
91public:
92 CFErrorFunctionChecker() : II(nullptr) {}
93
94 void checkASTDecl(const FunctionDecl *D,
95 AnalysisManager &mgr, BugReporter &BR) const;
96};
97}
98
100 if (isa<CXXConstructorDecl>(D))
101 return true;
102
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;
106}
107
108void CFErrorFunctionChecker::checkASTDecl(const FunctionDecl *D,
109 AnalysisManager &mgr,
110 BugReporter &BR) const {
111 if (!D->doesThisDeclarationHaveABody())
112 return;
113 if (!D->getReturnType()->isVoidType())
114 return;
116 return;
117
118 if (!II)
119 II = &D->getASTContext().Idents.get("CFErrorRef");
120
121 bool hasCFError = false;
122 for (auto *I : D->parameters()) {
123 if (IsCFError(I->getType(), II)) {
124 hasCFError = true;
125 break;
126 }
127 }
128
129 if (hasCFError) {
130 const char *err = "Function accepting CFErrorRef* "
131 "should have a non-void return value to indicate whether or not an "
132 "error occurred";
135 BR.EmitBasicReport(D, this, "Bad return type when passing CFErrorRef*",
136 "Coding conventions (Apple)", err, L);
137 }
138}
139
140//===----------------------------------------------------------------------===//
141// NSOrCFErrorDerefChecker
142//===----------------------------------------------------------------------===//
143
144namespace {
145class NSOrCFErrorDerefChecker
146 : public CheckerFamily<check::Location,
147 check::Event<ImplicitNullDerefEvent>> {
148 mutable IdentifierInfo *NSErrorII = nullptr;
149 mutable IdentifierInfo *CFErrorII = nullptr;
150
151public:
152 CheckerFrontendWithBugType NSError{"NSError** null dereference",
153 "Coding conventions (Apple)"};
154 CheckerFrontendWithBugType CFError{"CFErrorRef* null dereference",
155 "Coding conventions (Apple)"};
156
157 StringRef getDebugTag() const override { return "NSOrCFErrorDerefChecker"; }
158
159 void checkLocation(SVal loc, bool isLoad, const Stmt *S,
160 CheckerContext &C) const;
161 void checkEvent(ImplicitNullDerefEvent event) const;
162};
163}
164
165typedef llvm::ImmutableMap<SymbolRef, unsigned> ErrorOutFlag;
168
169template <typename T>
170static bool hasFlag(SVal val, ProgramStateRef state) {
171 if (SymbolRef sym = val.getAsSymbol())
172 if (const unsigned *attachedFlags = state->get<T>(sym))
173 return *attachedFlags;
174 return false;
175}
176
177template <typename T>
178static void setFlag(ProgramStateRef state, SVal val, CheckerContext &C) {
179 // We tag the symbol that the SVal wraps.
180 if (SymbolRef sym = val.getAsSymbol())
181 C.addTransition(state->set<T>(sym, true));
182}
183
185 const StackFrameContext * SFC = C.getStackFrame();
186 if (std::optional<loc::MemRegionVal> X = val.getAs<loc::MemRegionVal>()) {
187 const MemRegion* R = X->getRegion();
188 if (const VarRegion *VR = R->getAs<VarRegion>())
189 if (const auto *StackSpace =
190 VR->getMemorySpaceAs<StackArgumentsSpaceRegion>(C.getState()))
191 if (StackSpace->getStackFrame() == SFC)
192 return VR->getValueType();
193 }
194
195 return QualType();
196}
197
198void NSOrCFErrorDerefChecker::checkLocation(SVal loc, bool isLoad,
199 const Stmt *S,
200 CheckerContext &C) const {
201 if (!isLoad)
202 return;
203 if (loc.isUndef() || !isa<Loc>(loc))
204 return;
205
206 ASTContext &Ctx = C.getASTContext();
207 ProgramStateRef state = C.getState();
208
209 // If we are loading from NSError**/CFErrorRef* parameter, mark the resulting
210 // SVal so that we can later check it when handling the
211 // ImplicitNullDerefEvent event.
212 // FIXME: Cumbersome! Maybe add hook at construction of SVals at start of
213 // function ?
214
215 QualType parmT = parameterTypeFromSVal(loc, C);
216 if (parmT.isNull())
217 return;
218
219 if (!NSErrorII)
220 NSErrorII = &Ctx.Idents.get("NSError");
221 if (!CFErrorII)
222 CFErrorII = &Ctx.Idents.get("CFErrorRef");
223
224 if (NSError.isEnabled() && IsNSError(parmT, NSErrorII)) {
225 setFlag<NSErrorOut>(state, state->getSVal(loc.castAs<Loc>()), C);
226 return;
227 }
228
229 if (CFError.isEnabled() && IsCFError(parmT, CFErrorII)) {
230 setFlag<CFErrorOut>(state, state->getSVal(loc.castAs<Loc>()), C);
231 return;
232 }
233}
234
235void NSOrCFErrorDerefChecker::checkEvent(ImplicitNullDerefEvent event) const {
236 if (event.IsLoad)
237 return;
238
239 SVal loc = event.Location;
240 ProgramStateRef state = event.SinkNode->getState();
241 BugReporter &BR = *event.BR;
242
243 bool isNSError = hasFlag<NSErrorOut>(loc, state);
244 bool isCFError = false;
245 if (!isNSError)
246 isCFError = hasFlag<CFErrorOut>(loc, state);
247
248 if (!(isNSError || isCFError))
249 return;
250
251 // Storing to possible null NSError/CFErrorRef out parameter.
253 llvm::raw_svector_ostream os(Buf);
254
255 os << "Potential null dereference. According to coding standards ";
256 os << (isNSError
257 ? "in 'Creating and Returning NSError Objects' the parameter"
258 : "documented in CoreFoundation/CFError.h the parameter");
259
260 os << " may be null";
261
262 const BugType &BT = isNSError ? NSError : CFError;
263 BR.emitReport(
264 std::make_unique<PathSensitiveBugReport>(BT, os.str(), event.SinkNode));
265}
266
268
269 const PointerType* PPT = T->getAs<PointerType>();
270 if (!PPT)
271 return false;
272
273 const ObjCObjectPointerType* PT =
275
276 if (!PT)
277 return false;
278
279 const ObjCInterfaceDecl *ID = PT->getInterfaceDecl();
280
281 // FIXME: Can ID ever be NULL?
282 if (ID)
283 return II == ID->getIdentifier();
284
285 return false;
286}
287
289 const PointerType* PPT = T->getAs<PointerType>();
290 if (!PPT) return false;
291
292 const TypedefType* TT = PPT->getPointeeType()->getAs<TypedefType>();
293 if (!TT) return false;
294
295 return TT->getDecl()->getIdentifier() == II;
296}
297
298// This source file implements two user-facing checkers ("osx.cocoa.NSError"
299// and "osx.coreFoundation.CFError") which are both implemented as the
300// combination of two `CheckerFrontend`s that are registered under the same
301// name (but otherwise act independently). Among these 2+2 `CheckerFrontend`s
302// two are coming from the checker family `NSOrCFErrorDerefChecker` while the
303// other two (the `ADDITIONAL_PART`s) are small standalone checkers.
304#define REGISTER_CHECKER(NAME, ADDITIONAL_PART) \
305 void ento::register##NAME##Checker(CheckerManager &Mgr) { \
306 Mgr.getChecker<NSOrCFErrorDerefChecker>()->NAME.enable(Mgr); \
307 Mgr.registerChecker<ADDITIONAL_PART>(); \
308 } \
309 \
310 bool ento::shouldRegister##NAME##Checker(const CheckerManager &) { \
311 return true; \
312 }
313
314REGISTER_CHECKER(NSError, NSErrorMethodChecker)
315REGISTER_CHECKER(CFError, CFErrorFunctionChecker)
const Decl * D
#define X(type, name)
Definition: Value.h:145
#define REGISTER_CHECKER(NAME, ADDITIONAL_PART)
static bool hasFlag(SVal val, ProgramStateRef state)
static bool IsCFError(QualType T, IdentifierInfo *II)
static void setFlag(ProgramStateRef state, SVal val, CheckerContext &C)
static QualType parameterTypeFromSVal(SVal val, CheckerContext &C)
static bool IsNSError(QualType T, IdentifierInfo *II)
static bool hasReservedReturnType(const FunctionDecl *D)
llvm::ImmutableMap< SymbolRef, unsigned > ErrorOutFlag
#define REGISTER_TRAIT_WITH_PROGRAMSTATE(Name, Type)
Declares a program state trait for type Type called Name, and introduce a type named NameTy.
Holds long-lived AST nodes (such as types and decls) that can be referred to throughout the semantic ...
Definition: ASTContext.h:188
IdentifierTable & Idents
Definition: ASTContext.h:740
ASTContext & getASTContext() const LLVM_READONLY
Definition: DeclBase.cpp:524
Represents a function declaration or definition.
Definition: Decl.h:1999
One of these records is kept for each identifier that is lexed.
IdentifierInfo & get(StringRef Name)
Return the identifier token info for the specified named identifier.
IdentifierInfo * getIdentifier() const
Get the identifier that names this declaration, if there is one.
Definition: Decl.h:294
Represents an ObjC class declaration.
Definition: DeclObjC.h:1154
ObjCMethodDecl - Represents an instance or class method declaration.
Definition: DeclObjC.h:140
Represents a pointer to an Objective C object.
Definition: TypeBase.h:7961
ObjCInterfaceDecl * getInterfaceDecl() const
If this pointer points to an Objective @interface type, gets the declaration for that interface.
Definition: TypeBase.h:8013
PointerType - C99 6.7.5.1 - Pointer Declarators.
Definition: TypeBase.h:3346
QualType getPointeeType() const
Definition: TypeBase.h:3356
virtual StringRef getDebugTag() const =0
The description of this program point which will be dumped for debugging purposes.
A (possibly-)qualified type.
Definition: TypeBase.h:937
bool isNull() const
Return true if this QualType doesn't point to a type yet.
Definition: TypeBase.h:1004
It represents a stack frame of the call stack (based on CallEvent).
Stmt - This represents one statement.
Definition: Stmt.h:85
const T * getAs() const
Member-template getAs<specific type>'.
Definition: TypeBase.h:9159
TypedefNameDecl * getDecl() const
Definition: TypeBase.h:6127
BugReporter is a utility class for generating PathDiagnostics for analysis.
Definition: BugReporter.h:586
const SourceManager & getSourceManager()
Definition: BugReporter.h:625
void EmitBasicReport(const Decl *DeclWithIssue, const CheckerFrontend *Checker, StringRef BugName, StringRef BugCategory, StringRef BugStr, PathDiagnosticLocation Loc, ArrayRef< SourceRange > Ranges={}, ArrayRef< FixItHint > Fixits={})
virtual void emitReport(std::unique_ptr< BugReport > R)
Add the given report to the set of reports tracked by BugReporter.
Checker families (where a single backend class implements multiple related frontends) should derive f...
Definition: Checker.h:584
Trivial convenience class for the common case when a certain checker frontend always uses the same bu...
Definition: BugType.h:80
Simple checker classes that implement one frontend (i.e.
Definition: Checker.h:553
MemRegion - The root abstract class for all memory regions.
Definition: MemRegion.h:98
const RegionTy * getAs() const
Definition: MemRegion.h:1416
static PathDiagnosticLocation create(const Decl *D, const SourceManager &SM)
Create a location corresponding to the given declaration.
SVal - This represents a symbolic expression, which can be either an L-value or an R-value.
Definition: SVals.h:56
bool isUndef() const
Definition: SVals.h:107
SymbolRef getAsSymbol(bool IncludeBaseRegions=false) const
If this SVal wraps a symbol return that SymbolRef.
Definition: SVals.cpp:103
std::optional< T > getAs() const
Convert to the specified SVal type, returning std::nullopt if this SVal is not of the desired type.
Definition: SVals.h:87
T castAs() const
Convert to the specified SVal type, asserting that this SVal is of the desired type.
Definition: SVals.h:83
Symbolic value.
Definition: SymExpr.h:32
The JSON file list parser is used to communicate input to InstallAPI.
const FunctionProtoType * T
We dereferenced a location that may be null.
Definition: Checker.h:612