Skip to content

Commit c8ec807

Browse files
authored
[alpha.webkit.NoUnretainedMemberChecker] Add a new WebKit checker for unretained member variables and ivars. (#128641)
Add a new WebKit checker for member variables and instance variables of NS and CF types. A member variable or instance variable to a CF type should be RetainPtr regardless of whether ARC is enabled or disabled, and that of a NS type should be RetainPtr when ARC is disabled.
1 parent b2b267e commit c8ec807

File tree

5 files changed

+211
-20
lines changed

5 files changed

+211
-20
lines changed

clang/docs/analyzer/checkers.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3522,6 +3522,19 @@ Raw pointers and references to an object which supports CheckedPtr or CheckedRef
35223522
35233523
See `WebKit Guidelines for Safer C++ Programming <https://github.com/WebKit/WebKit/wiki/Safer-CPP-Guidelines>`_ for details.
35243524
3525+
alpha.webkit.NoUnretainedMemberChecker
3526+
""""""""""""""""""""""""""""""""""""""""
3527+
Raw pointers and references to a NS or CF object can't be used as class members or ivars. Only RetainPtr is allowed for CF types regardless of whether ARC is enabled or disabled. Only RetainPtr is allowed for NS types when ARC is disabled.
3528+
3529+
.. code-block:: cpp
3530+
3531+
struct Foo {
3532+
NSObject *ptr; // warn
3533+
// ...
3534+
};
3535+
3536+
See `WebKit Guidelines for Safer C++ Programming <https://github.com/WebKit/WebKit/wiki/Safer-CPP-Guidelines>`_ for details.
3537+
35253538
alpha.webkit.UnretainedLambdaCapturesChecker
35263539
""""""""""""""""""""""""""""""""""""""""""""
35273540
Raw pointers and references to NS or CF types can't be captured in lambdas. Only RetainPtr is allowed for CF types regardless of whether ARC is enabled or disabled, and only RetainPtr is allowed for NS types when ARC is disabled.

clang/include/clang/StaticAnalyzer/Checkers/Checkers.td

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1765,6 +1765,10 @@ def NoUncheckedPtrMemberChecker : Checker<"NoUncheckedPtrMemberChecker">,
17651765
HelpText<"Check for no unchecked member variables.">,
17661766
Documentation<HasDocumentation>;
17671767

1768+
def NoUnretainedMemberChecker : Checker<"NoUnretainedMemberChecker">,
1769+
HelpText<"Check for no unretained member variables.">,
1770+
Documentation<HasDocumentation>;
1771+
17681772
def UnretainedLambdaCapturesChecker : Checker<"UnretainedLambdaCapturesChecker">,
17691773
HelpText<"Check unretained lambda captures.">,
17701774
Documentation<HasDocumentation>;

clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefMemberChecker.cpp

Lines changed: 95 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,16 @@ class RawPtrRefMemberChecker
3131
BugType Bug;
3232
mutable BugReporter *BR;
3333

34+
protected:
35+
mutable std::optional<RetainTypeChecker> RTC;
36+
3437
public:
3538
RawPtrRefMemberChecker(const char *description)
3639
: Bug(this, description, "WebKit coding guidelines") {}
3740

3841
virtual std::optional<bool>
39-
isPtrCompatible(const clang::CXXRecordDecl *) const = 0;
42+
isPtrCompatible(const clang::QualType,
43+
const clang::CXXRecordDecl *R) const = 0;
4044
virtual bool isPtrCls(const clang::CXXRecordDecl *) const = 0;
4145
virtual const char *typeName() const = 0;
4246
virtual const char *invariant() const = 0;
@@ -57,6 +61,12 @@ class RawPtrRefMemberChecker
5761
ShouldVisitImplicitCode = false;
5862
}
5963

64+
bool VisitTypedefDecl(const TypedefDecl *TD) override {
65+
if (Checker->RTC)
66+
Checker->RTC->visitTypedef(TD);
67+
return true;
68+
}
69+
6070
bool VisitRecordDecl(const RecordDecl *RD) override {
6171
Checker->visitRecordDecl(RD);
6272
return true;
@@ -69,6 +79,8 @@ class RawPtrRefMemberChecker
6979
};
7080

7181
LocalVisitor visitor(this);
82+
if (RTC)
83+
RTC->visitTranslationUnitDecl(TUD);
7284
visitor.TraverseDecl(TUD);
7385
}
7486

@@ -77,16 +89,22 @@ class RawPtrRefMemberChecker
7789
return;
7890

7991
for (auto *Member : RD->fields()) {
80-
const Type *MemberType = Member->getType().getTypePtrOrNull();
92+
auto QT = Member->getType();
93+
const Type *MemberType = QT.getTypePtrOrNull();
8194
if (!MemberType)
8295
continue;
8396

8497
if (auto *MemberCXXRD = MemberType->getPointeeCXXRecordDecl()) {
85-
// If we don't see the definition we just don't know.
86-
if (MemberCXXRD->hasDefinition()) {
87-
std::optional<bool> isRCAble = isPtrCompatible(MemberCXXRD);
88-
if (isRCAble && *isRCAble)
89-
reportBug(Member, MemberType, MemberCXXRD, RD);
98+
std::optional<bool> IsCompatible = isPtrCompatible(QT, MemberCXXRD);
99+
if (IsCompatible && *IsCompatible)
100+
reportBug(Member, MemberType, MemberCXXRD, RD);
101+
} else {
102+
std::optional<bool> IsCompatible = isPtrCompatible(QT, nullptr);
103+
auto *PointeeType = MemberType->getPointeeType().getTypePtrOrNull();
104+
if (IsCompatible && *IsCompatible) {
105+
auto *Desugared = PointeeType->getUnqualifiedDesugaredType();
106+
if (auto *ObjCType = dyn_cast_or_null<ObjCInterfaceType>(Desugared))
107+
reportBug(Member, MemberType, ObjCType->getDecl(), RD);
90108
}
91109
}
92110
}
@@ -107,11 +125,12 @@ class RawPtrRefMemberChecker
107125

108126
void visitIvarDecl(const ObjCContainerDecl *CD,
109127
const ObjCIvarDecl *Ivar) const {
110-
const Type *IvarType = Ivar->getType().getTypePtrOrNull();
128+
auto QT = Ivar->getType();
129+
const Type *IvarType = QT.getTypePtrOrNull();
111130
if (!IvarType)
112131
return;
113132
if (auto *IvarCXXRD = IvarType->getPointeeCXXRecordDecl()) {
114-
std::optional<bool> IsCompatible = isPtrCompatible(IvarCXXRD);
133+
std::optional<bool> IsCompatible = isPtrCompatible(QT, IvarCXXRD);
115134
if (IsCompatible && *IsCompatible)
116135
reportBug(Ivar, IvarType, IvarCXXRD, CD);
117136
}
@@ -151,13 +170,13 @@ class RawPtrRefMemberChecker
151170
return false;
152171
}
153172

154-
template <typename DeclType, typename ParentDeclType>
173+
template <typename DeclType, typename PointeeType, typename ParentDeclType>
155174
void reportBug(const DeclType *Member, const Type *MemberType,
156-
const CXXRecordDecl *MemberCXXRD,
175+
const PointeeType *Pointee,
157176
const ParentDeclType *ClassCXXRD) const {
158177
assert(Member);
159178
assert(MemberType);
160-
assert(MemberCXXRD);
179+
assert(Pointee);
161180

162181
SmallString<100> Buf;
163182
llvm::raw_svector_ostream Os(Buf);
@@ -169,10 +188,13 @@ class RawPtrRefMemberChecker
169188
printQuotedName(Os, Member);
170189
Os << " in ";
171190
printQuotedQualifiedName(Os, ClassCXXRD);
172-
Os << " is a "
173-
<< (isa<PointerType>(MemberType) ? "raw pointer" : "reference") << " to "
174-
<< typeName() << " ";
175-
printQuotedQualifiedName(Os, MemberCXXRD);
191+
Os << " is a ";
192+
if (printPointer(Os, MemberType) == PrintDeclKind::Pointer) {
193+
auto Typedef = MemberType->getAs<TypedefType>();
194+
assert(Typedef);
195+
printQuotedQualifiedName(Os, Typedef->getDecl());
196+
} else
197+
printQuotedQualifiedName(Os, Pointee);
176198
Os << "; " << invariant() << ".";
177199

178200
PathDiagnosticLocation BSLoc(Member->getSourceRange().getBegin(),
@@ -181,6 +203,15 @@ class RawPtrRefMemberChecker
181203
Report->addRange(Member->getSourceRange());
182204
BR->emitReport(std::move(Report));
183205
}
206+
207+
enum class PrintDeclKind { Pointee, Pointer };
208+
virtual PrintDeclKind printPointer(llvm::raw_svector_ostream &Os,
209+
const Type *T) const {
210+
T = T->getUnqualifiedDesugaredType();
211+
bool IsPtr = isa<PointerType>(T) || isa<ObjCObjectPointerType>(T);
212+
Os << (IsPtr ? "raw pointer" : "reference") << " to " << typeName() << " ";
213+
return PrintDeclKind::Pointee;
214+
}
184215
};
185216

186217
class NoUncountedMemberChecker final : public RawPtrRefMemberChecker {
@@ -190,8 +221,9 @@ class NoUncountedMemberChecker final : public RawPtrRefMemberChecker {
190221
"reference-countable type") {}
191222

192223
std::optional<bool>
193-
isPtrCompatible(const clang::CXXRecordDecl *R) const final {
194-
return isRefCountable(R);
224+
isPtrCompatible(const clang::QualType,
225+
const clang::CXXRecordDecl *R) const final {
226+
return R && isRefCountable(R);
195227
}
196228

197229
bool isPtrCls(const clang::CXXRecordDecl *R) const final {
@@ -212,8 +244,9 @@ class NoUncheckedPtrMemberChecker final : public RawPtrRefMemberChecker {
212244
"checked-pointer capable type") {}
213245

214246
std::optional<bool>
215-
isPtrCompatible(const clang::CXXRecordDecl *R) const final {
216-
return isCheckedPtrCapable(R);
247+
isPtrCompatible(const clang::QualType,
248+
const clang::CXXRecordDecl *R) const final {
249+
return R && isCheckedPtrCapable(R);
217250
}
218251

219252
bool isPtrCls(const clang::CXXRecordDecl *R) const final {
@@ -228,6 +261,40 @@ class NoUncheckedPtrMemberChecker final : public RawPtrRefMemberChecker {
228261
}
229262
};
230263

264+
class NoUnretainedMemberChecker final : public RawPtrRefMemberChecker {
265+
public:
266+
NoUnretainedMemberChecker()
267+
: RawPtrRefMemberChecker("Member variable is a raw-pointer/reference to "
268+
"retainable type") {
269+
RTC = RetainTypeChecker();
270+
}
271+
272+
std::optional<bool>
273+
isPtrCompatible(const clang::QualType QT,
274+
const clang::CXXRecordDecl *) const final {
275+
return RTC->isUnretained(QT);
276+
}
277+
278+
bool isPtrCls(const clang::CXXRecordDecl *R) const final {
279+
return isRetainPtr(R);
280+
}
281+
282+
const char *typeName() const final { return "retainable type"; }
283+
284+
const char *invariant() const final {
285+
return "member variables must be a RetainPtr";
286+
}
287+
288+
PrintDeclKind printPointer(llvm::raw_svector_ostream &Os,
289+
const Type *T) const final {
290+
if (!isa<ObjCObjectPointerType>(T) && T->getAs<TypedefType>()) {
291+
Os << typeName() << " ";
292+
return PrintDeclKind::Pointer;
293+
}
294+
return RawPtrRefMemberChecker::printPointer(Os, T);
295+
}
296+
};
297+
231298
} // namespace
232299

233300
void ento::registerNoUncountedMemberChecker(CheckerManager &Mgr) {
@@ -246,3 +313,11 @@ bool ento::shouldRegisterNoUncheckedPtrMemberChecker(
246313
const CheckerManager &Mgr) {
247314
return true;
248315
}
316+
317+
void ento::registerNoUnretainedMemberChecker(CheckerManager &Mgr) {
318+
Mgr.registerChecker<NoUnretainedMemberChecker>();
319+
}
320+
321+
bool ento::shouldRegisterNoUnretainedMemberChecker(const CheckerManager &Mgr) {
322+
return true;
323+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.webkit.NoUnretainedMemberChecker -fobjc-arc -verify %s
2+
3+
#include "objc-mock-types.h"
4+
5+
namespace members {
6+
7+
struct Foo {
8+
private:
9+
SomeObj* a = nullptr;
10+
11+
[[clang::suppress]]
12+
SomeObj* a_suppressed = nullptr;
13+
14+
protected:
15+
RetainPtr<SomeObj> b;
16+
17+
public:
18+
SomeObj* c = nullptr;
19+
RetainPtr<SomeObj> d;
20+
21+
CFMutableArrayRef e = nullptr;
22+
// expected-warning@-1{{Member variable 'e' in 'members::Foo' is a retainable type 'CFMutableArrayRef'}}
23+
};
24+
25+
template<class T, class S>
26+
struct FooTmpl {
27+
T* x;
28+
S y;
29+
// expected-warning@-1{{Member variable 'y' in 'members::FooTmpl<SomeObj, __CFArray *>' is a raw pointer to retainable type}}
30+
};
31+
32+
void forceTmplToInstantiate(FooTmpl<SomeObj, CFMutableArrayRef>) {}
33+
34+
struct [[clang::suppress]] FooSuppressed {
35+
private:
36+
SomeObj* a = nullptr;
37+
};
38+
39+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.webkit.NoUnretainedMemberChecker -verify %s
2+
3+
#include "objc-mock-types.h"
4+
5+
namespace members {
6+
7+
struct Foo {
8+
private:
9+
SomeObj* a = nullptr;
10+
// expected-warning@-1{{Member variable 'a' in 'members::Foo' is a raw pointer to retainable type}}
11+
12+
[[clang::suppress]]
13+
SomeObj* a_suppressed = nullptr;
14+
// No warning.
15+
16+
protected:
17+
RetainPtr<SomeObj> b;
18+
// No warning.
19+
20+
public:
21+
SomeObj* c = nullptr;
22+
// expected-warning@-1{{Member variable 'c' in 'members::Foo' is a raw pointer to retainable type}}
23+
RetainPtr<SomeObj> d;
24+
25+
CFMutableArrayRef e = nullptr;
26+
// expected-warning@-1{{Member variable 'e' in 'members::Foo' is a retainable type 'CFMutableArrayRef'}}
27+
};
28+
29+
template<class T, class S>
30+
struct FooTmpl {
31+
T* a;
32+
// expected-warning@-1{{Member variable 'a' in 'members::FooTmpl<SomeObj, __CFArray *>' is a raw pointer to retainable type}}
33+
S b;
34+
// expected-warning@-1{{Member variable 'b' in 'members::FooTmpl<SomeObj, __CFArray *>' is a raw pointer to retainable type}}
35+
};
36+
37+
void forceTmplToInstantiate(FooTmpl<SomeObj, CFMutableArrayRef>) {}
38+
39+
struct [[clang::suppress]] FooSuppressed {
40+
private:
41+
SomeObj* a = nullptr;
42+
// No warning.
43+
};
44+
45+
}
46+
47+
namespace ignore_unions {
48+
union Foo {
49+
SomeObj* a;
50+
RetainPtr<SomeObj> b;
51+
CFMutableArrayRef c;
52+
};
53+
54+
template<class T>
55+
union RefPtr {
56+
T* a;
57+
};
58+
59+
void forceTmplToInstantiate(RefPtr<SomeObj>) {}
60+
}

0 commit comments

Comments
 (0)