llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT--> @llvm/pr-subscribers-clang Author: Ryosuke Niwa (rniwa) <details> <summary>Changes</summary> Add the support for OSObjectPtr, which behaves like RetainPtr. --- Patch is 51.33 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/159484.diff 15 Files Affected: - (modified) clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp (+4-2) - (modified) clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefMemberChecker.cpp (+6-5) - (modified) clang/lib/StaticAnalyzer/Checkers/WebKit/RetainPtrCtorAdoptChecker.cpp (+2-1) - (modified) clang/test/Analysis/Checkers/WebKit/call-args-checked.cpp (-14) - (modified) clang/test/Analysis/Checkers/WebKit/mock-types.h (+10) - (modified) clang/test/Analysis/Checkers/WebKit/objc-mock-types.h (+156) - (modified) clang/test/Analysis/Checkers/WebKit/uncounted-obj-arg.cpp (-3) - (modified) clang/test/Analysis/Checkers/WebKit/unretained-call-args-arc.mm (+12-3) - (modified) clang/test/Analysis/Checkers/WebKit/unretained-call-args.mm (+81-14) - (modified) clang/test/Analysis/Checkers/WebKit/unretained-lambda-captures-arc.mm (+46-3) - (modified) clang/test/Analysis/Checkers/WebKit/unretained-lambda-captures.mm (+64-3) - (modified) clang/test/Analysis/Checkers/WebKit/unretained-local-vars-arc.mm (+16-3) - (modified) clang/test/Analysis/Checkers/WebKit/unretained-local-vars.mm (+120-8) - (modified) clang/test/Analysis/Checkers/WebKit/unretained-members-arc.mm (+18-6) - (modified) clang/test/Analysis/Checkers/WebKit/unretained-members.mm (+35-13) ``````````diff diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp index 90b2343b4be77..44a0cc52662d8 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp @@ -130,7 +130,8 @@ bool isRefType(const std::string &Name) { } bool isRetainPtr(const std::string &Name) { - return Name == "RetainPtr" || Name == "RetainPtrArc"; + return Name == "RetainPtr" || Name == "RetainPtrArc" || + Name == "OSObjectPtr" || Name == "OSObjectPtrArc"; } bool isCheckedPtr(const std::string &Name) { @@ -170,7 +171,8 @@ bool isCtorOfRetainPtr(const clang::FunctionDecl *F) { const std::string &FunctionName = safeGetName(F); return FunctionName == "RetainPtr" || FunctionName == "adoptNS" || FunctionName == "adoptCF" || FunctionName == "retainPtr" || - FunctionName == "RetainPtrArc" || FunctionName == "adoptNSArc"; + FunctionName == "RetainPtrArc" || FunctionName == "adoptNSArc" || + FunctionName == "adoptOSObject" || FunctionName == "adoptOSObjectArc"; } bool isCtorOfSafePtr(const clang::FunctionDecl *F) { diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefMemberChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefMemberChecker.cpp index 8faf6a219450a..a776aa575fc94 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefMemberChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefMemberChecker.cpp @@ -119,10 +119,11 @@ class RawPtrRefMemberChecker auto *Desugared = PointeeType->getUnqualifiedDesugaredType(); if (!Desugared) return nullptr; - auto *ObjCType = dyn_cast<ObjCInterfaceType>(Desugared); - if (!ObjCType) - return nullptr; - return ObjCType->getDecl(); + if (auto *ObjCType = dyn_cast<ObjCInterfaceType>(Desugared)) + return ObjCType->getDecl(); + if (auto* ObjCType = dyn_cast<ObjCObjectType>(Desugared)) + return ObjCType->getInterface(); + return nullptr; } void visitObjCDecl(const ObjCContainerDecl *CD) const { @@ -369,7 +370,7 @@ class NoUnretainedMemberChecker final : public RawPtrRefMemberChecker { const char *typeName() const final { return "retainable type"; } const char *invariant() const final { - return "member variables must be a RetainPtr"; + return "member variables must be a RetainPtr or OSObjectPtr"; } PrintDeclKind printPointer(llvm::raw_svector_ostream &Os, diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/RetainPtrCtorAdoptChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/RetainPtrCtorAdoptChecker.cpp index 5c1b2d7cce45d..572cbc6bb40a9 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/RetainPtrCtorAdoptChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/RetainPtrCtorAdoptChecker.cpp @@ -121,7 +121,8 @@ class RetainPtrCtorAdoptChecker } bool isAdoptFnName(const std::string &Name) const { - return isAdoptNS(Name) || Name == "adoptCF" || Name == "adoptCFArc"; + return isAdoptNS(Name) || Name == "adoptCF" || Name == "adoptCFArc" || + Name == "adoptOSObject" || Name == "adoptOSObjectArc"; } bool isAdoptNS(const std::string &Name) const { diff --git a/clang/test/Analysis/Checkers/WebKit/call-args-checked.cpp b/clang/test/Analysis/Checkers/WebKit/call-args-checked.cpp index b2fb042e7deff..e9f01c8ed7540 100644 --- a/clang/test/Analysis/Checkers/WebKit/call-args-checked.cpp +++ b/clang/test/Analysis/Checkers/WebKit/call-args-checked.cpp @@ -2,20 +2,6 @@ #include "mock-types.h" -namespace std { - -template <typename T> struct remove_reference { - typedef T type; -}; - -template <typename T> struct remove_reference<T&> { - typedef T type; -}; - -template<typename T> typename remove_reference<T>::type&& move(T&& t); - -} // namespace std - RefCountableAndCheckable* makeObj(); CheckedRef<RefCountableAndCheckable> makeObjChecked(); void someFunction(RefCountableAndCheckable*); diff --git a/clang/test/Analysis/Checkers/WebKit/mock-types.h b/clang/test/Analysis/Checkers/WebKit/mock-types.h index a03d31870ee0d..08b8ad0675946 100644 --- a/clang/test/Analysis/Checkers/WebKit/mock-types.h +++ b/clang/test/Analysis/Checkers/WebKit/mock-types.h @@ -3,6 +3,16 @@ namespace std { +template <typename T> struct remove_reference { +typedef T type; +}; + +template <typename T> struct remove_reference<T&> { +typedef T type; +}; + +template<typename T> typename remove_reference<T>::type&& move(T&& t); + template <typename T> class unique_ptr { private: diff --git a/clang/test/Analysis/Checkers/WebKit/objc-mock-types.h b/clang/test/Analysis/Checkers/WebKit/objc-mock-types.h index 09b303961fd6a..5bdd14c6afd1f 100644 --- a/clang/test/Analysis/Checkers/WebKit/objc-mock-types.h +++ b/clang/test/Analysis/Checkers/WebKit/objc-mock-types.h @@ -157,6 +157,49 @@ __attribute__((objc_root_class)) - (void)doMoreWork:(OtherObj *)other; @end +@protocol OS_dispatch_queue +@end + +typedef NSObject<OS_dispatch_queue> *dispatch_queue_t; + +@protocol OS_dispatch_queue_attr +@end + +typedef NSObject<OS_dispatch_queue_attr> *dispatch_queue_attr_t; + +NS_RETURNS_RETAINED dispatch_queue_t dispatch_queue_create(const char *label, dispatch_queue_attr_t attr); +const char *dispatch_queue_get_label(dispatch_queue_t queue); + +namespace std { + +template <typename T> struct remove_reference { +typedef T type; +}; + +template <typename T> struct remove_reference<T&> { +typedef T type; +}; + +template<typename T> typename remove_reference<T>::type&& move(T&& t); + +template <typename StorageType> +void swap(StorageType& a, StorageType& b) +{ + StorageType temp = static_cast<StorageType&&>(a); + a = static_cast<StorageType&&>(b); + b = static_cast<StorageType&&>(temp); +} + +template <typename StorageType, typename ValueType> +StorageType exchange(StorageType& obj, ValueType& value) +{ + StorageType returnValue = static_cast<StorageType&&>(obj); + obj = static_cast<StorageType&&>(value); + return returnValue; +} + +} + namespace WTF { void WTFCrash(void); @@ -293,6 +336,117 @@ template<typename T> inline RetainPtr<T> retainPtr(T* ptr) return ptr; } +template<typename> class OSObjectPtr; +template<typename T> OSObjectPtr<T> adoptOSObject(T); + +template<typename T> static inline void retainOSObject(T ptr) +{ +#if !__has_feature(objc_arc) + [ptr retain]; +#endif +} + +template<typename T> static inline void releaseOSObject(T ptr) +{ +#if !__has_feature(objc_arc) + [ptr release]; +#endif +} + +template<typename T> class OSObjectPtr { +public: + OSObjectPtr() + : m_ptr(nullptr) + { + } + + ~OSObjectPtr() + { + if (m_ptr) + releaseOSObject(m_ptr); + } + + T get() const { return m_ptr; } + + explicit operator bool() const { return m_ptr; } + bool operator!() const { return !m_ptr; } + + OSObjectPtr(const OSObjectPtr& other) + : m_ptr(other.m_ptr) + { + if (m_ptr) + retainOSObject(m_ptr); + } + + OSObjectPtr(OSObjectPtr&& other) + : m_ptr(std::move(other.m_ptr)) + { + other.m_ptr = nullptr; + } + + OSObjectPtr(T ptr) + : m_ptr(std::move(ptr)) + { + if (m_ptr) + retainOSObject(m_ptr); + } + + OSObjectPtr& operator=(const OSObjectPtr& other) + { + OSObjectPtr ptr = other; + swap(ptr); + return *this; + } + + OSObjectPtr& operator=(OSObjectPtr&& other) + { + OSObjectPtr ptr = std::move(other); + swap(ptr); + return *this; + } + + OSObjectPtr& operator=(decltype(nullptr)) + { + if (m_ptr) + releaseOSObject(m_ptr); + m_ptr = nullptr; + return *this; + } + + OSObjectPtr& operator=(T other) + { + OSObjectPtr ptr = std::move(other); + swap(ptr); + return *this; + } + + void swap(OSObjectPtr& other) + { + std::swap(m_ptr, other.m_ptr); + } + + T leakRef() + { + return std::exchange(m_ptr, nullptr); + } + + friend OSObjectPtr adoptOSObject<T>(T); + +private: + struct AdoptOSObject { }; + OSObjectPtr(AdoptOSObject, T ptr) + : m_ptr(std::move(ptr)) + { + } + + T m_ptr; +}; + +template<typename T> inline OSObjectPtr<T> adoptOSObject(T ptr) +{ + return OSObjectPtr<T> { typename OSObjectPtr<T>::AdoptOSObject { }, std::move(ptr) }; +} + inline NSObject *bridge_cast(CFTypeRef object) { return (__bridge NSObject *)object; @@ -455,6 +609,8 @@ using WTF::RetainPtr; using WTF::adoptNS; using WTF::adoptCF; using WTF::retainPtr; +using WTF::OSObjectPtr; +using WTF::adoptOSObject; using WTF::downcast; using WTF::bridge_cast; using WTF::bridge_id_cast; diff --git a/clang/test/Analysis/Checkers/WebKit/uncounted-obj-arg.cpp b/clang/test/Analysis/Checkers/WebKit/uncounted-obj-arg.cpp index 2c6ccb55e2ce8..a9cd77c066f6f 100644 --- a/clang/test/Analysis/Checkers/WebKit/uncounted-obj-arg.cpp +++ b/clang/test/Analysis/Checkers/WebKit/uncounted-obj-arg.cpp @@ -74,9 +74,6 @@ T* addressof(T& arg); template<typename T> T&& forward(T& arg); -template<typename T> -T&& move( T&& t ); - template<typename ToType, typename FromType> ToType bit_cast(FromType from); diff --git a/clang/test/Analysis/Checkers/WebKit/unretained-call-args-arc.mm b/clang/test/Analysis/Checkers/WebKit/unretained-call-args-arc.mm index fa866258a2f6d..2763d8a188d8a 100644 --- a/clang/test/Analysis/Checkers/WebKit/unretained-call-args-arc.mm +++ b/clang/test/Analysis/Checkers/WebKit/unretained-call-args-arc.mm @@ -4,6 +4,7 @@ SomeObj *provide(); CFMutableArrayRef provide_cf(); +dispatch_queue_t provide_os(); void someFunction(); CGImageRef provideImage(); NSString *stringForImage(CGImageRef); @@ -14,6 +15,7 @@ void foo() { [provide() doWork]; CFArrayAppendValue(provide_cf(), nullptr); // expected-warning@-1{{Call argument for parameter 'theArray' is unretained and unsafe [alpha.webkit.UnretainedCallArgsChecker]}} + dispatch_queue_get_label(provide_os()); } } // namespace raw_ptr @@ -22,15 +24,17 @@ void foo() { extern NSString * const SomeConstant; extern CFDictionaryRef const SomeDictionary; -void doWork(NSString *str, CFDictionaryRef dict); +extern dispatch_queue_t const SomeDispatch; +void doWork(NSString *str, CFDictionaryRef dict, dispatch_queue_t dispatch); void use_const_global() { - doWork(SomeConstant, SomeDictionary); + doWork(SomeConstant, SomeDictionary, SomeDispatch); } NSString *provide_str(); CFDictionaryRef provide_dict(); +dispatch_queue_t provide_dispatch(); void use_const_local() { - doWork(provide_str(), provide_dict()); + doWork(provide_str(), provide_dict(), provide_dispatch()); // expected-warning@-1{{Call argument for parameter 'dict' is unretained and unsafe}} } @@ -65,4 +69,9 @@ - (NSString *)convertImage { RetainPtr<CGImageRef> image = [self createImage]; return stringForImage(image.get()); } + +- (const char *)dispatchLabel { + OSObjectPtr obj = provide_os(); + return dispatch_queue_get_label(obj.get()); +} @end diff --git a/clang/test/Analysis/Checkers/WebKit/unretained-call-args.mm b/clang/test/Analysis/Checkers/WebKit/unretained-call-args.mm index 75eead070fdf9..343e37d30e054 100644 --- a/clang/test/Analysis/Checkers/WebKit/unretained-call-args.mm +++ b/clang/test/Analysis/Checkers/WebKit/unretained-call-args.mm @@ -9,6 +9,9 @@ CFMutableArrayRef provide_cf(); void consume_cf(CFMutableArrayRef); +dispatch_queue_t provide_dispatch(); +void consume_dispatch(dispatch_queue_t); + CGImageRef provideImage(); NSString *stringForImage(CGImageRef); @@ -20,6 +23,8 @@ void foo() { // expected-warning@-1{{Call argument is unretained and unsafe}} consume_cf(provide_cf()); // expected-warning@-1{{Call argument is unretained and unsafe}} + consume_dispatch(provide_dispatch()); + // expected-warning@-1{{Call argument is unretained and unsafe}} } // Test that the checker works with [[clang::suppress]]. @@ -31,32 +36,32 @@ void foo_suppressed() { } namespace multi_arg { - void consume_retainable(int, SomeObj* foo, CFMutableArrayRef bar, bool); + void consume_retainable(int, SomeObj* foo, CFMutableArrayRef bar, dispatch_queue_t baz, bool); void foo() { - consume_retainable(42, provide(), provide_cf(), true); + consume_retainable(42, provide(), provide_cf(), provide_dispatch(), true); // expected-warning@-1{{Call argument for parameter 'foo' is unretained and unsafe}} // expected-warning@-2{{Call argument for parameter 'bar' is unretained and unsafe}} + // expected-warning@-3{{Call argument for parameter 'baz' is unretained and unsafe}} } void consume_retainable(SomeObj* foo, ...); void bar() { - consume_retainable(provide(), 1, provide_cf(), RetainPtr<CFMutableArrayRef> { provide_cf() }.get()); + consume_retainable(provide(), 1, provide_cf(), RetainPtr<CFMutableArrayRef> { provide_cf() }.get(), provide_dispatch()); // expected-warning@-1{{Call argument for parameter 'foo' is unretained and unsafe}} // expected-warning@-2{{Call argument is unretained and unsafe}} + // expected-warning@-3{{Call argument is unretained and unsafe}} consume_retainable(RetainPtr<SomeObj> { provide() }.get(), 1, RetainPtr<CFMutableArrayRef> { provide_cf() }.get()); } } namespace retained { - RetainPtr<SomeObj> provide_obj() { return RetainPtr<SomeObj>{}; } - void consume_obj(RetainPtr<SomeObj>) {} - - RetainPtr<CFMutableArrayRef> provide_cf() { return CFMutableArrayRef{}; } - void consume_cf(RetainPtr<CFMutableArrayRef>) {} - + RetainPtr<SomeObj> provide_obj(); + RetainPtr<CFMutableArrayRef> provide_cf(); + OSObjectPtr<dispatch_queue_t> provide_dispatch(); void foo() { consume_obj(provide_obj().get()); // no warning consume_cf(provide_cf().get()); // no warning + consume_dispatch(provide_dispatch().get()); // no warning } } @@ -64,6 +69,7 @@ void foo() { struct Consumer { void consume_obj(SomeObj* ptr); void consume_cf(CFMutableArrayRef ref); + void consume_dispatch(dispatch_queue_t ptr); }; void foo() { @@ -73,6 +79,8 @@ void foo() { // expected-warning@-1{{Call argument for parameter 'ptr' is unretained and unsafe}} c.consume_cf(provide_cf()); // expected-warning@-1{{Call argument for parameter 'ref' is unretained and unsafe}} + c.consume_dispatch(provide_dispatch()); + // expected-warning@-1{{Call argument for parameter 'ptr' is unretained and unsafe}} } void foo2() { @@ -88,6 +96,12 @@ void something() { consume_cf(provide_cf()); // expected-warning@-1{{Call argument is unretained and unsafe}} } + + void consume_dispatch(dispatch_queue_t) { some_function(); } + void anything() { + consume_dispatch(provide_dispatch()); + // expected-warning@-1{{Call argument is unretained and unsafe}} + } }; } @@ -104,6 +118,12 @@ void something() { this->consume_cf(provide_cf()); // expected-warning@-1{{Call argument is unretained and unsafe}} } + + void consume_dispatch(dispatch_queue_t) { some_function(); } + void anything() { + this->consume_dispatch(provide_dispatch()); + // expected-warning@-1{{Call argument is unretained and unsafe}} + } }; } @@ -131,6 +151,8 @@ void foo_ref() { consume_obj(0); consume_cf(nullptr); consume_cf(0); + consume_dispatch(nullptr); + consume_dispatch(0); } } @@ -161,6 +183,7 @@ void bar() { namespace param_formarding_function { void consume_more_obj(OtherObj*); void consume_more_cf(CFMutableArrayRef); + void consume_more_dispatch(dispatch_queue_t); namespace objc { void foo(SomeObj* param) { @@ -178,6 +201,7 @@ void foo(CFMutableArrayRef param) { namespace param_formarding_lambda { auto consume_more_obj = [](OtherObj*) { some_function(); }; auto consume_more_cf = [](CFMutableArrayRef) { some_function(); }; + auto consume_more_dispatch = [](dispatch_queue_t) { some_function(); }; namespace objc { void foo(SomeObj* param) { @@ -190,6 +214,12 @@ void foo(CFMutableArrayRef param) { consume_more_cf(param); } } + + namespace os_obj { + void foo(dispatch_queue_t param) { + consume_more_dispatch(param); + } + } } namespace param_forwarding_method { @@ -198,6 +228,8 @@ void foo(CFMutableArrayRef param) { static void consume_obj_s(SomeObj*); void consume_cf(CFMutableArrayRef); static void consume_cf_s(CFMutableArrayRef); + void consume_dispatch(dispatch_queue_t); + static void consume_dispatch_s(dispatch_queue_t); }; void bar(Consumer* consumer, SomeObj* param) { @@ -212,12 +244,18 @@ void baz(Consumer* consumer, CFMutableArrayRef param) { consumer->consume_cf(param); Consumer::consume_cf_s(param); } + + void baz(Consumer* consumer, dispatch_queue_t param) { + consumer->consume_dispatch(param); + Consumer::consume_dispatch_s(param); + } } namespace default_arg { SomeObj* global; CFMutableArrayRef global_cf; + dispatch_queue_t global_dispatch; void function_with_default_arg1(SomeObj* param = global); // expected-warning@-1{{Call argument for parameter 'param' is unretained and unsafe}} @@ -225,9 +263,13 @@ void baz(Consumer* consumer, CFMutableArrayRef param) { void function_with_default_arg2(CFMutableArrayRef param = global_cf); // expected-warning@-1{{Call argument for parameter 'param' is unretained and unsafe}} + void function_with_default_arg3(dispatch_queue_t param = global_dispatch); + // expected-warning@-1{{Call argument for parameter 'param' is unretained and unsafe}} + void foo() { function_with_default_arg1(); function_with_default_arg2(); + function_with_default_arg3(); } } @@ -259,9 +301,11 @@ void bar() { Foo& operator+(SomeObj* bad); friend Foo& operator-(Foo& lhs, SomeObj* bad); void operator()(SomeObj* bad); + Foo& operator<<(dispatch_queue_t bad); }; SomeObj* global; + dispatch_queue_t global_dispatch; void foo() { Foo f; @@ -271,6 +315,8 @@ void foo() { // expected-warning@-1{{Call argument for parameter 'bad' is unretained and unsafe}} f(global); // expected-warning@-1{{Call argument for parameter 'bad' is unretained and unsafe}} + f << global_dispatch; + // expected-warning@-1{{Call argument for parameter 'bad' is unretained and unsafe}} } } @@ -280,6 +326,8 @@ void foo() { void foo() { RetainPtr<SomeObj> ptr; ptr = provide(); + OSObjectPtr<dispatch_queue_t> objPtr; + objPtr = provide_dispatch(); } } @@ -287,8 +335,10 @@ void foo() { namespace call_with_ptr_on_ref { RetainPtr<SomeObj> provideProtected(); RetainPtr<CFMutableArrayRef> provideProtectedCF(); + OSObjectPtr<dispatch_queue_t> provideProtectedDispatch(); void bar(SomeObj* bad); void bar_cf(CFMutableArrayRef bad); + void bar_dispatch(dispatch_queue_t); bool baz(); void foo(bool v) { bar(v ? nullptr : provideProtected().get()); @@ -304,6 +354,13 @@ void foo(bool v) { // expected-warning@-1{{Call argument for parameter 'bad' is unretained and unsafe}} bar_cf(v ? provideProtectedCF().get() : provide_cf()); // expected-warning@-1{{Call argument for parameter 'bad' is unretained and unsafe}} + + bar_dispatch(v ? nullptr : provideProtectedDispatch().get()); + bar_dispatch(baz() ? provideProtectedDispatch().get() : nullptr); + bar_dispatch(v ? provide_dispatch() : provideProtectedDispatch().get()); + // expected-warning@-1{{Call argument is unretained and unsafe}} + bar_dispatch(v ? provideProtectedDispatch().get() : provide_dispatch()); + // expected-warning@-1{{Call argument is unretained and unsafe}} } } @@ -320,12 +377,16 @@ void bar() { void baz() { bar<int>(); } + void os_ptr() { + consume_dispatch(OSObjectPtr { provide_dispatch() }.get()); + } } namespace call_with_adopt_ref { void foo() { [adoptNS(provide()).get() doWork]; CFArrayAppendValue(adoptCF(provide_cf()).get(), nullptr); + consume_dispatch(adoptOSObject(provide_dispatch()).get()); } } @@ -423,17 +484,19 @@ void idcf(CFTypeRef obj) { extern NSString * const SomeConstant; extern CFDictionaryRef const SomeDictionary; -void doWork(NSString *str, CFDictiona... [truncated] `````````` </details> https://github.com/llvm/llvm-project/pull/159484 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
