Skip to content

Commit 3cce6fc

Browse files
dcharkescommit-bot@chromium.org
authored andcommitted
[vm/ffi] Fix direct interaction with Pointer<Native>
This CL changes the static checks and runtime behavior of Pointer<T>.store<T>(T v) and T Pointer<T>.load() behavior to be consistent with the way Dart handles instances methods with generics. For more details see the issue. Fixes: #37254 Change-Id: Ifcf89646f8e357d8592c38bb340942d522dac941 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/107404 Commit-Queue: Daco Harkes <[email protected]> Reviewed-by: Martin Kustermann <[email protected]>
1 parent 9edecbc commit 3cce6fc

File tree

5 files changed

+51
-71
lines changed

5 files changed

+51
-71
lines changed

pkg/vm/lib/transformations/ffi_use_sites.dart

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -192,17 +192,20 @@ class _FfiUseSiteTransformer extends FfiTransformer {
192192
}
193193

194194
void _ensureNativeTypeToDartType(
195-
DartType nativeType, DartType dartType, Expression node) {
196-
DartType shouldBeDartType = convertNativeTypeToDartType(nativeType);
197-
if (dartType != shouldBeDartType) {
198-
diagnosticReporter.report(
199-
templateFfiTypeMismatch.withArguments(
200-
dartType, shouldBeDartType, nativeType),
201-
node.fileOffset,
202-
1,
203-
node.location.file);
204-
throw _FfiStaticTypeError();
205-
}
195+
DartType containerTypeArg, DartType elementType, Expression node) {
196+
final DartType shouldBeElementType =
197+
convertNativeTypeToDartType(containerTypeArg);
198+
if (elementType == shouldBeElementType) return;
199+
// Both subtypes and implicit downcasts are allowed statically.
200+
if (env.isSubtypeOf(shouldBeElementType, elementType)) return;
201+
if (env.isSubtypeOf(elementType, shouldBeElementType)) return;
202+
diagnosticReporter.report(
203+
templateFfiTypeMismatch.withArguments(
204+
elementType, shouldBeElementType, containerTypeArg),
205+
node.fileOffset,
206+
1,
207+
node.location.file);
208+
throw _FfiStaticTypeError();
206209
}
207210

