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

Fix corner case errors in platform channels (#3631)

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