Fabric: Checking for `nullptr` before calling `CFRelease` in `ManagedObjectWrapper`

Summary:
ManagedObjectWrapper (`wrapManagedObject`) is used to pass pointers managed by Objective-C runtime object thought C++ parts of the framework. The wrapper consists of a shared pointer to an object with a custom deleter that calls `CFRelease`.
Apparently, there is a caveat here: shared ptr implementation always calls a deleter even if the object points to `nullptr`. It's usually fine because in C++ calling `delete` on a nullptr is a no-op, not an error. But it's an error from the `CFRelease` perspective, which checks the pointer and crashes if it's nullptr.

More info:
https://stackoverflow.com/questions/1135214/why-would-cfreleasenull-crash
https://stackoverflow.com/questions/50201967/why-unique-ptr-with-custom-deleter-wont-work-for-nullptr-while-shared-ptr-does

Changelog: [Internal] Fabric-specific internal change.

Reviewed By: sammy-SC

Differential Revision: D21943011

fbshipit-source-id: 442ad5e274a146de112e6bd8f3c2d20f0225bf77
This commit is contained in:
Valentin Shergin 2020-06-09 12:37:54 -07:00 коммит произвёл Facebook GitHub Bot
Родитель 485475ab3e
Коммит 2a80579ea1
1 изменённых файлов: 26 добавлений и 5 удалений

Просмотреть файл

@ -21,6 +21,27 @@
namespace facebook {
namespace react {
namespace detail {
/*
* A custom deleter used for the deallocation of Objective-C managed objects.
* To be used only by `wrapManagedObject`.
*/
static void wrappedManagedObjectDeleter(void *cfPointer) noexcept
{
// A shared pointer does call custom deleter on `nullptr`s.
// This is somewhat counter-intuitively but makes sense considering the type-erasured nature of shared pointer and an
// aliasing constructor feature. `CFRelease` crashes on null pointer though. Therefore we must check for this case
// explicitly.
if (cfPointer == NULL) {
return;
}
CFRelease(cfPointer);
}
}
/*
* `wrapManagedObject` and `unwrapManagedObject` are wrapper functions that
* convert ARC-managed objects into `std::shared_ptr<void>` and vice-versa. It's
@ -35,24 +56,24 @@ namespace react {
* represented as multiple bumps of C++ counter, so we can have multiple
* counters for the same object that form some kind of counters tree.
*/
inline std::shared_ptr<void> wrapManagedObject(id object)
inline std::shared_ptr<void> wrapManagedObject(id object) noexcept
{
return std::shared_ptr<void>((__bridge_retained void *)object, CFRelease);
return std::shared_ptr<void>((__bridge_retained void *)object, detail::wrappedManagedObjectDeleter);
}
inline id unwrapManagedObject(std::shared_ptr<void> const &object)
inline id unwrapManagedObject(std::shared_ptr<void> const &object) noexcept
{
return (__bridge id)object.get();
}
inline std::shared_ptr<void> wrapManagedObjectWeakly(id object)
inline std::shared_ptr<void> wrapManagedObjectWeakly(id object) noexcept
{
RCTInternalGenericWeakWrapper *weakWrapper = [RCTInternalGenericWeakWrapper new];
weakWrapper.object = object;
return wrapManagedObject(weakWrapper);
}
inline id unwrapManagedObjectWeakly(std::shared_ptr<void> const &object)
inline id unwrapManagedObjectWeakly(std::shared_ptr<void> const &object) noexcept
{
RCTInternalGenericWeakWrapper *weakWrapper = (RCTInternalGenericWeakWrapper *)unwrapManagedObject(object);
assert(weakWrapper && "`RCTInternalGenericWeakWrapper` instance must not be `nil`.");