208211
void _ensureNativeTypeValid(DartType nativeType, Expression node) {

runtime/lib/ffi.cc

Lines changed: 36 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,6 @@ namespace dart {
2626
// Some checks are only performed at runtime to allow for generic code, these
2727
// throw ArgumentExceptions.
2828

29-
static void ThrowTypeArgumentError(const AbstractType& type_arg,
30-
const char* expected) {
31-
const String& error = String::Handle(String::NewFormatted(
32-
"Type argument (%s) should be a %s",
33-
String::Handle(type_arg.UserVisibleName()).ToCString(), expected));
34-
Exceptions::ThrowArgumentError(error);
35-
}
36-
3729
static bool IsPointerType(const AbstractType& type) {
3830
// Do a fast check for predefined types.
3931
classid_t type_cid = type.type_class_id();
@@ -54,38 +46,15 @@ static bool IsPointerType(const AbstractType& type) {
5446
return type.IsSubtypeOf(pointer_type, Heap::kNew);
5547
}
5648

57-
static bool IsConcreteNativeType(const AbstractType& type) {
58-
// Do a fast check for predefined types.
59-
classid_t type_cid = type.type_class_id();
60-
if (RawObject::IsFfiNativeTypeTypeClassId(type_cid)) {
61-
return false;
62-
}
63-
if (RawObject::IsFfiTypeClassId(type_cid)) {
64-
return true;
65-
}
66-
67-
// Do a slow check for subtyping.
68-
const Class& native_type_class = Class::Handle(
69-
Isolate::Current()->object_store()->ffi_native_type_class());
70-
AbstractType& native_type_type =
71-
AbstractType::Handle(native_type_class.DeclarationType());
72-
return type.IsSubtypeOf(native_type_type, Heap::kNew);
73-
}
74-
75-
static void CheckIsConcreteNativeType(const AbstractType& type) {
76-
if (!IsConcreteNativeType(type)) {
77-
ThrowTypeArgumentError(type, "concrete sub type of NativeType");
78-
}
79-
}
80-
8149
static bool IsNativeFunction(const AbstractType& type_arg) {
8250
classid_t type_cid = type_arg.type_class_id();
8351
return RawObject::IsFfiTypeNativeFunctionClassId(type_cid);
8452
}
8553

8654
static void CheckSized(const AbstractType& type_arg) {
87-
classid_t type_cid = type_arg.type_class_id();
88-
if (RawObject::IsFfiTypeVoidClassId(type_cid) ||
55+
const classid_t type_cid = type_arg.type_class_id();
56+
if (RawObject::IsFfiNativeTypeTypeClassId(type_cid) ||
57+
RawObject::IsFfiTypeVoidClassId(type_cid) ||
8958
RawObject::IsFfiTypeNativeFunctionClassId(type_cid)) {
9059
const String& error = String::Handle(String::NewFormatted(
9160
"%s does not have a predefined size (@unsized). "
@@ -98,6 +67,8 @@ static void CheckSized(const AbstractType& type_arg) {
9867
}
9968
}
10069

70+
enum class FfiVariance { kInvariant = 0, kCovariant = 1, kContravariant = 2 };
71+
10172
// Checks that a dart type correspond to a [NativeType].
10273
// Because this is checked already in a kernel transformation, it does not throw
10374
// an ArgumentException but a boolean which should be asserted.
@@ -118,7 +89,8 @@ static void CheckSized(const AbstractType& type_arg) {
11889
// [NativeFunction]<T1 Function(T2, T3) -> S1 Function(S2, S3)
11990
// where DartRepresentationOf(Tn) -> Sn
12091
static bool DartAndCTypeCorrespond(const AbstractType& native_type,
121-
const AbstractType& dart_type) {
92+
const AbstractType& dart_type,
93+
FfiVariance variance) {
12294
classid_t native_type_cid = native_type.type_class_id();
12395
if (RawObject::IsFfiTypeIntClassId(native_type_cid)) {
12496
return dart_type.IsSubtypeOf(AbstractType::Handle(Type::IntType()),
@@ -129,7 +101,13 @@ static bool DartAndCTypeCorrespond(const AbstractType& native_type,
129101
Heap::kNew);
130102
}
131103
if (RawObject::IsFfiPointerClassId(native_type_cid)) {
132-
return native_type.Equals(dart_type) || dart_type.IsNullType();
104+
return (variance == FfiVariance::kInvariant &&
105+
dart_type.Equals(native_type)) ||
106+
(variance == FfiVariance::kCovariant &&
107+
dart_type.IsSubtypeOf(native_type, Heap::kNew)) ||
108+
(variance == FfiVariance::kContravariant &&
109+
native_type.IsSubtypeOf(dart_type, Heap::kNew)) ||
110+
dart_type.IsNullType();
133111
}
134112
if (RawObject::IsFfiTypeNativeFunctionClassId(native_type_cid)) {
135113
if (!dart_type.IsFunctionType()) {
@@ -161,20 +139,34 @@ static bool DartAndCTypeCorrespond(const AbstractType& native_type,
161139
}
162140
if (!DartAndCTypeCorrespond(
163141
AbstractType::Handle(nativefunction_function.result_type()),
164-
AbstractType::Handle(dart_function.result_type()))) {
142+
AbstractType::Handle(dart_function.result_type()), variance)) {
165143
return false;
166144
}
167145
for (intptr_t i = 0; i < dart_function.NumParameters(); i++) {
168146
if (!DartAndCTypeCorrespond(
169147
AbstractType::Handle(nativefunction_function.ParameterTypeAt(i)),
170-
AbstractType::Handle(dart_function.ParameterTypeAt(i)))) {
148+
AbstractType::Handle(dart_function.ParameterTypeAt(i)),
149+
variance)) {
171150
return false;
172151
}
173152
}
174153
}
175154
return true;
176155
}
177156

157+
static void CheckDartAndCTypeCorrespond(const AbstractType& native_type,
158+
const AbstractType& dart_type,
159+
FfiVariance variance) {
160+
if (!DartAndCTypeCorrespond(native_type, dart_type, variance)) {
161+
const String& error = String::Handle(String::NewFormatted(
162+
"Expected type '%s' to be different, it should be "
163+
"DartRepresentationOf('%s').",
164+
String::Handle(dart_type.UserVisibleName()).ToCString(),
165+
String::Handle(native_type.UserVisibleName()).ToCString()));
166+
Exceptions::ThrowArgumentError(error);
167+
}
168+
}
169+
178170
// The following functions are runtime checks on arguments.
179171

180172
// Note that expected_from and expected_to are inclusive.
@@ -224,7 +216,6 @@ DEFINE_NATIVE_ENTRY(Ffi_allocate, 1, 1) {
224216
// Pointer. https://github.com/dart-lang/sdk/issues/35782
225217
GET_NATIVE_TYPE_ARGUMENT(type_arg, arguments->NativeTypeArgAt(0));
226218

227-
CheckIsConcreteNativeType(type_arg);
228219
CheckSized(type_arg);
229220

230221
GET_NON_NULL_NATIVE_ARGUMENT(Integer, argCount, arguments->NativeArgAt(0));
@@ -251,7 +242,6 @@ DEFINE_NATIVE_ENTRY(Ffi_fromAddress, 1, 1) {
251242
TypeArguments& type_args = TypeArguments::Handle(type_arg.arguments());
252243
AbstractType& native_type = AbstractType::Handle(
253244
type_args.TypeAtNullSafe(Pointer::kNativeTypeArgPos));
254-
CheckIsConcreteNativeType(native_type);
255245
GET_NON_NULL_NATIVE_ARGUMENT(Integer, arg_ptr, arguments->NativeArgAt(0));
256246

257247
// TODO(dacoharkes): should this return NULL if address is 0?
@@ -298,7 +288,6 @@ DEFINE_NATIVE_ENTRY(Ffi_cast, 1, 1) {
298288
TypeArguments& type_args = TypeArguments::Handle(type_arg.arguments());
299289
AbstractType& native_type = AbstractType::Handle(
300290
type_args.TypeAtNullSafe(Pointer::kNativeTypeArgPos));
301-
CheckIsConcreteNativeType(native_type);
302291

303292
const Integer& address = Integer::Handle(zone, pointer.GetCMemoryAddress());
304293
RawPointer* result =
@@ -379,7 +368,8 @@ DEFINE_NATIVE_ENTRY(Ffi_load, 1, 1) {
379368
AbstractType& pointer_type_arg =
380369
AbstractType::Handle(pointer.type_argument());
381370
CheckSized(pointer_type_arg);
382-
ASSERT(DartAndCTypeCorrespond(pointer_type_arg, type_arg));
371+
CheckDartAndCTypeCorrespond(pointer_type_arg, type_arg,
372+
FfiVariance::kContravariant);
383373

384374
uint8_t* address = reinterpret_cast<uint8_t*>(
385375
Integer::Handle(pointer.GetCMemoryAddress()).AsInt64Value());
@@ -460,7 +450,8 @@ DEFINE_NATIVE_ENTRY(Ffi_store, 0, 2) {
460450
AbstractType& pointer_type_arg =
461451
AbstractType::Handle(pointer.type_argument());
462452
CheckSized(pointer_type_arg);
463-
ASSERT(DartAndCTypeCorrespond(pointer_type_arg, arg_type));
453+
CheckDartAndCTypeCorrespond(pointer_type_arg, arg_type,
454+
FfiVariance::kCovariant);
464455

465456
classid_t type_cid = pointer_type_arg.type_class_id();
466457
StoreValue(zone, pointer, type_cid, new_value);
@@ -469,7 +460,6 @@ DEFINE_NATIVE_ENTRY(Ffi_store, 0, 2) {
469460

470461
DEFINE_NATIVE_ENTRY(Ffi_sizeOf, 1, 0) {
471462
GET_NATIVE_TYPE_ARGUMENT(type_arg, arguments->NativeTypeArgAt(0));
472-
CheckIsConcreteNativeType(type_arg);
473463
CheckSized(type_arg);
474464

475465
classid_t type_cid = type_arg.type_class_id();
@@ -523,7 +513,8 @@ DEFINE_NATIVE_ENTRY(Ffi_asFunction, 1, 1) {
523513
AbstractType::Handle(pointer.type_argument());
524514
ASSERT(IsNativeFunction(pointer_type_arg));
525515
GET_NATIVE_TYPE_ARGUMENT(type_arg, arguments->NativeTypeArgAt(0));
526-
ASSERT(DartAndCTypeCorrespond(pointer_type_arg, type_arg));
516+
CheckDartAndCTypeCorrespond(pointer_type_arg, type_arg,
517+
FfiVariance::kInvariant);
527518

528519
Function& dart_signature = Function::Handle(Type::Cast(type_arg).signature());
529520
TypeArguments& nativefunction_type_args =

tests/ffi/data_test.dart

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -125,9 +125,7 @@ void testCastGeneric2() {
125125

126126
void testCastNativeType() {
127127
ffi.Pointer<ffi.Int64> p = ffi.allocate();
128-
Expect.throws(() {
129-
p.cast<ffi.Pointer>();
130-
});
128+
p.cast<ffi.Pointer>();
131129
p.free();
132130
}
133131

tests/ffi/ffi.status

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
# for details. All rights reserved. Use of this source code is governed by a
33
# BSD-style license that can be found in the LICENSE file.
44

5-
regress_37254_test:Skip # dartbug.com/37254
6-
75
[ $arch == simdbc ]
86
*: Skip # FFI not yet supported on SimDBC32: dartbug.com/36809
97

tests/ffi/regress_37254_test.dart

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,6 @@ void store2() {
8383

8484
// Successful implicit downcast of argument at runtime.
8585
// Should succeed now, should statically be rejected when NNBD lands.
86-
// TODO(37254): Currently rejected by the frontend.
8786
a.store(b);
8887

8988
a.free();
@@ -92,12 +91,10 @@ void store2() {
9291

9392
void store3() {
9493
final Pointer<Pointer<Int8>> a = allocate<Pointer<Int8>>();
95-
// TODO(37254): We currently disallow obtaining a Pointer<NativeType>.
9694
final Pointer<NativeType> b = allocate<Int8>().cast<Pointer<NativeType>>();
9795

9896
// Failing implicit downcast of argument at runtime.
9997
// Should fail now at runtime, should statically be rejected when NNBD lands.
100-
// TODO(37254): Currently rejected by the frontend.
10198
Expect.throws(() {
10299
a.store(b);
103100
});
@@ -111,7 +108,6 @@ void store4() {
111108
Pointer<Int8>>(); // Reified as Pointer<Pointer<Int8>> at runtime.
112109
final Pointer<Int8> b = allocate<Int8>();
113110

114-
// TODO(37254): Currently rejected by the frontend.
115111
a.store(b);
116112

117113
a.free();
@@ -133,7 +129,6 @@ void store5() {
133129
void store6() {
134130
final Pointer<Pointer<NativeType>> a = allocate<
135131
Pointer<Int8>>(); // Reified as Pointer<Pointer<Int8>> at runtime.
136-
// TODO(37254): We currently disallow obtaining a Pointer<NativeType>.
137132
final Pointer<NativeType> b = allocate<Int8>().cast<Pointer<NativeType>>();
138133

139134
// Fails on type check of argument.
@@ -149,7 +144,6 @@ void store7() {
149144
final Pointer<Pointer<NativeType>> a = allocate<Pointer<NativeType>>();
150145
final Pointer<Int8> b = allocate<Int8>();
151146

152-
// TODO(37254): Currently rejected by the frontend.
153147
a.store(b);
154148

155149
a.free();
@@ -161,7 +155,6 @@ void store8() {
161155
final Pointer<NativeType> b =
162156
allocate<Int8>(); // Reified as Pointer<Int8> at runtime.
163157

164-
// TODO(37254): Currently hits assertion in VM.
165158
a.store(b);
166159

167160
a.free();
@@ -170,7 +163,6 @@ void store8() {
170163

171164
void store9() {
172165
final Pointer<Pointer<NativeType>> a = allocate<Pointer<NativeType>>();
173-
// TODO(37254): We currently disallow obtaining a Pointer<NativeType>.
174166
final Pointer<NativeType> b = allocate<Int8>().cast<Pointer<NativeType>>();
175167

176168
a.store(b);
@@ -203,7 +195,6 @@ void load3() {
203195
final Pointer<Pointer<NativeType>> a = allocate<
204196
Pointer<Int8>>(); // Reified as Pointer<Pointer<Int8>> at runtime.
205197

206-
// TODO(37254): Currently hits assertion in VM.
207198
Pointer<Int8> b = a.load<Pointer<NativeType>>();
208199
Expect.type<Pointer<Int8>>(b);
209200

@@ -215,7 +206,6 @@ void load4() {
215206
Pointer<Int8>>(); // Reified as Pointer<Pointer<Int8>> at runtime.
216207

217208
// Return value runtime type is Pointer<Int8>.
218-
// TODO(37254): Currently hits assertion in VM.
219209
Pointer<NativeType> b = a.load();
220210
Expect.type<Pointer<Int8>>(b);
221211

0 commit comments

Comments
 (0)