blob: 1a4eb3fb70a1156d46078941a16f7ee94089f0da [file] [log] [blame]
// 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;
}
}