Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit abdc8d3

Browse files
authored
Forward Error objects to uncaught exception handler if there is one. (#21806)
1 parent 15768e5 commit abdc8d3

File tree

5 files changed

+77
-3
lines changed

5 files changed

+77
-3
lines changed

shell/platform/android/BUILD.gn

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,7 @@ action("robolectric_tests") {
459459
"test/io/flutter/embedding/engine/PluginComponentTest.java",
460460
"test/io/flutter/embedding/engine/RenderingComponentTest.java",
461461
"test/io/flutter/embedding/engine/dart/DartExecutorTest.java",
462+
"test/io/flutter/embedding/engine/dart/DartMessengerTest.java",
462463
"test/io/flutter/embedding/engine/loader/ApplicationInfoLoaderTest.java",
463464
"test/io/flutter/embedding/engine/loader/FlutterLoaderTest.java",
464465
"test/io/flutter/embedding/engine/mutatorsstack/FlutterMutatorViewTest.java",

shell/platform/android/io/flutter/embedding/engine/dart/DartMessenger.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ public void handleMessageFromDart(
8686
} catch (Exception ex) {
8787
Log.e(TAG, "Uncaught exception in binary message listener", ex);
8888
flutterJNI.invokePlatformMessageEmptyResponseCallback(replyId);
89+
} catch (Error err) {
90+
handleError(err);
8991
}
9092
} else {
9193
Log.v(TAG, "No registered handler for message. Responding to Dart with empty reply message.");
@@ -103,6 +105,8 @@ public void handlePlatformMessageResponse(int replyId, @Nullable byte[] reply) {
103105
callback.reply(reply == null ? null : ByteBuffer.wrap(reply));
104106
} catch (Exception ex) {
105107
Log.e(TAG, "Uncaught exception in binary message reply handler", ex);
108+
} catch (Error err) {
109+
handleError(err);
106110
}
107111
}
108112
}
@@ -123,7 +127,19 @@ public int getPendingChannelResponseCount() {
123127
return pendingReplies.size();
124128
}
125129

126-
private static class Reply implements BinaryMessenger.BinaryReply {
130+
// Handles `Error` objects which are not supposed to be caught.
131+
//
132+
// We forward them to the thread's uncaught exception handler if there is one. If not, they
133+
// are rethrown.
134+
private static void handleError(Error err) {
135+
Thread currentThread = Thread.currentThread();
136+
if (currentThread.getUncaughtExceptionHandler() == null) {
137+
throw err;
138+
}
139+
currentThread.getUncaughtExceptionHandler().uncaughtException(currentThread, err);
140+
}
141+
142+
static class Reply implements BinaryMessenger.BinaryReply {
127143
@NonNull private final FlutterJNI flutterJNI;
128144
private final int replyId;
129145
private final AtomicBoolean done = new AtomicBoolean(false);

shell/platform/android/test/io/flutter/FlutterTestSuite.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
import io.flutter.embedding.engine.FlutterJNITest;
1717
import io.flutter.embedding.engine.LocalizationPluginTest;
1818
import io.flutter.embedding.engine.RenderingComponentTest;
19+
import io.flutter.embedding.engine.dart.DartExecutorTest;
20+
import io.flutter.embedding.engine.dart.DartMessengerTest;
1921
import io.flutter.embedding.engine.loader.ApplicationInfoLoaderTest;
2022
import io.flutter.embedding.engine.loader.FlutterLoaderTest;
2123
import io.flutter.embedding.engine.mutatorsstack.FlutterMutatorViewTest;
@@ -41,14 +43,14 @@
4143
import test.io.flutter.embedding.engine.FlutterEngineTest;
4244
import test.io.flutter.embedding.engine.FlutterShellArgsTest;
4345
import test.io.flutter.embedding.engine.PluginComponentTest;
44-
import test.io.flutter.embedding.engine.dart.DartExecutorTest;
4546

4647
@RunWith(Suite.class)
4748
@SuiteClasses({
4849
AccessibilityBridgeTest.class,
4950
AndroidKeyProcessorTest.class,
5051
ApplicationInfoLoaderTest.class,
5152
DartExecutorTest.class,
53+
DartMessengerTest.class,
5254
FlutterActivityAndFragmentDelegateTest.class,
5355
FlutterActivityTest.class,
5456
FlutterAndroidComponentTest.class,

shell/platform/android/test/io/flutter/embedding/engine/dart/DartExecutorTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package test.io.flutter.embedding.engine.dart;
1+
package io.flutter.embedding.engine.dart;
22

33
import static junit.framework.TestCase.assertNotNull;
44
import static org.junit.Assert.assertEquals;
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package io.flutter.embedding.engine.dart;
2+
3+
import static junit.framework.TestCase.assertNotNull;
4+
import static junit.framework.TestCase.assertTrue;
5+
import static org.mockito.Matchers.any;
6+
import static org.mockito.Mockito.mock;
7+
8+
import io.flutter.embedding.engine.FlutterJNI;
9+
import io.flutter.plugin.common.BinaryMessenger.BinaryMessageHandler;
10+
import java.nio.ByteBuffer;
11+
import org.junit.Test;
12+
import org.junit.runner.RunWith;
13+
import org.mockito.Mockito;
14+
import org.robolectric.RobolectricTestRunner;
15+
import org.robolectric.annotation.Config;
16+
17+
@Config(manifest = Config.NONE)
18+
@RunWith(RobolectricTestRunner.class)
19+
public class DartMessengerTest {
20+
private static class ReportingUncaughtExceptionHandler
21+
implements Thread.UncaughtExceptionHandler {
22+
public Throwable latestException;
23+
24+
@Override
25+
public void uncaughtException(Thread t, Throwable e) {
26+
latestException = e;
27+
}
28+
}
29+
30+
@Test
31+
public void itHandlesErrors() {
32+
// Setup test.
33+
final FlutterJNI fakeFlutterJni = mock(FlutterJNI.class);
34+
final Thread currentThread = Thread.currentThread();
35+
final Thread.UncaughtExceptionHandler savedHandler =
36+
currentThread.getUncaughtExceptionHandler();
37+
final ReportingUncaughtExceptionHandler reportingHandler =
38+
new ReportingUncaughtExceptionHandler();
39+
currentThread.setUncaughtExceptionHandler(reportingHandler);
40+
41+
// Create object under test.
42+
final DartMessenger messenger = new DartMessenger(fakeFlutterJni);
43+
final BinaryMessageHandler throwingHandler = mock(BinaryMessageHandler.class);
44+
Mockito.doThrow(AssertionError.class)
45+
.when(throwingHandler)
46+
.onMessage(any(ByteBuffer.class), any(DartMessenger.Reply.class));
47+
48+
messenger.setMessageHandler("test", throwingHandler);
49+
messenger.handleMessageFromDart("test", new byte[] {}, 0);
50+
assertNotNull(reportingHandler.latestException);
51+
assertTrue(reportingHandler.latestException instanceof AssertionError);
52+
currentThread.setUncaughtExceptionHandler(savedHandler);
53+
}
54+
}
55+

0 commit comments

Comments
 (0)