Skip to content

Commit c1ba3cf

Browse files
srujzsCommit Queue
authored and
Commit Queue
committed
[dart2wasm] Fix handling of generic callbacks in trampoline
Closes #54192 Callbacks were being casted to their static type before being called. With generic callbacks, we do not know their exact type at compile time, therefore cannot make that cast. Similarly, a null check was being done if the static type of the parameter was potentially non-nullable, whereas it should be done if it's *not* potentially nullable. Change-Id: I3d30e901f1ff2ae4c17887564f15368244baeb24 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/340390 Reviewed-by: Sigmund Cherem <[email protected]>
1 parent 60c640f commit c1ba3cf

File tree

3 files changed

+87
-17
lines changed

3 files changed

+87
-17
lines changed

pkg/_js_interop_checks/lib/src/transformations/js_util_optimizer.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -957,7 +957,7 @@ class ExtensionIndex {
957957
} else if (type is ExtensionType) {
958958
return isInteropExtensionType(type.extensionTypeDeclaration);
959959
} else if (type is TypeParameterType) {
960-
return isStaticInteropType(type.bound);
960+
return isStaticInteropType(type.nonTypeVariableBound);
961961
}
962962
return false;
963963
}

pkg/dart2wasm/lib/js/callback_specializer.dart

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ class CallbackSpecializer {
3535
if (_extensionIndex.isStaticInteropType(callbackParameterType) &&
3636
boxExternRef) {
3737
expression = _createJSValue(v);
38-
if (callbackParameterType.isPotentiallyNonNullable) {
38+
if (!callbackParameterType.isPotentiallyNullable) {
3939
expression = NullCheck(expression);
4040
}
4141
} else {
@@ -47,23 +47,24 @@ class CallbackSpecializer {
4747
return ReturnStatement(StaticInvocation(
4848
_util.jsifyTarget(function.returnType),
4949
Arguments([
50-
FunctionInvocation(
51-
FunctionAccessKind.FunctionType,
52-
AsExpression(VariableGet(callbackVariable), function),
53-
Arguments(callbackArguments),
54-
functionType: function),
50+
FunctionInvocation(FunctionAccessKind.FunctionType,
51+
VariableGet(callbackVariable), Arguments(callbackArguments),
52+
functionType: null),
5553
])));
5654
}
5755

58-
/// Creates a callback trampoline for the given [function]. This callback
59-
/// trampoline expects a Dart callback as its first argument, then an integer
60-
/// value(double type) indicating the position of the last defined
61-
/// argument(only for callbacks that take optional parameters), followed by
62-
/// all of the arguments to the Dart callback as JS objects. The trampoline
63-
/// will `dartifyRaw` all incoming JS objects and then cast them to their
64-
/// appropriate types, dispatch, and then `jsifyRaw` any returned value.
65-
/// [_createFunctionTrampoline] Returns a [String] function name representing
66-
/// the name of the wrapping function.
56+
/// Creates a callback trampoline for the given [function].
57+
///
58+
/// This callback trampoline expects a Dart callback as its first argument,
59+
/// then an integer value(double type) indicating the position of the last
60+
/// defined argument(only for callbacks that take optional parameters),
61+
/// followed by all of the arguments to the Dart callback as JS objects. The
62+
/// trampoline will `dartifyRaw` or box all incoming JS objects and then cast
63+
/// them to their appropriate types, dispatch, and then `jsifyRaw` or box any
64+
/// returned value.
65+
///
66+
/// Returns a [String] function name representing the name of the wrapping
67+
/// function.
6768
String _createFunctionTrampoline(Procedure node, FunctionType function,
6869
{required bool boxExternRef}) {
6970
// Create arguments for each positional parameter in the function. These

tests/lib/js/static_interop_test/js_function_conversions_test.dart

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ external JSBoolean boolF(JSBoolean b);
3030
external void voidF(JSString s);
3131

3232
@JS('jsFunction')
33-
external JSAny? anyF(JSAny? n);
33+
external JSAny? anyF([JSAny? n]);
3434

3535
@JS()
3636
external JSAny? callFunctionWithUndefined();
@@ -39,6 +39,7 @@ external JSAny? callFunctionWithUndefined();
3939
external JSAny? callFunctionWithJSNull();
4040

4141
void main() {
42+
// Test primitive conversions.
4243
jsFunction = ((String arg) => arg).toJS;
4344
expect(stringF('stringF'.toJS).toDart, 'stringF');
4445
Expect.throws(() => anyF(0.toJS));
@@ -70,6 +71,7 @@ void main() {
7071
jsFunction = ((String arg) => arg).toJS;
7172
voidF('voidF'.toJS);
7273

74+
// Test nullability with JS null and JS undefined.
7375
eval('''
7476
globalThis.callFunctionWithUndefined = function() {
7577
return globalThis.jsFunction(undefined);
@@ -108,4 +110,71 @@ void main() {
108110
expectNullFail(((String arg) => arg).toJS);
109111

110112
expectNullFail(((JSString arg) => arg).toJS);
113+
114+
// Test conversions with allowed type parameters.
115+
void setBoundAnyFunction<T extends JSAny?>() {
116+
jsFunction = ((T t) => t).toJS;
117+
}
118+
119+
final zero = 0.toJS;
120+
final empty = ''.toJS;
121+
122+
setBoundAnyFunction();
123+
expect(anyF(null), null);
124+
expect(anyF(zero), zero);
125+
setBoundAnyFunction<JSAny>();
126+
// TODO(srujzs): The commented out null checks do not throw. There should be a
127+
// check within the body of the callback that the parameter is the right
128+
// generic type, but there isn't.
129+
// Expect.throws(() => anyF());
130+
// Expect.throws(() => anyF(null));
131+
expect(anyF(zero), zero);
132+
setBoundAnyFunction<JSNumber>();
133+
// Expect.throws(() => anyF(null));
134+
Expect.throws(() {
135+
final any = anyF(empty);
136+
// TODO(54179): Better way of writing this is to cast to JSNumber and
137+
// convert, but currently that does not throw on dart2wasm.
138+
if (!any.typeofEquals('number')) {
139+
throw TypeError();
140+
}
141+
});
142+
expect(anyF(zero), zero);
143+
144+
void setBoundNonNullAnyFunction<T extends JSAny>() {
145+
jsFunction = ((T t) => t).toJS;
146+
}
147+
148+
setBoundNonNullAnyFunction();
149+
Expect.throws(() => anyF());
150+
if (hasSoundNullSafety) Expect.throws(() => anyF(null));
151+
expect(anyF(zero), zero);
152+
setBoundNonNullAnyFunction<JSNumber>();
153+
if (hasSoundNullSafety) Expect.throws(() => anyF(null));
154+
Expect.throws(() {
155+
final any = anyF(empty);
156+
// TODO(54179): Better way of writing this is to cast to JSNumber and
157+
// convert, but currently that does not throw on dart2wasm.
158+
if (!any.typeofEquals('number')) {
159+
throw TypeError();
160+
}
161+
});
162+
expect(anyF(zero), zero);
163+
164+
void setBoundJSNumberFunction<T extends JSNumber>() {
165+
jsFunction = ((T t) => t).toJS;
166+
}
167+
168+
setBoundJSNumberFunction();
169+
Expect.throws(() => anyF());
170+
if (hasSoundNullSafety) Expect.throws(() => anyF(null));
171+
Expect.throws(() {
172+
final any = anyF(empty);
173+
// TODO(54179): Better way of writing this is to cast to JSNumber and
174+
// convert, but currently that does not throw on dart2wasm.
175+
if (!any.typeofEquals('number')) {
176+
throw TypeError();
177+
}
178+
});
179+
expect(anyF(zero), zero);
111180
}

0 commit comments

Comments
 (0)