Repository navigation
Description
Steps to reproduce
Reproduced on Flutter 3.24.5 in iOS release mode on a physical device:
-
Disable Impeller in
Info.plist:<key>FLTEnableImpeller</key> <false/>
-
Render four live remote videos with
AgoraVideoViewand
useFlutterTexture: true. The texture provider returns NV12
CVPixelBuffers. -
Place the video textures under a
RepaintBoundary. -
Call
RenderRepaintBoundary.toImage()when the application enters
AppLifecycleState.inactive. -
After all remote videos are rendering, rapidly switch to another foreground
application and back. The successful run used approximately 0.5 seconds
outside the application and 0.7 seconds inside it.
Build command:
flutter build ios --release \
--dart-define=JOIN_MULTIPLE_CHANNEL_REPRO=true \
--dart-define=SENTRY_DSN=<YOUR_TEST_SENTRY_DSN>The target report was produced by a direct toImage() call. The failing
lifecycle path did not call SentryFlutter.captureScreenshot(),
Sentry.captureMessage(), or another Sentry capture API. sentry_flutter
remained initialized globally in this build.
The final lifecycle log was:
14:01:31 lifecycle=AppLifecycleState.inactive
14:01:31 begin toImage reason=lifecycle-inactive-toImage#0, remote0=4
14:01:31 completed toImage reason=lifecycle-inactive-toImage#0
14:01:33 lifecycle=AppLifecycleState.inactive
14:01:33 begin toImage reason=lifecycle-inactive-toImage#0, remote0=4
<no completion; process terminated>
Expected results
toImage()should complete, reject the snapshot, or fail gracefully. Flutter should not terminate the process with a nativeSIGSEGV`.
Actual results
Crash
EXC_BAD_ACCESS / SIGSEGV
KERN_INVALID_ADDRESS at 0x48
procRole: Foreground
thread: io.flutter.1.raster
Flutter.framework UUID: 4C4C4450-5555-3144-A1A5-C934648E82E4
first Flutter image offset: 0x7006a0
Symbolicated stack:
SkYUVAInfo::NumPlanes
SkYUVAInfo::numPlanes
SkImages::TextureFromYUVATextures
FlutterDarwinExternalTextureSkImageWrapper wrapYUVATexture
FlutterDarwinExternalTextureMetal wrapNV12ExternalPixelBuffer
FlutterDarwinExternalTextureMetal wrapExternalPixelBuffer
FlutterDarwinExternalTextureMetal onNeedsUpdatedTexture
FlutterDarwinExternalTextureMetal paintContext
TextureLayer::Paint
LayerTree::Flatten
Picture::DoRasterizeToImage
Null-context evidence
The faulting instructions are:
Flutter[0x70069c] <+900>: ldr x19, [sp, #0x8]
Flutter[0x7006a0] <+904>: ldr x23, [x19, #0x48]The crash report records:
x19 = 0x0
FAR = 0x48
ESR = (Data Abort) byte read Translation fault
LLDB's DWARF lookup for this exact Flutter binary maps [sp + 0x8] to:
name = "context", type = "GrRecordingContext *"
location = DW_OP_breg31 WSP+8
decl = SkImage_GaneshYUVA.cpp:287
The wrapper argument is:
name = "grContext", type = "GrDirectContext *"
decl = FlutterDarwinExternalTextureMetal.mm:295
At Engine revision
a18df97ca57a249df5d8d68cd0820600223ce262, the wrapper calls Skia without a
runtime null check:
return SkImages::TextureFromYUVATextures(
grContext, yuvaBackendTextures, nullptr, nullptr, nullptr);This evidence identifies a null rendering context at the 0x48 access. The
inlined SkYUVAInfo::NumPlanes symbol does not by itself prove that the input
pixel buffer had an invalid plane count.
Control results
| Variant | Result |
|---|---|
Skia; direct toImage() on lifecycle change; no lifecycle Sentry capture calls |
Target SIGSEGV on the second observed inactive transition |
Skia; capture only while resumed |
No target crash in two independent runs of 20 and 50 rapid cycles |
Skia; Sentry screenshot, direct toImage(), and event capture on inactive and resumed |
Three matching crashes in five cycles |
NV12 observations
In the earlier inactive-plus-Sentry variant, paired producer and texture-copy
samples reported:
requestedFormat=12
vf.type=12
pixelFormat=420f
planes=2
badNV12=NO
Those 420f samples had matching anonymized buffer tokens, dimensions, pixel
formats, and plane counts. The direct-only process preserved the following
copy-boundary samples but no paired receive sample:
pixelFormat=420v
planes=2
badNV12=NO
No [AGORA_REPRO_BAD_NV12] marker was observed before the target crashes.
These logs sample frames; they do not prove that every frame or its lifetime
was valid. They do show that no malformed one-plane NV12 buffer was observed in
the captured evidence.
Current Engine relevance
This exact crash has been reproduced only on Flutter 3.24.5 with the iOS Skia
backend. Current official iOS builds force Slimpeller, disable Ganesh, and reject
the Skia backend, so this report does not claim that current iOS is affected:
The shared non-Slimpeller Darwin code still forwards context.gr_context to
Skia without a runtime null check. Current macOS source states that Skia remains
the default, and the Metal embedder external-texture path forwards the same
nullable context into the Skia wrappers:
- Shared Darwin external-texture wrapper
- NV12 Skia dispatch
- macOS Skia default
- Metal embedder external-texture path
No current macOS crash is claimed here. This report asks whether the shared
non-Slimpeller Darwin external-texture path should reject a null
GrDirectContext before calling Skia and add regression coverage that verifies
the frame is skipped instead of terminating the process.
This report does not request a Flutter 3.24.x hotfix.
Environment
Flutter: 3.24.5 stable
Framework revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
Engine revision: a18df97ca57a249df5d8d68cd0820600223ce262
Dart: 3.5.4
Build mode: release
Host: macOS 15.3.1 (24D70)
Xcode: 16.4 (16F6)
CocoaPods: 1.16.2
Device: iPhone13,2
iOS: 17.5.1 (21F90)
Agora Flutter SDK: 6.5.2-sp.452202
Agora iOS SDK: 4.5.2.202.2
Agora Iris iOS: 4.5.2.202-build.1
sentry_flutter: 9.20.0
Code sample
Code sample
final GlobalKey captureBoundaryKey = GlobalKey();
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
// The target crash occurred in inactive. The successful diagnostic harness
// also captured on resumed as a control path.
if (state == AppLifecycleState.inactive ||
state == AppLifecycleState.resumed) {
unawaited(capture());
}
}
Future<void> capture() async {
final context = captureBoundaryKey.currentContext;
final boundary = context?.findRenderObject();
if (boundary is! RenderRepaintBoundary) {
return;
}
final image = await boundary.toImage(
pixelRatio: View.of(context!).devicePixelRatio,
);
image.dispose();
}
@override
Widget build(BuildContext context) {
return RepaintBoundary(
key: captureBoundaryKey,
child: AgoraVideoView(
controller: VideoViewController.remote(
rtcEngine: engine,
canvas: VideoCanvas(uid: remoteUid),
connection: connection,
useFlutterTexture: true,
),
),
);
}Screenshots or Video
Screenshots / Video demonstration
[Upload media here]
Logs
Logs
[direct-toimage-lifecycle-redacted.log](https://github.com/user-attachments/files/30072928/direct-toimage-lifecycle-redacted.log)
[flutter-3.24.5-symbolication-and-dwarf.txt](https://github.com/user-attachments/files/30072930/flutter-3.24.5-symbolication-and-dwarf.txt)
[flutter-doctor-v-redacted.txt](https://github.com/user-attachments/files/30072925/flutter-doctor-v-redacted.txt)
[nv12-boundary-trace-redacted.log](https://github.com/user-attachments/files/30072926/nv12-boundary-trace-redacted.log)
[repro-source-redacted.zip](https://github.com/user-attachments/files/30072929/repro-source-redacted.zip)
[Runner-direct-toimage-redacted.zip](https://github.com/user-attachments/files/30072927/Runner-direct-toimage-redacted.zip)
Flutter Doctor output
Doctor output
[OK] Flutter (Channel stable, 3.24.5, on macOS 15.3.1 24D70 darwin-arm64, locale zh-Hans-CN)
- Flutter version 3.24.5 on channel stable at <REDACTED_FLUTTER_SDK_PATH>
- Upstream repository https://github.com/flutter/flutter.git
- Framework revision dec2ee5c1f, 2024-11-13 11:13:06 -0800
- Engine revision a18df97ca5
- Dart version 3.5.4
- DevTools version 2.37.3
[OK] Android toolchain (Android SDK version 36.0.0)
- Platform android-36, build-tools 36.0.0
- Java version OpenJDK Runtime Environment 21.0.6
- All Android licenses accepted
[OK] Xcode - develop for iOS and macOS (Xcode 16.4)
- Xcode build 16F6
- CocoaPods version 1.16.2
[OK] Chrome - develop for the web
[OK] Android Studio (version 2024.3)
- Java version OpenJDK Runtime Environment 21.0.6
[OK] VS Code (version 1.114.0)
- Flutter extension version 3.136.0
[OK] Connected target device used for reproduction
- iPhone13,2
- iOS 17.5.1 (21F90)
Activity
jtmcdole commentedon Jul 16, 2026
Flutter 3.24.5 was released 2024-11-23. We do not support the Skia backend on iOS devices.
github-actions commentedon Jul 30, 2026
This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of
flutter doctor -vand a minimal reproduction of the issue.