Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "Defer PowerShell discovery until a command or health check needs it so CLI configuration can load without Windows build tools.",
"packageName": "@react-native-windows/cli",
"email": "14055146+shirakaba@users.noreply.github.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "Fix use-after-free crash when an Image is destroyed while its download is still in flight",
"packageName": "react-native-windows",
"email": "gordomacmaster@gmail.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import type {
import {findPowerShell} from '@react-native-windows/find-dotnet-tools';
import {HealthCheckList} from './healthCheckList';

const powershell = findPowerShell();
let powershell: string | undefined;

export function getHealthChecks(): HealthCheckCategory[] | undefined {
// #8471: There are known cases where the dependencies script will error out.
Expand Down Expand Up @@ -68,6 +68,7 @@ function getHealthChecksUnsafe(): HealthCheckCategory[] | undefined {
getDiagnostics: async () => {
let needsToBeFixed = true;
try {
powershell ??= findPowerShell();
await execa(
`"${powershell}" -ExecutionPolicy Unrestricted -NoProfile "${rnwDepScriptPath}" -NoPrompt -Check ${id}`,
);
Expand All @@ -78,6 +79,21 @@ function getHealthChecksUnsafe(): HealthCheckCategory[] | undefined {
};
},
runAutomaticFix: async ({loader, logManualInstallation}) => {
try {
powershell ??= findPowerShell();
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : undefined;
logManualInstallation({
healthcheck: `react-native-windows dependency "${id}"`,
message: `Error finding PowerShell${
errorMessage ? `: ${errorMessage}` : ''
}`,
});
loader.fail();
return;
}

const command = `"${powershell}" -ExecutionPolicy Unrestricted -NoProfile "${rnwDepScriptPath}" -Check ${id}`;
try {
const {exitCode} = await execa(command, {stdio: 'inherit'});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export function newSpinner(text: string) {
return ora(options).start();
}

const powershell = findPowerShell();
let powershell: string | undefined;

export async function runPowerShellScriptFunction(
taskDescription: string,
Expand All @@ -58,6 +58,8 @@ export async function runPowerShellScriptFunction(
errorCategory: CodedErrorType,
useAppxCompatibility = false,
) {
powershell ??= findPowerShell();

try {
const printException = verbose ? '$_;' : '';
const importAppx = useAppxCompatibility
Expand Down
53 changes: 40 additions & 13 deletions vnext/Microsoft.ReactNative/Fabric/WindowsImageManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,16 @@ facebook::react::ImageRequest WindowsImageManager::requestImage(
auto weakObserverCoordinator = (std::weak_ptr<const facebook::react::ImageResponseObserverCoordinator>)
imageRequest.getSharedObserverCoordinator();

// ImageResponseObserverCoordinator copies its observer list under a lock but dereferences the raw
// observer pointers after releasing it. Observers are added and removed on the UI thread (from
// ImageComponentView::setStateAndResubscribeImageResponseObserver), and that is also where the
// owning ImageComponentView - and with it the WindowsImageResponseObserver - is destroyed. Notifying
// the coordinator from the download/completion threads therefore races that teardown and can call
// into a freed observer. Marshal every notification onto the UI thread so subscription and
// notification are serialized on the same thread. Image decoding deliberately stays off the UI
// thread; only the notification itself is posted.
auto uiDispatcher = m_reactContext.UIDispatcher();

auto rnImageSource = winrt::Microsoft::ReactNative::Composition::implementation::MakeImageSource(imageSource);
auto provider = m_uriImageManager->TryGetUriImageProvider(m_reactContext.Handle(), rnImageSource);

Expand All @@ -202,45 +212,62 @@ facebook::react::ImageRequest WindowsImageManager::requestImage(
source.sourceType = ImageSourceType::Download;
source.body = imageSource.body;

auto progressCallback = [weakObserverCoordinator](int64_t loaded, int64_t total) {
if (auto observerCoordinator = weakObserverCoordinator.lock()) {
float progress = total > 0 ? static_cast<float>(loaded) / static_cast<float>(total) : 1.0f;
observerCoordinator->nativeImageResponseProgress(progress, loaded, total);
}
auto progressCallback = [weakObserverCoordinator, uiDispatcher](int64_t loaded, int64_t total) {
float progress = total > 0 ? static_cast<float>(loaded) / static_cast<float>(total) : 1.0f;
uiDispatcher.Post([weakObserverCoordinator, progress, loaded, total]() {
if (auto observerCoordinator = weakObserverCoordinator.lock()) {
observerCoordinator->nativeImageResponseProgress(progress, loaded, total);
}
});
};
imageResponseTask = GetImageRandomAccessStreamAsync(source, progressCallback);
}

imageResponseTask.Completed([weakObserverCoordinator](auto asyncOp, auto status) {
auto observerCoordinator = weakObserverCoordinator.lock();
if (!observerCoordinator) {
imageResponseTask.Completed([weakObserverCoordinator, uiDispatcher](auto asyncOp, auto status) {
if (weakObserverCoordinator.expired()) {
return;
}

auto postComplete = [weakObserverCoordinator, uiDispatcher](auto image) {
uiDispatcher.Post([weakObserverCoordinator, image = std::move(image)]() {
if (auto observerCoordinator = weakObserverCoordinator.lock()) {
observerCoordinator->nativeImageResponseComplete(facebook::react::ImageResponse(image, nullptr /*metadata*/));
}
});
};

auto postFailure = [weakObserverCoordinator,
uiDispatcher](std::shared_ptr<facebook::react::ImageErrorInfo> errorInfo) {
uiDispatcher.Post([weakObserverCoordinator, errorInfo = std::move(errorInfo)]() {
if (auto observerCoordinator = weakObserverCoordinator.lock()) {
observerCoordinator->nativeImageResponseFailed(facebook::react::ImageLoadError(errorInfo));
}
});
};

switch (status) {
case winrt::Windows::Foundation::AsyncStatus::Completed: {
auto imageResponse = asyncOp.GetResults();
auto selfImageResponse =
winrt::get_self<winrt::Microsoft::ReactNative::Composition::implementation::ImageResponse>(imageResponse);
auto imageResultOrError = selfImageResponse->ResolveImage();
if (imageResultOrError.image) {
observerCoordinator->nativeImageResponseComplete(
facebook::react::ImageResponse(imageResultOrError.image, nullptr /*metadata*/));
postComplete(std::move(imageResultOrError.image));
} else {
observerCoordinator->nativeImageResponseFailed(facebook::react::ImageLoadError(imageResultOrError.errorInfo));
postFailure(std::move(imageResultOrError.errorInfo));
}
break;
}
case winrt::Windows::Foundation::AsyncStatus::Canceled: {
auto errorInfo = std::make_shared<facebook::react::ImageErrorInfo>();
errorInfo->error = FormatHResultError(winrt::hresult_error(asyncOp.ErrorCode()));
observerCoordinator->nativeImageResponseFailed(facebook::react::ImageLoadError(errorInfo));
postFailure(std::move(errorInfo));
break;
}
case winrt::Windows::Foundation::AsyncStatus::Error: {
auto errorInfo = std::make_shared<facebook::react::ImageErrorInfo>();
errorInfo->error = FormatHResultError(winrt::hresult_error(asyncOp.ErrorCode()));
observerCoordinator->nativeImageResponseFailed(facebook::react::ImageLoadError(errorInfo));
postFailure(std::move(errorInfo));
break;
}
}
Expand Down
Loading