Files
sdk/pkg/vm/lib/modular/transformations/ffi/native.dart
T
Johnni Winther e30cd0322c [cfe][Contexts] Split VariableDeclaration and VariableStatement
This separates VariableDeclaration from Statement. VariableDeclaration no longer implements Statement and variable declared in a block or in a for-statement are now wrapped by a VariableStatement.

Currently there are two VariableStatement implementations; LegacyVariableStatement for variables in the current model, called LegacyVariable, and VariableInitialization for variables used in the new, still experimental, encoding that supports scope computation.

This CL is a step towards realigning the AST nodes to the new model in which each kind of variable has its own distinct subclass. (LocalVariable, PositionalParameter, NamedParameter, SyntheticVariable, etc.)

Note that it is not the intent to use VariableStatement in ForStatement going forward but that will be handled in a follow-up.

TEST=existing.

Change-Id: I5b309cd62c9b138f95b74fb054686edffa49a393
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/502681
Reviewed-by: Chloe Stefantsova <cstefantsova@google.com>
Reviewed-by: Nicholas Shahan <nshahan@google.com>
Reviewed-by: Alexander Markov <alexmarkov@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Johnni Winther <johnniwinther@google.com>
2026-05-18 05:49:28 -07:00

1170 lines
35 KiB
Dart

// 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.
// This imports 'codes/cfe_codes.dart' instead of 'api_prototype/codes.dart' to
// avoid cyclic dependency between `package:vm/modular` and `package:front_end`.
import 'package:front_end/src/codes/diagnostic.dart' as diag;
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 '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(
diag.ffiDefaultAssetDuplicate,
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(
diag.ffiNativeDuplicateAnnotations,
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)) {
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),
) &&
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) &&
!env.isSubtypeOf(dartParameterType, pointerVoidType));
}
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([
VariableStatement(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 = <Statement>[];
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(VariableStatement(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),
)) {
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,
VariableStatement(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(
diag.ffiNativeOnlyNativeFieldWrapperClassCanBePointer,
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(
diag.cantHaveNamedParameters.withArguments(name: 'FfiNative'),
annotationOffset,
0,
node.location?.file,
);
return false;
}
if (ffiFunctionType.positionalParameters.length >
ffiFunctionType.requiredParameterCount) {
diagnosticReporter.report(
diag.cantHaveOptionalParameters.withArguments(name: 'FfiNative'),
annotationOffset,
0,
node.location?.file,
);
return false;
}
if (dartFunctionType.positionalParameters.length !=
ffiFunctionType.positionalParameters.length) {
final template = (node.isStatic
? diag.ffiNativeUnexpectedNumberOfParameters
: diag.ffiNativeUnexpectedNumberOfParametersWithReceiver);
diagnosticReporter.report(
template.withArguments(
expectedCount: dartFunctionType.positionalParameters.length,
actualCount: 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,
)..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(
diag.ffiNativeFieldMissingType,
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, false);
return (
ffiType,
NativeTypeCfe(
this,
dartType,
arrayDimensions: dimensions,
variableLength: false,
withLayout: false,
),
);
}
// 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(
diag.ffiNativeFieldType,
node.fileOffset,
1,
node.location?.file,
);
throw FfiStaticTypeError();
}
}
return (ffiType, NativeTypeCfe(this, ffiType, withLayout: false));
}
DartType _validateOrInferNativeFunctionType(
Procedure node,
DartType nativeType,
FunctionType dartType,
) {
if (nativeType is DynamicType) {
// No type argument was given on the @Native annotation, so we try to
// infer the native type from the Dart signature.
final inferred = convertDartTypeToNativeType(dartType, allowVoid: true);
if (inferred != null) {
nativeType = inferred;
}
final nativeFunctionType = InterfaceType(
nativeFunctionClass,
Nullability.nonNullable,
[nativeType],
);
if (!isNativeTypeValid(
nativeFunctionType,
allowHandle: true,
allowStructAndUnion: true,
)) {
diagnosticReporter.report(
diag.ffiNativeFunctionMissingType,
node.fileOffset,
1,
node.location?.file,
);
throw FfiStaticTypeError();
}
}
return nativeType;
}
@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(
diag.ffiNativeMustBeExternal,
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(
diag.ffiNativeMustBeExternal,
node.fileOffset,
1,
node.location?.file,
);
return node;
}
node.annotations.remove(ffiNativeAnnotation);
final ffiConstant = ffiNativeAnnotation.constant as InstanceConstant;
var nativeType = ffiConstant.typeArguments[0];
if (node.kind == ProcedureKind.Method) {
final dartType = node.function.computeFunctionType(
Nullability.nonNullable,
);
try {
nativeType = _validateOrInferNativeFunctionType(
node,
nativeType,
dartType,
);
} on FfiStaticTypeError {
// We've already reported an error.
return node;
}
}
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;
}
if (!node.isStatic) {
return _transformInstanceMethod(
node,
nativeType,
nativeName,
overriddenAssetName,
isLeaf,
ffiNativeAnnotation.fileOffset,
);
}
return _transformStaticFunction(
node,
nativeType,
nativeName,
overriddenAssetName,
isLeaf,
ffiNativeAnnotation.fileOffset,
);
} else if (node.kind == ProcedureKind.Getter ||
node.kind == ProcedureKind.Setter) {
if (!node.isStatic) {
diagnosticReporter.report(
diag.ffiNativeFieldMustBeStatic,
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(
diag.ffiTypeInvalid.withArguments(type: nativeType),
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.nonNullable,
[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;
}
}