Skip to content

Commit 1f26ddf

Browse files
chloestefantsovaCommit Queue
authored and
Commit Queue
committed
[ffi] Account for position variance in converting native types
This CL introduces variance-dependent treatment of the `Handle` native type when converting native types to Dart types. Since `Handle` can represent any object, it should match any type. To achive that in cases when `Handle` appears as the subtype in the subtype checks, it's converted to `Never` in covariant positions and to `Object?` in covariant ones. TEST=existing Issue #49518 Change-Id: Ie16a210491ada80d21f4d0f1c0fa3b3804881ede Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/426880 Reviewed-by: Martin Kustermann <[email protected]> Reviewed-by: Daco Harkes <[email protected]> Commit-Queue: Chloe Stefantsova <[email protected]>
1 parent 72b0d2d commit 1f26ddf

File tree

7 files changed

+226
-45
lines changed

7 files changed

+226
-45
lines changed

pkg/analyzer/lib/src/generated/ffi_verifier.dart

+3-27
Original file line numberDiff line numberDiff line change
@@ -729,7 +729,6 @@ class FfiVerifier extends RecursiveAstVisitor<void> {
729729
dartType,
730730
nativeType,
731731
nativeFieldWrappersAsPointer: true,
732-
permissiveReturnType: true,
733732
)) {
734733
_errorReporter.atToken(
735734
errorToken,
@@ -1296,14 +1295,11 @@ class FfiVerifier extends RecursiveAstVisitor<void> {
12961295

12971296
/// Validates that the given [nativeType] is, when native types are converted
12981297
/// to their Dart equivalent, a subtype of [dartType].
1299-
/// [permissiveReturnType] means that the [direction] is ignored for return
1300-
/// types, and subtyping is allowed in either direction.
13011298
bool _validateCompatibleFunctionTypes(
13021299
_FfiTypeCheckDirection direction,
13031300
TypeImpl dartType,
13041301
TypeImpl nativeType, {
13051302
bool nativeFieldWrappersAsPointer = false,
1306-
bool permissiveReturnType = false,
13071303
}) {
13081304
// We require both to be valid function types.
13091305
if (dartType is! FunctionTypeImpl ||
@@ -1334,22 +1330,7 @@ class FfiVerifier extends RecursiveAstVisitor<void> {
13341330
}
13351331

13361332
// Validate that the return types are compatible.
1337-
if (permissiveReturnType) {
1338-
// TODO(dacoharkes): Fix inconsistency between `FfiNative` and
1339-
// `asFunction`. http://dartbug.com/49518.
1340-
if (!(_validateCompatibleNativeType(
1341-
_FfiTypeCheckDirection.nativeToDart,
1342-
dartType.returnType,
1343-
nativeType.returnType,
1344-
) ||
1345-
_validateCompatibleNativeType(
1346-
_FfiTypeCheckDirection.dartToNative,
1347-
dartType.returnType,
1348-
nativeType.returnType,
1349-
))) {
1350-
return false;
1351-
}
1352-
} else if (!_validateCompatibleNativeType(
1333+
if (!_validateCompatibleNativeType(
13531334
direction,
13541335
dartType.returnType,
13551336
nativeType.returnType,
@@ -1401,13 +1382,8 @@ class FfiVerifier extends RecursiveAstVisitor<void> {
14011382
// Don't allow other native subtypes if the Dart return type is void.
14021383
return nativeReturnType == _PrimitiveDartType.void_;
14031384
} else if (nativeReturnType == _PrimitiveDartType.handle) {
1404-
switch (direction) {
1405-
case _FfiTypeCheckDirection.dartToNative:
1406-
// Everything is a subtype of `Object?`.
1407-
return true;
1408-
case _FfiTypeCheckDirection.nativeToDart:
1409-
return typeSystem.isSubtypeOf(typeSystem.objectNone, dartType);
1410-
}
1385+
// `Handle` matches against any type in positions of any variance.
1386+
return true;
14111387
} else if (dartType is InterfaceTypeImpl &&
14121388
nativeType is InterfaceTypeImpl) {
14131389
if (nativeFieldWrappersAsPointer &&

pkg/vm/lib/modular/transformations/ffi/common.dart

+85-11
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,23 @@ enum FfiTypeCheckDirection {
172172
dartToNative,
173173
}
174174

175+
/// Converting mode of `Handle` in [convertNativeTypeToDartType].
176+
enum HandleToDartTypeConversionMode {
177+
/// The variance-based conversion mode, where `Handle` is converted to
178+
/// `Never` if it appears in covariant positions of the type being converted,
179+
/// or to `Object?` when it appears in contravariant positions of the type.
180+
/// This mode is used when the type being converted appears as the subtype of
181+
/// a type check.
182+
varianceBasedBehaviorForSubtype,
183+
184+
/// The variance-based conversion mode, where `Handle` is converted to
185+
/// `Object?` if it appears in covariant positions of the type being
186+
/// converted, or to `Never` when it appears in contravariant positions of
187+
/// the type. This mode is used when the type being converted appears as the
188+
/// supertype of a type check.
189+
varianceBasedBehaviorForSupertype,
190+
}
191+
175192
/// [FfiTransformer] contains logic which is shared between
176193
/// _FfiUseSiteTransformer and _FfiDefinitionTransformer.
177194
class FfiTransformer extends Transformer {
@@ -1080,16 +1097,38 @@ class FfiTransformer extends Transformer {
10801097
/// [Pointer]<T> -> [Pointer]<T>
10811098
/// T extends [Struct] -> T
10821099
/// T extends [Union] -> T
1083-
/// [Handle] -> [Object]
1100+
/// [Handle] -> [Object?]|[Never]
10841101
/// [NativeFunction]<T1 Function(T2, T3) -> S1 Function(S2, S3)
10851102
/// where DartRepresentationOf(Tn) -> Sn
10861103
/// ```
1104+
///
1105+
/// If [mode] is [HandleToDartTypeConversionMode.oldNativeBehavior], `Handle`
1106+
/// is converted to `Object?`. Otherwise, `Handle` is converted to either
1107+
/// `Object?` or `Never`. Since `Handle` represents any possible Dart object,
1108+
/// it should match any type. To achieve that, in the subtype checks where
1109+
/// `Handle` appears as the subtype (as indicated by [mode] being
1110+
/// [HandleToDartTypeConversionMode.varianceBasedBehaviorForSubtype]), it
1111+
/// should be converted depending on the variance of its position: `Never` for
1112+
/// covariant positions, and `Object?` for contravariant ones. If `Handle`
1113+
/// appears as the supertype in the check (as indicated by [mode] being
1114+
/// [HandleToDartTypeConversionMode.varianceBasedBehaviorForSupertype]), the
1115+
/// treatment of covariant and contravariant positions is reversed. For more
1116+
/// details, see https://github.com/dart-lang/sdk/issues/49518.
1117+
///
1118+
/// [isCovariant] is the flag that helps compute the variance of the position
1119+
/// in [nativeType]. It's updated in the recursive invocations of
1120+
/// [convertNativeTypeToDartType] and should be omitted in regular call sites.
1121+
/// [isCovariant] is ignored if [mode] is
1122+
/// [HandleToDartTypeConversionMode.oldNativeBehavior].
10871123
DartType? convertNativeTypeToDartType(
10881124
DartType nativeType, {
10891125
bool allowStructAndUnion = false,
10901126
bool allowHandle = false,
10911127
bool allowInlineArray = false,
10921128
bool allowVoid = false,
1129+
HandleToDartTypeConversionMode mode =
1130+
HandleToDartTypeConversionMode.varianceBasedBehaviorForSubtype,
1131+
Variance variance = Variance.invariant,
10931132
}) {
10941133
if (nativeType is! InterfaceType) {
10951134
return null;
@@ -1146,7 +1185,22 @@ class FfiTransformer extends Transformer {
11461185
return VoidType();
11471186
}
11481187
if (nativeType_ == NativeType.kHandle && allowHandle) {
1149-
return coreTypes.objectNonNullableRawType;
1188+
switch (mode) {
1189+
case HandleToDartTypeConversionMode.varianceBasedBehaviorForSubtype:
1190+
return switch (variance) {
1191+
Variance.contravariant => coreTypes.objectNullableRawType,
1192+
Variance.covariant ||
1193+
Variance.invariant ||
1194+
Variance.unrelated => const NeverType.nonNullable(),
1195+
};
1196+
case HandleToDartTypeConversionMode.varianceBasedBehaviorForSupertype:
1197+
return switch (variance) {
1198+
Variance.contravariant => const NeverType.nonNullable(),
1199+
Variance.covariant ||
1200+
Variance.invariant ||
1201+
Variance.unrelated => coreTypes.objectNullableRawType,
1202+
};
1203+
}
11501204
}
11511205
if (nativeType_ != NativeType.kNativeFunction ||
11521206
native.typeArguments[0] is! FunctionType) {
@@ -1165,6 +1219,8 @@ class FfiTransformer extends Transformer {
11651219
allowStructAndUnion: true,
11661220
allowHandle: true,
11671221
allowVoid: true,
1222+
mode: mode,
1223+
variance: variance,
11681224
);
11691225
if (returnType == null) return null;
11701226
final argumentTypes = <DartType>[];
@@ -1174,6 +1230,8 @@ class FfiTransformer extends Transformer {
11741230
paramDartType,
11751231
allowStructAndUnion: true,
11761232
allowHandle: true,
1233+
mode: mode,
1234+
variance: variance.combine(Variance.contravariant),
11771235
) ??
11781236
dummyDartType,
11791237
);
@@ -1856,17 +1914,21 @@ class FfiTransformer extends Transformer {
18561914
bool allowVoid = false,
18571915
bool allowArray = false,
18581916
}) {
1859-
final DartType correspondingDartType =
1860-
convertNativeTypeToDartType(
1861-
nativeType,
1862-
allowStructAndUnion: true,
1863-
allowHandle: allowHandle,
1864-
allowInlineArray: allowArray,
1865-
allowVoid: allowVoid,
1866-
)!;
1867-
if (dartType == correspondingDartType) return correspondingDartType;
1917+
final DartType correspondingDartType;
18681918
switch (direction) {
18691919
case FfiTypeCheckDirection.nativeToDart:
1920+
correspondingDartType =
1921+
convertNativeTypeToDartType(
1922+
nativeType,
1923+
allowStructAndUnion: true,
1924+
allowHandle: allowHandle,
1925+
allowInlineArray: allowArray,
1926+
allowVoid: allowVoid,
1927+
mode:
1928+
HandleToDartTypeConversionMode
1929+
.varianceBasedBehaviorForSubtype,
1930+
variance: Variance.covariant,
1931+
)!;
18701932
if (env.isSubtypeOf(
18711933
correspondingDartType,
18721934
dartType,
@@ -1888,6 +1950,18 @@ class FfiTransformer extends Transformer {
18881950
}
18891951
}
18901952
case FfiTypeCheckDirection.dartToNative:
1953+
correspondingDartType =
1954+
convertNativeTypeToDartType(
1955+
nativeType,
1956+
allowStructAndUnion: true,
1957+
allowHandle: allowHandle,
1958+
allowInlineArray: allowArray,
1959+
allowVoid: allowVoid,
1960+
mode:
1961+
HandleToDartTypeConversionMode
1962+
.varianceBasedBehaviorForSupertype,
1963+
variance: Variance.covariant,
1964+
)!;
18911965
if (env.isSubtypeOf(
18921966
dartType,
18931967
correspondingDartType,

pkg/vm/lib/modular/transformations/ffi/definitions.dart

+8
Original file line numberDiff line numberDiff line change
@@ -530,7 +530,15 @@ class _FfiDefinitionTransformer extends FfiTransformer {
530530
allowStructAndUnion: true,
531531
allowHandle: false,
532532
);
533+
534+
// Since fields induce both setters and getters, the type checks should
535+
// be made both ways.
533536
if (shouldBeDartType == null ||
537+
!env.isSubtypeOf(
538+
shouldBeDartType,
539+
type,
540+
SubtypeCheckMode.ignoringNullabilities,
541+
) ||
534542
!env.isSubtypeOf(
535543
type,
536544
shouldBeDartType,

runtime/bin/ffi_test/ffi_test_functions_vmspecific.cc

+7
Original file line numberDiff line numberDiff line change
@@ -989,6 +989,13 @@ DART_EXPORT Dart_Handle PassObjectToC(Dart_Handle h) {
989989
return return_value;
990990
}
991991

992+
DART_EXPORT Dart_Handle
993+
CallClosureWithArgumentViaHandle(Dart_Handle (*callback)(Dart_Handle),
994+
Dart_Handle argument) {
995+
printf("CallClosureWithArgumentViaHandle %p %p\n", callback, argument);
996+
return callback(argument);
997+
}
998+
992999
DART_EXPORT void ClosureCallbackThroughHandle(void (*callback)(Dart_Handle),
9931000
Dart_Handle closureHandle) {
9941001
printf("ClosureCallbackThroughHandle %p %p\n", callback, closureHandle);

tests/ffi/static_checks/vmspecific_static_checks_test.dart

+2-7
Original file line numberDiff line numberDiff line change
@@ -926,13 +926,8 @@ void testHandleVariance() {
926926
testLibrary.lookupFunction<Handle Function(Handle), Object Function(MyClass)>(
927927
"PassObjectToC");
928928

929-
// Requiring a more specific return type is not, this requires a cast from
930-
// the user.
929+
// Returning a more specific type than Handle is okay too: it will be checked at runtime.
931930
testLibrary.lookupFunction<Handle Function(Handle), MyClass Function(Object)>(
932-
// ^^^^^^^^^^^^^^^^^^^^^^^^
933-
// [analyzer] COMPILE_TIME_ERROR.MUST_BE_A_SUBTYPE
934-
// ^
935-
// [cfe] Expected type 'MyClass Function(Object)' to be 'Object Function(Object)', which is the Dart type corresponding to 'NativeFunction<Handle Function(Handle)>'.
936931
"PassObjectToC");
937932
}
938933

@@ -1451,7 +1446,7 @@ void testReturnVoidNotVoid() {
14511446
// Taking a more specific argument is okay.
14521447
testLibrary.lookupFunction<Handle Function(), void Function()>("unused");
14531448
// ^
1454-
// [cfe] Expected type 'void Function()' to be 'Object Function()', which is the Dart type corresponding to 'NativeFunction<Handle Function()>'.
1449+
// [cfe] Expected type 'void Function()' to be 'Never Function()', which is the Dart type corresponding to 'NativeFunction<Handle Function()>'.
14551450
// ^^^^^^^^^^^^^^^
14561451
// [analyzer] COMPILE_TIME_ERROR.MUST_BE_A_SUBTYPE
14571452
}

tests/ffi/vmspecific_ffi_native_handles_test.dart

+88
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import 'dart:ffi';
88
import 'dart:typed_data';
99

10+
import 'package:expect/expect.dart';
11+
1012
import 'dylib_utils.dart';
1113

1214
final ffiTestFunctions = dlopenPlatformSpecific("ffi_test_functions");
@@ -17,8 +19,94 @@ void main() {
1719
testMixed();
1820
testManyHandlesAllDifferent();
1921
testManyHandlesAllSame();
22+
testCallbackHandleCases();
23+
}
24+
25+
void testHandleInReturn() {
26+
final string = 'foo';
27+
Expect.identical(string, stringFromString(string));
28+
Expect.identical(string, stringFromObject(string));
29+
Expect.identical(42, intFromInt(42));
30+
Expect.identical(42, intFromObject(42));
31+
// TODO(https://dartbug.com/49518): Uncomment the lines below when the
32+
// runtime checks are added.
33+
//
34+
// Expect.throws(() => stringFromObject(Object()));
35+
// Expect.throws(() => stringFromObject(42));
36+
// Expect.throws(() => intFromObject(Object()));
37+
// Expect.throws(() => intFromObject(string));
2038
}
2139

40+
@Native<Handle Function(Handle)>(symbol: "PassObjectToC")
41+
external String stringFromString(String arg);
42+
43+
@Native<Handle Function(Handle)>(symbol: "PassObjectToC")
44+
external String stringFromObject(Object arg);
45+
46+
@Native<Handle Function(Handle)>(symbol: "PassObjectToC")
47+
external int intFromInt(int arg);
48+
49+
@Native<Handle Function(Handle)>(symbol: "PassObjectToC")
50+
external int intFromObject(Object arg);
51+
52+
void testCallbackHandleCases() {
53+
final o = Object();
54+
final s = 'foo';
55+
56+
Object returnObject(Object obj) => obj;
57+
final nc0 = NativeCallable<Handle Function(Handle)>.isolateLocal(returnObject)
58+
..keepIsolateAlive = false;
59+
Expect.identical(o, callClosureWithArgumentViaHandle(nc0.nativeFunction, o));
60+
Expect.identical(s, callClosureWithArgumentViaHandle(nc0.nativeFunction, s));
61+
Expect.identical(
62+
42,
63+
callClosureWithArgumentViaHandle(nc0.nativeFunction, 42),
64+
);
65+
// TODO(https://dartbug.com/49518): Uncomment the lines below when the
66+
// runtime checks are added.
67+
//
68+
// Expect.throws(
69+
// () => callClosureWithArgumentViaHandle(nc0.nativeFunction, null),
70+
// );
71+
72+
int returnInt(int obj) => obj;
73+
final nc1 = NativeCallable<Handle Function(Handle)>.isolateLocal(returnInt)
74+
..keepIsolateAlive = false;
75+
Expect.identical(
76+
42,
77+
callClosureWithArgumentViaHandle(nc1.nativeFunction, 42),
78+
);
79+
// TODO(https://dartbug.com/49518): Uncomment the lines below when the
80+
// runtime checks are added.
81+
//
82+
// Expect.throws(() => callClosureWithArgumentViaHandle(nc1.nativeFunction, o));
83+
// Expect.throws(() => callClosureWithArgumentViaHandle(nc1.nativeFunction, s));
84+
// Expect.throws(
85+
// () => callClosureWithArgumentViaHandle(nc1.nativeFunction, null),
86+
// );
87+
88+
String returnString(String obj) => obj;
89+
final nc2 = NativeCallable<Handle Function(Handle)>.isolateLocal(returnString)
90+
..keepIsolateAlive = false;
91+
Expect.identical(s, callClosureWithArgumentViaHandle(nc2.nativeFunction, s));
92+
// TODO(https://dartbug.com/49518): Uncomment the lines below when the
93+
// runtime checks are added.
94+
//
95+
// Expect.throws(() => callClosureWithArgumentViaHandle(nc2.nativeFunction, o));
96+
// Expect.throws(() => callClosureWithArgumentViaHandle(nc2.nativeFunction, 42));
97+
// Expect.throws(
98+
// () => callClosureWithArgumentViaHandle(nc2.nativeFunction, null),
99+
// );
100+
}
101+
102+
@Native<
103+
Handle Function(Pointer<NativeFunction<Handle Function(Handle)>>, Handle)
104+
>(symbol: "CallClosureWithArgumentViaHandle")
105+
external Object? callClosureWithArgumentViaHandle(
106+
Pointer<NativeFunction<Handle Function(Handle)>> callback,
107+
Object? argument,
108+
);
109+
22110
void testMixed() {
23111
callUpdateNode(
24112
id: 42,

0 commit comments

Comments
 (0)