Skip to content

Commit

Permalink
Add nullptr check to SharedFunction (#38075)
Browse files Browse the repository at this point in the history
Summary:
Pull Request resolved: #38075

changelog: [internal]

`SharedFunction<>` is created with nullptr for its internal `std::function`. If called after created with default constructor, it crashes app. It also does not have API to check if its internal function is not nullptr.

With image cancelation, there is a race between when native component calls `imageRequest.cancel()` and when cancelation function is set in `RCTImageManager`.
To fix this, this diff adds a nullptr check inside SharedFunction. So it is always safe to call.

Reviewed By: javache

Differential Revision: D47022957

fbshipit-source-id: 0a04a87cd1ffe6bf3ca2fded38f689f06cc92ca9
  • Loading branch information
sammy-SC authored and kelset committed Jun 28, 2023
1 parent ba46e5b commit 69705a4
Show file tree
Hide file tree
Showing 9 changed files with 31 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,9 @@ - (void)_setStateAndResubscribeImageResponseObserver:(ImageShadowNode::ConcreteS
// This will only become issue if we decouple life cycle of a
// ShadowNode from ComponentView, which is not something we do now.
imageRequest.cancel();
imageRequest.cancel();
imageRequest.cancel();
imageRequest.cancel();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class ImageShadowNode final : public ConcreteViewShadowNode<
ShadowNodeFamilyFragment const &familyFragment,
ComponentDescriptor const &componentDescriptor) {
auto imageSource = ImageSource{ImageSource::Type::Invalid};
return {imageSource, {imageSource, nullptr}, 0};
return {imageSource, {imageSource, nullptr, {}}, 0};
}

#pragma mark - LayoutableShadowNode
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <react/renderer/imagemanager/ImageResponseObserverCoordinator.h>
#include <react/renderer/imagemanager/ImageTelemetry.h>
#include <react/renderer/imagemanager/primitives.h>
#include <react/utils/SharedFunction.h>

namespace facebook {
namespace react {
Expand All @@ -30,7 +31,8 @@ class ImageRequest final {
*/
ImageRequest(
ImageSource imageSource,
std::shared_ptr<const ImageTelemetry> telemetry);
std::shared_ptr<const ImageTelemetry> telemetry,
SharedFunction<> cancelationFunction);

/*
* The move constructor.
Expand All @@ -42,11 +44,6 @@ class ImageRequest final {
*/
ImageRequest(const ImageRequest &other) = delete;

/**
* Set cancelation function.
*/
void setCancelationFunction(std::function<void(void)> cancelationFunction);

/*
* Calls cancel function if one is defined. Should be when downloading
* image isn't needed anymore. E.g. <ImageView /> was removed.
Expand Down Expand Up @@ -95,9 +92,9 @@ class ImageRequest final {
std::shared_ptr<const ImageResponseObserverCoordinator> coordinator_{};

/*
* Function we can call to cancel image request (see destructor).
* Function we can call to cancel image request.
*/
std::function<void(void)> cancelRequest_;
SharedFunction<> cancelRequest_;
};

} // namespace react
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ ImageRequest ImageManager::requestImage(
const ImageSource &imageSource,
SurfaceId /*surfaceId*/) const {
// Not implemented.
return {imageSource, nullptr};
return {imageSource, nullptr, {}};
}

} // namespace facebook::react
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@ namespace facebook::react {

ImageRequest::ImageRequest(
ImageSource imageSource,
std::shared_ptr<const ImageTelemetry> telemetry)
: imageSource_(std::move(imageSource)), telemetry_(std::move(telemetry)) {
std::shared_ptr<const ImageTelemetry> telemetry,
SharedFunction<> cancelationFunction)
: imageSource_(std::move(imageSource)),
telemetry_(std::move(telemetry)),
cancelRequest_(std::move(cancelationFunction)) {
// Not implemented.
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,16 @@ namespace react {

ImageRequest::ImageRequest(
ImageSource imageSource,
std::shared_ptr<const ImageTelemetry> telemetry)
: imageSource_(std::move(imageSource)), telemetry_(std::move(telemetry)) {
std::shared_ptr<const ImageTelemetry> telemetry,
SharedFunction<> cancelationFunction)
: imageSource_(std::move(imageSource)),
telemetry_(std::move(telemetry)),
cancelRequest_(std::move(cancelationFunction)) {
coordinator_ = std::make_shared<ImageResponseObserverCoordinator>();
}

void ImageRequest::setCancelationFunction(
std::function<void(void)> cancelationFunction) {
cancelRequest_ = cancelationFunction;
}

void ImageRequest::cancel() const {
if (cancelRequest_) {
cancelRequest_();
}
cancelRequest_();
}

const ImageSource &ImageRequest::getImageSource() const {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,11 @@ - (ImageRequest)requestImage:(ImageSource)imageSource surfaceId:(SurfaceId)surfa
telemetry = nullptr;
}

auto imageRequest = ImageRequest(imageSource, telemetry);
auto sharedCancelationFunction = SharedFunction<>();
auto imageRequest = ImageRequest(imageSource, telemetry, sharedCancelationFunction);
auto weakObserverCoordinator =
(std::weak_ptr<const ImageResponseObserverCoordinator>)imageRequest.getSharedObserverCoordinator();

auto sharedCancelationFunction = SharedFunction<>();
imageRequest.setCancelationFunction(sharedCancelationFunction);

/*
* Even if an image is being loaded asynchronously on some other background thread, some other preparation
* work (such as creating an `NSURLRequest` object and some obscure logic inside `RCTImageLoader`) can take a couple
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,11 @@ - (instancetype)initWithImageLoader:(id<RCTImageLoaderWithAttributionProtocol>)i
- (ImageRequest)requestImage:(ImageSource)imageSource surfaceId:(SurfaceId)surfaceId
{
auto telemetry = std::make_shared<ImageTelemetry>(surfaceId);
auto imageRequest = ImageRequest(imageSource, telemetry);
auto sharedCancelationFunction = SharedFunction<>();
auto imageRequest = ImageRequest(imageSource, telemetry, sharedCancelationFunction);
auto weakObserverCoordinator =
(std::weak_ptr<const ImageResponseObserverCoordinator>)imageRequest.getSharedObserverCoordinator();

auto sharedCancelationFunction = SharedFunction<>();
imageRequest.setCancelationFunction(sharedCancelationFunction);

dispatch_group_t imageWaitGroup = dispatch_group_create();

dispatch_group_enter(imageWaitGroup);
Expand Down
10 changes: 6 additions & 4 deletions packages/react-native/ReactCommon/react/utils/SharedFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ namespace react {
* - When captured by `std::function` arguments are not copyable;
* - When we need to replace the content of the callable later on the go.
*/
template <typename ReturnT = void, typename... ArgumentT>
template <typename... ArgumentT>
class SharedFunction {
using T = ReturnT(ArgumentT...);
using T = void(ArgumentT...);

struct Pair {
Pair(std::function<T> &&function) : function(std::move(function)) {}
Expand All @@ -47,9 +47,11 @@ class SharedFunction {
pair_->function = function;
}

ReturnT operator()(ArgumentT... args) const {
void operator()(ArgumentT... args) const {
std::shared_lock lock(pair_->mutex);
return pair_->function(args...);
if (pair_->function) {
pair_->function(args...);
}
}

private:
Expand Down

0 comments on commit 69705a4

Please sign in to comment.