提交 3211d2fc 编写于 作者: M Mikkel Nygaard Ravn 提交者: GitHub

Fix corner case errors in platform channels (#3631)

上级 faa4aba8
......@@ -154,12 +154,7 @@ public final class BasicMessageChannel<T> {
handler.onMessage(codec.decodeMessage(message), new Reply<T>() {
@Override
public void reply(T reply) {
try {
callback.reply(codec.encodeMessage(reply));
} catch (RuntimeException e) {
Log.e(TAG + name, "Failed to encode reply", e);
callback.reply(null);
}
callback.reply(codec.encodeMessage(reply));
}
});
} catch (RuntimeException e) {
......
......@@ -17,8 +17,8 @@ public interface BinaryMessenger {
* Sends a binary message to the Flutter application.
*
* @param channel the name {@link String} of the logical channel used for the message.
* @param message the message payload, a {@link ByteBuffer} with the message bytes between position
* zero and current position, or null.
* @param message the message payload, a direct-allocated {@link ByteBuffer} with the message bytes
* between position zero and current position, or null.
*/
void send(String channel, ByteBuffer message);
......@@ -28,8 +28,8 @@ public interface BinaryMessenger {
* <p>Any uncaught exception thrown by the reply callback will be caught and logged.</p>
*
* @param channel the name {@link String} of the logical channel used for the message.
* @param message the message payload, a {@link ByteBuffer} with the message bytes between position
* zero and current position, or null.
* @param message the message payload, a direct-allocated {@link ByteBuffer} with the message bytes
* between position zero and current position, or null.
* @param callback a {@link BinaryReply} callback invoked when the Flutter application responds to the
* message, possibly null.
*/
......@@ -79,8 +79,8 @@ public interface BinaryMessenger {
/**
* Handles the specified reply.
*
* @param reply the reply payload, a {@link ByteBuffer} or null. Senders of outgoing
* replies must place the reply bytes between position zero and current position.
* @param reply the reply payload, a direct-allocated {@link ByteBuffer} or null. Senders of
* outgoing replies must place the reply bytes between position zero and current position.
* Reply receivers can read from the buffer directly.
*/
void reply(ByteBuffer reply);
......
......@@ -91,13 +91,8 @@ public final class EventChannel {
/**
* Handles a request to set up an event stream.
*
* <p>Any uncaught exception thrown by this method, or the preceding arguments
* decoding, will be caught by the channel implementation and logged. An error result
* message will be sent back to Flutter.</p>
*
* <p>Any uncaught exception thrown during encoding an event or error submitted to the
* {@link EventSink} is treated similarly: the exception is logged, and an error event
* is sent to Flutter.</p>
* <p>Any uncaught exception thrown by this method will be caught by the channel
* implementation and logged. An error result message will be sent back to Flutter.</p>
*
* @param arguments stream configuration arguments, possibly null.
* @param events an {@link EventSink} for emitting events to the Flutter receiver.
......@@ -107,9 +102,8 @@ public final class EventChannel {
/**
* Handles a request to tear down an event stream.
*
* <p>Any uncaught exception thrown by this method, or the preceding arguments
* decoding, will be caught by the channel implementation and logged. An error result
* result message will be sent back to Flutter.</p>
* <p>Any uncaught exception thrown by this method will be caught by the channel
* implementation and logged. An error result message will be sent back to Flutter.</p>
*
* @param arguments stream configuration arguments, possibly null.
*/
......@@ -156,18 +150,13 @@ public final class EventChannel {
@Override
public void onMessage(ByteBuffer message, final BinaryReply reply) {
try {
final MethodCall call = codec.decodeMethodCall(message);
if (call.method.equals("listen")) {
onListen(call.arguments, reply);
} else if (call.method.equals("cancel")) {
onCancel(call.arguments, reply);
} else {
reply.reply(null);
}
} catch (RuntimeException e) {
Log.e(TAG + name, "Failed to decode event stream lifecycle call", e);
reply.reply(codec.encodeErrorEnvelope("decode", e.getMessage(), null));
final MethodCall call = codec.decodeMethodCall(message);
if (call.method.equals("listen")) {
onListen(call.arguments, reply);
} else if (call.method.equals("cancel")) {
onCancel(call.arguments, reply);
} else {
reply.reply(null);
}
}
......@@ -180,7 +169,7 @@ public final class EventChannel {
} catch (RuntimeException e) {
activeSink.set(null);
Log.e(TAG + name, "Failed to open event stream", e);
callback.reply(codec.encodeErrorEnvelope("uncaught", e.getMessage(), null));
callback.reply(codec.encodeErrorEnvelope("error", e.getMessage(), null));
}
} else {
callback.reply(codec.encodeErrorEnvelope("error", "Stream already active", null));
......@@ -195,7 +184,7 @@ public final class EventChannel {
callback.reply(codec.encodeSuccessEnvelope(null));
} catch (RuntimeException e) {
Log.e(TAG + name, "Failed to close event stream", e);
callback.reply(codec.encodeErrorEnvelope("uncaught", e.getMessage(), null));
callback.reply(codec.encodeErrorEnvelope("error", e.getMessage(), null));
}
} else {
callback.reply(codec.encodeErrorEnvelope("error", "No active stream to cancel", null));
......@@ -210,12 +199,7 @@ public final class EventChannel {
if (hasEnded.get() || activeSink.get() != this) {
return;
}
try {
EventChannel.this.messenger.send(name, codec.encodeSuccessEnvelope(event));
} catch (RuntimeException e) {
Log.e(TAG + name, "Failed to encode event", e);
EventChannel.this.messenger.send(name, codec.encodeErrorEnvelope("encode", e.getMessage(), null));
}
EventChannel.this.messenger.send(name, codec.encodeSuccessEnvelope(event));
}
@Override
......@@ -223,14 +207,9 @@ public final class EventChannel {
if (hasEnded.get() || activeSink.get() != this) {
return;
}
try {
EventChannel.this.messenger.send(
name,
codec.encodeErrorEnvelope(errorCode, errorMessage, errorDetails));
} catch (RuntimeException e) {
Log.e(TAG + name, "Failed to encode error", e);
EventChannel.this.messenger.send(name, codec.encodeErrorEnvelope("encode", e.getMessage(), null));
}
EventChannel.this.messenger.send(
name,
codec.encodeErrorEnvelope(errorCode, errorMessage, errorDetails));
}
@Override
......
......@@ -26,7 +26,12 @@ public final class JSONMessageCodec implements MessageCodec<Object> {
if (message == null) {
return null;
}
return StringCodec.INSTANCE.encodeMessage(JSONUtil.wrap(message).toString());
final Object wrapped = JSONUtil.wrap(message);
if (wrapped instanceof String) {
return StringCodec.INSTANCE.encodeMessage(JSONObject.quote((String) wrapped));
} else {
return StringCodec.INSTANCE.encodeMessage(wrapped.toString());
}
}
@Override
......
......@@ -36,7 +36,7 @@ public final class JSONMethodCodec implements MethodCodec {
if (json instanceof JSONObject) {
final JSONObject map = (JSONObject) json;
final Object method = map.get("method");
final Object arguments = map.get("args");
final Object arguments = unwrapNull(map.opt("args"));
if (method instanceof String) {
return new MethodCall((String) method, arguments);
}
......@@ -58,7 +58,7 @@ public final class JSONMethodCodec implements MethodCodec {
Object errorDetails) {
return JSONMessageCodec.INSTANCE.encodeMessage(new JSONArray()
.put(errorCode)
.put(errorMessage)
.put(JSONUtil.wrap(errorMessage))
.put(JSONUtil.wrap(errorDetails)));
}
......@@ -69,20 +69,24 @@ public final class JSONMethodCodec implements MethodCodec {
if (json instanceof JSONArray) {
final JSONArray array = (JSONArray) json;
if (array.length() == 1) {
return array.get(0);
return unwrapNull(array.opt(0));
}
if (array.length() == 3) {
final Object code = array.get(0);
final Object message = array.get(1);
final Object details = array.get(2);
final Object message = unwrapNull(array.opt(1));
final Object details = unwrapNull(array.opt(2));
if (code instanceof String && (message == null || message instanceof String)) {
throw new FlutterException((String) code, (String) message, details);
}
}
}
throw new IllegalArgumentException("Invalid method call: " + json);
throw new IllegalArgumentException("Invalid envelope: " + json);
} catch (JSONException e) {
throw new IllegalArgumentException("Invalid JSON", e);
}
}
Object unwrapNull(Object value) {
return (value == JSONObject.NULL) ? null : value;
}
}
......@@ -114,11 +114,8 @@ public final class MethodChannel {
* handlers. The result may be submitted asynchronously. Calls to unknown or unimplemented methods
* should be handled using {@link Result#notImplemented()}.</p>
*
* <p>Any uncaught exception thrown by this method, or the preceding method call decoding, will be
* caught by the channel implementation and logged, and an error result will be sent back to Flutter.</p>
*
* <p>Any uncaught exception thrown during encoding a result submitted to the {@link Result}
* is treated similarly: the exception is logged, and an error result is sent to Flutter.</p>
* <p>Any uncaught exception thrown by this method will be caught by the channel implementation and
* logged, and an error result will be sent back to Flutter.</p>
*
* @param call A {@link MethodCall}.
* @param result A {@link Result} used for submitting the result of the call.
......@@ -169,8 +166,7 @@ public final class MethodChannel {
callback.notImplemented();
} else {
try {
final Object result = codec.decodeEnvelope(reply);
callback.success(result);
callback.success(codec.decodeEnvelope(reply));
} catch (FlutterException e) {
callback.error(e.code, e.getMessage(), e.details);
}
......@@ -190,35 +186,17 @@ public final class MethodChannel {
@Override
public void onMessage(ByteBuffer message, final BinaryReply reply) {
MethodCall call;
try {
call = codec.decodeMethodCall(message);
} catch (RuntimeException e) {
Log.e(TAG + name, "Failed to decode method call", e);
reply.reply(codec.encodeErrorEnvelope("decode", e.getMessage(), null));
return;
}
final MethodCall call = codec.decodeMethodCall(message);
try {
handler.onMethodCall(call, new Result() {
@Override
public void success(Object result) {
try {
reply.reply(codec.encodeSuccessEnvelope(result));
} catch (RuntimeException e) {
Log.e(TAG + name, "Failed to encode success result", e);
reply.reply(codec.encodeErrorEnvelope("encode", e.getMessage(), null));
}
reply.reply(codec.encodeSuccessEnvelope(result));
}
@Override
public void error(String errorCode, String errorMessage, Object errorDetails) {
try {
reply.reply(codec.encodeErrorEnvelope(
errorCode, errorMessage, errorDetails));
} catch (RuntimeException e) {
Log.e(TAG + name, "Failed to encode error result", e);
reply.reply(codec.encodeErrorEnvelope("encode", e.getMessage(), null));
}
reply.reply(codec.encodeErrorEnvelope(errorCode, errorMessage, errorDetails));
}
@Override
......@@ -228,7 +206,7 @@ public final class MethodChannel {
});
} catch (RuntimeException e) {
Log.e(TAG + name, "Failed to handle method call", e);
reply.reply(codec.encodeErrorEnvelope("uncaught", e.getMessage(), null));
reply.reply(codec.encodeErrorEnvelope("error", e.getMessage(), null));
}
}
}
......
......@@ -57,7 +57,17 @@
- (NSData*)encode:(id)message {
if (message == nil)
return nil;
NSData* encoding = [NSJSONSerialization dataWithJSONObject:message options:0 error:nil];
NSData* encoding;
if ([message isKindOfClass:[NSArray class]] || [message isKindOfClass:[NSDictionary class]]) {
encoding = [NSJSONSerialization dataWithJSONObject:message options:0 error:nil];
} else {
// NSJSONSerialization does not support top-level simple values.
// We encode as singleton array, then extract the relevant bytes.
encoding = [NSJSONSerialization dataWithJSONObject:@[message] options:0 error:nil];
if (encoding) {
encoding = [encoding subdataWithRange:NSMakeRange(1, encoding.length - 2)];
}
}
NSAssert(encoding, @"Invalid JSON message, encoding failed");
return encoding;
}
......@@ -65,9 +75,28 @@
- (id)decode:(NSData*)message {
if (message == nil)
return nil;
id decoded = [NSJSONSerialization JSONObjectWithData:message options:0 error:nil];
BOOL isSimpleValue = NO;
id decoded = nil;
if (0 < message.length) {
UInt8 first;
[message getBytes:&first length:1];
isSimpleValue = first != '{' && first != '[';
if (isSimpleValue) {
// NSJSONSerialization does not support top-level simple values.
// We expand encoding to singleton array, then decode that and extract
// the single entry.
UInt8 begin = '[';
UInt8 end = ']';
NSMutableData* expandedMessage = [NSMutableData dataWithLength:message.length + 2];
[expandedMessage replaceBytesInRange:NSMakeRange(0, 1) withBytes:&begin];
[expandedMessage replaceBytesInRange:NSMakeRange(1, message.length) withBytes:message.bytes];
[expandedMessage replaceBytesInRange:NSMakeRange(message.length + 1, 1) withBytes:&end];
message = expandedMessage;
}
decoded = [NSJSONSerialization JSONObjectWithData:message options:0 error:nil];
}
NSAssert(decoded, @"Invalid JSON message, decoding failed");
return decoded;
return isSimpleValue ? ((NSArray*) decoded)[0] : decoded;
}
@end
......@@ -83,27 +112,26 @@
- (NSData*)encodeMethodCall:(FlutterMethodCall*)call {
return [[FlutterJSONMessageCodec sharedInstance] encode:@{
@"method" : call.method,
@"args" : (call.arguments == nil ? [NSNull null] : call.arguments),
@"args" : [self wrapNil:call.arguments],
}];
}
- (NSData*)encodeSuccessEnvelope:(id)result {
return
[[FlutterJSONMessageCodec sharedInstance] encode:@[ result == nil ? [NSNull null] : result ]];
return [[FlutterJSONMessageCodec sharedInstance] encode:@[[self wrapNil:result]]];
}
- (NSData*)encodeErrorEnvelope:(FlutterError*)error {
return [[FlutterJSONMessageCodec sharedInstance] encode:@[
error.code,
error.message == nil ? [NSNull null] : error.message,
error.details == nil ? [NSNull null] : error.details,
[self wrapNil:error.message],
[self wrapNil:error.details],
]];
}
- (FlutterMethodCall*)decodeMethodCall:(NSData*)message {
NSDictionary* dictionary = [[FlutterJSONMessageCodec sharedInstance] decode:message];
id method = dictionary[@"method"];
id arguments = dictionary[@"args"];
id arguments = [self unwrapNil:dictionary[@"args"]];
NSAssert([method isKindOfClass:[NSString class]], @"Invalid JSON method call");
return [FlutterMethodCall methodCallWithMethodName:method arguments:arguments];
}
......@@ -111,10 +139,20 @@
- (id)decodeEnvelope:(NSData*)envelope {
NSArray* array = [[FlutterJSONMessageCodec sharedInstance] decode:envelope];
if (array.count == 1)
return array[0];
return [self unwrapNil:array[0]];
NSAssert(array.count == 3, @"Invalid JSON envelope");
NSAssert([array[0] isKindOfClass:[NSString class]], @"Invalid JSON envelope");
NSAssert(array[1] == nil || [array[1] isKindOfClass:[NSString class]], @"Invalid JSON envelope");
return [FlutterError errorWithCode:array[0] message:array[1] details:array[2]];
id code = array[0];
id message = [self unwrapNil:array[1]];
id details = [self unwrapNil:array[2]];
NSAssert([code isKindOfClass:[NSString class]], @"Invalid JSON envelope");
NSAssert(message == nil || [message isKindOfClass:[NSString class]], @"Invalid JSON envelope");
return [FlutterError errorWithCode:code message:message details:details];
}
- (id)wrapNil:(id)value {
return value == nil ? [NSNull null] : value;
}
- (id)unwrapNil:(id)value {
return value == [NSNull null] ? nil : value;
}
@end
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册