diff --git a/packages/flutter/lib/src/services/message_codec.dart b/packages/flutter/lib/src/services/message_codec.dart index fb61573d757..063fa75a805 100644 --- a/packages/flutter/lib/src/services/message_codec.dart +++ b/packages/flutter/lib/src/services/message_codec.dart @@ -80,9 +80,9 @@ abstract class MethodCodec { /// Encodes an error result into a binary envelope. /// - /// The specified error [code], human-readable error [message], and error + /// The specified error [code], human-readable error [message] and error /// [details] correspond to the fields of [PlatformException]. - ByteData encodeErrorEnvelope({ required String code, String? message, dynamic details }); + ByteData encodeErrorEnvelope({ required String code, String? message, dynamic details}); } @@ -107,6 +107,7 @@ class PlatformException implements Exception { required this.code, this.message, this.details, + this.stacktrace, }) : assert(code != null); /// An error code. @@ -118,8 +119,18 @@ class PlatformException implements Exception { /// Error details, possibly null. final dynamic details; + /// Native stacktrace for the error, possibly null. + /// This is strictly for native platform stacktrace. + /// The stacktrace info on dart platform can be found within the try-catch block for example: + /// try { + /// ... + /// } catch (e, stacktrace) { + /// print(stacktrace); + /// } + final String? stacktrace; + @override - String toString() => 'PlatformException($code, $message, $details)'; + String toString() => 'PlatformException($code, $message, $details, $stacktrace)'; } /// Thrown to indicate that a platform interaction failed to find a handling diff --git a/packages/flutter/lib/src/services/message_codecs.dart b/packages/flutter/lib/src/services/message_codecs.dart index cff3924f384..d21f469d9fd 100644 --- a/packages/flutter/lib/src/services/message_codecs.dart +++ b/packages/flutter/lib/src/services/message_codecs.dart @@ -152,6 +152,16 @@ class JSONMethodCodec implements MethodCodec { message: decoded[1] as String, details: decoded[2], ); + if (decoded.length == 4 + && decoded[0] is String + && (decoded[1] == null || decoded[1] is String) + && (decoded[3] == null || decoded[3] is String)) + throw PlatformException( + code: decoded[0] as String, + message: decoded[1] as String, + details: decoded[2], + stacktrace: decoded[3] as String, + ); throw FormatException('Invalid envelope: $decoded'); } @@ -161,7 +171,7 @@ class JSONMethodCodec implements MethodCodec { } @override - ByteData encodeErrorEnvelope({ required String code, String? message, dynamic details }) { + ByteData encodeErrorEnvelope({ required String code, String? message, dynamic details}) { assert(code != null); return const JSONMessageCodec().encodeMessage([code, message, details])!; } @@ -547,7 +557,7 @@ class StandardMethodCodec implements MethodCodec { } @override - ByteData encodeErrorEnvelope({ required String code, String? message, dynamic details }) { + ByteData encodeErrorEnvelope({ required String code, String? message, dynamic details}) { final WriteBuffer buffer = WriteBuffer(); buffer.putUint8(1); messageCodec.writeValue(buffer, code); @@ -567,8 +577,9 @@ class StandardMethodCodec implements MethodCodec { final dynamic errorCode = messageCodec.readValue(buffer); final dynamic errorMessage = messageCodec.readValue(buffer); final dynamic errorDetails = messageCodec.readValue(buffer); + final String? errorStacktrace = (buffer.hasRemaining) ? messageCodec.readValue(buffer) as String : null; if (errorCode is String && (errorMessage == null || errorMessage is String) && !buffer.hasRemaining) - throw PlatformException(code: errorCode, message: errorMessage as String, details: errorDetails); + throw PlatformException(code: errorCode, message: errorMessage as String, details: errorDetails, stacktrace: errorStacktrace); else throw const FormatException('Invalid envelope'); } diff --git a/packages/flutter/test/services/message_codecs_test.dart b/packages/flutter/test/services/message_codecs_test.dart index 83bb4ef53c9..46c966060f4 100644 --- a/packages/flutter/test/services/message_codecs_test.dart +++ b/packages/flutter/test/services/message_codecs_test.dart @@ -7,9 +7,12 @@ // This files contains message codec tests that are supported both on the Web // and in the VM. For VM-only tests see message_codecs_vm_test.dart. +import 'dart:convert'; import 'dart:typed_data'; +import 'package:flutter/foundation.dart' show WriteBuffer; import 'package:flutter/services.dart'; +import 'package:matcher/matcher.dart'; import '../flutter_test_alternative.dart'; import 'message_codecs_testing.dart'; @@ -36,14 +39,77 @@ void main() { final ByteData helloByteData = string.encodeMessage('hello'); final ByteData offsetByteData = ByteData.view( - helloWorldByteData.buffer, - helloByteData.lengthInBytes, - helloWorldByteData.lengthInBytes - helloByteData.lengthInBytes, + helloWorldByteData.buffer, + helloByteData.lengthInBytes, + helloWorldByteData.lengthInBytes - helloByteData.lengthInBytes, ); expect(string.decodeMessage(offsetByteData), ' world'); }); }); + group('Standard method codec', () { + const MethodCodec method = StandardMethodCodec(); + const StandardMessageCodec messageCodec = StandardMessageCodec(); + test('should decode error envelope without native stacktrace', () { + final ByteData errorData = method.encodeErrorEnvelope( + code: 'errorCode', + message: 'errorMessage', + details: 'errorDetails', + ); + expect( + () => method.decodeEnvelope(errorData), + throwsA(predicate((PlatformException e) => + e is PlatformException && + e.code == 'errorCode' && + e.message == 'errorMessage' && + e.details == 'errorDetails'))); + }); + test('should decode error envelope with native stacktrace.', () { + final WriteBuffer buffer = WriteBuffer(); + buffer.putUint8(1); + messageCodec.writeValue(buffer, 'errorCode'); + messageCodec.writeValue(buffer, 'errorMessage'); + messageCodec.writeValue(buffer, 'errorDetails'); + messageCodec.writeValue(buffer, 'errorStacktrace'); + final ByteData errorData = buffer.done(); + expect( + () => method.decodeEnvelope(errorData), + throwsA(predicate((PlatformException e) => + e is PlatformException && e.stacktrace == 'errorStacktrace'))); + }); + }); + group('Json method codec', () { + const JsonCodec json = JsonCodec(); + const StringCodec stringCodec = StringCodec(); + const JSONMethodCodec jsonMethodCodec = JSONMethodCodec(); + test('should decode error envelope without native stacktrace', () { + final ByteData errorData = jsonMethodCodec.encodeErrorEnvelope( + code: 'errorCode', + message: 'errorMessage', + details: 'errorDetails', + ); + expect( + () => jsonMethodCodec.decodeEnvelope(errorData), + throwsA(predicate((PlatformException e) => + e is PlatformException && + e.code == 'errorCode' && + e.message == 'errorMessage' && + e.details == 'errorDetails'))); + }); + test('should decode error envelope with native stacktrace.', () { + final ByteData errorData = stringCodec.encodeMessage(json + .encode([ + 'errorCode', + 'errorMessage', + 'errorDetails', + 'errorStacktrace' + ])); + expect( + () => jsonMethodCodec.decodeEnvelope(errorData), + throwsA(predicate((PlatformException e) => + e is PlatformException && e.stacktrace == 'errorStacktrace'))); + }); + }); group('JSON message codec', () { const MessageCodec json = JSONMessageCodec(); test('should encode and decode simple messages', () { @@ -151,8 +217,22 @@ void main() { standard, 1.0, [ - 6, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0xf0, 0x3f, + 6, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0xf0, + 0x3f, ], ); });