Skip to content

Commit b38b4de

Browse files
Fix race condition in ImageFetcher when registering/unregistering commitHooks (facebook#53862)
Summary: Recently, we observed a `use-after-free race condition` where the ImageFetcher object was destroyed while it was still registered as a UIManagerCommitHook. The crash occurred in `std::vector::size()` at line 635 when accessing corrupted memory. The root cause appears to be improper lifecycle management between ImageFetcher destruction and commit hook execution. The issue seems to be that `UIManagerCommitHook*` are raw pointers. If we register or unregister them off the JS thread, we will encounter issues with the lifecycle management of the commit hooks. Therefore, it is advisable to revert parts of this change: [https://github.com/facebook/react-native/pull/53491/files](https://github.com/facebook/react-native/pull/53491/files) and remove `UIManagerCommitHookManager`. Alternatively, we can register or unregister the CommitHook by scheduling a task on the RuntimeScheduler and accessing the UIManagerBinding. Changelog: [Internal] Differential Revision: D82846245
1 parent 53f89ba commit b38b4de

File tree

5 files changed

+68
-69
lines changed

5 files changed

+68
-69
lines changed

packages/react-native/ReactCommon/react/renderer/imagemanager/platform/android/react/renderer/imagemanager/ImageFetcher.cpp

Lines changed: 51 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,36 +7,56 @@
77

88
#include "ImageFetcher.h"
99

10-
#include <glog/logging.h>
1110
#include <react/common/mapbuffer/JReadableMapBuffer.h>
1211
#include <react/renderer/imagemanager/conversions.h>
12+
#include <react/renderer/runtimescheduler/RuntimeScheduler.h>
13+
#include <react/renderer/uimanager/UIManager.h>
14+
#include <react/renderer/uimanager/UIManagerBinding.h>
1315

1416
namespace facebook::react {
1517

16-
ImageFetcher::ImageFetcher(
17-
std::shared_ptr<const ContextContainer> contextContainer)
18-
: contextContainer_(std::move(contextContainer)) {
19-
if (ReactNativeFeatureFlags::enableImagePrefetchingJNIBatchingAndroid()) {
20-
if (auto uiManagerCommitHookManager =
21-
contextContainer_
22-
->find<std::shared_ptr<UIManagerCommitHookManager>>(
23-
std::string(UIManagerCommitHookManagerKey));
24-
uiManagerCommitHookManager.has_value()) {
25-
(*uiManagerCommitHookManager)->registerCommitHook(*this);
26-
}
18+
using RunOnUIManagerFn = std::function<void(UIManager&, ImageFetcher&)>;
19+
20+
namespace {
21+
22+
void runOnUIManager(
23+
const ContextContainer& contextContainer,
24+
std::shared_ptr<ImageFetcher> strongThis,
25+
RunOnUIManagerFn&& runOnUIManagerFn) {
26+
auto weakRuntimeScheduler =
27+
contextContainer.find<std::weak_ptr<RuntimeScheduler>>(
28+
RuntimeSchedulerKey);
29+
auto runtimeScheduler = weakRuntimeScheduler.has_value()
30+
? weakRuntimeScheduler.value().lock()
31+
: nullptr;
32+
if (runtimeScheduler == nullptr) {
33+
return;
2734
}
35+
runtimeScheduler->scheduleTask(
36+
SchedulerPriority::NormalPriority,
37+
[strongThis,
38+
runOnUIManagerFn = std::move(runOnUIManagerFn)](jsi::Runtime& runtime) {
39+
runOnUIManagerFn(
40+
UIManagerBinding::getBinding(runtime)->getUIManager(), *strongThis);
41+
});
2842
}
2943

44+
} // namespace
45+
46+
ImageFetcher::ImageFetcher(
47+
std::shared_ptr<const ContextContainer> contextContainer)
48+
: contextContainer_(std::move(contextContainer)) {}
49+
3050
ImageFetcher::~ImageFetcher() {
31-
if (ReactNativeFeatureFlags::enableImagePrefetchingJNIBatchingAndroid()) {
32-
if (auto uiManagerCommitHookManager =
33-
contextContainer_
34-
->find<std::shared_ptr<UIManagerCommitHookManager>>(
35-
std::string(UIManagerCommitHookManagerKey));
36-
uiManagerCommitHookManager.has_value()) {
37-
(*uiManagerCommitHookManager)->unregisterCommitHook(*this);
38-
}
51+
if (!commitHookRegistered_) {
52+
return;
3953
}
54+
runOnUIManager(
55+
*contextContainer_,
56+
shared_from_this(),
57+
[](UIManager& uiManager, ImageFetcher& strongThis) {
58+
uiManager.unregisterCommitHook(strongThis);
59+
});
4060
}
4161

4262
ImageRequest ImageFetcher::requestImage(
@@ -51,7 +71,17 @@ ImageRequest ImageFetcher::requestImage(
5171

5272
auto telemetry = std::make_shared<ImageTelemetry>(surfaceId);
5373

54-
if (!ReactNativeFeatureFlags::enableImagePrefetchingJNIBatchingAndroid()) {
74+
if (ReactNativeFeatureFlags::enableImagePrefetchingJNIBatchingAndroid()) {
75+
if (!commitHookRegistered_) {
76+
runOnUIManager(
77+
*contextContainer_,
78+
shared_from_this(),
79+
[](UIManager& uiManager, ImageFetcher& strongThis) {
80+
uiManager.registerCommitHook(strongThis);
81+
});
82+
commitHookRegistered_ = true;
83+
}
84+
} else {
5585
flushImageRequests();
5686
}
5787

packages/react-native/ReactCommon/react/renderer/imagemanager/platform/android/react/renderer/imagemanager/ImageFetcher.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818

1919
namespace facebook::react {
2020

21-
class ImageFetcher : public UIManagerCommitHook {
21+
class ImageFetcher : public UIManagerCommitHook,
22+
public std::enable_shared_from_this<ImageFetcher> {
2223
public:
2324
ImageFetcher(std::shared_ptr<const ContextContainer> contextContainer);
2425
~ImageFetcher() override;
@@ -47,7 +48,9 @@ class ImageFetcher : public UIManagerCommitHook {
4748
private:
4849
void flushImageRequests();
4950

51+
bool commitHookRegistered_{false};
5052
std::unordered_map<SurfaceId, std::vector<ImageRequestItem>> items_;
5153
std::shared_ptr<const ContextContainer> contextContainer_;
5254
};
55+
5356
} // namespace facebook::react

packages/react-native/ReactCommon/react/renderer/imagemanager/platform/android/react/renderer/imagemanager/ImageManager.cpp

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,22 @@ constexpr inline bool isInteger(const std::string& str) {
1818
return str.find_first_not_of("0123456789") == std::string::npos;
1919
}
2020

21+
struct ImageFetcherHolder {
22+
explicit ImageFetcherHolder(
23+
const std::shared_ptr<const ContextContainer>& contextContainer)
24+
: imageFetcher_(std::make_shared<ImageFetcher>(contextContainer)) {}
25+
26+
const std::shared_ptr<ImageFetcher> imageFetcher_;
27+
};
28+
2129
} // namespace
2230

2331
ImageManager::ImageManager(
2432
const std::shared_ptr<const ContextContainer>& contextContainer)
25-
: self_(new ImageFetcher(contextContainer)) {}
33+
: self_(new ImageFetcherHolder(contextContainer)) {}
2634

2735
ImageManager::~ImageManager() {
28-
delete static_cast<ImageFetcher*>(self_);
36+
delete static_cast<ImageFetcherHolder*>(self_);
2937
}
3038

3139
ImageRequest ImageManager::requestImage(
@@ -35,8 +43,9 @@ ImageRequest ImageManager::requestImage(
3543
Tag tag) const {
3644
if (ReactNativeFeatureFlags::enableImagePrefetchingAndroid()) {
3745
if (!isInteger(imageSource.uri)) {
38-
return static_cast<ImageFetcher*>(self_)->requestImage(
39-
imageSource, surfaceId, imageRequestParams, tag);
46+
return static_cast<ImageFetcherHolder*>(self_)
47+
->imageFetcher_->requestImage(
48+
imageSource, surfaceId, imageRequestParams, tag);
4049
}
4150
}
4251
return {imageSource, nullptr, {}};

packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp

Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -24,33 +24,6 @@
2424

2525
namespace facebook::react {
2626

27-
namespace {
28-
29-
class UIManagerCommitHookManagerImpl : public UIManagerCommitHookManager {
30-
public:
31-
explicit UIManagerCommitHookManagerImpl(std::weak_ptr<UIManager> uiManager)
32-
: uiManager_(std::move(uiManager)) {}
33-
34-
~UIManagerCommitHookManagerImpl() override = default;
35-
36-
void registerCommitHook(UIManagerCommitHook& commitHook) override {
37-
if (auto uiManager = uiManager_.lock()) {
38-
uiManager->registerCommitHook(commitHook);
39-
}
40-
}
41-
42-
void unregisterCommitHook(UIManagerCommitHook& commitHook) override {
43-
if (auto uiManager = uiManager_.lock()) {
44-
uiManager->unregisterCommitHook(commitHook);
45-
}
46-
}
47-
48-
private:
49-
std::weak_ptr<UIManager> uiManager_;
50-
};
51-
52-
} // namespace
53-
5427
Scheduler::Scheduler(
5528
const SchedulerToolbox& schedulerToolbox,
5629
UIManagerAnimationDelegate* animationDelegate,
@@ -76,11 +49,6 @@ Scheduler::Scheduler(
7649

7750
auto uiManager =
7851
std::make_shared<UIManager>(runtimeExecutor_, contextContainer_);
79-
if (ReactNativeFeatureFlags::enableImagePrefetchingAndroid()) {
80-
contextContainer_->insert(
81-
std::string(UIManagerCommitHookManagerKey),
82-
std::make_shared<UIManagerCommitHookManagerImpl>(uiManager));
83-
}
8452

8553
auto eventOwnerBox = std::make_shared<EventBeat::OwnerBox>();
8654
eventOwnerBox->owner = eventDispatcher_;

packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerCommitHook.h

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -57,15 +57,4 @@ class UIManagerCommitHook {
5757
virtual ~UIManagerCommitHook() noexcept = default;
5858
};
5959

60-
constexpr std::string_view UIManagerCommitHookManagerKey =
61-
"UIManagerCommitHookManager";
62-
63-
class UIManagerCommitHookManager {
64-
public:
65-
virtual ~UIManagerCommitHookManager() = default;
66-
67-
virtual void registerCommitHook(UIManagerCommitHook& commitHook) = 0;
68-
virtual void unregisterCommitHook(UIManagerCommitHook& commitHook) = 0;
69-
};
70-
7160
} // namespace facebook::react

0 commit comments

Comments
 (0)