| // Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| import 'package:front_end/src/fasta/codes/fasta_codes.dart' |
| show |
| messageFfiDefaultAssetDuplicate, |
| messageFfiNativeDuplicateAnnotations, |
| messageFfiNativeFieldMissingType, |
| messageFfiNativeFieldMustBeStatic, |
| messageFfiNativeFieldType, |
| messageFfiNativeMustBeExternal, |
| messageFfiNativeOnlyNativeFieldWrapperClassCanBePointer, |
| templateCantHaveNamedParameters, |
| templateCantHaveOptionalParameters, |
| templateFfiNativeUnexpectedNumberOfParameters, |
| templateFfiNativeUnexpectedNumberOfParametersWithReceiver, |
| templateFfiTypeInvalid; |
| import 'package:kernel/ast.dart'; |
| import 'package:kernel/class_hierarchy.dart' show ClassHierarchy; |
| import 'package:kernel/core_types.dart'; |
| 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 'common.dart' |
| show FfiStaticTypeError, FfiTransformer, NativeType, FfiTypeCheckDirection; |
| import 'native_type_cfe.dart'; |
| |
| /// Transform @Native 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', |
| 'dart:_internal', |
| 'dart:typed_data', |
| 'dart:nativewrappers', |
| 'dart:isolate' |
| ]); |
| // Skip if dart:ffi isn't loaded (e.g. during incremental compile). |
| if (index.tryGetClass('dart:ffi', 'Native') == null) { |
| return; |
| } |
| final transformer = FfiNativeTransformer( |
| index, coreTypes, hierarchy, diagnosticReporter, referenceFromIndex); |
| libraries.forEach(transformer.visitLibrary); |
| } |
| |
| class FfiNativeTransformer extends FfiTransformer { |
| final DiagnosticReporter diagnosticReporter; |
| final ReferenceFromIndex? referenceFromIndex; |
| final Class assetClass; |
| final Class nativeClass; |
| final Class nativeFunctionClass; |
| final Field assetAssetField; |
| final Field nativeSymbolField; |
| final Field nativeAssetField; |
| final Field resolverField; |
| |
| StringConstant? currentAsset; |
| |
| // 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, |
| CoreTypes coreTypes, |
| ClassHierarchy hierarchy, |
| this.diagnosticReporter, |
| this.referenceFromIndex) |
| : assetClass = index.getClass('dart:ffi', 'DefaultAsset'), |
| nativeClass = index.getClass('dart:ffi', 'Native'), |
| nativeFunctionClass = index.getClass('dart:ffi', 'NativeFunction'), |
| assetAssetField = index.getField('dart:ffi', 'DefaultAsset', 'id'), |
| nativeSymbolField = index.getField('dart:ffi', 'Native', 'symbol'), |
| nativeAssetField = index.getField('dart:ffi', 'Native', 'assetId'), |
| resolverField = index.getField('dart:ffi', 'Native', '_ffi_resolver'), |
| super(index, coreTypes, hierarchy, diagnosticReporter, |
| referenceFromIndex); |
| |
| @override |
| TreeNode visitLibrary(Library node) { |
| assert(currentAsset == null); |
| final annotation = tryGetAssetAnnotationOrWarnOnDuplicates(node); |
| if (annotation != null) { |
| currentAsset = (annotation.constant as InstanceConstant) |
| .fieldValues[assetAssetField.fieldReference] as StringConstant; |
| } |
| final result = super.visitLibrary(node); |
| currentAsset = null; |
| return result; |
| } |
| |
| List<ConstantExpression> tryGetAnnotation( |
| Annotatable node, Class instanceOf) { |
| final annotations = <ConstantExpression>[]; |
| |
| for (final Expression annotation in node.annotations) { |
| if (annotation is! ConstantExpression) { |
| continue; |
| } |
| final annotationConstant = annotation.constant; |
| if (annotationConstant is! InstanceConstant) { |
| continue; |
| } |
| if (annotationConstant.classNode == instanceOf) { |
| annotations.add(annotation); |
| } |
| } |
| |
| return annotations; |
| } |
| |
| ConstantExpression? tryGetAssetAnnotationOrWarnOnDuplicates(Library node) { |
| ConstantExpression? assetAnnotation; |
| |
| for (final annotation in tryGetAnnotation(node, assetClass)) { |
| if (assetAnnotation != null) { |
| // Duplicate @DefaultAsset annotations are forbidden. |
| diagnosticReporter.report(messageFfiDefaultAssetDuplicate, |
| annotation.fileOffset, 1, annotation.location?.file); |
| return null; |
| } else { |
| assetAnnotation = annotation; |
| } |
| } |
| |
| return assetAnnotation; |
| } |
| |
| ConstantExpression? tryGetNativeAnnotationOrWarnOnDuplicates(Member node) { |
| ConstantExpression? nativeAnnotation; |
| |
| for (final annotation in tryGetAnnotation(node, nativeClass)) { |
| if (nativeAnnotation != null) { |
| // Duplicate @Native annotations are forbidden. |
| diagnosticReporter.report(messageFfiNativeDuplicateAnnotations, |
| node.fileOffset, 1, node.location?.file); |
| return null; |
| } else { |
| nativeAnnotation = annotation; |
| } |
| } |
| |
| return nativeAnnotation; |
| } |
| |
| 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; |
| } |
| |
| StringConstant _resolveNativeSymbolName( |
| Member member, InstanceConstant native) { |
| final nativeFunctionConst = |
| native.fieldValues[nativeSymbolField.fieldReference]; |
| return nativeFunctionConst is StringConstant |
| ? nativeFunctionConst |
| : StringConstant(member.name.text); |
| } |
| |
| StringConstant? _assetNameFromAnnotation(InstanceConstant native) { |
| final assetConstant = native.fieldValues[nativeAssetField.fieldReference]; |
| return assetConstant is StringConstant ? assetConstant : null; |
| } |
| |
| bool _isLeaf(InstanceConstant native) { |
| return (native.fieldValues[nativeIsLeafField.fieldReference] |
| as BoolConstant) |
| .value; |
| } |
| |
| // Replaces parameters with Pointer if: |
| // 1) they extend NativeFieldWrapperClass1, and |
| // 2) the corresponding FFI parameter is Pointer. |
| DartType _wrapArgumentType( |
| DartType dartParameterType, DartType ffiParameterType) { |
| if (dartParameterType is InterfaceType) { |
| if (_extendsNativeFieldWrapperClass1(dartParameterType) && |
| env.isSubtypeOf(ffiParameterType, pointerVoidType, |
| SubtypeCheckMode.ignoringNullabilities)) { |
| return pointerVoidType; |
| } |
| } |
| return dartParameterType; |
| } |
| |
| // Replaces return type with Object if it is Handle. |
| DartType _wrapReturnType(DartType dartReturnType, DartType ffiReturnType) { |
| if (env.isSubtypeOf( |
| ffiReturnType, |
| handleClass.getThisType(coreTypes, Nullability.nonNullable), |
| SubtypeCheckMode.ignoringNullabilities) && |
| dartReturnType is! VoidType) { |
| return objectClass.getThisType(coreTypes, dartReturnType.nullability); |
| } |
| return dartReturnType; |
| } |
| |
| // Compute synthetic FFI function type, accounting for Objects passed as |
| // Pointer, and Objects returned as Handles. |
| FunctionType _wrapFunctionType( |
| FunctionType dartFunctionType, FunctionType ffiFunctionType) { |
| return FunctionType( |
| <DartType>[ |
| for (var i = 0; i < dartFunctionType.positionalParameters.length; i++) |
| _wrapArgumentType(dartFunctionType.positionalParameters[i], |
| ffiFunctionType.positionalParameters[i]), |
| ], |
| _wrapReturnType(dartFunctionType.returnType, ffiFunctionType.returnType), |
| dartFunctionType.nullability, |
| ); |
| } |
| |
| // Whether a parameter of [dartParameterType], passed as [ffiParameterType], |
| // needs to be converted to Pointer. |
| bool _requiresPointerConversion( |
| DartType dartParameterType, DartType ffiParameterType) { |
| return (env.isSubtypeOf(ffiParameterType, pointerVoidType, |
| SubtypeCheckMode.ignoringNullabilities) && |
| !env.isSubtypeOf(dartParameterType, pointerVoidType, |
| SubtypeCheckMode.ignoringNullabilities)); |
| } |
| |
| VariableDeclaration _declareTemporary(Expression initializer, |
| DartType dartParameterType, DartType ffiParameterType) { |
| final wrappedType = |
| (_requiresPointerConversion(dartParameterType, ffiParameterType) |
| ? nativeFieldWrapperClass1Type |
| : dartParameterType); |
| return VariableDeclaration(variableDeclarationTemporaryName, |
| initializer: initializer, |
| type: wrappedType, |
| isFinal: true, |
| isSynthesized: true); |
| } |
| |
| Expression _getTemporary( |
| VariableDeclaration temporary, |
| DartType dartParameterType, |
| DartType ffiParameterType, { |
| required bool checkForNullptr, |
| }) { |
| if (_requiresPointerConversion(dartParameterType, ffiParameterType)) { |
| Expression pointerAddress = StaticInvocation(getNativeFieldFunction, |
| Arguments(<Expression>[VariableGet(temporary)])); |
| |
| if (checkForNullptr) { |
| final pointerAddressVar = VariableDeclaration("#pointerAddress", |
| initializer: pointerAddress, |
| type: coreTypes.intNonNullableRawType, |
| isSynthesized: true); |
| pointerAddress = BlockExpression( |
| Block([ |
| pointerAddressVar, |
| IfStatement( |
| InstanceInvocation( |
| InstanceAccessKind.Instance, |
| VariableGet(pointerAddressVar), |
| objectEquals.name, |
| Arguments([ConstantExpression(IntConstant(0))]), |
| interfaceTarget: objectEquals, |
| functionType: objectEquals.getterType as FunctionType, |
| ), |
| ExpressionStatement(StaticInvocation( |
| stateErrorThrowNewFunction, |
| Arguments([ |
| ConstantExpression(StringConstant( |
| 'A Dart object attempted to access a native peer, ' |
| 'but the native peer has been collected (nullptr). ' |
| 'This is usually the result of calling methods on a ' |
| 'native-backed object when the native resources have ' |
| 'already been disposed.', |
| )) |
| ]), |
| )), |
| EmptyStatement(), |
| ) |
| ]), |
| VariableGet(pointerAddressVar), |
| ); |
| } |
| |
| return StaticInvocation(fromAddressInternal, |
| Arguments(<Expression>[pointerAddress], types: <DartType>[voidType])); |
| } |
| return VariableGet(temporary); |
| } |
| |
| // Native 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 Native |
| // 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 _wrapArgumentsAndReturn({ |
| required StaticInvocation invocation, |
| required FunctionType dartFunctionType, |
| required FunctionType ffiFunctionType, |
| bool checkReceiverForNullptr = false, |
| }) { |
| List<DartType> ffiParameters = ffiFunctionType.positionalParameters; |
| List<DartType> dartParameters = dartFunctionType.positionalParameters; |
| // 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 = []; |
| for (int i = 0; i < invocation.arguments.positional.length; i++) { |
| final temporary = _declareTemporary(invocation.arguments.positional[i], |
| dartParameters[i], ffiParameters[i]); |
| // Note: We also evaluate, and assign temporaries for, non-wrapped |
| // arguments as we need to preserve the original evaluation order. |
| temporariesForArguments.add(temporary); |
| callArguments.add(_getTemporary( |
| temporary, |
| dartParameters[i], |
| ffiParameters[i], |
| checkForNullptr: checkReceiverForNullptr && i == 0, |
| )); |
| if (_requiresPointerConversion(dartParameters[i], ffiParameters[i])) { |
| fencedArguments.add(temporary); |
| continue; |
| } |
| } |
| |
| Expression resultInitializer = invocation; |
| if (env.isSubtypeOf( |
| ffiFunctionType.returnType, |
| handleClass.getThisType(coreTypes, Nullability.nonNullable), |
| SubtypeCheckMode.ignoringNullabilities)) { |
| resultInitializer = StaticInvocation(unsafeCastMethod, |
| Arguments([invocation], types: [dartFunctionType.returnType])); |
| } |
| |
| // final T #t1 = foo(Pointer.fromAddress(_getNativeField(#t0))); |
| final result = VariableDeclaration(variableDeclarationTemporaryName, |
| initializer: resultInitializer, |
| type: dartFunctionType.returnType, |
| isFinal: true, |
| isSynthesized: true); |
| |
| invocation.arguments = Arguments(callArguments); |
| |
| // { |
| // final NativeFieldWrapperClass1 #t0 = ClassWithNativeField(); |
| // .. #t1 .. |
| // reachabilityFence(#t0); |
| // } => #t1 |
| final resultBlock = BlockExpression( |
| Block(<Statement>[ |
| ...temporariesForArguments, |
| result, |
| for (final argument in fencedArguments) |
| ExpressionStatement(StaticInvocation(reachabilityFenceFunction, |
| Arguments(<Expression>[VariableGet(argument)]))) |
| ]), |
| VariableGet(result), |
| ); |
| |
| return resultBlock; |
| } |
| |
| // Verify the Dart and FFI parameter types are compatible. |
| bool _verifyParameter(DartType dartParameterType, DartType ffiParameterType, |
| int annotationOffset, Uri? file) { |
| // Only NativeFieldWrapperClass1 instances can be passed as pointer. |
| if (_requiresPointerConversion(dartParameterType, ffiParameterType) && |
| !_extendsNativeFieldWrapperClass1(dartParameterType)) { |
| diagnosticReporter.report( |
| messageFfiNativeOnlyNativeFieldWrapperClassCanBePointer, |
| annotationOffset, |
| 1, |
| file); |
| return false; |
| } |
| return true; |
| } |
| |
| // Verify the signatures of the Dart function and the accompanying FfiNative |
| // annotation matches. |
| bool _verifySignatures(Procedure node, FunctionType dartFunctionType, |
| FunctionType ffiFunctionType, int annotationOffset) { |
| if (ffiFunctionType.namedParameters.isNotEmpty) { |
| diagnosticReporter.report( |
| templateCantHaveNamedParameters.withArguments('FfiNative'), |
| annotationOffset, |
| 0, |
| node.location?.file); |
| return false; |
| } |
| |
| if (ffiFunctionType.positionalParameters.length > |
| ffiFunctionType.requiredParameterCount) { |
| diagnosticReporter.report( |
| templateCantHaveOptionalParameters.withArguments('FfiNative'), |
| annotationOffset, |
| 0, |
| node.location?.file); |
| return false; |
| } |
| |
| if (dartFunctionType.positionalParameters.length != |
| ffiFunctionType.positionalParameters.length) { |
| final template = (node.isStatic |
| ? templateFfiNativeUnexpectedNumberOfParameters |
| : templateFfiNativeUnexpectedNumberOfParametersWithReceiver); |
| diagnosticReporter.report( |
| template.withArguments(dartFunctionType.positionalParameters.length, |
| ffiFunctionType.positionalParameters.length), |
| annotationOffset, |
| 1, |
| node.location?.file); |
| return false; |
| } |
| |
| var validSignature = true; |
| for (var i = 0; i < dartFunctionType.positionalParameters.length; i++) { |
| final dartParameterType = dartFunctionType.positionalParameters[i]; |
| if (dartParameterType is InterfaceType) { |
| if (!_verifyParameter( |
| dartParameterType, |
| ffiFunctionType.positionalParameters[i], |
| annotationOffset, |
| node.location?.file)) { |
| validSignature = false; |
| } |
| } |
| } |
| |
| return validSignature; |
| } |
| |
| static const _vmFfiNative = 'vm:ffi:native'; |
| |
| /// A pragma annotation added to methods previously annotated with `@Native` |
| /// during the transformation so that the use site transformer can find valid |
| /// targets. |
| static const nativeMarker = 'cfe:ffi:native-marker'; |
| |
| Procedure _transformProcedure( |
| Procedure node, |
| StringConstant nativeFunctionName, |
| StringConstant? overriddenAssetName, |
| bool isLeaf, |
| int annotationOffset, |
| FunctionType dartFunctionType, |
| FunctionType ffiFunctionType, |
| List<Expression> argumentList, { |
| required bool checkReceiverForNullptr, |
| }) { |
| final wrappedDartFunctionType = checkFfiType( |
| node, dartFunctionType, ffiFunctionType, isLeaf, annotationOffset); |
| |
| if (wrappedDartFunctionType == null) { |
| // It's OK to continue because the diagnostics issued will cause |
| // compilation to fail. By continuing, we can report more diagnostics |
| // before compilation ends. |
| return node; |
| } |
| |
| final resolvedNative = _generateResolvedNativeConstant( |
| nativeType: ffiFunctionType, |
| nativeName: nativeFunctionName, |
| isLeaf: isLeaf, |
| overriddenAssetName: overriddenAssetName, |
| ); |
| final pragmaConstant = ConstantExpression( |
| InstanceConstant(pragmaClass.reference, [], { |
| pragmaName.fieldReference: StringConstant(_vmFfiNative), |
| pragmaOptions.fieldReference: resolvedNative, |
| }), |
| InterfaceType( |
| pragmaClass, |
| Nullability.nonNullable, |
| [], |
| ), |
| ); |
| |
| // The marker annotation is always added to the original method so that the |
| // use-sites transformer can find it when a tear-off is passed to e.g. |
| // Native.addressOf. |
| node.addAnnotation(ConstantExpression( |
| InstanceConstant(pragmaClass.reference, [], { |
| pragmaName.fieldReference: StringConstant(nativeMarker), |
| pragmaOptions.fieldReference: resolvedNative, |
| }), |
| InterfaceType( |
| pragmaClass, |
| Nullability.nonNullable, |
| [], |
| ), |
| )); |
| |
| final possibleCompoundReturn = findCompoundReturnType(dartFunctionType); |
| if (dartFunctionType == wrappedDartFunctionType && |
| node.isStatic && |
| possibleCompoundReturn == null) { |
| // We are not wrapping/unwrapping arguments or return value. |
| node.addAnnotation(pragmaConstant); |
| return node; |
| } |
| |
| // Introduce a new function as external with the annotation and give the |
| // current function a body that does the wrapping/unwrapping. |
| node.isExternal = false; |
| |
| addPragmaPreferInline(node); |
| |
| final parent = node.parent; |
| final fileUri = node.fileUri; |
| |
| final name = Name( |
| '_${node.name.text}\$${node.kind.name}\$FfiNative', currentLibrary); |
| final reference = currentLibraryIndex?.lookupGetterReference(name); |
| |
| int varCounter = 0; |
| final nonWrappedFfiNative = Procedure( |
| name, |
| ProcedureKind.Method, |
| FunctionNode( |
| /*body=*/ null, |
| requiredParameterCount: wrappedDartFunctionType.requiredParameterCount, |
| positionalParameters: [ |
| for (final positionalParameter |
| in wrappedDartFunctionType.positionalParameters) |
| VariableDeclaration( |
| /*name=*/ '#t${varCounter++}', |
| type: positionalParameter, |
| )..fileOffset = node.fileOffset, |
| ], |
| returnType: wrappedDartFunctionType.returnType, |
| )..fileOffset = node.fileOffset, |
| fileUri: fileUri, |
| isStatic: true, |
| isExternal: true, |
| reference: reference, |
| ) |
| ..isNonNullableByDefault = node.isNonNullableByDefault |
| ..fileOffset = node.fileOffset; |
| nonWrappedFfiNative.addAnnotation(pragmaConstant); |
| |
| // Add procedure to the parent the FfiNative function belongs to. |
| if (parent is Class) { |
| parent.addProcedure(nonWrappedFfiNative); |
| } else if (parent is Library) { |
| parent.addProcedure(nonWrappedFfiNative); |
| } else { |
| throw 'Unexpected parent of @Native function. ' |
| 'Expected Class or Library, but found ${parent}.'; |
| } |
| |
| final nonWrappedInvocation = StaticInvocation( |
| nonWrappedFfiNative, |
| Arguments(argumentList), |
| )..fileOffset = node.fileOffset; |
| |
| Expression result = (wrappedDartFunctionType == dartFunctionType |
| ? nonWrappedInvocation |
| : _wrapArgumentsAndReturn( |
| invocation: nonWrappedInvocation, |
| dartFunctionType: dartFunctionType, |
| ffiFunctionType: ffiFunctionType, |
| checkReceiverForNullptr: checkReceiverForNullptr, |
| )); |
| if (possibleCompoundReturn != null) { |
| result = invokeCompoundConstructor(result, possibleCompoundReturn); |
| } |
| |
| node.function.body = ReturnStatement(result)..parent = node.function; |
| |
| return node; |
| } |
| |
| /// Creates a `Native` constant with all fields resolved. |
| /// |
| /// Re-using the constant from the original `@Native` annotation doesn't work |
| /// because the asset field may be inferred from the library. |
| InstanceConstant _generateResolvedNativeConstant({ |
| required DartType nativeType, |
| required StringConstant nativeName, |
| required bool isLeaf, |
| StringConstant? overriddenAssetName, |
| }) { |
| return InstanceConstant( |
| nativeClass.reference, |
| [nativeType], |
| { |
| nativeSymbolField.fieldReference: nativeName, |
| nativeAssetField.fieldReference: overriddenAssetName ?? |
| currentAsset ?? |
| StringConstant(currentLibrary.importUri.toString()), |
| nativeIsLeafField.fieldReference: BoolConstant(isLeaf), |
| }, |
| ); |
| } |
| |
| // Transform Native instance methods. |
| // |
| // Example: |
| // |
| // class MyNativeClass extends NativeFieldWrapperClass1 { |
| // @Native<IntPtr Function(Pointer<Void>, IntPtr)>(symbol: 'MyClass_MyMethod') |
| // external int myMethod(int x); |
| // } |
| // |
| // Becomes, roughly: |
| // |
| // ... { |
| // @pragma( |
| // 'vm:ffi:native', |
| // Native<IntPtr Function(Pointer<Void>, IntPtr)>( |
| // symbol: 'MyClass_MyMethod', |
| // assetId: '<library uri>', |
| // ), |
| // ) |
| // external int _myFunction$FfiNative(Pointer<Void> self, int x); |
| // |
| // @pragma('vm:prefer-inline') |
| // int myMethod(int x) => _myMethod$FfiNative(_getNativeField(this), x); |
| // } |
| Procedure _transformInstanceMethod( |
| Procedure node, |
| FunctionType ffiFunctionType, |
| StringConstant nativeFunctionName, |
| StringConstant? assetName, |
| bool isLeaf, |
| int annotationOffset) { |
| final dartFunctionType = FunctionType([ |
| (node.parent as Class).getThisType(coreTypes, Nullability.nonNullable), |
| for (final parameter in node.function.positionalParameters) parameter.type |
| ], node.function.returnType, Nullability.nonNullable); |
| |
| final argumentList = <Expression>[ |
| ThisExpression(), |
| for (final parameter in node.function.positionalParameters) |
| VariableGet(parameter) |
| ]; |
| |
| return _transformProcedure( |
| node, |
| nativeFunctionName, |
| assetName, |
| isLeaf, |
| annotationOffset, |
| dartFunctionType, |
| ffiFunctionType, |
| argumentList, |
| checkReceiverForNullptr: true, |
| ); |
| } |
| |
| // Transform Native static functions. |
| // |
| // Example: |
| // |
| // @Native<IntPtr Function(Pointer<Void>, IntPtr)>(symbol: 'MyFunction') |
| // external int myFunction(MyNativeClass obj, int x); |
| // |
| // Becomes, roughly: |
| // |
| // @pragma( |
| // 'vm:ffi:native', |
| // Native<IntPtr Function(Pointer<Void>, IntPtr)>( |
| // symbol: 'MyFunction', |
| // assetId: '<library uri>', |
| // ), |
| // ) |
| // external int _myFunction$FfiNative(Pointer<Void> self, int x); |
| // |
| // @pragma('vm:prefer-inline') |
| // int myFunction(MyNativeClass obj, int x) |
| // => _myFunction$FfiNative( |
| // Pointer<Void>.fromAddress(_getNativeField(obj)), x); |
| Procedure _transformStaticFunction( |
| Procedure node, |
| FunctionType ffiFunctionType, |
| StringConstant nativeFunctionName, |
| StringConstant? overriddenAssetName, |
| bool isLeaf, |
| int annotationOffset) { |
| final dartFunctionType = |
| node.function.computeThisFunctionType(Nullability.nonNullable); |
| |
| final argumentList = <Expression>[ |
| for (final parameter in node.function.positionalParameters) |
| VariableGet(parameter) |
| ]; |
| |
| return _transformProcedure( |
| node, |
| nativeFunctionName, |
| overriddenAssetName, |
| isLeaf, |
| annotationOffset, |
| dartFunctionType, |
| ffiFunctionType, |
| argumentList, |
| checkReceiverForNullptr: false, |
| ); |
| } |
| |
| Expression _generateAddressOfField( |
| Member node, InterfaceType ffiType, InstanceConstant native) { |
| return StaticInvocation( |
| nativePrivateAddressOf, |
| Arguments([ConstantExpression(native)], types: [ffiType]), |
| )..fileOffset = node.fileOffset; |
| } |
| |
| (DartType, NativeTypeCfe) _validateOrInferNativeFieldType( |
| Member node, DartType ffiType, DartType dartType) { |
| if (ffiType is DynamicType) { |
| // If no type argument is given on the @Native annotation, try to infer |
| // it. |
| final inferred = convertDartTypeToNativeType(dartType); |
| if (inferred != null) { |
| ffiType = inferred; |
| } else { |
| diagnosticReporter.report(messageFfiNativeFieldMissingType, |
| node.fileOffset, 1, node.location?.file); |
| throw FfiStaticTypeError(); |
| } |
| } |
| |
| ensureNativeTypeValid( |
| ffiType, |
| node, |
| allowStructAndUnion: true, |
| // Handles are not currently supported, but checking them separately and |
| // allowing them here yields a more specific error message. |
| allowHandle: true, |
| allowInlineArray: true, |
| ); |
| ensureNativeTypeMatch( |
| FfiTypeCheckDirection.nativeToDart, ffiType, dartType, node, |
| allowHandle: true, allowArray: true); |
| |
| // Array types must have an @Array annotation denoting its size. |
| if (isArrayType(ffiType)) { |
| final dimensions = ensureArraySizeAnnotation(node, ffiType); |
| return ( |
| ffiType, |
| NativeTypeCfe.withoutLayout(this, dartType, arrayDimensions: dimensions) |
| ); |
| } |
| |
| // Apart from arrays, compound subtypes, pointers and numeric types are |
| // supported for fields. |
| if (!isStructOrUnionSubtype(ffiType) && |
| !isAbiSpecificIntegerSubtype(ffiType)) { |
| final type = switch (ffiType) { |
| InterfaceType(:var classNode) => getType(classNode), |
| _ => null, |
| }; |
| if (type == null || |
| type == NativeType.kNativeFunction || |
| type == NativeType.kHandle || |
| isArrayType(ffiType)) { |
| diagnosticReporter.report( |
| messageFfiNativeFieldType, node.fileOffset, 1, node.location?.file); |
| throw FfiStaticTypeError(); |
| } |
| } |
| |
| return (ffiType, NativeTypeCfe.withoutLayout(this, ffiType)); |
| } |
| |
| @override |
| TreeNode visitField(Field node) { |
| final nativeAnnotation = tryGetNativeAnnotationOrWarnOnDuplicates(node); |
| if (nativeAnnotation == null) { |
| return node; |
| } |
| // @Native annotations on fields are valid if the field is external. But |
| // external fields are represented as a getter/setter pair in Kernel, so |
| // only visit fields to verify that no native annotation is present. |
| assert(!node.isExternal); |
| diagnosticReporter.report(messageFfiNativeMustBeExternal, node.fileOffset, |
| 1, node.location?.file); |
| return node; |
| } |
| |
| @override |
| visitProcedure(Procedure node) { |
| // Only transform functions that are external and have Native annotation: |
| // @Native<Double Function(Double)>(symbol: 'Math_sqrt') |
| // external double _square_root(double x); |
| final ffiNativeAnnotation = tryGetNativeAnnotationOrWarnOnDuplicates(node); |
| if (ffiNativeAnnotation == null) { |
| return node; |
| } |
| |
| if (!node.isExternal) { |
| diagnosticReporter.report(messageFfiNativeMustBeExternal, node.fileOffset, |
| 1, node.location?.file); |
| return node; |
| } |
| |
| node.annotations.remove(ffiNativeAnnotation); |
| |
| final ffiConstant = ffiNativeAnnotation.constant as InstanceConstant; |
| var nativeType = ffiConstant.typeArguments[0]; |
| |
| final nativeName = _resolveNativeSymbolName(node, ffiConstant); |
| final overriddenAssetName = _assetNameFromAnnotation(ffiConstant); |
| final isLeaf = _isLeaf(ffiConstant); |
| |
| if (nativeType is FunctionType) { |
| try { |
| final nativeFunctionType = InterfaceType( |
| nativeFunctionClass, Nullability.nonNullable, [nativeType]); |
| ensureNativeTypeValid(nativeFunctionType, ffiNativeAnnotation, |
| allowStructAndUnion: true, allowHandle: true); |
| } on FfiStaticTypeError { |
| // We've already reported an error. |
| return node; |
| } |
| final ffiFunctionType = ffiConstant.typeArguments[0] as FunctionType; |
| |
| if (!node.isStatic) { |
| return _transformInstanceMethod(node, ffiFunctionType, nativeName, |
| overriddenAssetName, isLeaf, ffiNativeAnnotation.fileOffset); |
| } |
| |
| return _transformStaticFunction(node, ffiFunctionType, nativeName, |
| overriddenAssetName, isLeaf, ffiNativeAnnotation.fileOffset); |
| } else if (node.kind == ProcedureKind.Getter || |
| node.kind == ProcedureKind.Setter) { |
| if (!node.isStatic) { |
| diagnosticReporter.report(messageFfiNativeFieldMustBeStatic, |
| node.fileOffset, 1, node.location?.file); |
| } |
| |
| DartType dartType; |
| NativeTypeCfe nativeTypeCfe; |
| try { |
| dartType = node.kind == ProcedureKind.Getter |
| ? node.function.returnType |
| : node.function.positionalParameters[0].type; |
| (nativeType, nativeTypeCfe) = |
| _validateOrInferNativeFieldType(node, nativeType, dartType); |
| } on FfiStaticTypeError { |
| return node; |
| } |
| final resolved = _generateResolvedNativeConstant( |
| nativeType: nativeType, |
| nativeName: nativeName, |
| isLeaf: isLeaf, |
| overriddenAssetName: overriddenAssetName, |
| ); |
| node.isExternal = false; |
| |
| final zeroOffset = ConstantExpression(IntConstant(0)); |
| |
| if (node.kind == ProcedureKind.Getter) { |
| node.function.body = ReturnStatement(nativeTypeCfe.generateLoad( |
| dartType: dartType, |
| fileOffset: node.fileOffset, |
| typedDataBase: _generateAddressOfField( |
| node, nativeType as InterfaceType, resolved), |
| transformer: this, |
| offsetInBytes: zeroOffset, |
| )); |
| node.annotations.add(ConstantExpression( |
| InstanceConstant(pragmaClass.reference, [], { |
| pragmaName.fieldReference: StringConstant(nativeMarker), |
| pragmaOptions.fieldReference: resolved, |
| }), |
| InterfaceType( |
| pragmaClass, |
| Nullability.nonNullable, |
| [], |
| ), |
| )); |
| } else { |
| node.function.body = ExpressionStatement(nativeTypeCfe.generateStore( |
| node.function.positionalParameters[0], |
| dartType: dartType, |
| fileOffset: node.fileOffset, |
| typedDataBase: _generateAddressOfField( |
| node, nativeType as InterfaceType, resolved), |
| transformer: this, |
| offsetInBytes: zeroOffset, |
| )); |
| } |
| } else { |
| // This function is not annotated with a native function type, which is |
| // invalid. |
| diagnosticReporter.report( |
| templateFfiTypeInvalid.withArguments( |
| nativeType, currentLibrary.isNonNullableByDefault), |
| node.fileOffset, |
| 1, |
| node.location?.file); |
| } |
| |
| return node; |
| } |
| |
| /// Checks whether the FFI function type is valid and reports any errors. |
| /// Returns the Dart function type for the FFI function if the type is valid. |
| /// |
| /// For example, for FFI function type `Int8 Function(Double)`, this returns |
| /// `int Function(double)`. |
| FunctionType? checkFfiType( |
| Procedure node, |
| FunctionType dartFunctionType, |
| FunctionType ffiFunctionTypeWithPossibleVarArgs, |
| bool isLeaf, |
| int annotationOffset) { |
| final ffiFunctionType = flattenVarargs(ffiFunctionTypeWithPossibleVarArgs); |
| if (!_verifySignatures( |
| node, dartFunctionType, ffiFunctionType, annotationOffset)) { |
| return null; |
| } |
| |
| // int Function(Pointer<Void>) |
| final wrappedDartFunctionType = |
| _wrapFunctionType(dartFunctionType, ffiFunctionType); |
| |
| final nativeType = InterfaceType( |
| nativeFunctionClass, Nullability.legacy, [ffiFunctionType]); |
| |
| try { |
| ensureNativeTypeValid(nativeType, node); |
| ensureNativeTypeMatch( |
| FfiTypeCheckDirection.nativeToDart, |
| nativeType, |
| wrappedDartFunctionType, |
| node, |
| allowHandle: true, // Handle-specific errors emitted below. |
| ); |
| ensureLeafCallDoesNotUseHandles( |
| nativeType, |
| isLeaf, |
| reportErrorOn: node, |
| ); |
| } on FfiStaticTypeError { |
| // It's OK to swallow the exception because the diagnostics issued will |
| // cause compilation to fail. By continuing, we can report more |
| // diagnostics before compilation ends. |
| return null; |
| } |
| |
| return wrappedDartFunctionType; |
| } |
| } |