[vm/ffi/test] Replaces FfiNative transform test with expect.
We can't rely on GC to trigger the finalizer of a given test object,
so the previous liveness test was unreliable.
Instead we add an expect test to verify we generate the necessary
`reachabilityFence(..)`s to ensure liveness.
TEST=Adds expect.
Bug: https://github.com/dart-lang/sdk/issues/47362
Change-Id: Ia57a07522c8b8265b24780f00f3339b50534eb60
Cq-Include-Trybots: luci.dart.try:vm-kernel-precomp-nnbd-mac-release-arm64-try,vm-kernel-nnbd-mac-debug-x64-try,vm-kernel-mac-product-x64-try
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/215542
Commit-Queue: Clement Skau <cskau@google.com>
Reviewed-by: Daco Harkes <dacoharkes@google.com>
diff --git a/pkg/vm/lib/transformations/ffi_native.dart b/pkg/vm/lib/transformations/ffi_native.dart
index 0752ed2..3427f28 100644
--- a/pkg/vm/lib/transformations/ffi_native.dart
+++ b/pkg/vm/lib/transformations/ffi_native.dart
@@ -103,9 +103,9 @@
node.function.computeThisFunctionType(Nullability.nonNullable);
// Double Function(Double)
final nativeType = annotationConst.typeArguments[0] as FunctionType;
- // InterfaceType(NativeFunction<Double Function(Double)>*)
- final DartType nativeInterfaceType =
- InterfaceType(nativeFunctionClass, Nullability.legacy, [nativeType]);
+ // InterfaceType(NativeFunction<Double Function(Double)>)
+ final DartType nativeInterfaceType = InterfaceType(
+ nativeFunctionClass, Nullability.nonNullable, [nativeType]);
// Derive number of arguments from the native function signature.
final args_n = nativeType.positionalParameters.length;
diff --git a/pkg/vm/lib/transformations/ffi_use_sites.dart b/pkg/vm/lib/transformations/ffi_use_sites.dart
index a90f1ec..2dc593a 100644
--- a/pkg/vm/lib/transformations/ffi_use_sites.dart
+++ b/pkg/vm/lib/transformations/ffi_use_sites.dart
@@ -398,7 +398,7 @@
env.isSubtypeOf(ffiParams[i], pointerType,
SubtypeCheckMode.ignoringNullabilities)) {
// final NativeFieldWrapperClass1 #t1 = MyNFWC1();.
- final tmpPtr = VariableDeclaration('',
+ final tmpPtr = VariableDeclaration(null,
initializer: origArgs[i],
type: nativeFieldWrapperClassType,
isFinal: true);
@@ -419,7 +419,7 @@
}
// Note: We also evaluate, and assign temporaries for, non-wrapped
// arguments as we need to preserve the original evaluation order.
- final tmpArg = VariableDeclaration('',
+ final tmpArg = VariableDeclaration(null,
initializer: origArgs[i], isFinal: true);
tmpsArgs.add(tmpArg);
callArgs.add(VariableGet(tmpArg));
@@ -434,7 +434,7 @@
// reachabilityFence(#t1);
// } => #t0
final tmpResult =
- VariableDeclaration('', type: target.function.returnType);
+ VariableDeclaration(null, type: target.function.returnType);
return BlockExpression(
Block([
tmpResult,
diff --git a/pkg/vm/test/transformations/ffinative_test.dart b/pkg/vm/test/transformations/ffinative_test.dart
new file mode 100644
index 0000000..76eba47
--- /dev/null
+++ b/pkg/vm/test/transformations/ffinative_test.dart
@@ -0,0 +1,58 @@
+// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io';
+
+import 'package:kernel/ast.dart';
+import 'package:kernel/kernel.dart';
+import 'package:kernel/reference_from_index.dart';
+import 'package:kernel/target/targets.dart';
+import 'package:kernel/verifier.dart';
+
+import 'package:test/test.dart';
+
+import 'package:vm/transformations/ffi_native.dart' show transformLibraries;
+
+import '../common_test_utils.dart';
+
+final String pkgVmDir = Platform.script.resolve('../..').toFilePath();
+
+class TestDiagnosticReporter extends DiagnosticReporter<Object, Object> {
+ @override
+ void report(Object message, int charOffset, int length, Uri? fileUri,
+ {List<Object>? context}) {/* nop */}
+}
+
+runTestCase(Uri source) async {
+ final target = TestingVmTarget(TargetFlags());
+
+ Component component = await compileTestCaseToKernelProgram(source,
+ target: target, experimentalFlags: ['generic-metadata']);
+
+ final ReferenceFromIndex? referenceFromIndex = null;
+ final DiagnosticReporter diagnosticReporter = TestDiagnosticReporter();
+
+ transformLibraries(
+ component, component.libraries, diagnosticReporter, referenceFromIndex);
+
+ verifyComponent(component);
+
+ final actual = kernelLibraryToString(component.mainMethod!.enclosingLibrary);
+
+ compareResultWithExpectationsFile(source, actual);
+}
+
+main() {
+ group('ffi-transformations', () {
+ final testCasesDir = Directory(pkgVmDir + '/testcases/transformations/ffi');
+
+ for (var entry in testCasesDir
+ .listSync(recursive: true, followLinks: false)
+ .reversed) {
+ if (entry.path.endsWith(".dart")) {
+ test(entry.path, () => runTestCase(entry.uri));
+ }
+ }
+ });
+}
diff --git a/pkg/vm/testcases/transformations/ffi/ffinative.dart b/pkg/vm/testcases/transformations/ffi/ffinative.dart
new file mode 100644
index 0000000..5cb2397
--- /dev/null
+++ b/pkg/vm/testcases/transformations/ffi/ffinative.dart
@@ -0,0 +1,27 @@
+// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// Tests for @FfiNative related transformations.
+
+// @dart=2.14
+
+import 'dart:ffi';
+import 'dart:nativewrappers';
+
+@FfiNative<IntPtr Function(IntPtr)>('ReturnIntPtr')
+external int returnIntPtr(int x);
+
+@FfiNative<IntPtr Function(IntPtr)>('ReturnIntPtr', isLeaf: true)
+external int returnIntPtrLeaf(int x);
+
+class Classy {
+ @FfiNative<IntPtr Function(IntPtr)>('ReturnIntPtr')
+ external static int returnIntPtrStatic(int x);
+}
+
+void main() {
+ returnIntPtr(13);
+ returnIntPtrLeaf(37);
+ Classy.returnIntPtrStatic(0xDE);
+}
diff --git a/pkg/vm/testcases/transformations/ffi/ffinative.dart.expect b/pkg/vm/testcases/transformations/ffi/ffinative.dart.expect
new file mode 100644
index 0000000..09cb43b
--- /dev/null
+++ b/pkg/vm/testcases/transformations/ffi/ffinative.dart.expect
@@ -0,0 +1,55 @@
+library #lib /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+import "dart:ffi" as ffi;
+import "dart:_internal" as _in;
+
+import "dart:ffi";
+import "dart:nativewrappers";
+
+class Classy extends core::Object {
+ static final field (core::int) → core::int _@FfiNative_returnIntPtrStatic = ffi::_asFunctionInternal<(core::int) → core::int, (ffi::IntPtr*) →* ffi::IntPtr*>(ffi::_fromAddress<ffi::NativeFunction<(ffi::IntPtr*) →* ffi::IntPtr*>>(ffi::_ffi_resolver(#C1, #C2, #C3){(core::Object, core::Object, core::int) → core::int}), false)/*isLegacy*/;
+ synthetic constructor •() → self::Classy
+ : super core::Object::•()
+ ;
+ @#C5
+ static method returnIntPtrStatic(core::int x) → core::int
+ return self::Classy::_@FfiNative_returnIntPtrStatic(x){(core::int) → core::int};
+}
+static final field (core::int) → core::int _@FfiNative_returnIntPtr = ffi::_asFunctionInternal<(core::int) → core::int, (ffi::IntPtr*) →* ffi::IntPtr*>(ffi::_fromAddress<ffi::NativeFunction<(ffi::IntPtr*) →* ffi::IntPtr*>>(ffi::_ffi_resolver(#C1, #C2, #C3){(core::Object, core::Object, core::int) → core::int}), false)/*isLegacy*/;
+static final field (core::int) → core::int _@FfiNative_returnIntPtrLeaf = ffi::_asFunctionInternal<(core::int) → core::int, (ffi::IntPtr*) →* ffi::IntPtr*>(ffi::_fromAddress<ffi::NativeFunction<(ffi::IntPtr*) →* ffi::IntPtr*>>(ffi::_ffi_resolver(#C1, #C2, #C3){(core::Object, core::Object, core::int) → core::int}), true)/*isLegacy*/;
+@#C5
+static method returnIntPtr(core::int x) → core::int
+ return self::_@FfiNative_returnIntPtr(x){(core::int) → core::int};
+@#C7
+static method returnIntPtrLeaf(core::int x) → core::int
+ return self::_@FfiNative_returnIntPtrLeaf(x){(core::int) → core::int};
+static method main() → void {
+ block {
+ core::int #t1;
+ final dynamic #t2 = 13;
+ #t1 = self::returnIntPtr(#t2);
+ _in::reachabilityFence(#t2);
+ } =>#t1;
+ block {
+ core::int #t3;
+ final dynamic #t4 = 37;
+ #t3 = self::returnIntPtrLeaf(#t4);
+ _in::reachabilityFence(#t4);
+ } =>#t3;
+ block {
+ core::int #t5;
+ final dynamic #t6 = 222;
+ #t5 = self::Classy::returnIntPtrStatic(#t6);
+ _in::reachabilityFence(#t6);
+ } =>#t5;
+}
+constants {
+ #C1 = "#lib"
+ #C2 = "ReturnIntPtr"
+ #C3 = 1
+ #C4 = false
+ #C5 = ffi::FfiNative<(ffi::IntPtr*) →* ffi::IntPtr*> {nativeName:#C2, isLeaf:#C4}
+ #C6 = true
+ #C7 = ffi::FfiNative<(ffi::IntPtr*) →* ffi::IntPtr*> {nativeName:#C2, isLeaf:#C6}
+}
diff --git a/tests/ffi/vmspecific_ffi_native_test.dart b/tests/ffi/vmspecific_ffi_native_test.dart
index 8838ebc..9caa53d 100644
--- a/tests/ffi/vmspecific_ffi_native_test.dart
+++ b/tests/ffi/vmspecific_ffi_native_test.dart
@@ -23,9 +23,6 @@
nativeLib.lookupFunction<Void Function(Handle), void Function(Object)>(
'SetFfiNativeResolverForTest');
-final triggerGC = nativeLib
- .lookupFunction<Void Function(IntPtr), void Function(int)>('TriggerGC');
-
@FfiNative<Handle Function(Handle, IntPtr, IntPtr)>(
'Dart_SetNativeInstanceField')
external Object setNativeInstanceField(Object obj, int index, int ptr);
@@ -71,38 +68,6 @@
@FfiNative<IntPtr Function(IntPtr, Pointer<Void>)>('PassAsValueAndPointer')
external int passAsValueAndPointer(int value, NativeFieldWrapperClass1 obj);
-// Allocate new native resource we can use to keep track of whether the
-// finalizer has run.
-@FfiNative<Pointer<Void> Function(IntPtr)>('AllocateResource')
-external Pointer<Void> allocateResource(int value);
-
-@FfiNative<Void Function(Pointer<Void>)>('DeleteResource')
-external void deleteResource(Pointer<Void> resource);
-
-// Set up the object's finalizer to reset the resource.
-@FfiNative<Void Function(Handle, Pointer<Void>)>('SetResourceFinalizer')
-external void setResourceFinalizer(
- NativeFieldWrapperClass1 obj, Pointer<Void> resource);
-
-// Return the native resource's value.
-@FfiNative<IntPtr Function(Pointer<Void>)>('GetResourceValue')
-external int getResourceValue(Pointer<Void> resource);
-
-// Class which ties itself to a resource, resetting the value of the resource
-// when the instance gets collected.
-class ResourceResetter extends NativeFieldWrapperClass1 {
- ResourceResetter(Pointer<Void> resource) {
- setNativeInstanceField(this, 0, 0);
- setResourceFinalizer(this, resource);
- }
-}
-
-// Helper to embed triggerGC(..) as an expression.
-int triggerGCWrap() {
- triggerGC(0);
- return 0;
-}
-
// Helpers for testing argumnent evaluation order is preserved.
int state = 0;
int setState(int value) {
@@ -141,22 +106,6 @@
Expect.equals(123456, passAsPointer(cwnf));
}
- // Test that the transform to wrap NativeFieldWrapperClass1 objects in
- // _getNativeField(..) doesn't violate the original argument's liveness.
- final resource = allocateResource(314159);
- Expect.equals(
- 314159,
- passAsPointerAndValue(
- // 1: Locally alloc. instance.
- // If this gets wrapped in another call the instance does not live
- // past the return of the wrapper call.
- ResourceResetter(resource),
- // 2: Force GC, to collect the above if it isn't being kept alive.
- // 3: Check that the underlying (dummy) resource hasn't been
- // "collected" (i.e. reset to 0).
- triggerGCWrap() + getResourceValue(resource)));
- deleteResource(resource);
-
// Test that the order of argument evaluation is being preserved through the
// transform wrapping NativeFieldWrapperClass1 objects.
state = 0;