[vm/ffi] Adds FFI transform FfiNative instance methods.

- Allows FfiNative annotation to be attached to non-static
  methods of classes.
- Transforms non-static instance methods to static, adding
  wrappers with the receiver as an extra, implicit first paramters.
- Transform all parameters and arguments to Pointer if the object
  being passed supports it (i.e. extends NativeFieldWrapperClass1).
- Adds compile time errors for cases where the FfiNative annotation
  doesn't align with the annotated function. Taking into account
  implicit receivers and converted Pointers.
- Adds complimentary Analyzer checks for the above errors as well.
- Adds tests for the transforms, compile time errors and analyzer
  changes.

TEST=Adds new tests for instance methods, analyzer changes.

Change-Id: Idf54430acf2728a650008333b149b254941290ad
Cq-Do-Not-Cancel-Tryjobs: true
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/213773
Commit-Queue: Clement Skau <cskau@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
Reviewed-by: Daco Harkes <dacoharkes@google.com>
This commit is contained in:
Clement Skau 2021-10-12 10:58:44 +00:00 committed by commit-bot@chromium.org
parent d83e78c7a3
commit 998d5f5a9b
20 changed files with 1022 additions and 337 deletions

View file

@ -4106,14 +4106,79 @@ const MessageCode messageFfiLeafCallMustNotTakeHandle = const MessageCode(
problemMessage: r"""FFI leaf call must not have Handle argument types.""");
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Code<Null> codeFfiNativeAnnotationMustAnnotateStatic =
messageFfiNativeAnnotationMustAnnotateStatic;
const Code<Null> codeFfiNativeMustBeExternal = messageFfiNativeMustBeExternal;
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const MessageCode messageFfiNativeAnnotationMustAnnotateStatic =
const MessageCode("FfiNativeAnnotationMustAnnotateStatic",
const MessageCode messageFfiNativeMustBeExternal = const MessageCode(
"FfiNativeMustBeExternal",
problemMessage: r"""FfiNative functions must be marked external.""");
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Code<Null> codeFfiNativeOnlyNativeFieldWrapperClassCanBePointer =
messageFfiNativeOnlyNativeFieldWrapperClassCanBePointer;
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const MessageCode messageFfiNativeOnlyNativeFieldWrapperClassCanBePointer =
const MessageCode("FfiNativeOnlyNativeFieldWrapperClassCanBePointer",
problemMessage:
r"""FfiNative annotations can only be used on static functions.""");
r"""Only classes extending NativeFieldWrapperClass1 can be passed as Pointer.""");
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Template<Message Function(int count, int count2)>
templateFfiNativeUnexpectedNumberOfParameters =
const Template<Message Function(int count, int count2)>(
problemMessageTemplate:
r"""Unexpected number of FfiNative annotation parameters. Expected #count but has #count2.""",
withArguments: _withArgumentsFfiNativeUnexpectedNumberOfParameters);
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Code<Message Function(int count, int count2)>
codeFfiNativeUnexpectedNumberOfParameters =
const Code<Message Function(int count, int count2)>(
"FfiNativeUnexpectedNumberOfParameters",
);
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
Message _withArgumentsFfiNativeUnexpectedNumberOfParameters(
int count, int count2) {
// ignore: unnecessary_null_comparison
if (count == null) throw 'No count provided';
// ignore: unnecessary_null_comparison
if (count2 == null) throw 'No count provided';
return new Message(codeFfiNativeUnexpectedNumberOfParameters,
problemMessage:
"""Unexpected number of FfiNative annotation parameters. Expected ${count} but has ${count2}.""",
arguments: {'count': count, 'count2': count2});
}
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Template<Message Function(int count, int count2)>
templateFfiNativeUnexpectedNumberOfParametersWithReceiver =
const Template<Message Function(int count, int count2)>(
problemMessageTemplate:
r"""Unexpected number of FfiNative annotation parameters. Expected #count but has #count2. FfiNative instance method annotation must have receiver as first argument.""",
withArguments:
_withArgumentsFfiNativeUnexpectedNumberOfParametersWithReceiver);
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Code<Message Function(int count, int count2)>
codeFfiNativeUnexpectedNumberOfParametersWithReceiver =
const Code<Message Function(int count, int count2)>(
"FfiNativeUnexpectedNumberOfParametersWithReceiver",
);
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
Message _withArgumentsFfiNativeUnexpectedNumberOfParametersWithReceiver(
int count, int count2) {
// ignore: unnecessary_null_comparison
if (count == null) throw 'No count provided';
// ignore: unnecessary_null_comparison
if (count2 == null) throw 'No count provided';
return new Message(codeFfiNativeUnexpectedNumberOfParametersWithReceiver,
problemMessage:
"""Unexpected number of FfiNative annotation parameters. Expected ${count} but has ${count2}. FfiNative instance method annotation must have receiver as first argument.""",
arguments: {'count': count, 'count2': count2});
}
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Template<

View file

@ -480,7 +480,11 @@ const List<ErrorCode> errorCodeValues = [
FfiCode.EMPTY_STRUCT,
FfiCode.EXTRA_ANNOTATION_ON_STRUCT_FIELD,
FfiCode.EXTRA_SIZE_ANNOTATION_CARRAY,
FfiCode.FFI_NATIVE_ONLY_STATIC,
FfiCode.FFI_NATIVE_MUST_BE_EXTERNAL,
FfiCode
.FFI_NATIVE_ONLY_CLASSES_EXTENDING_NATIVEFIELDWRAPPERCLASS1_CAN_BE_POINTER,
FfiCode.FFI_NATIVE_UNEXPECTED_NUMBER_OF_PARAMETERS,
FfiCode.FFI_NATIVE_UNEXPECTED_NUMBER_OF_PARAMETERS_WITH_RECEIVER,
FfiCode.FIELD_IN_STRUCT_WITH_INITIALIZER,
FfiCode.FIELD_INITIALIZER_IN_STRUCT,
FfiCode.FIELD_MUST_BE_EXTERNAL_IN_STRUCT,

View file

@ -77,10 +77,45 @@ class FfiCode extends AnalyzerErrorCode {
/**
* No parameters.
*/
static const FfiCode FFI_NATIVE_ONLY_STATIC = FfiCode(
'FFI_NATIVE_ONLY_STATIC',
"FfiNative annotations can only be used on static functions.",
correctionMessage: "Change the method to static.",
static const FfiCode FFI_NATIVE_MUST_BE_EXTERNAL = FfiCode(
'FFI_NATIVE_MUST_BE_EXTERNAL',
"FfiNative functions must be declared external.",
correctionMessage: "Add the `external` keyword to the function.",
);
/**
* No parameters.
*/
static const FfiCode
FFI_NATIVE_ONLY_CLASSES_EXTENDING_NATIVEFIELDWRAPPERCLASS1_CAN_BE_POINTER =
FfiCode(
'FFI_NATIVE_ONLY_CLASSES_EXTENDING_NATIVEFIELDWRAPPERCLASS1_CAN_BE_POINTER',
"Only classes extending NativeFieldWrapperClass1 can be passed as Pointer.",
correctionMessage: "Pass as Handle instead.",
);
/**
* Parameters:
* 0: the expected number of parameters
* 1: the actual number of parameters
*/
static const FfiCode FFI_NATIVE_UNEXPECTED_NUMBER_OF_PARAMETERS = FfiCode(
'FFI_NATIVE_UNEXPECTED_NUMBER_OF_PARAMETERS',
"Unexpected number of FfiNative annotation parameters. Expected {0} but has {1}.",
correctionMessage: "Make sure parameters match the function annotated.",
);
/**
* Parameters:
* 0: the expected number of parameters
* 1: the actual number of parameters
*/
static const FfiCode
FFI_NATIVE_UNEXPECTED_NUMBER_OF_PARAMETERS_WITH_RECEIVER = FfiCode(
'FFI_NATIVE_UNEXPECTED_NUMBER_OF_PARAMETERS_WITH_RECEIVER',
"Unexpected number of FfiNative annotation parameters. Expected {0} but has {1}. FfiNative instance method annotation must have receiver as first argument.",
correctionMessage:
"Make sure parameters match the function annotated, including an extra first parameter for the receiver.",
);
/**

View file

@ -276,24 +276,119 @@ class FfiVerifier extends RecursiveAstVisitor<void> {
}
for (Annotation annotation in annotations) {
if (annotation.name.name == _ffiNativeName) {
// All FFI Natives must be static.
final isStatic = (node is FunctionDeclaration) ||
((node is MethodDeclaration) && node.isStatic);
if (!isStatic) {
if (annotation.name.name != _ffiNativeName) {
continue;
}
final NodeList<Expression> arguments = annotation.arguments!.arguments;
final NodeList<TypeAnnotation> typeArguments =
annotation.typeArguments!.arguments;
final ffiSignature = typeArguments[0].type! as FunctionType;
// Leaf call FFI Natives can't use Handles.
_validateFfiLeafCallUsesNoHandles(arguments, ffiSignature, node);
if (node is MethodDeclaration) {
if (!node.declaredElement!.isExternal) {
_errorReporter.reportErrorForNode(
FfiCode.FFI_NATIVE_ONLY_STATIC, node);
FfiCode.FFI_NATIVE_MUST_BE_EXTERNAL, node);
}
// Leaf call FFI Natives can't use Handles.
ArgumentList? argumentList = annotation.arguments;
if (argumentList != null) {
NodeList<Expression> arguments = argumentList.arguments;
TypeArgumentList? typeArgumentList = annotation.typeArguments;
if (typeArgumentList != null) {
NodeList<TypeAnnotation> typeArguments = typeArgumentList.arguments;
if (typeArguments.isNotEmpty && typeArguments[0].type != null) {
_validateFfiLeafCallUsesNoHandles(
arguments, typeArguments[0].type!, node);
List<DartType> ffiParameterTypes;
if (!node.isStatic) {
// Instance methods must have the receiver as an extra parameter in the
// FfiNative annotation.
if (node.parameters!.parameters.length + 1 !=
ffiSignature.parameters.length) {
_errorReporter.reportErrorForNode(
FfiCode
.FFI_NATIVE_UNEXPECTED_NUMBER_OF_PARAMETERS_WITH_RECEIVER,
node,
[
node.parameters!.parameters.length + 1,
ffiSignature.parameters.length
]);
return;
}
// Receiver can only be Pointer if the class extends
// NativeFieldWrapperClass1.
if (ffiSignature.normalParameterTypes[0].isPointer) {
final cls = node.declaredElement!.enclosingElement as ClassElement;
if (!_extendsNativeFieldWrapperClass1(cls.thisType)) {
_errorReporter.reportErrorForNode(
FfiCode
.FFI_NATIVE_ONLY_CLASSES_EXTENDING_NATIVEFIELDWRAPPERCLASS1_CAN_BE_POINTER,
node);
}
}
ffiParameterTypes = ffiSignature.normalParameterTypes.sublist(1);
} else {
// Number of parameters in the FfiNative annotation must match the
// annotated declaration.
if (node.parameters!.parameters.length !=
ffiSignature.parameters.length) {
_errorReporter.reportErrorForNode(
FfiCode.FFI_NATIVE_UNEXPECTED_NUMBER_OF_PARAMETERS, node, [
ffiSignature.parameters.length,
node.parameters!.parameters.length
]);
return;
}
ffiParameterTypes = ffiSignature.normalParameterTypes;
}
// Arguments can only be Pointer if the class extends
// NativeFieldWrapperClass1.
for (var i = 0; i < node.parameters!.parameters.length; i++) {
if (ffiParameterTypes[i].isPointer) {
final type = node.parameters!.parameters[i].declaredElement!.type;
if (!_extendsNativeFieldWrapperClass1(type as InterfaceType)) {
_errorReporter.reportErrorForNode(
FfiCode
.FFI_NATIVE_ONLY_CLASSES_EXTENDING_NATIVEFIELDWRAPPERCLASS1_CAN_BE_POINTER,
node);
}
}
}
continue;
}
if (node is FunctionDeclaration) {
if (!node.declaredElement!.isExternal) {
_errorReporter.reportErrorForNode(
FfiCode.FFI_NATIVE_MUST_BE_EXTERNAL, node);
}
// Number of parameters in the FfiNative annotation must match the
// annotated declaration.
if (node.functionExpression.parameters!.parameters.length !=
ffiSignature.parameters.length) {
_errorReporter.reportErrorForNode(
FfiCode.FFI_NATIVE_UNEXPECTED_NUMBER_OF_PARAMETERS, node, [
ffiSignature.parameters.length,
node.functionExpression.parameters!.parameters.length
]);
return;
}
// Arguments can only be Pointer if the class extends
// NativeFieldWrapperClass1.
for (var i = 0;
i < node.functionExpression.parameters!.parameters.length;
i++) {
if (ffiSignature.normalParameterTypes[i].isPointer) {
final type = node.functionExpression.parameters!.parameters[i]
.declaredElement!.type;
if (!_extendsNativeFieldWrapperClass1(type as InterfaceType)) {
_errorReporter.reportErrorForNode(
FfiCode
.FFI_NATIVE_ONLY_CLASSES_EXTENDING_NATIVEFIELDWRAPPERCLASS1_CAN_BE_POINTER,
node);
}
}
}
@ -301,6 +396,17 @@ class FfiVerifier extends RecursiveAstVisitor<void> {
}
}
bool _extendsNativeFieldWrapperClass1(InterfaceType? type) {
while (type != null) {
if (type.getDisplayString(withNullability: false) ==
'NativeFieldWrapperClass1') {
return true;
}
type = type.element.supertype;
}
return false;
}
/// Returns `true` if [nativeType] is a C type that has a size.
bool _isSized(DartType nativeType) {
switch (_primitiveNativeType(nativeType)) {
@ -639,27 +745,26 @@ class FfiVerifier extends RecursiveAstVisitor<void> {
void _validateFfiLeafCallUsesNoHandles(
NodeList<Expression> args, DartType nativeType, AstNode errorNode) {
if (args.isNotEmpty) {
for (final arg in args) {
if (arg is NamedExpression) {
if (arg.element?.name == _isLeafParamName) {
// Handles are ok for regular (non-leaf) calls. Check `isLeaf:true`.
final bool? isLeaf = _maybeGetBoolConstValue(arg.expression);
if (isLeaf != null && isLeaf) {
if (nativeType is FunctionType) {
if (_primitiveNativeType(nativeType.returnType) ==
_PrimitiveDartType.handle) {
_errorReporter.reportErrorForNode(
FfiCode.LEAF_CALL_MUST_NOT_RETURN_HANDLE, errorNode);
}
for (final param in nativeType.normalParameterTypes) {
if (_primitiveNativeType(param) ==
_PrimitiveDartType.handle) {
_errorReporter.reportErrorForNode(
FfiCode.LEAF_CALL_MUST_NOT_TAKE_HANDLE, errorNode);
}
}
}
if (args.isEmpty) {
return;
}
for (final arg in args) {
if (arg is! NamedExpression || arg.element?.name != _isLeafParamName) {
continue;
}
// Handles are ok for regular (non-leaf) calls. Check `isLeaf:true`.
final bool? isLeaf = _maybeGetBoolConstValue(arg.expression);
if (isLeaf != null && isLeaf) {
if (nativeType is FunctionType) {
if (_primitiveNativeType(nativeType.returnType) ==
_PrimitiveDartType.handle) {
_errorReporter.reportErrorForNode(
FfiCode.LEAF_CALL_MUST_NOT_RETURN_HANDLE, errorNode);
}
for (final param in nativeType.normalParameterTypes) {
if (_primitiveNativeType(param) == _PrimitiveDartType.handle) {
_errorReporter.reportErrorForNode(
FfiCode.LEAF_CALL_MUST_NOT_TAKE_HANDLE, errorNode);
}
}
}

View file

@ -13760,10 +13760,28 @@ FfiCode:
problemMessage: "'Array's must have exactly one 'Array' annotation."
correctionMessage: Try removing the extra annotation.
comment: No parameters.
FFI_NATIVE_ONLY_STATIC:
problemMessage: FfiNative annotations can only be used on static functions.
correctionMessage: Change the method to static.
FFI_NATIVE_MUST_BE_EXTERNAL:
problemMessage: FfiNative functions must be declared external.
correctionMessage: Add the `external` keyword to the function.
comment: No parameters.
FFI_NATIVE_ONLY_CLASSES_EXTENDING_NATIVEFIELDWRAPPERCLASS1_CAN_BE_POINTER:
problemMessage: Only classes extending NativeFieldWrapperClass1 can be passed as Pointer.
correctionMessage: Pass as Handle instead.
comment: No parameters.
FFI_NATIVE_UNEXPECTED_NUMBER_OF_PARAMETERS:
problemMessage: Unexpected number of FfiNative annotation parameters. Expected {0} but has {1}.
correctionMessage: Make sure parameters match the function annotated.
comment: |-
Parameters:
0: the expected number of parameters
1: the actual number of parameters
FFI_NATIVE_UNEXPECTED_NUMBER_OF_PARAMETERS_WITH_RECEIVER:
problemMessage: Unexpected number of FfiNative annotation parameters. Expected {0} but has {1}. FfiNative instance method annotation must have receiver as first argument.
correctionMessage: Make sure parameters match the function annotated, including an extra first parameter for the receiver.
comment: |-
Parameters:
0: the expected number of parameters
1: the actual number of parameters
FIELD_INITIALIZER_IN_STRUCT:
problemMessage: "Constructors in subclasses of 'Struct' and 'Union' can't have field initializers."
correctionMessage: Try removing the field initializer and marking the field as external.

View file

@ -15,18 +15,6 @@ main() {
@reflectiveTest
class FfiNativeTest extends PubPackageResolutionTest {
test_FfiNativeAnnotationOnInstanceMethod() async {
await assertErrorsInCode(r'''
import 'dart:ffi';
class K {
@FfiNative<Void Function()>('DoesntMatter')
external void doesntMatter();
}
''', [
error(FfiCode.FFI_NATIVE_ONLY_STATIC, 31, 75),
]);
}
test_FfiNativeCanUseHandles() async {
await assertErrorsInCode(r'''
import 'dart:ffi';
@ -35,6 +23,27 @@ external Object doesntMatter(Object);
''', []);
}
test_FfiNativeCanUseLeaf() async {
await assertErrorsInCode(r'''
import 'dart:ffi';
@FfiNative<Int8 Function(Int64)>('DoesntMatter', isLeaf:true)
external int doesntMatter(int);
''', []);
}
test_FfiNativeInstanceMethodsMustHaveReceiver() async {
await assertErrorsInCode(r'''
import 'dart:ffi';
class K {
@FfiNative<Void Function(Double)>('DoesntMatter')
external void doesntMatter(double x);
}
''', [
error(FfiCode.FFI_NATIVE_UNEXPECTED_NUMBER_OF_PARAMETERS_WITH_RECEIVER,
31, 89),
]);
}
test_FfiNativeLeafMustNotReturnHandle() async {
await assertErrorsInCode(r'''
import 'dart:ffi';
@ -54,4 +63,24 @@ external void doesntMatter(Object o);
error(FfiCode.LEAF_CALL_MUST_NOT_TAKE_HANDLE, 19, 100),
]);
}
test_FfiNativeTooFewParameters() async {
await assertErrorsInCode(r'''
import 'dart:ffi';
@FfiNative<Void Function(Double)>('DoesntMatter')
external void doesntMatter(double x, double y);
''', [
error(FfiCode.FFI_NATIVE_UNEXPECTED_NUMBER_OF_PARAMETERS, 19, 97),
]);
}
test_FfiNativeTooManyParameters() async {
await assertErrorsInCode(r'''
import 'dart:ffi';
@FfiNative<Void Function(Double, Double)>('DoesntMatter')
external void doesntMatter(double x);
''', [
error(FfiCode.FFI_NATIVE_UNEXPECTED_NUMBER_OF_PARAMETERS, 19, 95),
]);
}
}

View file

@ -54,8 +54,9 @@ export '../fasta/fasta_codes.dart'
messageFfiExpectedConstant,
messageFfiLeafCallMustNotReturnHandle,
messageFfiLeafCallMustNotTakeHandle,
messageFfiNativeMustBeExternal,
messageFfiNativeOnlyNativeFieldWrapperClassCanBePointer,
messageFfiPackedAnnotationAlignment,
messageFfiNativeAnnotationMustAnnotateStatic,
messageNonPositiveArrayDimensions,
noLength,
templateFfiDartTypeMismatch,
@ -69,6 +70,8 @@ export '../fasta/fasta_codes.dart'
templateFfiFieldInitializer,
templateFfiFieldNoAnnotation,
templateFfiFieldNull,
templateFfiNativeUnexpectedNumberOfParameters,
templateFfiNativeUnexpectedNumberOfParametersWithReceiver,
templateFfiNotStatic,
templateFfiPackedAnnotation,
templateFfiPackedNestingNonPacked,

View file

@ -347,7 +347,10 @@ FfiFieldNoAnnotation/analyzerCode: Fail
FfiFieldNull/analyzerCode: Fail
FfiLeafCallMustNotReturnHandle/analyzerCode: Fail
FfiLeafCallMustNotTakeHandle/analyzerCode: Fail
FfiNativeAnnotationMustAnnotateStatic/analyzerCode: Fail
FfiNativeMustBeExternal/analyzerCode: Fail
FfiNativeOnlyNativeFieldWrapperClassCanBePointer/analyzerCode: Fail
FfiNativeUnexpectedNumberOfParameters/analyzerCode: Fail
FfiNativeUnexpectedNumberOfParametersWithReceiver/analyzerCode: Fail
FfiNotStatic/analyzerCode: Fail
FfiPackedAnnotation/analyzerCode: Fail
FfiPackedAnnotationAlignment/analyzerCode: Fail

View file

@ -4672,9 +4672,24 @@ FfiLeafCallMustNotReturnHandle:
problemMessage: "FFI leaf call must not have Handle return type."
external: test/ffi_test.dart
FfiNativeAnnotationMustAnnotateStatic:
FfiNativeUnexpectedNumberOfParametersWithReceiver:
# Used by dart:ffi
problemMessage: "FfiNative annotations can only be used on static functions."
problemMessage: "Unexpected number of FfiNative annotation parameters. Expected #count but has #count2. FfiNative instance method annotation must have receiver as first argument."
external: test/ffi_test.dart
FfiNativeUnexpectedNumberOfParameters:
# Used by dart:ffi
problemMessage: "Unexpected number of FfiNative annotation parameters. Expected #count but has #count2."
external: test/ffi_test.dart
FfiNativeOnlyNativeFieldWrapperClassCanBePointer:
# Used by dart:ffi
problemMessage: "Only classes extending NativeFieldWrapperClass1 can be passed as Pointer."
external: test/ffi_test.dart
FfiNativeMustBeExternal:
# Used by dart:ffi
problemMessage: "FfiNative functions must be marked external."
external: test/ffi_test.dart
SpreadTypeMismatch:

View file

@ -49,6 +49,7 @@ name.#name
name.stack
nameokempty
native('native
nativefieldwrapperclass
natively
nativetype
nnbd

View file

@ -157,10 +157,15 @@ class VmTarget extends Target {
if (!ffiHelper.importsFfi(component, libraries)) {
logger?.call("Skipped ffi transformation");
} else {
// Transform @FfiNative(..) functions into ffi native call functions.
transformFfiNative.transformLibraries(
component, libraries, diagnosticReporter, referenceFromIndex);
// Transform @FfiNative(..) functions into FFI native call functions.
// Pass instance method receivers as implicit first argument to the static
// native function.
// Transform arguments that extend NativeFieldWrapperClass1 to Pointer if
// the native function expects Pointer (to avoid Handle overhead).
transformFfiNative.transformLibraries(component, coreTypes, hierarchy,
libraries, diagnosticReporter, referenceFromIndex);
logger?.call("Transformed ffi natives");
// TODO(jensj/dacoharkes): We can probably limit the transformations to
// libraries that transitivley depend on dart:ffi.
transformFfiDefinitions.transformLibraries(

View file

@ -235,7 +235,7 @@ class FfiTransformer extends Transformer {
final Class structClass;
final Class unionClass;
final Class ffiNativeClass;
final Class nativeFieldWrapperClass;
final Class nativeFieldWrapperClass1Class;
final Class ffiStructLayoutClass;
final Field ffiStructLayoutTypesField;
final Field ffiStructLayoutPackingField;
@ -291,9 +291,9 @@ class FfiTransformer extends Transformer {
final Procedure getNativeFieldFunction;
final Procedure reachabilityFenceFunction;
late final DartType nativeFieldWrapperClassType;
late final DartType voidType;
late final DartType pointerType;
late final InterfaceType nativeFieldWrapperClass1Type;
late final InterfaceType voidType;
late final InterfaceType pointerVoidType;
/// Classes corresponding to [NativeType], indexed by [NativeType].
final List<Class> nativeTypesClasses;
@ -355,7 +355,7 @@ class FfiTransformer extends Transformer {
structClass = index.getClass('dart:ffi', 'Struct'),
unionClass = index.getClass('dart:ffi', 'Union'),
ffiNativeClass = index.getClass('dart:ffi', 'FfiNative'),
nativeFieldWrapperClass =
nativeFieldWrapperClass1Class =
index.getClass('dart:nativewrappers', 'NativeFieldWrapperClass1'),
ffiStructLayoutClass = index.getClass('dart:ffi', '_FfiStructLayout'),
ffiStructLayoutTypesField =
@ -471,11 +471,11 @@ class FfiTransformer extends Transformer {
'dart:nativewrappers', '_getNativeField'),
reachabilityFenceFunction =
index.getTopLevelProcedure('dart:_internal', 'reachabilityFence') {
nativeFieldWrapperClassType =
nativeFieldWrapperClass.getThisType(coreTypes, Nullability.nonNullable);
nativeFieldWrapperClass1Type = nativeFieldWrapperClass1Class.getThisType(
coreTypes, Nullability.nonNullable);
voidType = nativeTypesClasses[NativeType.kVoid.index]
.getThisType(coreTypes, Nullability.nonNullable);
pointerType =
pointerVoidType =
InterfaceType(pointerClass, Nullability.nonNullable, [voidType]);
}

View file

@ -2,35 +2,44 @@
// 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 'package:kernel/ast.dart';
import 'package:kernel/library_index.dart' show LibraryIndex;
import 'package:kernel/reference_from_index.dart'
show IndexedLibrary, ReferenceFromIndex;
import 'package:kernel/target/targets.dart' show DiagnosticReporter;
import 'package:front_end/src/api_unstable/vm.dart'
show messageFfiNativeAnnotationMustAnnotateStatic;
show
messageFfiNativeMustBeExternal,
messageFfiNativeOnlyNativeFieldWrapperClassCanBePointer,
templateFfiNativeUnexpectedNumberOfParameters,
templateFfiNativeUnexpectedNumberOfParametersWithReceiver;
import 'package:kernel/ast.dart';
import 'package:kernel/core_types.dart';
import 'package:kernel/class_hierarchy.dart' show ClassHierarchy;
import 'package:kernel/library_index.dart' show LibraryIndex;
import 'package:kernel/reference_from_index.dart' show ReferenceFromIndex;
import 'package:kernel/target/targets.dart' show DiagnosticReporter;
import 'package:kernel/type_environment.dart';
import 'ffi.dart' show FfiTransformer;
/// Transform @FfiNative annotated functions into FFI native function pointer
/// functions.
void transformLibraries(
Component component,
CoreTypes coreTypes,
ClassHierarchy hierarchy,
List<Library> libraries,
DiagnosticReporter diagnosticReporter,
ReferenceFromIndex? referenceFromIndex) {
final index = LibraryIndex(component, ['dart:ffi']);
final index = LibraryIndex(component,
['dart:ffi', 'dart:_internal', 'dart:typed_data', 'dart:nativewrappers']);
// Skip if dart:ffi isn't loaded (e.g. during incremental compile).
if (index.tryGetClass('dart:ffi', 'FfiNative') == null) {
return;
}
final transformer =
FfiNativeTransformer(index, diagnosticReporter, referenceFromIndex);
final transformer = FfiNativeTransformer(
index, coreTypes, hierarchy, diagnosticReporter, referenceFromIndex);
libraries.forEach(transformer.visitLibrary);
}
class FfiNativeTransformer extends Transformer {
Library? currentLibrary;
IndexedLibrary? currentLibraryIndex;
class FfiNativeTransformer extends FfiTransformer {
final DiagnosticReporter diagnosticReporter;
final ReferenceFromIndex? referenceFromIndex;
final Class ffiNativeClass;
@ -38,11 +47,17 @@ class FfiNativeTransformer extends Transformer {
final Field ffiNativeNameField;
final Field ffiNativeIsLeafField;
final Field resolverField;
final Procedure asFunctionProcedure;
final Procedure fromAddressInternal;
// VariableDeclaration names can be null or empty string, in which case
// they're automatically assigned a "temporary" name like `#t0`.
static const variableDeclarationTemporaryName = null;
FfiNativeTransformer(
LibraryIndex index, this.diagnosticReporter, this.referenceFromIndex)
LibraryIndex index,
CoreTypes coreTypes,
ClassHierarchy hierarchy,
this.diagnosticReporter,
this.referenceFromIndex)
: ffiNativeClass = index.getClass('dart:ffi', 'FfiNative'),
nativeFunctionClass = index.getClass('dart:ffi', 'NativeFunction'),
ffiNativeNameField =
@ -50,122 +65,396 @@ class FfiNativeTransformer extends Transformer {
ffiNativeIsLeafField =
index.getField('dart:ffi', 'FfiNative', 'isLeaf'),
resolverField = index.getTopLevelField('dart:ffi', '_ffi_resolver'),
asFunctionProcedure = index.getProcedure(
'dart:ffi', 'NativeFunctionPointer', 'asFunction'),
fromAddressInternal =
index.getTopLevelProcedure('dart:ffi', '_fromAddress') {}
super(index, coreTypes, hierarchy, diagnosticReporter,
referenceFromIndex);
@override
TreeNode visitLibrary(Library node) {
assert(currentLibrary == null);
currentLibrary = node;
currentLibraryIndex = referenceFromIndex?.lookupLibrary(node);
final result = super.visitLibrary(node);
currentLibrary = null;
return result;
}
InstanceConstant? _tryGetFfiNativeAnnotation(Member node) {
ConstantExpression? _tryGetFfiNativeAnnotation(Member node) {
for (final Expression annotation in node.annotations) {
if (annotation is ConstantExpression) {
if (annotation.constant is InstanceConstant) {
final instConst = annotation.constant as InstanceConstant;
if (instConst.classNode == ffiNativeClass) {
return instConst;
}
}
if (annotation is! ConstantExpression) {
continue;
}
final annotationConstant = annotation.constant;
if (annotationConstant is! InstanceConstant) {
continue;
}
if (annotationConstant.classNode == ffiNativeClass) {
return annotation;
}
}
return null;
}
// Transform:
// @FfiNative<Double Function(Double)>('Math_sqrt', isLeaf:true)
// external double _square_root(double x);
bool _extendsNativeFieldWrapperClass1(DartType type) {
if (type is InterfaceType) {
Class? cls = type.classNode;
while (cls != null) {
if (cls == nativeFieldWrapperClass1Class) {
return true;
}
cls = cls.superclass;
}
}
return false;
}
// Replaces parameters with Pointer if:
// 1) they extend NativeFieldWrapperClass1, and
// 2) the corresponding native parameter is Pointer.
FunctionType _pointerizeFunctionType(
FunctionType dartType, FunctionType nativeType) {
final parameters = <DartType>[];
for (var i = 0; i < dartType.positionalParameters.length; i++) {
final parameter = dartType.positionalParameters[i];
if (parameter is InterfaceType) {
final nativeParameter = nativeType.positionalParameters[i];
if (_extendsNativeFieldWrapperClass1(parameter) &&
env.isSubtypeOf(nativeParameter, pointerVoidType,
SubtypeCheckMode.ignoringNullabilities)) {
parameters.add(pointerVoidType);
continue;
}
}
parameters.add(parameter);
}
return FunctionType(parameters, dartType.returnType, dartType.nullability);
}
// Create field holding the resolved native function pointer.
//
// Into:
// final _@FfiNative__square_root =
// Pointer<NativeFunction<Double Function(Double)>>
// .fromAddress(_ffi_resolver('dart:math', 'Math_sqrt'))
// .asFunction<double Function(double)>(isLeaf:true);
// double _square_root(double x) => _@FfiNative__square_root(x);
Statement transformFfiNative(
Procedure node, InstanceConstant annotationConst) {
assert(currentLibrary != null);
final params = node.function.positionalParameters;
final functionName = annotationConst
// For:
// @FfiNative<IntPtr Function(Pointer<Void>)>('DoXYZ', isLeaf:true)
// external int doXyz(NativeFieldWrapperClass1 obj);
//
// Create:
// static final _doXyz$FfiNative$ptr =
// Pointer<NativeFunction<IntPtr Function(Pointer<Void>)>>
// .fromAddress(_ffi_resolver('..', 'DoXYZ', 1))
// .asFunction<int Function(Pointer<Void>)>(isLeaf:true);
Field _createResolvedFfiNativeField(
InstanceConstant annotationConst,
String dartFunctionName,
FunctionType dartType,
FunctionType nativeType,
TreeNode? parent,
int fileOffset) {
final nativeFunctionName = annotationConst
.fieldValues[ffiNativeNameField.fieldReference] as StringConstant;
final isLeaf = annotationConst
.fieldValues[ffiNativeIsLeafField.fieldReference] as BoolConstant;
// double Function(double)
final DartType dartType =
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.nonNullable, [nativeType]);
// int Function(Pointer<Void>)
final dartTypePointerized = _pointerizeFunctionType(dartType, nativeType);
// Derive number of arguments from the native function signature.
final args_n = nativeType.positionalParameters.length;
final numberNativeArgs = nativeType.positionalParameters.length;
// TODO(dartbug.com/31579): Add `..fileOffset`s once we can handle these in
// patch files.
// _ffi_resolver('dart:math', 'Math_sqrt', 1)
// _ffi_resolver('...', 'DoXYZ', 1)
final resolverInvocation = FunctionInvocation(
FunctionAccessKind.FunctionType,
StaticGet(resolverField),
Arguments([
ConstantExpression(
StringConstant(currentLibrary!.importUri.toString())),
ConstantExpression(functionName),
ConstantExpression(IntConstant(args_n)),
StringConstant(currentLibrary.importUri.toString())),
ConstantExpression(nativeFunctionName),
ConstantExpression(IntConstant(numberNativeArgs)),
]),
functionType: resolverField.type as FunctionType);
functionType: resolverField.type as FunctionType)
..fileOffset = fileOffset;
// _fromAddress<NativeFunction<Double Function(Double)>>(...)
final fromAddressInvocation = StaticInvocation(fromAddressInternal,
Arguments([resolverInvocation], types: [nativeInterfaceType]));
final fromAddressInvocation = StaticInvocation(
fromAddressInternal,
Arguments([
resolverInvocation
], types: [
InterfaceType(nativeFunctionClass, Nullability.legacy, [nativeType])
]))
..fileOffset = fileOffset;
// NativeFunctionPointer.asFunction
// <Double Function(Double), double Function(double)>(..., isLeaf:true)
final asFunctionInvocation = StaticInvocation(
asFunctionProcedure,
asFunctionMethod,
Arguments([fromAddressInvocation],
types: [nativeType, dartType],
named: [NamedExpression("isLeaf", BoolLiteral(isLeaf.value))]));
types: [nativeType, dartTypePointerized],
named: [NamedExpression('isLeaf', BoolLiteral(isLeaf.value))]))
..fileOffset = fileOffset;
// final _@FfiNative__square_root = ...
final fieldName = Name('_@FfiNative_${node.name.text}', currentLibrary);
final funcPtrField = Field.immutable(fieldName,
type: dartType,
// static final _doXyz$FfiNative$Ptr = ...
final fieldName =
Name('_$dartFunctionName\$FfiNative\$Ptr', currentLibrary);
final functionPointerField = Field.immutable(fieldName,
type: dartTypePointerized,
initializer: asFunctionInvocation,
isStatic: true,
isFinal: true,
fileUri: currentLibrary!.fileUri,
fileUri: currentLibrary.fileUri,
getterReference: currentLibraryIndex?.lookupGetterReference(fieldName))
..fileOffset = node.fileOffset;
..fileOffset = fileOffset;
// Add field to the parent the FfiNative function belongs to.
final parent = node.parent;
if (parent is Class) {
parent.addField(funcPtrField);
parent.addField(functionPointerField);
} else if (parent is Library) {
parent.addField(funcPtrField);
parent.addField(functionPointerField);
} else {
throw 'Unexpected parent of @FfiNative function. '
'Expected Class or Library, but found ${parent}.';
}
// _@FfiNative__square_root(x)
final callFuncPtrInvocation = FunctionInvocation(
FunctionAccessKind.FunctionType,
StaticGet(funcPtrField),
Arguments(params.map<Expression>((p) => VariableGet(p)).toList()),
functionType: dartType as FunctionType);
return functionPointerField;
}
return ReturnStatement(callFuncPtrInvocation);
// FfiNative calls that pass objects extending NativeFieldWrapperClass1
// should be passed as Pointer instead so we don't have the overhead of
// converting Handles.
// If we find a NativeFieldWrapperClass1 object being passed to an FfiNative
// signature taking a Pointer, we automatically wrap the argument in a call to
// `Pointer.fromAddress(_getNativeField(obj))`.
//
// Example:
// passAsPointer(ClassWithNativeField());
//
// Becomes, roughly:
// {
// final NativeFieldWrapperClass1#t0 = ClassWithNativeField();
// final #t1 = passAsPointer(Pointer.fromAddress(_getNativeField(#t0)));
// reachabilityFence(#t0);
// } => #t1
Expression _convertArgumentsNativeFieldWrapperClass1ToPointer(
FunctionInvocation invocation,
List<DartType> ffiParameters,
List<VariableDeclaration> dartParameters) {
// Create lists of temporary variables for arguments potentially being
// wrapped, and the (potentially) wrapped arguments to be passed.
final temporariesForArguments = [];
final callArguments = <Expression>[];
final fencedArguments = [];
bool hasPointer = false;
for (int i = 0; i < invocation.arguments.positional.length; i++) {
if (env.isSubtypeOf(ffiParameters[i], pointerVoidType,
SubtypeCheckMode.ignoringNullabilities) &&
!env.isSubtypeOf(dartParameters[i].type, pointerVoidType,
SubtypeCheckMode.ignoringNullabilities)) {
// Only NativeFieldWrapperClass1 instances can be passed as pointer.
if (!_extendsNativeFieldWrapperClass1(dartParameters[i].type)) {
diagnosticReporter.report(
messageFfiNativeOnlyNativeFieldWrapperClassCanBePointer,
dartParameters[i].fileOffset,
1,
invocation.location?.file);
}
hasPointer = true;
// final NativeFieldWrapperClass1 #t0 = ClassWithNativeField();
final argument = VariableDeclaration(variableDeclarationTemporaryName,
initializer: invocation.arguments.positional[i],
type: nativeFieldWrapperClass1Type,
isFinal: true);
temporariesForArguments.add(argument);
fencedArguments.add(argument);
// Pointer.fromAddress(_getNativeField(#t0))
final ptr = StaticInvocation(
fromAddressInternal,
Arguments([
StaticInvocation(
getNativeFieldFunction, Arguments([VariableGet(argument)]))
], types: [
voidType
]));
callArguments.add(ptr);
continue;
}
// Note: We also evaluate, and assign temporaries for, non-wrapped
// arguments as we need to preserve the original evaluation order.
final argument = VariableDeclaration(variableDeclarationTemporaryName,
initializer: invocation.arguments.positional[i],
type: dartParameters[i].type,
isFinal: true);
temporariesForArguments.add(argument);
callArguments.add(VariableGet(argument));
}
// If there are no arguments to convert then we can drop the whole wrap.
if (!hasPointer) {
return invocation;
}
invocation.arguments = Arguments(callArguments);
// {
// final NativeFieldWrapperClass1 #t0 = ClassWithNativeField();
// final T #t1 = foo(Pointer.fromAddress(_getNativeField(#t0)));
// reachabilityFence(#t0);
// } => #t1
final result = VariableDeclaration(variableDeclarationTemporaryName,
initializer: invocation,
type: invocation.functionType!.returnType,
isFinal: true);
return BlockExpression(
Block([
...temporariesForArguments,
result,
for (final argument in fencedArguments)
ExpressionStatement(StaticInvocation(
reachabilityFenceFunction, Arguments([VariableGet(argument)])))
]),
VariableGet(result),
);
}
// Transform FfiNative instance methods.
// Example:
// class MyNativeClass extends NativeFieldWrapperClass1 {
// @FfiNative<IntPtr Function(Pointer<Void>, IntPtr)>('MyClass_MyMethod')
// external int myMethod(int x);
// }
// Becomes, roughly:
// ... {
// static final _myMethod$FfiNative$Ptr = ...
// static _myMethod$FfiNative(MyNativeClass self, int x)
// => _myMethod$FfiNative$Ptr(
// Pointer<Void>.fromAddress(_getNativeField(self)), x);
// int myMethod(int x) => _myMethod$FfiNative(this, x);
// }
Procedure _transformInstanceMethod(
Procedure node, InstanceConstant ffiConstant, int annotationOffset) {
final ffiSignature = ffiConstant.typeArguments[0] as FunctionType;
// The FfiNative annotation should have an extra parameter for `self`.
if (node.function.positionalParameters.length + 1 !=
ffiSignature.positionalParameters.length) {
diagnosticReporter.report(
templateFfiNativeUnexpectedNumberOfParametersWithReceiver
.withArguments(node.function.positionalParameters.length + 1,
ffiSignature.positionalParameters.length),
annotationOffset,
1,
node.location?.file);
return node;
}
final cls = node.parent as Class;
final staticParameters = [
// Add the implicit `self` for the inner glue functions.
VariableDeclaration('self',
type: cls.getThisType(coreTypes, Nullability.nonNullable)),
for (final parameter in node.function.positionalParameters)
VariableDeclaration(parameter.name, type: parameter.type)
];
final staticFunctionType = FunctionType(
staticParameters.map((e) => e.type).toList(),
node.function.returnType,
Nullability.nonNullable);
// static final _myMethod$FfiNative$Ptr = ..
final resolvedField = _createResolvedFfiNativeField(
ffiConstant,
node.name.text,
staticFunctionType,
ffiSignature,
node.parent,
node.fileOffset);
// _myMethod$FfiNative$Ptr(self, x)
final functionPointerInvocation = FunctionInvocation(
FunctionAccessKind.FunctionType,
StaticGet(resolvedField),
Arguments(
[for (final parameter in staticParameters) VariableGet(parameter)]),
functionType: staticFunctionType)
..fileOffset = node.fileOffset;
// static _myMethod$FfiNative(MyNativeClass self, int x)
// => _myMethod$FfiNative$Ptr(
// Pointer<Void>.fromAddress(_getNativeField(self)), x)
final ffiProcedure = Procedure(
Name('_${node.name.text}\$FfiNative', currentLibrary),
ProcedureKind.Method,
FunctionNode(
ReturnStatement(_convertArgumentsNativeFieldWrapperClass1ToPointer(
functionPointerInvocation,
ffiSignature.positionalParameters,
staticParameters)),
positionalParameters: staticParameters),
isStatic: true,
fileUri: node.fileUri)
..fileOffset = node.fileOffset;
cls.addProcedure(ffiProcedure);
// => _myMethod$FfiNative(this, x)
node.function.body = ReturnStatement(StaticInvocation(
ffiProcedure,
Arguments([
ThisExpression(),
for (var parameter in node.function.positionalParameters)
VariableGet(parameter)
])))
..parent = node.function;
return node;
}
// Transform FfiNative static functions.
// Example:
// @FfiNative<IntPtr Function(Pointer<Void>, IntPtr)>('MyFunction')
// external int myFunction(MyNativeClass obj, int x);
// Becomes, roughly:
// static final _myFunction$FfiNative$Ptr = ...
// int myFunction(MyNativeClass obj, int x)
// => myFunction$FfiNative$Ptr(
// Pointer<Void>.fromAddress(_getNativeField(obj)), x);
Procedure _transformStaticFunction(
Procedure node, InstanceConstant ffiConstant, int annotationOffset) {
final ffiSignature = ffiConstant.typeArguments[0] as FunctionType;
if (node.function.positionalParameters.length !=
ffiSignature.positionalParameters.length) {
diagnosticReporter.report(
templateFfiNativeUnexpectedNumberOfParameters.withArguments(
node.function.positionalParameters.length,
ffiSignature.positionalParameters.length),
annotationOffset,
1,
node.location?.file);
return node;
}
// _myFunction$FfiNative$Ptr = ..
final resolvedField = _createResolvedFfiNativeField(
ffiConstant,
node.name.text,
node.function.computeThisFunctionType(Nullability.nonNullable),
ffiSignature,
node.parent,
node.fileOffset);
// _myFunction$FfiNative$Ptr(obj, x)
final functionPointerInvocation = FunctionInvocation(
FunctionAccessKind.FunctionType,
StaticGet(resolvedField),
Arguments([
for (final parameter in node.function.positionalParameters)
VariableGet(parameter)
]),
functionType: resolvedField.type as FunctionType)
..fileOffset = node.fileOffset;
// => _myFunction$FfiNative$Ptr(
// Pointer<Void>.fromAddress(_getNativeField(obj)), x)
node.function.body = ReturnStatement(
_convertArgumentsNativeFieldWrapperClass1ToPointer(
functionPointerInvocation,
ffiSignature.positionalParameters,
node.function.positionalParameters))
..parent = node.function;
return node;
}
@override
@ -173,23 +462,30 @@ class FfiNativeTransformer extends Transformer {
// Only transform functions that are external and have FfiNative annotation:
// @FfiNative<Double Function(Double)>('Math_sqrt')
// external double _square_root(double x);
if (!node.isExternal) {
return node;
}
InstanceConstant? ffiNativeAnnotation = _tryGetFfiNativeAnnotation(node);
final ffiNativeAnnotation = _tryGetFfiNativeAnnotation(node);
if (ffiNativeAnnotation == null) {
return node;
}
if (!node.isExternal) {
diagnosticReporter.report(messageFfiNativeMustBeExternal, node.fileOffset,
1, node.location?.file);
return node;
}
node.isExternal = false;
node.annotations.remove(ffiNativeAnnotation);
if (!node.isStatic) {
diagnosticReporter.report(messageFfiNativeAnnotationMustAnnotateStatic,
node.fileOffset, 1, node.location!.file);
return _transformInstanceMethod(
node,
ffiNativeAnnotation.constant as InstanceConstant,
ffiNativeAnnotation.fileOffset);
}
node.isExternal = false;
node.function.body = transformFfiNative(node, ffiNativeAnnotation)
..parent = node.function;
return node;
return _transformStaticFunction(
node,
ffiNativeAnnotation.constant as InstanceConstant,
ffiNativeAnnotation.fileOffset);
}
}

View file

@ -131,20 +131,6 @@ class _FfiUseSiteTransformer extends FfiTransformer {
return result;
}
InstanceConstant? _tryGetFfiNativeAnnotation(Member node) {
for (final Expression annotation in node.annotations) {
if (annotation is ConstantExpression) {
if (annotation.constant is InstanceConstant) {
final instConst = annotation.constant as InstanceConstant;
if (instConst.classNode == ffiNativeClass) {
return instConst;
}
}
}
}
return null;
}
@override
visitStaticInvocation(StaticInvocation node) {
super.visitStaticInvocation(node);
@ -366,88 +352,6 @@ class _FfiUseSiteTransformer extends FfiTransformer {
.substituteType(allocateFunctionType
.withoutTypeParameters) as FunctionType);
}
} else if (target is Procedure) {
// FfiNative calls that pass objects extending NativeFieldWrapperClass1
// (NFWC1) should be passed as Pointer instead so we don't have the
// overhead of converting Handles.
// If we find an NFWC1 object being passed to an FfiNative signature
// taking a Pointer, we automatically wrap the argument in a call to
// `Pointer.fromAddress(_getNativeField(obj))`.
// Example:
// passAsPointer(ClassWithNativeField());
// Becomes, roughly:
// #t0 = PointerClassWithNativeField();
// passAsPointer(Pointer.fromAddress(_getNativeField(#t0)));
// reachabilityFence(#t0);
final ffiNativeAnn = _tryGetFfiNativeAnnotation(target);
if (ffiNativeAnn != null) {
final DartType ffiSignature = ffiNativeAnn.typeArguments[0];
if (ffiSignature is FunctionType) {
final List<DartType> ffiParams = ffiSignature.positionalParameters;
final List<VariableDeclaration> dartParams =
target.function.positionalParameters;
List<VariableDeclaration> tmpsArgs = [];
List<Expression> callArgs = [];
final origArgs = node.arguments.positional;
for (int i = 0; i < origArgs.length; i++) {
if (env.isSubtypeOf(
dartParams[i].type,
nativeFieldWrapperClassType,
SubtypeCheckMode.ignoringNullabilities) &&
env.isSubtypeOf(ffiParams[i], pointerType,
SubtypeCheckMode.ignoringNullabilities)) {
// final NativeFieldWrapperClass1 #t1 = MyNFWC1();.
final tmpPtr = VariableDeclaration(null,
initializer: origArgs[i],
type: nativeFieldWrapperClassType,
isFinal: true);
tmpsArgs.add(tmpPtr);
// Pointer.fromAddress(_getNativeField(#t1)).
final ptr = StaticInvocation(
fromAddressInternal,
Arguments([
StaticInvocation(getNativeFieldFunction,
Arguments([VariableGet(tmpPtr)]))
], types: [
voidType
]));
callArgs.add(ptr);
continue;
}
// Note: We also evaluate, and assign temporaries for, non-wrapped
// arguments as we need to preserve the original evaluation order.
final tmpArg = VariableDeclaration(null,
initializer: origArgs[i], isFinal: true);
tmpsArgs.add(tmpArg);
callArgs.add(VariableGet(tmpArg));
}
final targetCall = StaticInvocation(target, Arguments(callArgs));
// {
// T #t0;
// final NativeFieldWrapperClass1 #t1 = MyNFWC1();
// #t0 = foo(Pointer.fromAddress(_getNativeField(#t1)));
// reachabilityFence(#t1);
// } => #t0
final tmpResult =
VariableDeclaration(null, type: target.function.returnType);
return BlockExpression(
Block([
tmpResult,
...tmpsArgs,
ExpressionStatement(VariableSet(tmpResult, targetCall)),
for (final ta in tmpsArgs)
ExpressionStatement(StaticInvocation(
reachabilityFenceFunction, Arguments([VariableGet(ta)])))
]),
VariableGet(tmpResult),
);
}
}
}
} on _FfiStaticTypeError {
// It's OK to swallow the exception because the diagnostics issued will
@ -809,22 +713,6 @@ class _FfiUseSiteTransformer extends FfiTransformer {
return pointerType is InterfaceType ? pointerType.typeArguments[0] : null;
}
// Replaces all NativeFieldWrapperClass1 parameters with Pointer.
FunctionType _pointerizeFunctionType(FunctionType dartType) {
List<DartType> parameters = [];
for (final parameter in dartType.positionalParameters) {
if (parameter is InterfaceType) {
if (env.isSubtypeOf(parameter, nativeFieldWrapperClassType,
SubtypeCheckMode.ignoringNullabilities)) {
parameters.add(pointerType);
continue;
}
}
parameters.add(parameter);
}
return FunctionType(parameters, dartType.returnType, dartType.nullability);
}
void _ensureNativeTypeToDartType(
DartType nativeType, DartType dartType, Expression node,
{bool allowHandle: false}) {
@ -837,15 +725,6 @@ class _FfiUseSiteTransformer extends FfiTransformer {
SubtypeCheckMode.ignoringNullabilities)) {
return;
}
// We do automatic argument conversion from NativeFieldWrapperClass1 to
// Pointer, so we specifically allow for NFWC1 to be passed as Pointer.
if (dartType is FunctionType) {
final ptrDartType = _pointerizeFunctionType(dartType);
if (env.isSubtypeOf(correspondingDartType, ptrDartType,
SubtypeCheckMode.ignoringNullabilities)) {
return;
}
}
diagnosticReporter.report(
templateFfiTypeMismatch.withArguments(dartType, correspondingDartType,
nativeType, currentLibrary.isNonNullableByDefault),

View file

@ -5,8 +5,9 @@
import 'dart:io';
import 'package:kernel/ast.dart';
import 'package:kernel/class_hierarchy.dart';
import 'package:kernel/core_types.dart';
import 'package:kernel/kernel.dart';
import 'package:kernel/reference_from_index.dart';
import 'package:kernel/target/targets.dart';
import 'package:kernel/verifier.dart';
@ -30,11 +31,15 @@ runTestCase(Uri source) async {
Component component = await compileTestCaseToKernelProgram(source,
target: target, experimentalFlags: ['generic-metadata']);
final ReferenceFromIndex? referenceFromIndex = null;
final DiagnosticReporter diagnosticReporter = TestDiagnosticReporter();
final coreTypes = CoreTypes(component);
transformLibraries(
component, component.libraries, diagnosticReporter, referenceFromIndex);
component,
coreTypes,
ClassHierarchy(component, coreTypes),
component.libraries,
TestDiagnosticReporter(),
/*referenceFromIndex=*/ null);
verifyComponent(component);

View file

@ -20,8 +20,30 @@ class Classy {
external static int returnIntPtrStatic(int x);
}
class NativeClassy extends NativeFieldWrapperClass1 {
@FfiNative<Void Function(Pointer<Void>, IntPtr)>('doesntmatter')
external void goodHasReceiverPointer(int v);
@FfiNative<Void Function(Handle, IntPtr)>('doesntmatter')
external void goodHasReceiverHandle(int v);
@FfiNative<Void Function(Handle, Pointer<Void>)>('doesntmatter')
external void goodHasReceiverHandleAndPtr(NativeClassy v);
@FfiNative<Void Function(Handle, Handle)>('doesntmatter')
external void goodHasReceiverHandleAndHandle(NativeClassy v);
@FfiNative<Void Function(Pointer<Void>, Handle)>('doesntmatter')
external void goodHasReceiverPtrAndHandle(NativeClassy v);
}
void main() {
returnIntPtr(13);
returnIntPtrLeaf(37);
Classy.returnIntPtrStatic(0xDE);
NativeClassy().goodHasReceiverPointer(0xAF);
NativeClassy().goodHasReceiverHandle(0xAF);
NativeClassy().goodHasReceiverHandleAndPtr(NativeClassy());
NativeClassy().goodHasReceiverHandleAndHandle(NativeClassy());
NativeClassy().goodHasReceiverPtrAndHandle(NativeClassy());
}

View file

@ -2,54 +2,85 @@ library #lib /*isNonNullableByDefault*/;
import self as self;
import "dart:core" as core;
import "dart:ffi" as ffi;
import "dart:nativewrappers" as nat;
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*/;
static final field (core::int) → core::int _returnIntPtrStatic$FfiNative$Ptr = 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};
return self::Classy::_returnIntPtrStatic$FfiNative$Ptr(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
class NativeClassy extends nat::NativeFieldWrapperClass1 {
static final field (ffi::Pointer<ffi::Void>, core::int) → void _goodHasReceiverPointer$FfiNative$Ptr = ffi::_asFunctionInternal<(ffi::Pointer<ffi::Void>, core::int) → void, (ffi::Pointer<ffi::Void*>*, ffi::IntPtr*) →* ffi::Void*>(ffi::_fromAddress<ffi::NativeFunction<(ffi::Pointer<ffi::Void*>*, ffi::IntPtr*) →* ffi::Void*>*>(ffi::_ffi_resolver(#C1, #C4, #C5){(core::Object, core::Object, core::int) → core::int}), false)/*isLegacy*/;
static final field (self::NativeClassy, core::int) → void _goodHasReceiverHandle$FfiNative$Ptr = ffi::_asFunctionInternal<(self::NativeClassy, core::int) → void, (ffi::Handle*, ffi::IntPtr*) →* ffi::Void*>(ffi::_fromAddress<ffi::NativeFunction<(ffi::Handle*, ffi::IntPtr*) →* ffi::Void*>*>(ffi::_ffi_resolver(#C1, #C4, #C5){(core::Object, core::Object, core::int) → core::int}), false)/*isLegacy*/;
static final field (self::NativeClassy, ffi::Pointer<ffi::Void>) → void _goodHasReceiverHandleAndPtr$FfiNative$Ptr = ffi::_asFunctionInternal<(self::NativeClassy, ffi::Pointer<ffi::Void>) → void, (ffi::Handle*, ffi::Pointer<ffi::Void*>*) →* ffi::Void*>(ffi::_fromAddress<ffi::NativeFunction<(ffi::Handle*, ffi::Pointer<ffi::Void*>*) →* ffi::Void*>*>(ffi::_ffi_resolver(#C1, #C4, #C5){(core::Object, core::Object, core::int) → core::int}), false)/*isLegacy*/;
static final field (self::NativeClassy, self::NativeClassy) → void _goodHasReceiverHandleAndHandle$FfiNative$Ptr = ffi::_asFunctionInternal<(self::NativeClassy, self::NativeClassy) → void, (ffi::Handle*, ffi::Handle*) →* ffi::Void*>(ffi::_fromAddress<ffi::NativeFunction<(ffi::Handle*, ffi::Handle*) →* ffi::Void*>*>(ffi::_ffi_resolver(#C1, #C4, #C5){(core::Object, core::Object, core::int) → core::int}), false)/*isLegacy*/;
static final field (ffi::Pointer<ffi::Void>, self::NativeClassy) → void _goodHasReceiverPtrAndHandle$FfiNative$Ptr = ffi::_asFunctionInternal<(ffi::Pointer<ffi::Void>, self::NativeClassy) → void, (ffi::Pointer<ffi::Void*>*, ffi::Handle*) →* ffi::Void*>(ffi::_fromAddress<ffi::NativeFunction<(ffi::Pointer<ffi::Void*>*, ffi::Handle*) →* ffi::Void*>*>(ffi::_ffi_resolver(#C1, #C4, #C5){(core::Object, core::Object, core::int) → core::int}), false)/*isLegacy*/;
synthetic constructor •() → self::NativeClassy
: super nat::NativeFieldWrapperClass1::•()
;
method goodHasReceiverPointer(core::int v) → void
return self::NativeClassy::_goodHasReceiverPointer$FfiNative(this, v);
method goodHasReceiverHandle(core::int v) → void
return self::NativeClassy::_goodHasReceiverHandle$FfiNative(this, v);
method goodHasReceiverHandleAndPtr(self::NativeClassy v) → void
return self::NativeClassy::_goodHasReceiverHandleAndPtr$FfiNative(this, v);
method goodHasReceiverHandleAndHandle(self::NativeClassy v) → void
return self::NativeClassy::_goodHasReceiverHandleAndHandle$FfiNative(this, v);
method goodHasReceiverPtrAndHandle(self::NativeClassy v) → void
return self::NativeClassy::_goodHasReceiverPtrAndHandle$FfiNative(this, v);
static method /*isLegacy*/ _goodHasReceiverPointer$FfiNative(self::NativeClassy self, core::int v) → dynamic
return block {
final nat::NativeFieldWrapperClass1 #t1 = self;
final core::int #t2 = v;
final void #t3 = self::NativeClassy::_goodHasReceiverPointer$FfiNative$Ptr(ffi::_fromAddress<ffi::Void>(nat::_getNativeField(#t1)), #t2){(self::NativeClassy, core::int) → void};
_in::reachabilityFence(#t1);
} =>#t3;
static method /*isLegacy*/ _goodHasReceiverHandle$FfiNative(self::NativeClassy self, core::int v) → dynamic
return self::NativeClassy::_goodHasReceiverHandle$FfiNative$Ptr(self, v){(self::NativeClassy, core::int) → void};
static method /*isLegacy*/ _goodHasReceiverHandleAndPtr$FfiNative(self::NativeClassy self, self::NativeClassy v) → dynamic
return block {
final self::NativeClassy #t4 = self;
final nat::NativeFieldWrapperClass1 #t5 = v;
final void #t6 = self::NativeClassy::_goodHasReceiverHandleAndPtr$FfiNative$Ptr(#t4, ffi::_fromAddress<ffi::Void>(nat::_getNativeField(#t5))){(self::NativeClassy, self::NativeClassy) → void};
_in::reachabilityFence(#t5);
} =>#t6;
static method /*isLegacy*/ _goodHasReceiverHandleAndHandle$FfiNative(self::NativeClassy self, self::NativeClassy v) → dynamic
return self::NativeClassy::_goodHasReceiverHandleAndHandle$FfiNative$Ptr(self, v){(self::NativeClassy, self::NativeClassy) → void};
static method /*isLegacy*/ _goodHasReceiverPtrAndHandle$FfiNative(self::NativeClassy self, self::NativeClassy v) → dynamic
return block {
final nat::NativeFieldWrapperClass1 #t7 = self;
final self::NativeClassy #t8 = v;
final void #t9 = self::NativeClassy::_goodHasReceiverPtrAndHandle$FfiNative$Ptr(ffi::_fromAddress<ffi::Void>(nat::_getNativeField(#t7)), #t8){(self::NativeClassy, self::NativeClassy) → void};
_in::reachabilityFence(#t7);
} =>#t9;
}
static final field (core::int) → core::int _returnIntPtr$FfiNative$Ptr = 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 _returnIntPtrLeaf$FfiNative$Ptr = 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*/;
static method returnIntPtr(core::int x) → core::int
return self::_@FfiNative_returnIntPtr(x){(core::int) → core::int};
@#C7
return self::_returnIntPtr$FfiNative$Ptr(x){(core::int) → core::int};
static method returnIntPtrLeaf(core::int x) → core::int
return self::_@FfiNative_returnIntPtrLeaf(x){(core::int) → core::int};
return self::_returnIntPtrLeaf$FfiNative$Ptr(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;
self::returnIntPtr(13);
self::returnIntPtrLeaf(37);
self::Classy::returnIntPtrStatic(222);
new self::NativeClassy::•().{self::NativeClassy::goodHasReceiverPointer}(175){(core::int) → void};
new self::NativeClassy::•().{self::NativeClassy::goodHasReceiverHandle}(175){(core::int) → void};
new self::NativeClassy::•().{self::NativeClassy::goodHasReceiverHandleAndPtr}(new self::NativeClassy::•()){(self::NativeClassy) → void};
new self::NativeClassy::•().{self::NativeClassy::goodHasReceiverHandleAndHandle}(new self::NativeClassy::•()){(self::NativeClassy) → void};
new self::NativeClassy::•().{self::NativeClassy::goodHasReceiverPtrAndHandle}(new self::NativeClassy::•()){(self::NativeClassy) → void};
}
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}
#C4 = "doesntmatter"
#C5 = 2
}

View file

@ -1133,6 +1133,44 @@ void SetResourceFinalizer(Dart_Handle handle, intptr_t* resource) {
DummyResourceFinalizer);
}
intptr_t AddPtrAndInt(void* self, intptr_t x) {
return reinterpret_cast<intptr_t>(self) + x;
}
intptr_t AddHandleFieldAndInt(Dart_Handle self, intptr_t x) {
intptr_t field = 0;
ENSURE(!Dart_IsError(Dart_GetNativeInstanceField(self, 0, &field)));
return field + x;
}
intptr_t AddPtrAndPtr(void* self, void* other) {
return reinterpret_cast<intptr_t>(self) + reinterpret_cast<intptr_t>(other);
}
intptr_t AddHandleFieldAndPtr(Dart_Handle self, void* other) {
intptr_t field = 0;
ENSURE(!Dart_IsError(Dart_GetNativeInstanceField(self, 0, &field)));
return field + reinterpret_cast<intptr_t>(other);
}
intptr_t AddHandleFieldAndHandleField(Dart_Handle self, Dart_Handle other) {
intptr_t field1 = 0;
ENSURE(!Dart_IsError(Dart_GetNativeInstanceField(self, 0, &field1)));
intptr_t field2 = 0;
ENSURE(!Dart_IsError(Dart_GetNativeInstanceField(other, 0, &field2)));
return field1 + field2;
}
intptr_t AddPtrAndHandleField(void* self, Dart_Handle other) {
intptr_t field = 0;
ENSURE(!Dart_IsError(Dart_GetNativeInstanceField(other, 0, &field)));
return reinterpret_cast<intptr_t>(self) + field;
}
intptr_t ReturnIntPtrMethod(Dart_Handle self, intptr_t value) {
return value;
}
static void* FfiNativeResolver(const char* name, uintptr_t args_n) {
if (strcmp(name, "Dart_SetNativeInstanceField") == 0 && args_n == 3) {
return reinterpret_cast<void*>(Dart_SetNativeInstanceField);
@ -1167,6 +1205,27 @@ static void* FfiNativeResolver(const char* name, uintptr_t args_n) {
if (strcmp(name, "SetResourceFinalizer") == 0 && args_n == 2) {
return reinterpret_cast<void*>(SetResourceFinalizer);
}
if (strcmp(name, "AddPtrAndInt") == 0 && args_n == 2) {
return reinterpret_cast<void*>(AddPtrAndInt);
}
if (strcmp(name, "AddHandleFieldAndInt") == 0 && args_n == 2) {
return reinterpret_cast<void*>(AddHandleFieldAndInt);
}
if (strcmp(name, "AddPtrAndPtr") == 0 && args_n == 2) {
return reinterpret_cast<void*>(AddPtrAndPtr);
}
if (strcmp(name, "AddHandleFieldAndPtr") == 0 && args_n == 2) {
return reinterpret_cast<void*>(AddHandleFieldAndPtr);
}
if (strcmp(name, "AddHandleFieldAndHandleField") == 0 && args_n == 2) {
return reinterpret_cast<void*>(AddHandleFieldAndHandleField);
}
if (strcmp(name, "AddPtrAndHandleField") == 0 && args_n == 2) {
return reinterpret_cast<void*>(AddPtrAndHandleField);
}
if (strcmp(name, "ReturnIntPtrMethod") == 0 && args_n == 2) {
return reinterpret_cast<void*>(ReturnIntPtrMethod);
}
// This should be unreachable in tests.
ENSURE(false);
}

View file

@ -6,6 +6,7 @@
// with type arguments isn't supported in that version of Dart.
import 'dart:ffi';
import 'dart:nativewrappers';
// Error: FFI leaf call must not have Handle return type.
@FfiNative<Handle Function()>("foo", isLeaf: true) //# 01: compile-time error
@ -20,11 +21,48 @@ class Classy {
@FfiNative<IntPtr Function(IntPtr)>('ReturnIntPtr')
external static int returnIntPtrStatic(int x);
// Error: FfiNative annotations can only be used on static functions.
@FfiNative<IntPtr Function(IntPtr)>('ReturnIntPtr') //# 03: compile-time error
external int returnIntPtrMethod(int x); //# 03: compile-time error
// Error: Missing receiver in FfiNative annotation.
@FfiNative<Void Function(IntPtr)>('doesntmatter') //# 03: compile-time error
external void badMissingReceiver(int v); //# 03: compile-time error
// Error: Class doesn't extend NativeFieldWrapperClass1 - can't be converted
// to Pointer.
@FfiNative<Void Function(Pointer<Void>, IntPtr)>(//# 04: compile-time error
'doesntmatter') //# 04: compile-time error
external void badHasReceiverPointer(int v); //# 04: compile-time error
@FfiNative<Void Function(Handle, IntPtr)>('doesntmatter')
external void goodHasReceiverHandle(int v);
}
class NativeClassy extends NativeFieldWrapperClass1 {
@FfiNative<IntPtr Function(IntPtr)>('ReturnIntPtr')
external static int returnIntPtrStatic(int x);
// Error: Missing receiver in FfiNative annotation.
@FfiNative<Void Function(IntPtr)>('doesntmatter') //# 05: compile-time error
external void badMissingReceiver(int v); //# 05: compile-time error
@FfiNative<Void Function(Pointer<Void>, IntPtr)>('doesntmatter')
external void goodHasReceiverPointer(int v);
@FfiNative<Void Function(Handle, IntPtr)>('doesntmatter')
external void goodHasReceiverHandle(int v);
}
// Error: Too many FfiNative parameters.
@FfiNative<Handle Function(IntPtr, IntPtr)>(//# 06: compile-time error
'doesntmatter') //# 06: compile-time error
external Object badTooManyFfiParameter(int v); //# 06: compile-time error
// Error: Too few FfiNative parameters.
@FfiNative<Handle Function(IntPtr)>('doesntmatter') //# 07: compile-time error
external Object badTooFewFfiParameter(int v, int v2); //# 07: compile-time error
// Error: FfiNatives must be marked external (and by extension have no body).
@FfiNative<Void Function()>('doesntmatter') //# 08: compile-time error
void mustBeMarkedExternal() {} //# 08: compile-time error
// Regression test: Ensure same-name FfiNative functions don't collide in the
// top-level namespace, but instead live under their parent (Library, Class).
class A {
@ -37,4 +75,24 @@ class B {
external static void foo();
}
class DoesNotExtend implements NativeFieldWrapperClass1 {
// Error: Receiver type can't be converted to Pointer since it doesn't extend
// NativeFieldWrapperClass1.
@FfiNative<IntPtr Function(Pointer<Void>, Handle)>(//# 09: compile-time error
'doesntmatter') //# 09: compile-time error
external int bad1(DoesNotExtend obj); //# 09: compile-time error
// Error: Parameter type can't be converted to Pointer since it doesn't extend
// NativeFieldWrapperClass1.
@FfiNative<IntPtr Function(Handle, Pointer<Void>)>(//# 10: compile-time error
'doesntmatter') //# 10: compile-time error
external int bad2(DoesNotExtend obj); //# 10: compile-time error
// Error: Parameter type can't be converted to Pointer since it doesn't extend
// NativeFieldWrapperClass1.
@FfiNative<IntPtr Function(Pointer<Void>)>(//# 11: compile-time error
'doesntmatter') //# 11: compile-time error
external static int bad3(DoesNotExtend obj); //# 11: compile-time error
}
void main() {/* Intentionally empty: Compile-time error tests. */}

View file

@ -52,6 +52,36 @@ class ClassWithNativeField extends NativeFieldWrapperClass1 {
ClassWithNativeField(int value) {
setNativeInstanceField(this, 0, value);
}
// Instance methods implicitly pass a 'self' reference as the first argument.
// Passed as Pointer if the native function takes that (and the class can be
// converted).
@FfiNative<IntPtr Function(Pointer<Void>, IntPtr)>('AddPtrAndInt')
external int addSelfPtrAndIntMethod(int x);
// Instance methods implicitly pass a 'self' reference as the first argument.
// Passed as Handle if the native function takes that.
@FfiNative<IntPtr Function(Handle, IntPtr)>('AddHandleFieldAndInt')
external int addSelfHandleFieldAndIntMethod(int x);
@FfiNative<IntPtr Function(Pointer<Void>, Pointer<Void>)>('AddPtrAndPtr')
external int addSelfPtrAndPtrMethod(ClassWithNativeField other);
@FfiNative<IntPtr Function(Handle, Pointer<Void>)>('AddHandleFieldAndPtr')
external int addSelfHandleFieldAndPtrMethod(ClassWithNativeField other);
@FfiNative<IntPtr Function(Handle, Handle)>('AddHandleFieldAndHandleField')
external int addSelfHandleFieldAndHandleFieldMethod(
ClassWithNativeField other);
@FfiNative<IntPtr Function(Pointer<Void>, Handle)>('AddPtrAndHandleField')
external int addselfPtrAndHandleFieldMethod(ClassWithNativeField other);
}
class ClassWithoutNativeField {
// Instance methods implicitly pass their handle as the first arg.
@FfiNative<IntPtr Function(Handle, IntPtr)>('ReturnIntPtrMethod')
external int returnIntPtrMethod(int x);
}
// Native function takes a Handle, so a Handle is passed as-is.
@ -111,4 +141,26 @@ void main() {
state = 0;
passAsValueAndPointer(setState(7), StateSetter(3));
Expect.equals(3, state);
// Test transforms of instance methods.
Expect.equals(234, ClassWithoutNativeField().returnIntPtrMethod(234));
Expect.equals(1012, ClassWithNativeField(12).addSelfPtrAndIntMethod(1000));
Expect.equals(
2021, ClassWithNativeField(21).addSelfHandleFieldAndIntMethod(2000));
Expect.equals(
3031,
ClassWithNativeField(31)
.addSelfPtrAndPtrMethod(ClassWithNativeField(3000)));
Expect.equals(
4041,
ClassWithNativeField(41)
.addSelfHandleFieldAndPtrMethod(ClassWithNativeField(4000)));
Expect.equals(
5051,
ClassWithNativeField(51)
.addSelfHandleFieldAndHandleFieldMethod(ClassWithNativeField(5000)));
Expect.equals(
6061,
ClassWithNativeField(61)
.addselfPtrAndHandleFieldMethod(ClassWithNativeField(6000)));
}