1//=======- RawPtrRefMemberChecker.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"
12#include "RawPtrRefSafetyModel.h"
13#include "clang/AST/Decl.h"
14#include "clang/AST/DeclCXX.h"
15#include "clang/AST/DynamicRecursiveASTVisitor.h"
16#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
17#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
18#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
19#include "clang/StaticAnalyzer/Core/Checker.h"
20#include "llvm/Support/Casting.h"
21#include <optional>
22
23using namespace clang;
24using namespace ento;
25
26namespace {
27
28class RawPtrRefMemberChecker
29 : public Checker<check::ASTDecl<TranslationUnitDecl>> {
30private:
31 BugType Bug;
32 mutable BugReporter *BR;
33 mutable llvm::DenseSet<const ObjCIvarDecl *> IvarDeclsToIgnore;
34
35protected:
36 const std::unique_ptr<PtrRefSafetyModel> Model;
37
38public:
39 RawPtrRefMemberChecker(const char *description,
40 std::unique_ptr<PtrRefSafetyModel> Model)
41 : Bug(this, description, "WebKit coding guidelines"),
42 Model(std::move(Model)) {}
43
44 std::optional<bool> isUnsafePtr(QualType QT, bool IgnoreARC = false) const {
45 return isUnsafePtrForStorage(Model: *Model, T: QT, IgnoreARC);
46 }
47
48 void checkASTDecl(const TranslationUnitDecl *TUD, AnalysisManager &MGR,
49 BugReporter &BRArg) const {
50 BR = &BRArg;
51
52 // The calls to checkAST* from AnalysisConsumer don't
53 // visit template instantiations or lambda classes. We
54 // want to visit those, so we make our own RecursiveASTVisitor.
55 struct LocalVisitor : ConstDynamicRecursiveASTVisitor {
56 const RawPtrRefMemberChecker *Checker;
57 explicit LocalVisitor(const RawPtrRefMemberChecker *Checker)
58 : Checker(Checker) {
59 assert(Checker);
60 ShouldVisitTemplateInstantiations = true;
61 ShouldVisitImplicitCode = false;
62 }
63
64 bool VisitTypedefDecl(const TypedefDecl *TD) override {
65 if (auto *RTC = Checker->Model->retainTypeChecker())
66 RTC->visitTypedef(TD);
67 return true;
68 }
69
70 bool VisitRecordDecl(const RecordDecl *RD) override {
71 Checker->visitRecordDecl(RD);
72 return true;
73 }
74
75 bool VisitObjCContainerDecl(const ObjCContainerDecl *CD) override {
76 Checker->visitObjCDecl(CD);
77 return true;
78 }
79 };
80
81 LocalVisitor visitor(this);
82 if (auto *RTC = Model->retainTypeChecker())
83 RTC->visitTranslationUnitDecl(TUD);
84 visitor.TraverseDecl(D: TUD);
85 }
86
87 void visitRecordDecl(const RecordDecl *RD) const {
88 if (shouldSkipDecl(RD))
89 return;
90
91 for (auto *Member : RD->fields())
92 visitMember(Member, RD);
93 }
94
95 void visitMember(const FieldDecl *Member, const RecordDecl *RD) const {
96 auto QT = Member->getType();
97 const Type *MemberType = QT.getTypePtrOrNull();
98
99 while (MemberType) {
100 auto IsUnsafePtr = isUnsafePtr(QT);
101 if (IsUnsafePtr && *IsUnsafePtr)
102 break;
103 if (!MemberType->isPointerType())
104 return;
105 QT = MemberType->getPointeeType();
106 MemberType = QT.getTypePtrOrNull();
107 }
108
109 if (!MemberType)
110 return;
111
112 if (auto *MemberCXXRD = MemberType->getPointeeCXXRecordDecl())
113 reportBug(Member, MemberType, Pointee: MemberCXXRD, ClassCXXRD: RD);
114 else if (auto *ObjCDecl = getObjCDeclFromObjCPtr(TypePtr: MemberType))
115 reportBug(Member, MemberType, Pointee: ObjCDecl, ClassCXXRD: RD);
116 }
117
118 void visitObjCDecl(const ObjCContainerDecl *CD) const {
119 if (BR->getSourceManager().isInSystemHeader(Loc: CD->getLocation()))
120 return;
121
122 if (auto *ID = dyn_cast<ObjCImplementationDecl>(Val: CD)) {
123 ObjCContainerDecl::PropertyMap map;
124 CD->collectPropertiesToImplement(PM&: map);
125 for (auto it : map)
126 visitObjCPropertyDecl(CD, PD: it.second);
127
128 if (auto *Interface = ID->getClassInterface()) {
129 for (auto *Ivar : Interface->ivars())
130 visitIvarDecl(CD, Ivar);
131 }
132 for (auto *PropImpl : ID->property_impls())
133 visitPropImpl(CD, PID: PropImpl);
134 for (auto *Ivar : ID->ivars())
135 visitIvarDecl(CD, Ivar);
136 return;
137 }
138 }
139
140 void visitIvarDecl(const ObjCContainerDecl *CD,
141 const ObjCIvarDecl *Ivar) const {
142 if (BR->getSourceManager().isInSystemHeader(Loc: Ivar->getLocation()))
143 return;
144
145 if (IvarDeclsToIgnore.contains(V: Ivar))
146 return;
147
148 auto QT = Ivar->getType();
149 const Type *IvarType = QT.getTypePtrOrNull();
150 if (!IvarType)
151 return;
152
153 auto IsUnsafePtr = isUnsafePtr(QT);
154 if (!IsUnsafePtr || !*IsUnsafePtr)
155 return;
156
157 IvarDeclsToIgnore.insert(V: Ivar);
158
159 if (auto *MemberCXXRD = IvarType->getPointeeCXXRecordDecl())
160 reportBug(Member: Ivar, MemberType: IvarType, Pointee: MemberCXXRD, ClassCXXRD: CD);
161 else if (auto *ObjCDecl = getObjCDeclFromObjCPtr(TypePtr: IvarType))
162 reportBug(Member: Ivar, MemberType: IvarType, Pointee: ObjCDecl, ClassCXXRD: CD);
163 }
164
165 void visitObjCPropertyDecl(const ObjCContainerDecl *CD,
166 const ObjCPropertyDecl *PD) const {
167 if (BR->getSourceManager().isInSystemHeader(Loc: PD->getLocation()))
168 return;
169
170 if (const ObjCInterfaceDecl *ID = dyn_cast<ObjCInterfaceDecl>(Val: CD)) {
171 auto *RTC = Model->retainTypeChecker();
172 if (!RTC || !RTC->defaultSynthProperties() ||
173 ID->isObjCRequiresPropertyDefs())
174 return;
175 }
176
177 auto [IsUnsafe, PropType] = isPropImplUnsafePtr(PD);
178 if (!IsUnsafe)
179 return;
180
181 if (auto *MemberCXXRD = PropType->getPointeeCXXRecordDecl())
182 reportBug(Member: PD, MemberType: PropType, Pointee: MemberCXXRD, ClassCXXRD: CD);
183 else if (auto *ObjCDecl = getObjCDeclFromObjCPtr(TypePtr: PropType))
184 reportBug(Member: PD, MemberType: PropType, Pointee: ObjCDecl, ClassCXXRD: CD);
185 }
186
187 void visitPropImpl(const ObjCContainerDecl *CD,
188 const ObjCPropertyImplDecl *PID) const {
189 if (BR->getSourceManager().isInSystemHeader(Loc: PID->getLocation()))
190 return;
191
192 if (PID->getPropertyImplementation() != ObjCPropertyImplDecl::Synthesize)
193 return;
194
195 auto *PropDecl = PID->getPropertyDecl();
196 if (auto *IvarDecl = PID->getPropertyIvarDecl()) {
197 if (IvarDeclsToIgnore.contains(V: IvarDecl))
198 return;
199 IvarDeclsToIgnore.insert(V: IvarDecl);
200 }
201 auto [IsUnsafe, PropType] = isPropImplUnsafePtr(PD: PropDecl);
202 if (!IsUnsafe)
203 return;
204
205 if (auto *MemberCXXRD = PropType->getPointeeCXXRecordDecl())
206 reportBug(Member: PropDecl, MemberType: PropType, Pointee: MemberCXXRD, ClassCXXRD: CD);
207 else if (auto *ObjCDecl = getObjCDeclFromObjCPtr(TypePtr: PropType))
208 reportBug(Member: PropDecl, MemberType: PropType, Pointee: ObjCDecl, ClassCXXRD: CD);
209 }
210
211 std::pair<bool, const Type *>
212 isPropImplUnsafePtr(const ObjCPropertyDecl *PD) const {
213 if (!PD)
214 return {false, nullptr};
215
216 auto QT = PD->getType();
217 const Type *PropType = QT.getTypePtrOrNull();
218 if (!PropType)
219 return {false, nullptr};
220
221 // "assign" property doesn't retain even under ARC so treat it as unsafe.
222 bool ignoreARC =
223 !PD->isReadOnly() && PD->getSetterKind() == ObjCPropertyDecl::Assign;
224 bool IsWeak =
225 PD->getPropertyAttributes() & ObjCPropertyAttribute::kind_weak;
226 bool HasSafeAttr = PD->isRetaining() || IsWeak;
227 auto IsUnsafePtr = isUnsafePtr(QT, IgnoreARC: ignoreARC);
228 return {IsUnsafePtr && *IsUnsafePtr && !HasSafeAttr, PropType};
229 }
230
231 bool shouldSkipDecl(const RecordDecl *RD) const {
232 if (!RD->isThisDeclarationADefinition())
233 return true;
234
235 if (RD->isImplicit())
236 return true;
237
238 if (RD->isLambda())
239 return true;
240
241 // If the construct doesn't have a source file, then it's not something
242 // we want to diagnose.
243 const auto RDLocation = RD->getLocation();
244 if (!RDLocation.isValid())
245 return true;
246
247 const auto Kind = RD->getTagKind();
248 if (Kind != TagTypeKind::Struct && Kind != TagTypeKind::Class &&
249 Kind != TagTypeKind::Union)
250 return true;
251
252 // Ignore CXXRecords that come from system headers.
253 if (BR->getSourceManager().isInSystemHeader(Loc: RDLocation))
254 return true;
255
256 // Ref-counted smartpointers actually have raw-pointer to uncounted type as
257 // a member but we trust them to handle it correctly.
258 auto CXXRD = llvm::dyn_cast_or_null<CXXRecordDecl>(Val: RD);
259 if (CXXRD && isSmartPtr(Class: CXXRD))
260 return true;
261
262 return false;
263 }
264
265 template <typename DeclType, typename PointeeType, typename ParentDeclType>
266 void reportBug(const DeclType *Member, const Type *MemberType,
267 const PointeeType *Pointee,
268 const ParentDeclType *ClassCXXRD) const {
269 assert(Member);
270 assert(MemberType);
271 assert(Pointee);
272
273 SmallString<100> Buf;
274 llvm::raw_svector_ostream Os(Buf);
275
276 if (isa<ObjCContainerDecl>(ClassCXXRD)) {
277 if (isa<ObjCPropertyDecl>(Member))
278 Os << "Property ";
279 else
280 Os << "Instance variable ";
281 } else
282 Os << "Member variable ";
283 printQuotedName(Os, Member);
284 Os << " (of ";
285 printQuotedQualifiedName(Os, ClassCXXRD);
286 Os << ")";
287 if (Member->getType().getTypePtrOrNull() == MemberType)
288 Os << " is a ";
289 else
290 Os << " contains a ";
291 if (printPointer(Os, T: MemberType) == PrintDeclKind::Pointer) {
292 auto Typedef = MemberType->getAs<TypedefType>();
293 assert(Typedef);
294 printQuotedQualifiedName(Os, D: Typedef->getDecl());
295 } else
296 printQuotedQualifiedName(Os, Pointee);
297
298 PathDiagnosticLocation BSLoc(Member->getSourceRange().getBegin(),
299 BR->getSourceManager());
300 auto Report = std::make_unique<BasicBugReport>(args: Bug, args: Os.str(), args&: BSLoc);
301 Report->addRange(R: Member->getSourceRange());
302 if (auto *EnclosingCtx = Member->getDeclContext())
303 Report->setDeclWithIssue(Decl::castFromDeclContext(EnclosingCtx));
304 BR->emitReport(R: std::move(Report));
305 }
306
307 enum class PrintDeclKind { Pointee, Pointer };
308 PrintDeclKind printPointer(llvm::raw_svector_ostream &Os,
309 const Type *T) const {
310 // Retain/OS types are frequently spelled through a typedef (e.g. CFXXXRef);
311 // print the typedef name rather than desugaring to the pointee.
312 if (Model->retainTypeChecker() && !isa<ObjCObjectPointerType>(Val: T) &&
313 T->getAs<TypedefType>()) {
314 Os << Model->typeName() << " ";
315 return PrintDeclKind::Pointer;
316 }
317 T = T->getUnqualifiedDesugaredType();
318 bool IsPtr = isa<PointerType>(Val: T) || isa<ObjCObjectPointerType>(Val: T);
319 Os << (IsPtr ? "raw pointer" : "reference") << " to " << Model->typeName()
320 << " ";
321 return PrintDeclKind::Pointee;
322 }
323};
324
325class NoUncountedMemberChecker final : public RawPtrRefMemberChecker {
326public:
327 NoUncountedMemberChecker()
328 : RawPtrRefMemberChecker("Member variable is a raw-pointer/reference to "
329 "reference-countable type",
330 makeRefPtrSafetyModel()) {}
331};
332
333class NoUncheckedPtrMemberChecker final : public RawPtrRefMemberChecker {
334public:
335 NoUncheckedPtrMemberChecker()
336 : RawPtrRefMemberChecker("Member variable is a raw-pointer/reference to "
337 "checked-pointer capable type",
338 makeCheckedPtrSafetyModel()) {}
339};
340
341class NoUnretainedMemberChecker final : public RawPtrRefMemberChecker {
342public:
343 NoUnretainedMemberChecker()
344 : RawPtrRefMemberChecker("Member variable is a raw-pointer/reference to "
345 "retainable type",
346 makeRetainPtrSafetyModel()) {}
347};
348
349} // namespace
350
351void ento::registerNoUncountedMemberChecker(CheckerManager &Mgr) {
352 Mgr.registerChecker<NoUncountedMemberChecker>();
353}
354
355bool ento::shouldRegisterNoUncountedMemberChecker(const CheckerManager &Mgr) {
356 return true;
357}
358
359void ento::registerNoUncheckedPtrMemberChecker(CheckerManager &Mgr) {
360 Mgr.registerChecker<NoUncheckedPtrMemberChecker>();
361}
362
363bool ento::shouldRegisterNoUncheckedPtrMemberChecker(
364 const CheckerManager &Mgr) {
365 return true;
366}
367
368void ento::registerNoUnretainedMemberChecker(CheckerManager &Mgr) {
369 Mgr.registerChecker<NoUnretainedMemberChecker>();
370}
371
372bool ento::shouldRegisterNoUnretainedMemberChecker(const CheckerManager &Mgr) {
373 return true;
374}
375