https://github.com/rniwa updated https://github.com/llvm/llvm-project/pull/200599
>From d56a62ab277c3171351216d9c6b441e8f23e6a03 Mon Sep 17 00:00:00 2001 From: Ryosuke Niwa <[email protected]> Date: Sat, 30 May 2026 12:30:29 -0700 Subject: [PATCH 1/2] [alpha.webkit.NoUncheckedPtrMemberChecker] Emit a warning for a pointer to smart pointers Emit a warning for a member variable which is a raw pointer or raw reference to a smart pointer as such a variable is not memory safe as the underlying smart pointer might get free'ed elsewhere. Also add a support for [[clang::annotate_type("webkit.unsafeptr")]], which explicitly annotate and allow such a member variable in container classes such as Vector and HashTable. --- .../Checkers/WebKit/PtrTypesSemantics.cpp | 32 ++++- .../Checkers/WebKit/PtrTypesSemantics.h | 10 ++ .../WebKit/RawPtrRefMemberChecker.cpp | 112 ++++++++++++------ .../Checkers/WebKit/unchecked-members.cpp | 28 ++++- .../Checkers/WebKit/uncounted-members-objc.mm | 24 ++++ .../Checkers/WebKit/uncounted-members.cpp | 27 ++++- .../Checkers/WebKit/unretained-members-arc.mm | 42 ++++++- .../Checkers/WebKit/unretained-members.mm | 63 +++++++++- 8 files changed, 288 insertions(+), 50 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp index 2ca34ff0587e1..0565c7540f845 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp @@ -225,6 +225,14 @@ static bool isPtrOfType(const clang::QualType T, Predicate Pred) { return false; } +bool isRefPtrType(const clang::QualType T) { + return isPtrOfType(T, [](auto Name) { return isRefType(Name); }); +} + +bool isCheckedPtrType(const clang::QualType T) { + return isPtrOfType(T, [](auto Name) { return isCheckedPtr(Name); }); +} + bool isRefOrCheckedPtrType(const clang::QualType T) { return isPtrOfType( T, [](auto Name) { return isRefType(Name) || isCheckedPtr(Name); }); @@ -427,14 +435,15 @@ enum class WebKitAnnotation : uint8_t { None, PointerConversion, NoDelete, + UnsafePtr, }; -static WebKitAnnotation typeAnnotationForReturnType(const FunctionDecl *FD) { - auto RetType = FD->getReturnType(); - auto *Type = RetType.getTypePtrOrNull(); - if (auto *MacroQualified = dyn_cast_or_null<MacroQualifiedType>(Type)) - Type = MacroQualified->desugar().getTypePtrOrNull(); - auto *Attr = dyn_cast_or_null<AttributedType>(Type); +static WebKitAnnotation typeAnnotation(const Type *T) { + if (!T) + return WebKitAnnotation::None; + if (auto *MacroQualified = dyn_cast_or_null<MacroQualifiedType>(T)) + T = MacroQualified->desugar().getTypePtrOrNull(); + auto *Attr = dyn_cast_or_null<AttributedType>(T); if (!Attr) return WebKitAnnotation::None; auto *AnnotateType = dyn_cast_or_null<AnnotateTypeAttr>(Attr->getAttr()); @@ -445,9 +454,16 @@ static WebKitAnnotation typeAnnotationForReturnType(const FunctionDecl *FD) { return WebKitAnnotation::PointerConversion; if (Annotation == "webkit.nodelete") return WebKitAnnotation::NoDelete; + if (Annotation == "webkit.unsafeptr") + return WebKitAnnotation::UnsafePtr; return WebKitAnnotation::None; } +static WebKitAnnotation typeAnnotationForReturnType(const FunctionDecl *FD) { + auto RetType = FD->getReturnType(); + return typeAnnotation(RetType.getTypePtrOrNull()); +} + bool isPtrConversion(const FunctionDecl *F) { assert(F); if (isCtorOfRefCounted(F)) @@ -494,6 +510,10 @@ bool isNoDeleteFunction(const FunctionDecl *F) { return false; } +bool isExplicitlyAllowedUnsafePtr(const Type *T) { + return typeAnnotation(T) == WebKitAnnotation::UnsafePtr; +} + bool isTrivialBuiltinFunction(const FunctionDecl *F) { if (!F || !F->getDeclName().isIdentifier()) return false; diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h index a2fd12656d391..13e78e0beb0ae 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h @@ -109,6 +109,12 @@ std::optional<bool> isUncountedPtr(const clang::QualType T); /// class, false if not, std::nullopt if inconclusive. std::optional<bool> isUncheckedPtr(const clang::QualType T); +/// \returns true if \p T is a RefPtr or Ref or its variant, false if not. +bool isRefPtrType(const clang::QualType T); + +/// \returns true if \p T is a RefPtr or Ref or its variant, false if not. +bool isCheckedPtrType(const clang::QualType T); + /// \returns true if \p T is a RefPtr, Ref, CheckedPtr, CheckedRef, or its /// variant, false if not. bool isRefOrCheckedPtrType(const clang::QualType T); @@ -162,6 +168,10 @@ bool isPtrConversion(const FunctionDecl *F); /// [[clang::annotate_type("webkit.nodelete")]]. bool isNoDeleteFunction(const FunctionDecl *F); +/// \returns true if \p T is annotated with +/// [[clang::annotate_type("webkit.unsafeptr")]]. +bool isExplicitlyAllowedUnsafePtr(const Type *T); + /// \returns true if \p F is a builtin function which is considered trivial. bool isTrivialBuiltinFunction(const FunctionDecl *F); diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefMemberChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefMemberChecker.cpp index 0e23ae34ea212..1f0bbb09da61b 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefMemberChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefMemberChecker.cpp @@ -39,6 +39,7 @@ class RawPtrRefMemberChecker virtual std::optional<bool> isUnsafePtr(QualType, bool ignoreARC = false) const = 0; + virtual bool isSafePtr(QualType) const = 0; virtual const char *typeName() const = 0; virtual const char *invariant() const = 0; @@ -90,26 +91,39 @@ class RawPtrRefMemberChecker } void visitMember(const FieldDecl *Member, const RecordDecl *RD) const { + visitMemberDecl(Member, RD); + } + + template <typename DeclType, typename ParentDeclType> + bool visitMemberDecl(DeclType *Member, const ParentDeclType *D) const { auto QT = Member->getType(); const Type *MemberType = QT.getTypePtrOrNull(); + bool IsPtrToSafePtr = false; while (MemberType) { auto IsUnsafePtr = isUnsafePtr(QT); if (IsUnsafePtr && *IsUnsafePtr) break; - if (!MemberType->isPointerType()) - return; + if (!MemberType->isPointerType() && !MemberType->isReferenceType()) + return false; QT = MemberType->getPointeeType(); + if (isSafePtr(QT) && !isExplicitlyAllowedUnsafePtr(MemberType)) { + IsPtrToSafePtr = true; + break; + } MemberType = QT.getTypePtrOrNull(); } if (!MemberType) - return; + return false; if (auto *MemberCXXRD = MemberType->getPointeeCXXRecordDecl()) - reportBug(Member, MemberType, MemberCXXRD, RD); + reportBug(Member, MemberType, MemberCXXRD, D, IsPtrToSafePtr); else if (auto *ObjCDecl = getObjCDecl(MemberType)) - reportBug(Member, MemberType, ObjCDecl, RD); + reportBug(Member, MemberType, ObjCDecl, D, IsPtrToSafePtr); + else + return false; + return true; } ObjCInterfaceDecl *getObjCDecl(const Type *TypePtr) const { @@ -156,21 +170,8 @@ class RawPtrRefMemberChecker if (IvarDeclsToIgnore.contains(Ivar)) return; - auto QT = Ivar->getType(); - const Type *IvarType = QT.getTypePtrOrNull(); - if (!IvarType) - return; - - auto IsUnsafePtr = isUnsafePtr(QT); - if (!IsUnsafePtr || !*IsUnsafePtr) - return; - - IvarDeclsToIgnore.insert(Ivar); - - if (auto *MemberCXXRD = IvarType->getPointeeCXXRecordDecl()) - reportBug(Ivar, IvarType, MemberCXXRD, CD); - else if (auto *ObjCDecl = getObjCDecl(IvarType)) - reportBug(Ivar, IvarType, ObjCDecl, CD); + if (visitMemberDecl(Ivar, CD)) + IvarDeclsToIgnore.insert(Ivar); } void visitObjCPropertyDecl(const ObjCContainerDecl *CD, @@ -184,14 +185,14 @@ class RawPtrRefMemberChecker return; } - auto [IsUnsafe, PropType] = isPropImplUnsafePtr(PD); + auto [IsUnsafe, PropType, IsPtrToSafePtr] = isPropImplUnsafePtr(PD); if (!IsUnsafe) return; if (auto *MemberCXXRD = PropType->getPointeeCXXRecordDecl()) - reportBug(PD, PropType, MemberCXXRD, CD); + reportBug(PD, PropType, MemberCXXRD, CD, IsPtrToSafePtr); else if (auto *ObjCDecl = getObjCDecl(PropType)) - reportBug(PD, PropType, ObjCDecl, CD); + reportBug(PD, PropType, ObjCDecl, CD, IsPtrToSafePtr); } void visitPropImpl(const ObjCContainerDecl *CD, @@ -208,25 +209,25 @@ class RawPtrRefMemberChecker return; IvarDeclsToIgnore.insert(IvarDecl); } - auto [IsUnsafe, PropType] = isPropImplUnsafePtr(PropDecl); + auto [IsUnsafe, PropType, IsPtrToSafePtr] = isPropImplUnsafePtr(PropDecl); if (!IsUnsafe) return; if (auto *MemberCXXRD = PropType->getPointeeCXXRecordDecl()) - reportBug(PropDecl, PropType, MemberCXXRD, CD); + reportBug(PropDecl, PropType, MemberCXXRD, CD, IsPtrToSafePtr); else if (auto *ObjCDecl = getObjCDecl(PropType)) - reportBug(PropDecl, PropType, ObjCDecl, CD); + reportBug(PropDecl, PropType, ObjCDecl, CD, IsPtrToSafePtr); } - std::pair<bool, const Type *> + std::tuple<bool, const Type *, bool> isPropImplUnsafePtr(const ObjCPropertyDecl *PD) const { if (!PD) - return {false, nullptr}; + return {false, nullptr, false}; auto QT = PD->getType(); const Type *PropType = QT.getTypePtrOrNull(); if (!PropType) - return {false, nullptr}; + return {false, nullptr, false}; // "assign" property doesn't retain even under ARC so treat it as unsafe. bool ignoreARC = @@ -235,7 +236,22 @@ class RawPtrRefMemberChecker PD->getPropertyAttributes() & ObjCPropertyAttribute::kind_weak; bool HasSafeAttr = PD->isRetaining() || IsWeak; auto IsUnsafePtr = isUnsafePtr(QT, ignoreARC); - return {IsUnsafePtr && *IsUnsafePtr && !HasSafeAttr, PropType}; + if (IsUnsafePtr && *IsUnsafePtr) + return {!HasSafeAttr, PropType, false}; + + while (PropType->isPointerType() || PropType->isReferenceType()) { + auto PointeeQT = PropType->getPointeeType(); + if (isSafePtr(PointeeQT)) + return {true, PropType, true}; + PropType = PointeeQT.getTypePtrOrNull(); + if (!PropType) + break; + auto IsUnsafePtr = isUnsafePtr(PointeeQT); + if (IsUnsafePtr && *IsUnsafePtr) + return {true, PropType, false}; + } + + return {false, nullptr, false}; } bool shouldSkipDecl(const RecordDecl *RD) const { @@ -275,7 +291,8 @@ class RawPtrRefMemberChecker template <typename DeclType, typename PointeeType, typename ParentDeclType> void reportBug(const DeclType *Member, const Type *MemberType, const PointeeType *Pointee, - const ParentDeclType *ClassCXXRD) const { + const ParentDeclType *ClassCXXRD, + bool IsPtrToSafe = false) const { assert(Member); assert(MemberType); assert(Pointee); @@ -297,7 +314,7 @@ class RawPtrRefMemberChecker Os << " is a "; else Os << " contains a "; - if (printPointer(Os, MemberType) == PrintDeclKind::Pointer) { + if (printPointer(Os, MemberType, IsPtrToSafe) == PrintDeclKind::Pointer) { auto Typedef = MemberType->getAs<TypedefType>(); assert(Typedef); printQuotedQualifiedName(Os, Typedef->getDecl()); @@ -314,12 +331,22 @@ class RawPtrRefMemberChecker enum class PrintDeclKind { Pointee, Pointer }; virtual PrintDeclKind printPointer(llvm::raw_svector_ostream &Os, - const Type *T) const { + const Type *T, bool IsPtrToSafe) const { T = T->getUnqualifiedDesugaredType(); bool IsPtr = isa<PointerType>(T) || isa<ObjCObjectPointerType>(T); - Os << (IsPtr ? "raw pointer" : "reference") << " to " << typeName() << " "; + Os << "raw " << (IsPtr ? "pointer" : "reference") << " to "; + if (!IsPtrToSafe) + Os << typeName() << " "; return PrintDeclKind::Pointee; } + + void printTypeName(llvm::raw_ostream &Os, const Type *T) const { + if (auto *RD = T->getAsRecordDecl()) + RD->getNameForDiagnostic(Os, RD->getASTContext().getPrintingPolicy(), + /*Qualified=*/true); + else + Os << typeName(); + } }; class NoUncountedMemberChecker final : public RawPtrRefMemberChecker { @@ -332,6 +359,10 @@ class NoUncountedMemberChecker final : public RawPtrRefMemberChecker { return isUncountedPtr(QT.getCanonicalType()); } + bool isSafePtr(QualType QT) const final { + return isRefPtrType(QT); + } + const char *typeName() const final { return "ref-countable type"; } const char *invariant() const final { @@ -349,6 +380,10 @@ class NoUncheckedPtrMemberChecker final : public RawPtrRefMemberChecker { return isUncheckedPtr(QT.getCanonicalType()); } + bool isSafePtr(QualType QT) const final { + return isCheckedPtrType(QT); + } + const char *typeName() const final { return "CheckedPtr capable type"; } const char *invariant() const final { @@ -371,6 +406,10 @@ class NoUnretainedMemberChecker final : public RawPtrRefMemberChecker { return RTC->isUnretained(QT, ignoreARC); } + bool isSafePtr(QualType QT) const final { + return isRetainPtrOrOSPtrType(QT); + } + const char *typeName() const final { return "retainable type"; } const char *invariant() const final { @@ -378,12 +417,13 @@ class NoUnretainedMemberChecker final : public RawPtrRefMemberChecker { } PrintDeclKind printPointer(llvm::raw_svector_ostream &Os, - const Type *T) const final { + const Type *T, bool IsPtrToSafe) const final { + // FIXME: Support IsPtrToSafe. if (!isa<ObjCObjectPointerType>(T) && T->getAs<TypedefType>()) { Os << typeName() << " "; return PrintDeclKind::Pointer; } - return RawPtrRefMemberChecker::printPointer(Os, T); + return RawPtrRefMemberChecker::printPointer(Os, T, IsPtrToSafe); } }; diff --git a/clang/test/Analysis/Checkers/WebKit/unchecked-members.cpp b/clang/test/Analysis/Checkers/WebKit/unchecked-members.cpp index 3fe15d88ff312..57ca3f98a14da 100644 --- a/clang/test/Analysis/Checkers/WebKit/unchecked-members.cpp +++ b/clang/test/Analysis/Checkers/WebKit/unchecked-members.cpp @@ -9,7 +9,7 @@ namespace members { CheckedObj* a = nullptr; // expected-warning@-1{{Member variable 'a' in 'members::Foo' is a raw pointer to CheckedPtr capable type 'CheckedObj'}} CheckedObj& b; -// expected-warning@-1{{Member variable 'b' in 'members::Foo' is a reference to CheckedPtr capable type 'CheckedObj'}} +// expected-warning@-1{{Member variable 'b' in 'members::Foo' is a raw reference to CheckedPtr capable type 'CheckedObj'}} [[clang::suppress]] CheckedObj* a_suppressed = nullptr; @@ -19,6 +19,14 @@ namespace members { CheckedPtr<CheckedObj> c; CheckedRef<CheckedObj> d; + CheckedRef<RefCountable>* e; +// expected-warning@-1{{Member variable 'e' in 'members::Foo' is a raw pointer to 'CheckedRef<RefCountable>'}} + CheckedRef<RefCountable>& f; +// expected-warning@-1{{Member variable 'f' in 'members::Foo' is a raw reference to 'CheckedRef<RefCountable>'}} + CheckedRef<RefCountable>** g; +// expected-warning@-1{{Member variable 'g' in 'members::Foo' contains a raw pointer to 'CheckedRef<RefCountable>'}} + CheckedRef<RefCountable>* h; +// expected-warning@-1{{Member variable 'h' in 'members::Foo' is a raw pointer to 'CheckedRef<RefCountable>'}} public: Foo(); @@ -39,8 +47,12 @@ namespace unions { union Foo { CheckedObj* a; // expected-warning@-1{{Member variable 'a' in 'unions::Foo' is a raw pointer to CheckedPtr capable type 'CheckedObj'}} - CheckedPtr<CheckedObj> c; - CheckedRef<CheckedObj> d; + CheckedPtr<CheckedObj> b; + CheckedRef<CheckedObj> c; + CheckedObj** d; + // expected-warning@-1{{Member variable 'd' in 'unions::Foo' contains a raw pointer to CheckedPtr capable type 'CheckedObj'}} + CheckedPtr<CheckedObj>* e; + // expected-warning@-1{{Member variable 'e' in 'unions::Foo' is a raw pointer to 'CheckedPtr<CheckedObj>'}} }; template<class T> @@ -76,8 +88,16 @@ namespace ptr_to_ptr_to_checked_ptr_capable { }; TemplateList<CheckedObj> list; - struct SafeList { + struct FormerlySafeList { CheckedPtr<CheckedObj>* elements; + // expected-warning@-1{{Member variable 'elements' in 'ptr_to_ptr_to_checked_ptr_capable::FormerlySafeList' is a raw pointer to 'CheckedPtr<CheckedObj>'}} + }; + + struct Container { + CheckedPtr<CheckedObj>* [[clang::annotate_type("webkit.unsafeptr")]] elements1; + CheckedPtr<CheckedObj>** [[clang::annotate_type("webkit.unsafeptr")]] elements2; + // expected-warning@-1{{Member variable 'elements2' in 'ptr_to_ptr_to_checked_ptr_capable::Container' contains a raw pointer to 'CheckedPtr<CheckedObj>'}} + CheckedRef<CheckedObj>* [[clang::annotate_type("webkit.unsafeptr")]]* [[clang::annotate_type("webkit.unsafeptr")]] elements3; }; } // namespace ptr_to_ptr_to_checked_ptr_capable diff --git a/clang/test/Analysis/Checkers/WebKit/uncounted-members-objc.mm b/clang/test/Analysis/Checkers/WebKit/uncounted-members-objc.mm index 83b08a6841d26..dcbc6e74eee9b 100644 --- a/clang/test/Analysis/Checkers/WebKit/uncounted-members-objc.mm +++ b/clang/test/Analysis/Checkers/WebKit/uncounted-members-objc.mm @@ -17,7 +17,25 @@ @interface SomeObjC : NSObject { // expected-warning@-1{{Instance variable '_uncounted1' in 'SomeObjC' is a raw pointer to ref-countable type 'RefCountable'}} RefPtr<RefCountable> _counted1; [[clang::suppress]] RefCountable* _uncounted2; + RefCountable** _ptr_to_ptr_to_uncounted; +// expected-warning@-1{{Instance variable '_ptr_to_ptr_to_uncounted' in 'SomeObjC' contains a raw pointer to ref-countable type 'RefCountable'}} + RefPtr<RefCountable>* ptr_to_refptr1; +// expected-warning@-1{{Instance variable 'ptr_to_refptr1' in 'SomeObjC' is a raw pointer to 'RefPtr<RefCountable>'}} + RefPtr<RefCountable>* [[clang::annotate_type("webkit.unsafeptr")]] _ptr_to_refptr2; + RefPtr<RefCountable>** _ptr_to_refptr3; +// expected-warning@-1{{Instance variable '_ptr_to_refptr3' in 'SomeObjC' contains a raw pointer to 'RefPtr<RefCountable>'}} + Ref<RefCountable>* [[clang::annotate_type("webkit.unsafeptr")]] _ptr_to_ref1; + Ref<RefCountable>** _ptr_to_ref2; +// expected-warning@-1{{Instance variable '_ptr_to_ref2' in 'SomeObjC' contains a raw pointer to 'Ref<RefCountable>'}} } +@property (nonatomic) RefCountable **obj1; +// expected-warning@-1{{Property 'obj1' in 'SomeObjC' contains a raw pointer to ref-countable type 'RefCountable'}} +@property (nonatomic) RefPtr<RefCountable> *obj2; +// expected-warning@-1{{Property 'obj2' in 'SomeObjC' is a raw pointer to 'RefPtr<RefCountable>'}} +@property (nonatomic) RefPtr<RefCountable> **obj3; +// expected-warning@-1{{Property 'obj3' in 'SomeObjC' contains a raw pointer to 'RefPtr<RefCountable>'}} +@property(nonatomic, readonly) Ref<RefCountable> *syn_prop; +// expected-warning@-1{{Property 'syn_prop' in 'SomeObjC' is a raw pointer to 'Ref<RefCountable>'}} - (void)doWork; @end @@ -26,8 +44,14 @@ @implementation SomeObjC { // expected-warning@-1{{Instance variable '_uncounted3' in 'SomeObjC' is a raw pointer to ref-countable type 'RefCountable'}} RefPtr<RefCountable> _counted2; [[clang::suppress]] RefCountable* _uncounted4; + RefCountable** _uncounted5; +// expected-warning@-1{{Instance variable '_uncounted5' in 'SomeObjC' contains a raw pointer to ref-countable type 'RefCountable'}} + RefPtr<RefCountable>* _ptr_to_refptr1; +// expected-warning@-1{{Instance variable '_ptr_to_refptr1' in 'SomeObjC' is a raw pointer to 'RefPtr<RefCountable>'}} } +@synthesize syn_prop; + - (void)doWork { doSomeWork(); } diff --git a/clang/test/Analysis/Checkers/WebKit/uncounted-members.cpp b/clang/test/Analysis/Checkers/WebKit/uncounted-members.cpp index b8c443cda4f8e..37464d0ddb0ac 100644 --- a/clang/test/Analysis/Checkers/WebKit/uncounted-members.cpp +++ b/clang/test/Analysis/Checkers/WebKit/uncounted-members.cpp @@ -16,10 +16,20 @@ namespace members { RefPtr<RefCountable> b; public: + Foo(); + RefCountable silenceWarningAboutInit; RefCountable& c = silenceWarningAboutInit; -// expected-warning@-1{{Member variable 'c' in 'members::Foo' is a reference to ref-countable type 'RefCountable'}} +// expected-warning@-1{{Member variable 'c' in 'members::Foo' is a raw reference to ref-countable type 'RefCountable'}} Ref<RefCountable> d; + Ref<RefCountable>* e; +// expected-warning@-1{{Member variable 'e' in 'members::Foo' is a raw pointer to 'Ref<RefCountable>'}} + Ref<RefCountable>& f; +// expected-warning@-1{{Member variable 'f' in 'members::Foo' is a raw reference to 'Ref<RefCountable>'}} + Ref<RefCountable>** g; +// expected-warning@-1{{Member variable 'g' in 'members::Foo' contains a raw pointer to 'Ref<RefCountable>'}} + RefPtr<RefCountable>* h; +// expected-warning@-1{{Member variable 'h' in 'members::Foo' is a raw pointer to 'RefPtr<RefCountable>'}} }; template<class T> @@ -34,6 +44,7 @@ namespace members { private: RefCountable* a = nullptr; }; + } // members namespace unions { @@ -42,6 +53,10 @@ namespace unions { // expected-warning@-1{{Member variable 'a' in 'unions::Foo' is a raw pointer to ref-countable type 'RefCountable'}} RefPtr<RefCountable> b; Ref<RefCountable> c; + RefCountable** d; + // expected-warning@-1{{Member variable 'd' in 'unions::Foo' contains a raw pointer to ref-countable type 'RefCountable'}} + Ref<RefCountable>* e; + // expected-warning@-1{{Member variable 'e' in 'unions::Foo' is a raw pointer to 'Ref<RefCountable>'}} }; template<class T> @@ -94,8 +109,16 @@ namespace ptr_to_ptr_to_ref_counted { }; TemplateList<RefCountable> list; - struct SafeList { + struct FormerlySafeList { RefPtr<RefCountable>* elements; + // expected-warning@-1{{Member variable 'elements' in 'ptr_to_ptr_to_ref_counted::FormerlySafeList' is a raw pointer to 'RefPtr<RefCountable>'}} + }; + + struct Container { + RefPtr<CheckedObj>* [[clang::annotate_type("webkit.unsafeptr")]] elements1; + RefPtr<CheckedObj>** [[clang::annotate_type("webkit.unsafeptr")]] elements2; + // expected-warning@-1{{Member variable 'elements2' in 'ptr_to_ptr_to_ref_counted::Container' contains a raw pointer to 'RefPtr<CheckedObj>'}} + Ref<CheckedObj>* [[clang::annotate_type("webkit.unsafeptr")]]* [[clang::annotate_type("webkit.unsafeptr")]] elements3; }; } // namespace ptr_to_ptr_to_ref_counted diff --git a/clang/test/Analysis/Checkers/WebKit/unretained-members-arc.mm b/clang/test/Analysis/Checkers/WebKit/unretained-members-arc.mm index 4eef372d26480..aa5793fac9b3c 100644 --- a/clang/test/Analysis/Checkers/WebKit/unretained-members-arc.mm +++ b/clang/test/Analysis/Checkers/WebKit/unretained-members-arc.mm @@ -22,6 +22,14 @@ // expected-warning@-1{{Member variable 'e' in 'members::Foo' is a retainable type 'CFMutableArrayRef'}} dispatch_queue_t f = nullptr; + + RetainPtr<SomeObj>* g = nullptr; +// expected-warning@-1{{Member variable 'g' in 'members::Foo' is a raw pointer to 'WTF::RetainPtrArc<SomeObj>'}} + RetainPtr<SomeObj>** h = nullptr; +// expected-warning@-1{{Member variable 'h' in 'members::Foo' contains a raw pointer to 'WTF::RetainPtrArc<SomeObj>'}} + RetainPtr<SomeObj>* [[clang::annotate_type("webkit.unsafeptr")]] i = nullptr; + RetainPtr<SomeObj>** [[clang::annotate_type("webkit.unsafeptr")]] j = nullptr; +// expected-warning@-1{{Member variable 'j' in 'members::Foo' contains a raw pointer to 'WTF::RetainPtrArc<SomeObj>'}} }; union FooUnion { @@ -37,6 +45,12 @@ S y; // expected-warning@-1{{Member variable 'y' in 'members::FooTmpl<SomeObj, __CFArray *, NSObject<OS_dispatch_queue> *>' is a raw pointer to retainable type}} R z; + RetainPtr<T>* t; +// expected-warning@-1{{Member variable 't' in 'members::FooTmpl<SomeObj, __CFArray *, NSObject<OS_dispatch_queue> *>' is a raw pointer to 'WTF::RetainPtrArc<SomeObj>'}} + S* u; +// expected-warning@-1{{Member variable 'u' in 'members::FooTmpl<SomeObj, __CFArray *, NSObject<OS_dispatch_queue> *>' contains a raw pointer to retainable type}} + RetainPtr<S>* v; +// expected-warning@-1{{Member variable 'v' in 'members::FooTmpl<SomeObj, __CFArray *, NSObject<OS_dispatch_queue> *>' is a raw pointer to 'WTF::RetainPtrArc<__CFArray *>'}} }; void forceTmplToInstantiate(FooTmpl<SomeObj, CFMutableArrayRef, dispatch_queue_t>) {} @@ -64,12 +78,21 @@ void forceTmplToInstantiate(FooTmpl<SomeObj, CFMutableArrayRef, dispatch_queue_t }; TemplateList<SomeObj, CFMutableArrayRef, dispatch_queue_t> list; - struct SafeList { + struct FormerlySafeList { RetainPtr<SomeObj>* elements1; + // expected-warning@-1{{Member variable 'elements1' in 'ptr_to_ptr_to_retained::FormerlySafeList' is a raw pointer to 'WTF::RetainPtrArc<SomeObj>'}} RetainPtr<CFMutableArrayRef>* elements2; + // expected-warning@-1{{Member variable 'elements2' in 'ptr_to_ptr_to_retained::FormerlySafeList' is a raw pointer to 'WTF::RetainPtrArc<__CFArray *>'}} OSObjectPtr<dispatch_queue_t> elements3; }; + struct Container { + RetainPtr<SomeObj>* [[clang::annotate_type("webkit.unsafeptr")]] elements1; + RetainPtr<CFMutableArrayRef>** [[clang::annotate_type("webkit.unsafeptr")]] elements2; + // expected-warning@-1{{Member variable 'elements2' in 'ptr_to_ptr_to_retained::Container' contains a raw pointer to 'WTF::RetainPtrArc<__CFArray *>'}} + RetainPtr<SomeObj>* [[clang::annotate_type("webkit.unsafeptr")]]* [[clang::annotate_type("webkit.unsafeptr")]] elements3; + }; + } // namespace ptr_to_ptr_to_retained @interface AnotherObject : NSObject { @@ -77,6 +100,10 @@ @interface AnotherObject : NSObject { CFStringRef cf_string; // expected-warning@-1{{Instance variable 'cf_string' in 'AnotherObject' is a retainable type 'CFStringRef'; member variables must be a RetainPtr}} dispatch_queue_t queue; + RetainPtr<SomeObj>* retainptr_ptr; + // expected-warning@-1{{Instance variable 'retainptr_ptr' in 'AnotherObject' is a raw pointer to 'WTF::RetainPtrArc<SomeObj>'}} + RetainPtr<SomeObj>** retainptr_ptr_ptr; + // expected-warning@-1{{Instance variable 'retainptr_ptr_ptr' in 'AnotherObject' contains a raw pointer to 'WTF::RetainPtrArc<SomeObj>'}} } @property(nonatomic, strong) NSString *prop_string1; @property(nonatomic, assign) NSString *prop_string2; @@ -85,6 +112,8 @@ @interface AnotherObject : NSObject { // expected-warning@-1{{Property 'prop_string3' in 'AnotherObject' is a raw pointer to retainable type 'NSString'; member variables must be a RetainPtr}} @property(nonatomic, readonly) NSString *prop_string4; @property(nonatomic, readonly) NSString *prop_safe; +@property(nonatomic, readonly) RetainPtr<NSString> *prop_retainptr_ptr; +// expected-warning@-1{{Property 'prop_retainptr_ptr' in 'AnotherObject' is a raw pointer to 'WTF::RetainPtrArc<NSString>'}} @end @implementation AnotherObject @@ -107,6 +136,10 @@ @interface NoSynthObject : NSObject { NSString *ns_string; CFStringRef cf_string; // expected-warning@-1{{Instance variable 'cf_string' in 'NoSynthObject' is a retainable type 'CFStringRef'; member variables must be a RetainPtr}} + RetainPtr<NSString> *ns_string_retainptr_ptr; + // expected-warning@-1{{Instance variable 'ns_string_retainptr_ptr' in 'NoSynthObject' is a raw pointer to 'WTF::RetainPtrArc<NSString>'}} + RetainPtr<NSString> **ns_string_retainptr_ptr_ptr; + // expected-warning@-1{{Instance variable 'ns_string_retainptr_ptr_ptr' in 'NoSynthObject' contains a raw pointer to 'WTF::RetainPtrArc<NSString>'}} } @property(nonatomic, readonly, strong) NSString *prop_string1; @property(nonatomic, readonly, strong) NSString *prop_string2; @@ -116,6 +149,9 @@ @interface NoSynthObject : NSObject { // expected-warning@-1{{Property 'prop_string4' in 'NoSynthObject' is a raw pointer to retainable type 'NSString'; member variables must be a RetainPtr}} @property(nonatomic, unsafe_unretained) dispatch_queue_t prop_string5; // expected-warning@-1{{Property 'prop_string5' in 'NoSynthObject' is a retainable type 'dispatch_queue_t'}} +@property(nonatomic, readonly, strong) dispatch_queue_t dispatch; +@property(nonatomic, readonly) RetainPtr<SomeObj> *prop_retainptr_ptr; +// expected-warning@-1{{Property 'prop_retainptr_ptr' in 'NoSynthObject' is a raw pointer to 'WTF::RetainPtrArc<SomeObj>'}} @end @implementation NoSynthObject @@ -126,4 +162,8 @@ - (NSString *)prop_string1 { @synthesize prop_string3; @synthesize prop_string4; @synthesize prop_string5; +- (dispatch_queue_t)dispatch { + return nil; +} +@synthesize prop_retainptr_ptr; @end diff --git a/clang/test/Analysis/Checkers/WebKit/unretained-members.mm b/clang/test/Analysis/Checkers/WebKit/unretained-members.mm index 2b120b9b1385c..aaf08022d5f9f 100644 --- a/clang/test/Analysis/Checkers/WebKit/unretained-members.mm +++ b/clang/test/Analysis/Checkers/WebKit/unretained-members.mm @@ -33,6 +33,16 @@ CFMutableArrayRef e = nullptr; // expected-warning@-1{{Member variable 'e' in 'members::Foo' is a retainable type 'CFMutableArrayRef'}} + SomeObj** f = nullptr; +// expected-warning@-1{{Member variable 'f' in 'members::Foo' contains a raw pointer to retainable type}} + + RetainPtr<SomeObj>* g = nullptr; +// expected-warning@-1{{Member variable 'g' in 'members::Foo' is a raw pointer to 'WTF::RetainPtr<SomeObj>'}} + RetainPtr<SomeObj>** h = nullptr; +// expected-warning@-1{{Member variable 'h' in 'members::Foo' contains a raw pointer to 'WTF::RetainPtr<SomeObj>'}} + RetainPtr<SomeObj>* [[clang::annotate_type("webkit.unsafeptr")]] i = nullptr; + RetainPtr<SomeObj>** [[clang::annotate_type("webkit.unsafeptr")]] j = nullptr; +// expected-warning@-1{{Member variable 'j' in 'members::Foo' contains a raw pointer to 'WTF::RetainPtr<SomeObj>'}} }; template<class T, class S, class R> @@ -43,6 +53,14 @@ // expected-warning@-1{{Member variable 'b' in 'members::FooTmpl<SomeObj, __CFArray *, NSObject<OS_dispatch_queue> *>' is a raw pointer to retainable type}} R c; // expected-warning@-1{{Member variable 'c' in 'members::FooTmpl<SomeObj, __CFArray *, NSObject<OS_dispatch_queue> *>' is a raw pointer to retainable type}} + T** d; +// expected-warning@-1{{Member variable 'd' in 'members::FooTmpl<SomeObj, __CFArray *, NSObject<OS_dispatch_queue> *>' contains a raw pointer to retainable type}} + RetainPtr<T>* e; +// expected-warning@-1{{Member variable 'e' in 'members::FooTmpl<SomeObj, __CFArray *, NSObject<OS_dispatch_queue> *>' is a raw pointer to 'WTF::RetainPtr<SomeObj>'}} + S* f; +// expected-warning@-1{{Member variable 'f' in 'members::FooTmpl<SomeObj, __CFArray *, NSObject<OS_dispatch_queue> *>' contains a raw pointer to retainable type}} + RetainPtr<S>* g; +// expected-warning@-1{{Member variable 'g' in 'members::FooTmpl<SomeObj, __CFArray *, NSObject<OS_dispatch_queue> *>' is a raw pointer to 'WTF::RetainPtr<__CFArray *>'}} }; void forceTmplToInstantiate(FooTmpl<SomeObj, CFMutableArrayRef, dispatch_queue_t>) {} @@ -64,6 +82,10 @@ void forceTmplToInstantiate(FooTmpl<SomeObj, CFMutableArrayRef, dispatch_queue_t // expected-warning@-1{{Member variable 'c' in 'unions::Foo' is a retainable type 'CFMutableArrayRef'}} dispatch_queue_t d; // expected-warning@-1{{Member variable 'd' in 'unions::Foo' is a retainable type 'dispatch_queue_t'}} + SomeObj** e; + // expected-warning@-1{{Member variable 'e' in 'unions::Foo' contains a raw pointer to retainable type 'SomeObj'}} + RetainPtr<SomeObj>* f; + // expected-warning@-1{{Member variable 'f' in 'unions::Foo' is a raw pointer to 'WTF::RetainPtr<SomeObj>'}} }; template<class T> @@ -97,9 +119,18 @@ void forceTmplToInstantiate(FooTempl<SomeObj>) {} }; TemplateList<SomeObj, CFMutableArrayRef, dispatch_queue_t> list; - struct SafeList { + struct FormerlySafeList { RetainPtr<SomeObj>* elements1; + // expected-warning@-1{{Member variable 'elements1' in 'ptr_to_ptr_to_retained::FormerlySafeList' is a raw pointer to 'WTF::RetainPtr<SomeObj>'}} RetainPtr<CFMutableArrayRef>* elements2; + // expected-warning@-1{{Member variable 'elements2' in 'ptr_to_ptr_to_retained::FormerlySafeList' is a raw pointer to 'WTF::RetainPtr<__CFArray *>'}} + }; + + struct Container { + RetainPtr<SomeObj>* [[clang::annotate_type("webkit.unsafeptr")]] elements1; + RetainPtr<CFMutableArrayRef>** [[clang::annotate_type("webkit.unsafeptr")]] elements2; + // expected-warning@-1{{Member variable 'elements2' in 'ptr_to_ptr_to_retained::Container' contains a raw pointer to 'WTF::RetainPtr<__CFArray *>'}} + RetainPtr<SomeObj>* [[clang::annotate_type("webkit.unsafeptr")]]* [[clang::annotate_type("webkit.unsafeptr")]] elements3; }; } // namespace ptr_to_ptr_to_retained @@ -111,9 +142,19 @@ @interface AnotherObject : NSObject { // expected-warning@-1{{Instance variable 'cf_string' in 'AnotherObject' is a retainable type 'CFStringRef'}} dispatch_queue_t dispatch; // expected-warning@-1{{Instance variable 'dispatch' in 'AnotherObject' is a retainable type 'dispatch_queue_t'}} + NSString **ns_string_ptr; + // expected-warning@-1{{Instance variable 'ns_string_ptr' in 'AnotherObject' contains a raw pointer to retainable type 'NSString'}} + RetainPtr<SomeObj>* retainptr_ptr; + // expected-warning@-1{{Instance variable 'retainptr_ptr' in 'AnotherObject' is a raw pointer to 'WTF::RetainPtr<SomeObj>'}} + RetainPtr<SomeObj>** retainptr_ptr_ptr; + // expected-warning@-1{{Instance variable 'retainptr_ptr_ptr' in 'AnotherObject' contains a raw pointer to 'WTF::RetainPtr<SomeObj>'}} } @property(nonatomic, readonly, strong) NSString *prop_string; @property(nonatomic, readonly) NSString *prop_safe; +@property(nonatomic, readonly) NSString **prop_unsafe; +// expected-warning@-1{{Property 'prop_unsafe' in 'AnotherObject' contains a raw pointer to retainable type 'NSString'}} +@property(nonatomic, readonly) RetainPtr<NSString> *prop_retainptr_ptr; +// expected-warning@-1{{Property 'prop_retainptr_ptr' in 'AnotherObject' is a raw pointer to 'WTF::RetainPtr<NSString>'}} @end @implementation AnotherObject @@ -129,6 +170,12 @@ @interface DerivedObject : AnotherObject { // expected-warning@-1{{Instance variable 'cg_image' in 'DerivedObject' is a retainable type 'CGImageRef'}} dispatch_queue_t os_dispatch; // expected-warning@-1{{Instance variable 'os_dispatch' in 'DerivedObject' is a retainable type 'dispatch_queue_t'}} + NSNumber **ns_number_ptr; + // expected-warning@-1{{Instance variable 'ns_number_ptr' in 'DerivedObject' contains a raw pointer to retainable type 'NSNumber'}} + RetainPtr<CGImageRef>* cg_image_retainptr_ptr; + // expected-warning@-1{{Instance variable 'cg_image_retainptr_ptr' in 'DerivedObject' is a raw pointer to 'WTF::RetainPtr<CGImage *>'}} + RetainPtr<CGImageRef>** cg_image_retainptr_ptr_ptr; + // expected-warning@-1{{Instance variable 'cg_image_retainptr_ptr_ptr' in 'DerivedObject' contains a raw pointer to 'WTF::RetainPtr<CGImage *>'}} } @property(nonatomic, strong) NSNumber *prop_number; @property(nonatomic, readonly) NSString *prop_string; @@ -173,6 +220,12 @@ @interface NoSynthObject : NSObject { // expected-warning@-1{{Instance variable 'cf_string' in 'NoSynthObject' is a retainable type 'CFStringRef'}} dispatch_queue_t dispatch; // expected-warning@-1{{Instance variable 'dispatch' in 'NoSynthObject' is a retainable type 'dispatch_queue_t'}} + NSString **ns_string_ptr; + // expected-warning@-1{{Instance variable 'ns_string_ptr' in 'NoSynthObject' contains a raw pointer to retainable type 'NSString'}} + RetainPtr<NSString> *ns_string_retainptr_ptr; + // expected-warning@-1{{Instance variable 'ns_string_retainptr_ptr' in 'NoSynthObject' is a raw pointer to 'WTF::RetainPtr<NSString>'}} + RetainPtr<NSString> **ns_string_retainptr_ptr_ptr; + // expected-warning@-1{{Instance variable 'ns_string_retainptr_ptr_ptr' in 'NoSynthObject' contains a raw pointer to 'WTF::RetainPtr<NSString>'}} } @property(nonatomic, readonly, strong) NSString *prop_string1; @property(nonatomic, readonly, strong) NSString *prop_string2; @@ -182,6 +235,9 @@ @interface NoSynthObject : NSObject { // expected-warning@-1{{Property 'prop_string4' in 'NoSynthObject' is a raw pointer to retainable type 'NSString'; member variables must be a RetainPtr}} @property(nonatomic, copy) NSString *prop_string5; @property(nonatomic, readonly, strong) dispatch_queue_t dispatch; +@property(nonatomic, readonly) NSString **ns_string_ptr; +@property(nonatomic, readonly) RetainPtr<SomeObj> *prop_retainptr_ptr; +// expected-warning@-1{{Property 'prop_retainptr_ptr' in 'NoSynthObject' is a raw pointer to 'WTF::RetainPtr<SomeObj>'}} @end @implementation NoSynthObject @@ -195,4 +251,9 @@ - (NSString *)prop_string1 { - (dispatch_queue_t)dispatch { return nil; } +- (NSString **)ns_string_ptr { + return nullptr; +} +@synthesize prop_retainptr_ptr; + @end >From 50a9fb7d2ddbef72979f069906242ee2f4daf13a Mon Sep 17 00:00:00 2001 From: Ryosuke Niwa <[email protected]> Date: Sat, 30 May 2026 12:36:36 -0700 Subject: [PATCH 2/2] Fix formatting. --- .../WebKit/RawPtrRefMemberChecker.cpp | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefMemberChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefMemberChecker.cpp index 1f0bbb09da61b..bef7310aefa2f 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefMemberChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefMemberChecker.cpp @@ -290,8 +290,7 @@ class RawPtrRefMemberChecker template <typename DeclType, typename PointeeType, typename ParentDeclType> void reportBug(const DeclType *Member, const Type *MemberType, - const PointeeType *Pointee, - const ParentDeclType *ClassCXXRD, + const PointeeType *Pointee, const ParentDeclType *ClassCXXRD, bool IsPtrToSafe = false) const { assert(Member); assert(MemberType); @@ -359,9 +358,7 @@ class NoUncountedMemberChecker final : public RawPtrRefMemberChecker { return isUncountedPtr(QT.getCanonicalType()); } - bool isSafePtr(QualType QT) const final { - return isRefPtrType(QT); - } + bool isSafePtr(QualType QT) const final { return isRefPtrType(QT); } const char *typeName() const final { return "ref-countable type"; } @@ -380,9 +377,7 @@ class NoUncheckedPtrMemberChecker final : public RawPtrRefMemberChecker { return isUncheckedPtr(QT.getCanonicalType()); } - bool isSafePtr(QualType QT) const final { - return isCheckedPtrType(QT); - } + bool isSafePtr(QualType QT) const final { return isCheckedPtrType(QT); } const char *typeName() const final { return "CheckedPtr capable type"; } @@ -406,9 +401,7 @@ class NoUnretainedMemberChecker final : public RawPtrRefMemberChecker { return RTC->isUnretained(QT, ignoreARC); } - bool isSafePtr(QualType QT) const final { - return isRetainPtrOrOSPtrType(QT); - } + bool isSafePtr(QualType QT) const final { return isRetainPtrOrOSPtrType(QT); } const char *typeName() const final { return "retainable type"; } @@ -416,8 +409,8 @@ class NoUnretainedMemberChecker final : public RawPtrRefMemberChecker { return "member variables must be a RetainPtr or OSObjectPtr"; } - PrintDeclKind printPointer(llvm::raw_svector_ostream &Os, - const Type *T, bool IsPtrToSafe) const final { + PrintDeclKind printPointer(llvm::raw_svector_ostream &Os, const Type *T, + bool IsPtrToSafe) const final { // FIXME: Support IsPtrToSafe. if (!isa<ObjCObjectPointerType>(T) && T->getAs<TypedefType>()) { Os << typeName() << " "; _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
