mirror of
https://github.com/dart-lang/sdk
synced 2024-09-16 02:27:39 +00:00
[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:
parent
d83e78c7a3
commit
998d5f5a9b
|
@ -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<
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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.",
|
||||
);
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -49,6 +49,7 @@ name.#name
|
|||
name.stack
|
||||
nameokempty
|
||||
native('native
|
||||
nativefieldwrapperclass
|
||||
natively
|
||||
nativetype
|
||||
nnbd
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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]);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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. */}
|
||||
|
|
|
@ -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)));
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue