clang 22.0.0git
RawPtrRefLocalVarsChecker.cpp
Go to the documentation of this file.
1//=======- UncountedLocalVarsChecker.cpp -------------------------*- 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#include "ASTUtils.h"
10#include "DiagOutputUtils.h"
11#include "PtrTypesSemantics.h"
13#include "clang/AST/Decl.h"
14#include "clang/AST/DeclCXX.h"
23#include <optional>
24
25using namespace clang;
26using namespace ento;
27
28namespace {
29
30// FIXME: should be defined by anotations in the future
31bool isRefcountedStringsHack(const VarDecl *V) {
32 assert(V);
33 auto safeClass = [](const std::string &className) {
34 return className == "String" || className == "AtomString" ||
35 className == "UniquedString" || className == "Identifier";
36 };
37 QualType QT = V->getType();
38 auto *T = QT.getTypePtr();
39 if (auto *CXXRD = T->getAsCXXRecordDecl()) {
40 if (safeClass(safeGetName(CXXRD)))
41 return true;
42 }
43 if (T->isPointerType() || T->isReferenceType()) {
44 if (auto *CXXRD = T->getPointeeCXXRecordDecl()) {
45 if (safeClass(safeGetName(CXXRD)))
46 return true;
47 }
48 }
49 return false;
50}
51
52struct GuardianVisitor : DynamicRecursiveASTVisitor {
53 const VarDecl *Guardian{nullptr};
54
55 explicit GuardianVisitor(const VarDecl *Guardian) : Guardian(Guardian) {
56 assert(Guardian);
57 }
58
59 bool VisitBinaryOperator(BinaryOperator *BO) override {
60 if (BO->isAssignmentOp()) {
61 if (auto *VarRef = dyn_cast<DeclRefExpr>(BO->getLHS())) {
62 if (VarRef->getDecl() == Guardian)
63 return false;
64 }
65 }
66 return true;
67 }
68
69 bool VisitCXXConstructExpr(CXXConstructExpr *CE) override {
70 if (auto *Ctor = CE->getConstructor()) {
71 if (Ctor->isMoveConstructor() && CE->getNumArgs() == 1) {
72 auto *Arg = CE->getArg(0)->IgnoreParenCasts();
73 if (auto *VarRef = dyn_cast<DeclRefExpr>(Arg)) {
74 if (VarRef->getDecl() == Guardian)
75 return false;
76 }
77 }
78 }
79 return true;
80 }
81
82 bool VisitCXXMemberCallExpr(CXXMemberCallExpr *MCE) override {
83 auto MethodName = safeGetName(MCE->getMethodDecl());
84 if (MethodName == "swap" || MethodName == "leakRef" ||
85 MethodName == "releaseNonNull" || MethodName == "clear") {
86 auto *ThisArg = MCE->getImplicitObjectArgument()->IgnoreParenCasts();
87 if (auto *VarRef = dyn_cast<DeclRefExpr>(ThisArg)) {
88 if (VarRef->getDecl() == Guardian)
89 return false;
90 }
91 }
92 return true;
93 }
94
95 bool VisitCXXOperatorCallExpr(CXXOperatorCallExpr *OCE) override {
96 if (OCE->isAssignmentOp()) {
97 assert(OCE->getNumArgs() == 2);
98 auto *ThisArg = OCE->getArg(0)->IgnoreParenCasts();
99 if (auto *VarRef = dyn_cast<DeclRefExpr>(ThisArg)) {
100 if (VarRef->getDecl() == Guardian)
101 return false;
102 }
103 }
104 return true;
105 }
106};
107
108bool isGuardedScopeEmbeddedInGuardianScope(const VarDecl *Guarded,
109 const VarDecl *MaybeGuardian) {
110 assert(Guarded);
111 assert(MaybeGuardian);
112
113 if (!MaybeGuardian->isLocalVarDecl())
114 return false;
115
116 const CompoundStmt *guardiansClosestCompStmtAncestor = nullptr;
117
118 ASTContext &ctx = MaybeGuardian->getASTContext();
119
120 for (DynTypedNodeList guardianAncestors = ctx.getParents(*MaybeGuardian);
121 !guardianAncestors.empty();
122 guardianAncestors = ctx.getParents(
123 *guardianAncestors
124 .begin()) // FIXME - should we handle all of the parents?
125 ) {
126 for (auto &guardianAncestor : guardianAncestors) {
127 if (auto *CStmtParentAncestor = guardianAncestor.get<CompoundStmt>()) {
128 guardiansClosestCompStmtAncestor = CStmtParentAncestor;
129 break;
130 }
131 }
132 if (guardiansClosestCompStmtAncestor)
133 break;
134 }
135
136 if (!guardiansClosestCompStmtAncestor)
137 return false;
138
139 // We need to skip the first CompoundStmt to avoid situation when guardian is
140 // defined in the same scope as guarded variable.
141 const CompoundStmt *FirstCompondStmt = nullptr;
142 for (DynTypedNodeList guardedVarAncestors = ctx.getParents(*Guarded);
143 !guardedVarAncestors.empty();
144 guardedVarAncestors = ctx.getParents(
145 *guardedVarAncestors
146 .begin()) // FIXME - should we handle all of the parents?
147 ) {
148 for (auto &guardedVarAncestor : guardedVarAncestors) {
149 if (auto *CStmtAncestor = guardedVarAncestor.get<CompoundStmt>()) {
150 if (!FirstCompondStmt) {
151 FirstCompondStmt = CStmtAncestor;
152 continue;
153 }
154 if (CStmtAncestor == guardiansClosestCompStmtAncestor) {
155 GuardianVisitor guardianVisitor(MaybeGuardian);
156 auto *GuardedScope = const_cast<CompoundStmt *>(FirstCompondStmt);
157 return guardianVisitor.TraverseCompoundStmt(GuardedScope);
158 }
159 }
160 }
161 }
162
163 return false;
164}
165
166class RawPtrRefLocalVarsChecker
167 : public Checker<check::ASTDecl<TranslationUnitDecl>> {
168 BugType Bug;
169 EnsureFunctionAnalysis EFA;
170
171protected:
172 mutable BugReporter *BR;
173 mutable std::optional<RetainTypeChecker> RTC;
174
175public:
176 RawPtrRefLocalVarsChecker(const char *description)
177 : Bug(this, description, "WebKit coding guidelines") {}
178
179 virtual std::optional<bool> isUnsafePtr(const QualType T) const = 0;
180 virtual bool isSafePtr(const CXXRecordDecl *) const = 0;
181 virtual bool isSafePtrType(const QualType) const = 0;
182 virtual bool isSafeExpr(const Expr *) const { return false; }
183 virtual bool isSafeDecl(const Decl *) const { return false; }
184 virtual const char *ptrKind() const = 0;
185
186 void checkASTDecl(const TranslationUnitDecl *TUD, AnalysisManager &MGR,
187 BugReporter &BRArg) const {
188 BR = &BRArg;
189
190 // The calls to checkAST* from AnalysisConsumer don't
191 // visit template instantiations or lambda classes. We
192 // want to visit those, so we make our own RecursiveASTVisitor.
193 struct LocalVisitor : DynamicRecursiveASTVisitor {
194 const RawPtrRefLocalVarsChecker *Checker;
195 Decl *DeclWithIssue{nullptr};
196
197 TrivialFunctionAnalysis TFA;
198
199 explicit LocalVisitor(const RawPtrRefLocalVarsChecker *Checker)
200 : Checker(Checker) {
201 assert(Checker);
202 ShouldVisitTemplateInstantiations = true;
203 ShouldVisitImplicitCode = false;
204 }
205
206 bool TraverseDecl(Decl *D) override {
207 llvm::SaveAndRestore SavedDecl(DeclWithIssue);
208 if (D && (isa<FunctionDecl>(D) || isa<ObjCMethodDecl>(D)))
209 DeclWithIssue = D;
211 }
212
213 bool VisitTypedefDecl(TypedefDecl *TD) override {
214 if (Checker->RTC)
215 Checker->RTC->visitTypedef(TD);
216 return true;
217 }
218
219 bool VisitVarDecl(VarDecl *V) override {
220 auto *Init = V->getInit();
221 if (Init && V->isLocalVarDecl())
222 Checker->visitVarDecl(V, Init, DeclWithIssue);
223 return true;
224 }
225
226 bool VisitBinaryOperator(BinaryOperator *BO) override {
227 if (BO->isAssignmentOp()) {
228 if (auto *VarRef = dyn_cast<DeclRefExpr>(BO->getLHS())) {
229 if (auto *V = dyn_cast<VarDecl>(VarRef->getDecl()))
230 Checker->visitVarDecl(V, BO->getRHS(), DeclWithIssue);
231 }
232 }
233 return true;
234 }
235
236 bool TraverseIfStmt(IfStmt *IS) override {
237 if (IS->getConditionVariable()) {
238 // This code currently does not explicitly check the "else" statement
239 // since getConditionVariable returns nullptr when there is a
240 // condition defined after ";" as in "if (auto foo = ~; !foo)". If
241 // this semantics change, we should add an explicit check for "else".
242 if (auto *Then = IS->getThen(); !Then || TFA.isTrivial(Then))
243 return true;
244 }
245 if (!TFA.isTrivial(IS))
246 return DynamicRecursiveASTVisitor::TraverseIfStmt(IS);
247 return true;
248 }
249
250 bool TraverseForStmt(ForStmt *FS) override {
251 if (!TFA.isTrivial(FS))
252 return DynamicRecursiveASTVisitor::TraverseForStmt(FS);
253 return true;
254 }
255
256 bool TraverseCXXForRangeStmt(CXXForRangeStmt *FRS) override {
257 if (!TFA.isTrivial(FRS))
258 return DynamicRecursiveASTVisitor::TraverseCXXForRangeStmt(FRS);
259 return true;
260 }
261
262 bool TraverseWhileStmt(WhileStmt *WS) override {
263 if (!TFA.isTrivial(WS))
264 return DynamicRecursiveASTVisitor::TraverseWhileStmt(WS);
265 return true;
266 }
267
268 bool TraverseCompoundStmt(CompoundStmt *CS) override {
269 if (!TFA.isTrivial(CS))
270 return DynamicRecursiveASTVisitor::TraverseCompoundStmt(CS);
271 return true;
272 }
273
274 bool TraverseClassTemplateDecl(ClassTemplateDecl *Decl) override {
275 if (isSmartPtrClass(safeGetName(Decl)))
276 return true;
277 return DynamicRecursiveASTVisitor::TraverseClassTemplateDecl(Decl);
278 }
279 };
280
281 LocalVisitor visitor(this);
282 if (RTC)
283 RTC->visitTranslationUnitDecl(TUD);
284 visitor.TraverseDecl(const_cast<TranslationUnitDecl *>(TUD));
285 }
286
287 void visitVarDecl(const VarDecl *V, const Expr *Value,
288 const Decl *DeclWithIssue) const {
289 if (shouldSkipVarDecl(V))
290 return;
291
292 std::optional<bool> IsUncountedPtr = isUnsafePtr(V->getType());
293 if (IsUncountedPtr && *IsUncountedPtr) {
295 Value, /*StopAtFirstRefCountedObj=*/false,
296 [&](const clang::CXXRecordDecl *Record) {
297 return isSafePtr(Record);
298 },
299 [&](const clang::QualType Type) { return isSafePtrType(Type); },
300 [&](const clang::Decl *D) { return isSafeDecl(D); },
301 [&](const clang::Expr *InitArgOrigin, bool IsSafe) {
302 if (!InitArgOrigin || IsSafe)
303 return true;
304
305 if (isa<CXXThisExpr>(InitArgOrigin))
306 return true;
307
308 if (isNullPtr(InitArgOrigin))
309 return true;
310
311 if (isa<IntegerLiteral>(InitArgOrigin))
312 return true;
313
314 if (isConstOwnerPtrMemberExpr(InitArgOrigin))
315 return true;
316
317 if (EFA.isACallToEnsureFn(InitArgOrigin))
318 return true;
319
320 if (isSafeExpr(InitArgOrigin))
321 return true;
322
323 if (auto *Ref = llvm::dyn_cast<DeclRefExpr>(InitArgOrigin)) {
324 if (auto *MaybeGuardian =
325 dyn_cast_or_null<VarDecl>(Ref->getFoundDecl())) {
326 const auto *MaybeGuardianArgType =
327 MaybeGuardian->getType().getTypePtr();
328 if (MaybeGuardianArgType) {
329 const CXXRecordDecl *const MaybeGuardianArgCXXRecord =
330 MaybeGuardianArgType->getAsCXXRecordDecl();
331 if (MaybeGuardianArgCXXRecord) {
332 if (MaybeGuardian->isLocalVarDecl() &&
333 (isSafePtr(MaybeGuardianArgCXXRecord) ||
334 isRefcountedStringsHack(MaybeGuardian)) &&
335 isGuardedScopeEmbeddedInGuardianScope(
336 V, MaybeGuardian))
337 return true;
338 }
339 }
340
341 // Parameters are guaranteed to be safe for the duration of
342 // the call by another checker.
343 if (isa<ParmVarDecl>(MaybeGuardian))
344 return true;
345 }
346 }
347
348 return false;
349 }))
350 return;
351
352 reportBug(V, Value, DeclWithIssue);
353 }
354 }
355
356 bool shouldSkipVarDecl(const VarDecl *V) const {
357 assert(V);
359 return true;
360 return BR->getSourceManager().isInSystemHeader(V->getLocation());
361 }
362
363 void reportBug(const VarDecl *V, const Expr *Value,
364 const Decl *DeclWithIssue) const {
365 assert(V);
366 SmallString<100> Buf;
367 llvm::raw_svector_ostream Os(Buf);
368
369 if (isa<ParmVarDecl>(V)) {
370 Os << "Assignment to an " << ptrKind() << " parameter ";
372 Os << " is unsafe.";
373
374 PathDiagnosticLocation BSLoc(Value->getExprLoc(), BR->getSourceManager());
375 auto Report = std::make_unique<BasicBugReport>(Bug, Os.str(), BSLoc);
376 Report->addRange(Value->getSourceRange());
377 BR->emitReport(std::move(Report));
378 } else {
379 if (V->hasLocalStorage())
380 Os << "Local variable ";
381 else if (V->isStaticLocal())
382 Os << "Static local variable ";
383 else if (V->hasGlobalStorage())
384 Os << "Global variable ";
385 else
386 Os << "Variable ";
388 Os << " is " << ptrKind() << " and unsafe.";
389
390 PathDiagnosticLocation BSLoc(V->getLocation(), BR->getSourceManager());
391 auto Report = std::make_unique<BasicBugReport>(Bug, Os.str(), BSLoc);
392 Report->addRange(V->getSourceRange());
393 Report->setDeclWithIssue(DeclWithIssue);
394 BR->emitReport(std::move(Report));
395 }
396 }
397};
398
399class UncountedLocalVarsChecker final : public RawPtrRefLocalVarsChecker {
400public:
401 UncountedLocalVarsChecker()
402 : RawPtrRefLocalVarsChecker("Uncounted raw pointer or reference not "
403 "provably backed by ref-counted variable") {}
404 std::optional<bool> isUnsafePtr(const QualType T) const final {
405 return isUncountedPtr(T);
406 }
407 bool isSafePtr(const CXXRecordDecl *Record) const final {
409 }
410 bool isSafePtrType(const QualType type) const final {
412 }
413 const char *ptrKind() const final { return "uncounted"; }
414};
415
416class UncheckedLocalVarsChecker final : public RawPtrRefLocalVarsChecker {
417public:
418 UncheckedLocalVarsChecker()
419 : RawPtrRefLocalVarsChecker("Unchecked raw pointer or reference not "
420 "provably backed by checked variable") {}
421 std::optional<bool> isUnsafePtr(const QualType T) const final {
422 return isUncheckedPtr(T);
423 }
424 bool isSafePtr(const CXXRecordDecl *Record) const final {
426 }
427 bool isSafePtrType(const QualType type) const final {
429 }
430 bool isSafeExpr(const Expr *E) const final {
432 }
433 const char *ptrKind() const final { return "unchecked"; }
434};
435
436class UnretainedLocalVarsChecker final : public RawPtrRefLocalVarsChecker {
437public:
438 UnretainedLocalVarsChecker()
439 : RawPtrRefLocalVarsChecker("Unretained raw pointer or reference not "
440 "provably backed by a RetainPtr") {
441 RTC = RetainTypeChecker();
442 }
443 std::optional<bool> isUnsafePtr(const QualType T) const final {
444 if (T.hasStrongOrWeakObjCLifetime())
445 return false;
446 return RTC->isUnretained(T);
447 }
448 bool isSafePtr(const CXXRecordDecl *Record) const final {
450 }
451 bool isSafePtrType(const QualType type) const final {
453 }
454 bool isSafeExpr(const Expr *E) const final {
455 return ento::cocoa::isCocoaObjectRef(E->getType()) &&
457 }
458 bool isSafeDecl(const Decl *D) const final {
459 // Treat NS/CF globals in system header as immortal.
461 }
462 const char *ptrKind() const final { return "unretained"; }
463};
464
465} // namespace
466
467void ento::registerUncountedLocalVarsChecker(CheckerManager &Mgr) {
468 Mgr.registerChecker<UncountedLocalVarsChecker>();
469}
470
471bool ento::shouldRegisterUncountedLocalVarsChecker(const CheckerManager &) {
472 return true;
473}
474
475void ento::registerUncheckedLocalVarsChecker(CheckerManager &Mgr) {
476 Mgr.registerChecker<UncheckedLocalVarsChecker>();
477}
478
479bool ento::shouldRegisterUncheckedLocalVarsChecker(const CheckerManager &) {
480 return true;
481}
482
483void ento::registerUnretainedLocalVarsChecker(CheckerManager &Mgr) {
484 Mgr.registerChecker<UnretainedLocalVarsChecker>();
485}
486
487bool ento::shouldRegisterUnretainedLocalVarsChecker(const CheckerManager &) {
488 return true;
489}
#define V(N, I)
Defines the C++ Decl subclasses, other than those for templates (found in DeclTemplate....
llvm::MachO::Record Record
Definition MachO.h:31
Defines the clang::SourceLocation class and associated facilities.
Holds long-lived AST nodes (such as types and decls) that can be referred to throughout the semantic ...
Definition ASTContext.h:220
DynTypedNodeList getParents(const NodeT &Node)
Forwards to get node parents from the ParentMapContext.
Expr * getLHS() const
Definition Expr.h:4022
Expr * getRHS() const
Definition Expr.h:4024
static bool isAssignmentOp(Opcode Opc)
Definition Expr.h:4108
Expr * getArg(unsigned Arg)
Return the specified argument.
Definition ExprCXX.h:1691
CXXConstructorDecl * getConstructor() const
Get the constructor that this expression will (ultimately) call.
Definition ExprCXX.h:1611
unsigned getNumArgs() const
Return the number of arguments to the constructor call.
Definition ExprCXX.h:1688
CXXMethodDecl * getMethodDecl() const
Retrieve the declaration of the called method.
Definition ExprCXX.cpp:741
Expr * getImplicitObjectArgument() const
Retrieve the implicit object argument for the member call.
Definition ExprCXX.cpp:722
static bool isAssignmentOp(OverloadedOperatorKind Opc)
Definition ExprCXX.h:119
Expr * getArg(unsigned Arg)
getArg - Return the specified argument.
Definition Expr.h:3081
unsigned getNumArgs() const
getNumArgs - Return the number of actual arguments to this call.
Definition Expr.h:3068
CompoundStmt - This represents a group of statements like { stmt stmt }.
Definition Stmt.h:1730
ASTContext & getASTContext() const LLVM_READONLY
Definition DeclBase.cpp:546
SourceLocation getLocation() const
Definition DeclBase.h:439
Container for either a single DynTypedNode or for an ArrayRef to DynTypedNode.
virtual bool TraverseDecl(MaybeConst< Decl > *D)
Expr * IgnoreParenCasts() LLVM_READONLY
Skip past any parentheses and casts which might surround this expression until reaching a fixed point...
Definition Expr.cpp:3094
Stmt * getThen()
Definition Stmt.h:2338
VarDecl * getConditionVariable()
Retrieve the variable declared in this "if" statement, if any.
Definition Stmt.cpp:1030
A (possibly-)qualified type.
Definition TypeBase.h:937
const Type * getTypePtr() const
Retrieves a pointer to the underlying (unqualified) type.
Definition TypeBase.h:8293
bool isInSystemHeader(SourceLocation Loc) const
Returns if a SourceLocation is in a system header.
bool isTrivial(const Decl *D) const
QualType getType() const
Definition Decl.h:723
Represents a variable declaration or definition.
Definition Decl.h:926
bool isLocalVarDecl() const
Returns true for local variable declarations other than parameters.
Definition Decl.h:1253
const SourceManager & getSourceManager()
CHECKER * registerChecker(AT &&...Args)
Register a single-part checker (derived from Checker): construct its singleton instance,...
Simple checker classes that implement one frontend (i.e.
Definition Checker.h:553
const internal::VariadicAllOfMatcher< Type > type
Matches Types in the clang AST.
bool isCocoaObjectRef(QualType T)
std::variant< struct RequiresDecl, struct HeaderDecl, struct UmbrellaDirDecl, struct ModuleDecl, struct ExcludeDecl, struct ExportDecl, struct ExportAsDecl, struct ExternModuleDecl, struct UseDecl, struct LinkDecl, struct ConfigMacrosDecl, struct ConflictDecl > Decl
All declarations that can appear in a module declaration.
The JSON file list parser is used to communicate input to InstallAPI.
bool isa(CodeGen::Address addr)
Definition Address.h:330
bool isExprToGetCheckedPtrCapableMember(const clang::Expr *E)
Definition ASTUtils.cpp:304
bool isRefOrCheckedPtrType(const clang::QualType T)
void printQuotedQualifiedName(llvm::raw_ostream &Os, const NamedDeclDerivedT &D)
bool isRetainPtrOrOSPtrType(const clang::QualType T)
bool tryToFindPtrOrigin(const Expr *E, bool StopAtFirstRefCountedObj, std::function< bool(const clang::CXXRecordDecl *)> isSafePtr, std::function< bool(const clang::QualType)> isSafePtrType, std::function< bool(const clang::Decl *)> isSafeGlobalDecl, std::function< bool(const clang::Expr *, bool)> callback)
This function de-facto defines a set of transformations that we consider safe (in heuristical sense).
Definition ASTUtils.cpp:25
const FunctionProtoType * T
bool isSmartPtrClass(const std::string &Name)
bool isRefCounted(const CXXRecordDecl *R)
@ Type
The name was classified as a type.
Definition Sema.h:563
bool isRetainPtrOrOSPtr(const std::string &Name)
std::optional< bool > isUncountedPtr(const QualType T)
bool isSafePtr(clang::CXXRecordDecl *Decl)
Definition ASTUtils.cpp:21
std::string safeGetName(const T *ASTNode)
Definition ASTUtils.h:95
bool isNullPtr(const clang::Expr *E)
Definition ASTUtils.cpp:270
bool isCheckedPtr(const std::string &Name)
DynamicRecursiveASTVisitorBase< false > DynamicRecursiveASTVisitor
bool isConstOwnerPtrMemberExpr(const clang::Expr *E)
Definition ASTUtils.cpp:280
std::optional< bool > isUncheckedPtr(const QualType T)