[pigeon] Fixes bug where Dart FlutterApis would assert that a nullable argument was nonnull (#1515)

This commit is contained in:
Maurice Parrish
2022-04-14 07:19:47 -07:00
committed by GitHub
parent b3ab4a5fe8
commit 4eda7ad208
19 changed files with 162 additions and 88 deletions

View File

@ -22,6 +22,9 @@ analyzer:
todo: ignore
# Turned off until null-safe rollout is complete.
unnecessary_null_comparison: ignore
exclude:
# Ignore generated files
- '**/*.mocks.dart' # Mockito @GenerateMocks
linter:
rules:

View File

@ -1,3 +1,7 @@
## 2.0.4
* Fixes bug where Dart `FlutterApi`s would assert that a nullable argument was nonnull.
## 2.0.3
* Makes the generated Java Builder class final.

View File

@ -338,13 +338,17 @@ void _writeFlutterApi(
indent.writeln(
'final $argType$nullTag $argName = ($argsArray[$count] as $genericArgType$nullTag)${castCall.isEmpty ? '' : '$nullTag$castCall'};');
indent.writeln(
'assert($argName != null, \'Argument for $channelName was null, expected non-null $argType.\');');
if (!arg.type.isNullable) {
indent.writeln(
'assert($argName != null, \'Argument for $channelName was null, expected non-null $argType.\');');
}
});
final Iterable<String> argNames =
indexMap(func.arguments, argNameFunc);
call =
'api.${func.name}(${argNames.map<String>((String x) => '$x$unwrapOperator').join(', ')})';
indexMap(func.arguments, (int index, NamedType field) {
final String name = _getSafeArgumentName(index, field);
return '$name${field.type.isNullable ? '' : unwrapOperator}';
});
call = 'api.${func.name}(${argNames.join(', ')})';
}
if (func.returnType.isVoid) {
if (isAsync) {

View File

@ -8,7 +8,7 @@ import 'dart:mirrors';
import 'ast.dart';
/// The current version of pigeon. This must match the version in pubspec.yaml.
const String pigeonVersion = '2.0.3';
const String pigeonVersion = '2.0.4';
/// Read all the content from [stdin] to a String.
String readStdin() {

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// Autogenerated from Pigeon (v0.3.0), do not edit directly.
// Autogenerated from Pigeon (v2.0.4), do not edit directly.
// See also: https://pub.dev/packages/pigeon
// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name
// @dart = 2.12
@ -19,6 +19,12 @@ enum RequestState {
}
class SearchRequest {
SearchRequest({
this.query,
this.anInt,
this.aBool,
});
String? query;
int? anInt;
bool? aBool;
@ -33,14 +39,21 @@ class SearchRequest {
static SearchRequest decode(Object message) {
final Map<Object?, Object?> pigeonMap = message as Map<Object?, Object?>;
return SearchRequest()
..query = pigeonMap['query'] as String?
..anInt = pigeonMap['anInt'] as int?
..aBool = pigeonMap['aBool'] as bool?;
return SearchRequest(
query: pigeonMap['query'] as String?,
anInt: pigeonMap['anInt'] as int?,
aBool: pigeonMap['aBool'] as bool?,
);
}
}
class SearchReply {
SearchReply({
this.result,
this.error,
this.state,
});
String? result;
String? error;
RequestState? state;
@ -55,16 +68,21 @@ class SearchReply {
static SearchReply decode(Object message) {
final Map<Object?, Object?> pigeonMap = message as Map<Object?, Object?>;
return SearchReply()
..result = pigeonMap['result'] as String?
..error = pigeonMap['error'] as String?
..state = pigeonMap['state'] != null
return SearchReply(
result: pigeonMap['result'] as String?,
error: pigeonMap['error'] as String?,
state: pigeonMap['state'] != null
? RequestState.values[pigeonMap['state']! as int]
: null;
: null,
);
}
}
class Nested {
Nested({
this.request,
});
SearchRequest? request;
Object encode() {
@ -75,10 +93,11 @@ class Nested {
static Nested decode(Object message) {
final Map<Object?, Object?> pigeonMap = message as Map<Object?, Object?>;
return Nested()
..request = pigeonMap['request'] != null
return Nested(
request: pigeonMap['request'] != null
? SearchRequest.decode(pigeonMap['request']!)
: null;
: null,
);
}
}
@ -132,7 +151,6 @@ class Api {
throw PlatformException(
code: 'channel-error',
message: 'Unable to establish connection on channel.',
details: null,
);
} else if (replyMap['error'] != null) {
final Map<Object?, Object?> error =
@ -152,12 +170,11 @@ class Api {
'dev.flutter.pigeon.Api.search', codec,
binaryMessenger: _binaryMessenger);
final Map<Object?, Object?>? replyMap =
await channel.send(<Object>[arg_request]) as Map<Object?, Object?>?;
await channel.send(<Object?>[arg_request]) as Map<Object?, Object?>?;
if (replyMap == null) {
throw PlatformException(
code: 'channel-error',
message: 'Unable to establish connection on channel.',
details: null,
);
} else if (replyMap['error'] != null) {
final Map<Object?, Object?> error =
@ -167,6 +184,11 @@ class Api {
message: error['message'] as String?,
details: error['details'],
);
} else if (replyMap['result'] == null) {
throw PlatformException(
code: 'null-error',
message: 'Host platform returned null value for non-null return value.',
);
} else {
return (replyMap['result'] as SearchReply?)!;
}
@ -183,6 +205,9 @@ class _NestedApiCodec extends StandardMessageCodec {
} else if (value is SearchReply) {
buffer.putUint8(129);
writeValue(buffer, value.encode());
} else if (value is SearchRequest) {
buffer.putUint8(130);
writeValue(buffer, value.encode());
} else {
super.writeValue(buffer, value);
}
@ -197,6 +222,9 @@ class _NestedApiCodec extends StandardMessageCodec {
case 129:
return SearchReply.decode(readValue(buffer)!);
case 130:
return SearchRequest.decode(readValue(buffer)!);
default:
return super.readValueOfType(type, buffer);
}
@ -219,12 +247,11 @@ class NestedApi {
'dev.flutter.pigeon.NestedApi.search', codec,
binaryMessenger: _binaryMessenger);
final Map<Object?, Object?>? replyMap =
await channel.send(<Object>[arg_nested]) as Map<Object?, Object?>?;
await channel.send(<Object?>[arg_nested]) as Map<Object?, Object?>?;
if (replyMap == null) {
throw PlatformException(
code: 'channel-error',
message: 'Unable to establish connection on channel.',
details: null,
);
} else if (replyMap['error'] != null) {
final Map<Object?, Object?> error =
@ -234,6 +261,11 @@ class NestedApi {
message: error['message'] as String?,
details: error['details'],
);
} else if (replyMap['result'] == null) {
throw PlatformException(
code: 'null-error',
message: 'Host platform returned null value for non-null return value.',
);
} else {
return (replyMap['result'] as SearchReply?)!;
}
@ -274,10 +306,11 @@ abstract class FlutterSearchApi {
static const MessageCodec<Object?> codec = _FlutterSearchApiCodec();
SearchReply search(SearchRequest request);
static void setup(FlutterSearchApi? api) {
static void setup(FlutterSearchApi? api, {BinaryMessenger? binaryMessenger}) {
{
const BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.FlutterSearchApi.search', codec);
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.FlutterSearchApi.search', codec,
binaryMessenger: binaryMessenger);
if (api == null) {
channel.setMessageHandler(null);
} else {
@ -285,7 +318,7 @@ abstract class FlutterSearchApi {
assert(message != null,
'Argument for dev.flutter.pigeon.FlutterSearchApi.search was null.');
final List<Object?> args = (message as List<Object?>?)!;
final SearchRequest? arg_request = args[0] as SearchRequest?;
final SearchRequest? arg_request = (args[0] as SearchRequest?);
assert(arg_request != null,
'Argument for dev.flutter.pigeon.FlutterSearchApi.search was null, expected non-null SearchRequest.');
final SearchReply output = api.search(arg_request!);

View File

@ -2,9 +2,10 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// Autogenerated from Pigeon (v0.3.0), do not edit directly.
// Autogenerated from Pigeon (v2.0.4), do not edit directly.
// See also: https://pub.dev/packages/pigeon
// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import
// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis
// ignore_for_file: avoid_relative_lib_imports
// @dart = 2.12
import 'dart:async';
import 'dart:typed_data' show Uint8List, Int32List, Int64List, Float64List;
@ -49,10 +50,11 @@ abstract class TestHostApi {
void initialize();
SearchReply search(SearchRequest request);
static void setup(TestHostApi? api) {
static void setup(TestHostApi? api, {BinaryMessenger? binaryMessenger}) {
{
const BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.Api.initialize', codec);
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.Api.initialize', codec,
binaryMessenger: binaryMessenger);
if (api == null) {
channel.setMockMessageHandler(null);
} else {
@ -64,8 +66,9 @@ abstract class TestHostApi {
}
}
{
const BasicMessageChannel<Object?> channel =
BasicMessageChannel<Object?>('dev.flutter.pigeon.Api.search', codec);
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.Api.search', codec,
binaryMessenger: binaryMessenger);
if (api == null) {
channel.setMockMessageHandler(null);
} else {
@ -73,7 +76,7 @@ abstract class TestHostApi {
assert(message != null,
'Argument for dev.flutter.pigeon.Api.search was null.');
final List<Object?> args = (message as List<Object?>?)!;
final SearchRequest? arg_request = args[0] as SearchRequest?;
final SearchRequest? arg_request = (args[0] as SearchRequest?);
assert(arg_request != null,
'Argument for dev.flutter.pigeon.Api.search was null, expected non-null SearchRequest.');
final SearchReply output = api.search(arg_request!);
@ -94,6 +97,9 @@ class _TestNestedApiCodec extends StandardMessageCodec {
} else if (value is SearchReply) {
buffer.putUint8(129);
writeValue(buffer, value.encode());
} else if (value is SearchRequest) {
buffer.putUint8(130);
writeValue(buffer, value.encode());
} else {
super.writeValue(buffer, value);
}
@ -108,6 +114,9 @@ class _TestNestedApiCodec extends StandardMessageCodec {
case 129:
return SearchReply.decode(readValue(buffer)!);
case 130:
return SearchRequest.decode(readValue(buffer)!);
default:
return super.readValueOfType(type, buffer);
}
@ -118,10 +127,11 @@ abstract class TestNestedApi {
static const MessageCodec<Object?> codec = _TestNestedApiCodec();
SearchReply search(Nested nested);
static void setup(TestNestedApi? api) {
static void setup(TestNestedApi? api, {BinaryMessenger? binaryMessenger}) {
{
const BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.NestedApi.search', codec);
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.NestedApi.search', codec,
binaryMessenger: binaryMessenger);
if (api == null) {
channel.setMockMessageHandler(null);
} else {
@ -129,7 +139,7 @@ abstract class TestNestedApi {
assert(message != null,
'Argument for dev.flutter.pigeon.NestedApi.search was null.');
final List<Object?> args = (message as List<Object?>?)!;
final Nested? arg_nested = args[0] as Nested?;
final Nested? arg_nested = (args[0] as Nested?);
assert(arg_nested != null,
'Argument for dev.flutter.pigeon.NestedApi.search was null, expected non-null Nested.');
final SearchReply output = api.search(arg_nested!);

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// Autogenerated from Pigeon (v1.0.19), do not edit directly.
// Autogenerated from Pigeon (v2.0.4), do not edit directly.
// See also: https://pub.dev/packages/pigeon
// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name
// @dart = 2.12

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// Autogenerated from Pigeon (v1.0.19), do not edit directly.
// Autogenerated from Pigeon (v2.0.4), do not edit directly.
// See also: https://pub.dev/packages/pigeon
// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name
// @dart = 2.12

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// Autogenerated from Pigeon (v1.0.19), do not edit directly.
// Autogenerated from Pigeon (v2.0.4), do not edit directly.
// See also: https://pub.dev/packages/pigeon
// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name
// @dart = 2.12

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// Autogenerated from Pigeon (v2.0.2), do not edit directly.
// Autogenerated from Pigeon (v2.0.4), do not edit directly.
// See also: https://pub.dev/packages/pigeon
// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name
// @dart = 2.12

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// Autogenerated from Pigeon (v1.0.19), do not edit directly.
// Autogenerated from Pigeon (v2.0.4), do not edit directly.
// See also: https://pub.dev/packages/pigeon
// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name
// @dart = 2.12

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// Autogenerated from Pigeon (v1.0.19), do not edit directly.
// Autogenerated from Pigeon (v2.0.4), do not edit directly.
// See also: https://pub.dev/packages/pigeon
// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name
// @dart = 2.12
@ -146,9 +146,7 @@ abstract class NullableArgFlutterApi {
'Argument for dev.flutter.pigeon.NullableArgFlutterApi.doit was null.');
final List<Object?> args = (message as List<Object?>?)!;
final int? arg_x = (args[0] as int?);
assert(arg_x != null,
'Argument for dev.flutter.pigeon.NullableArgFlutterApi.doit was null, expected non-null int.');
final int output = api.doit(arg_x!);
final int output = api.doit(arg_x);
return output;
});
}

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// Autogenerated from Pigeon (v1.0.19), do not edit directly.
// Autogenerated from Pigeon (v2.0.4), do not edit directly.
// See also: https://pub.dev/packages/pigeon
// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name
// @dart = 2.12

View File

@ -11,8 +11,7 @@ import 'package:mockito/mockito.dart' as _i1;
// ignore_for_file: comment_references
// ignore_for_file: unnecessary_parenthesis
// ignore_for_file: always_specify_types
// ignore_for_file: implicit_dynamic_type
// ignore_for_file: prefer_const_constructors
// ignore_for_file: avoid_redundant_argument_values

View File

@ -1,5 +1,5 @@
// Mocks generated by Mockito 5.0.7 from annotations
// in flutter_unit_tests/test/null_safe_test.dart.
// in flutter_unit_tests/test/multiple_arity_test.dart.
// Do not manually edit this file.
import 'dart:async' as _i3;
@ -11,11 +11,10 @@ import 'package:mockito/mockito.dart' as _i1;
// ignore_for_file: comment_references
// ignore_for_file: unnecessary_parenthesis
// ignore_for_file: always_specify_types
// Added manually; several methods have moved to
// flutter_test/lib/src/deprecated.dart on master, but that hasn't reached
// stable yet.
// ignore_for_file: override_on_non_overriding_member
// ignore_for_file: prefer_const_constructors
// ignore_for_file: avoid_redundant_argument_values
/// A class which mocks [BinaryMessenger].
///
@ -31,8 +30,7 @@ class MockBinaryMessenger extends _i1.Mock implements _i2.BinaryMessenger {
(super.noSuchMethod(
Invocation.method(#handlePlatformMessage, [channel, data, callback]),
returnValue: Future<void>.value(null),
returnValueForMissingStub:
Future<dynamic>.value()) as _i3.Future<void>);
returnValueForMissingStub: Future.value()) as _i3.Future<void>);
@override
_i3.Future<_i4.ByteData?>? send(String? channel, _i4.ByteData? message) =>
(super.noSuchMethod(Invocation.method(#send, [channel, message]))

View File

@ -13,8 +13,10 @@ import 'package:mockito/mockito.dart';
import 'null_safe_test.mocks.dart';
import 'test_util.dart';
@GenerateMocks(<Type>[BinaryMessenger])
@GenerateMocks(<Type>[BinaryMessenger, NullableArgFlutterApi])
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
test('with values filled', () {
final SearchReply reply = SearchReply()
..result = 'foo'
@ -102,4 +104,31 @@ void main() {
NullableArgHostApi(binaryMessenger: mockMessenger);
expect(await api.doit(null), 123);
});
test('receive null parameters', () {
final MockNullableArgFlutterApi mockFlutterApi =
MockNullableArgFlutterApi();
when(mockFlutterApi.doit(null)).thenReturn(14);
NullableArgFlutterApi.setup(mockFlutterApi);
final Completer<int> resultCompleter = Completer<int>();
// Null check operator is used because ServicesBinding.instance is nullable
// in earlier versions of Flutter.
// ignore: unnecessary_non_null_assertion
ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage(
'dev.flutter.pigeon.NullableArgFlutterApi.doit',
NullableArgFlutterApi.codec.encodeMessage(<Object?>[null]),
(ByteData? data) {
resultCompleter.complete(
NullableArgFlutterApi.codec.decodeMessage(data)! as int,
);
},
);
expect(resultCompleter.future, completion(14));
// Removes message handlers from global default binary messenger.
NullableArgFlutterApi.setup(null);
});
}

View File

@ -1,4 +1,4 @@
// Mocks generated by Mockito 5.0.4 from annotations
// Mocks generated by Mockito 5.0.7 from annotations
// in flutter_unit_tests/test/null_safe_test.dart.
// Do not manually edit this file.
@ -7,15 +7,15 @@ import 'dart:typed_data' as _i4;
import 'dart:ui' as _i5;
import 'package:flutter/src/services/binary_messenger.dart' as _i2;
import 'package:flutter_unit_tests/nullable_returns.gen.dart' as _i6;
import 'package:mockito/mockito.dart' as _i1;
// ignore_for_file: comment_references
// ignore_for_file: unnecessary_parenthesis
// ignore_for_file: always_specify_types
// Added manually; several methods have moved to
// flutter_test/lib/src/deprecated.dart on master, but that hasn't reached
// stable yet.
// ignore_for_file: override_on_non_overriding_member
// ignore_for_file: prefer_const_constructors
// ignore_for_file: avoid_redundant_argument_values
/// A class which mocks [BinaryMessenger].
///
@ -31,8 +31,7 @@ class MockBinaryMessenger extends _i1.Mock implements _i2.BinaryMessenger {
(super.noSuchMethod(
Invocation.method(#handlePlatformMessage, [channel, data, callback]),
returnValue: Future<void>.value(null),
returnValueForMissingStub:
Future<dynamic>.value()) as _i3.Future<void>);
returnValueForMissingStub: Future.value()) as _i3.Future<void>);
@override
_i3.Future<_i4.ByteData?>? send(String? channel, _i4.ByteData? message) =>
(super.noSuchMethod(Invocation.method(#send, [channel, message]))
@ -41,19 +40,19 @@ class MockBinaryMessenger extends _i1.Mock implements _i2.BinaryMessenger {
void setMessageHandler(String? channel, _i2.MessageHandler? handler) => super
.noSuchMethod(Invocation.method(#setMessageHandler, [channel, handler]),
returnValueForMissingStub: null);
@override
bool checkMessageHandler(String? channel, _i2.MessageHandler? handler) =>
(super.noSuchMethod(
Invocation.method(#checkMessageHandler, [channel, handler]),
returnValue: false) as bool);
@override
void setMockMessageHandler(String? channel, _i2.MessageHandler? handler) =>
super.noSuchMethod(
Invocation.method(#setMockMessageHandler, [channel, handler]),
returnValueForMissingStub: null);
@override
bool checkMockMessageHandler(String? channel, _i2.MessageHandler? handler) =>
(super.noSuchMethod(
Invocation.method(#checkMockMessageHandler, [channel, handler]),
returnValue: false) as bool);
}
/// A class which mocks [NullableArgFlutterApi].
///
/// See the documentation for Mockito's code generation for more information.
class MockNullableArgFlutterApi extends _i1.Mock
implements _i6.NullableArgFlutterApi {
MockNullableArgFlutterApi() {
_i1.throwOnMissingStub(this);
}
@override
int doit(int? x) =>
(super.noSuchMethod(Invocation.method(#doit, [x]), returnValue: 0)
as int);
}

View File

@ -16,9 +16,6 @@ import 'package:mockito/mockito.dart' as _i1;
// ignore_for_file: prefer_const_constructors
// ignore_for_file: avoid_redundant_argument_values
// ignore_for_file: always_specify_types
// ignore_for_file: implicit_dynamic_type
// ignore_for_file: subtype_of_disallowed_type
class _FakeInt32List extends _i1.Fake implements _i2.Int32List {}

View File

@ -2,7 +2,7 @@ name: pigeon
description: Code generator tool to make communication between Flutter and the host platform type-safe and easier.
repository: https://github.com/flutter/packages/tree/main/packages/pigeon
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3Apigeon
version: 2.0.3 # This must match the version in lib/generator_tools.dart
version: 2.0.4 # This must match the version in lib/generator_tools.dart
environment:
sdk: ">=2.12.0 <3.0.0"