| // Copyright (c) 2019, 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 file contains logic which is shared between the ffi_definition and |
| // ffi_use_site transformers. |
| |
| library vm.transformations.ffi; |
| |
| // 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/cfe_codes.dart' |
| show |
| messageFfiLeafCallMustNotReturnHandle, |
| messageFfiLeafCallMustNotTakeHandle, |
| messageFfiVariableLengthArrayNotLast, |
| messageNegativeVariableDimension, |
| messageNonPositiveArrayDimensions, |
| templateFfiSizeAnnotation, |
| templateFfiSizeAnnotationDimensions, |
| templateFfiTypeInvalid, |
| templateFfiTypeMismatch; |
| 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'; |
| import 'package:kernel/target/targets.dart' show DiagnosticReporter; |
| import 'package:kernel/type_algebra.dart' show Substitution; |
| import 'package:kernel/type_environment.dart' show TypeEnvironment; |
| import 'package:kernel/util/graph.dart' as kernelGraph; |
| |
| import 'abi.dart'; |
| import 'native_type_cfe.dart'; |
| |
| /// Represents the (instantiated) ffi.NativeType. |
| enum NativeType { |
| kNativeType, |
| kNativeInteger, |
| kNativeDouble, |
| kPointer, |
| kNativeFunction, |
| kInt8, |
| kInt16, |
| kInt32, |
| kInt64, |
| kUint8, |
| kUint16, |
| kUint32, |
| kUint64, |
| kFloat, |
| kDouble, |
| kVoid, |
| kOpaque, |
| kStruct, |
| kHandle, |
| kBool, |
| } |
| |
| const Set<NativeType> nativeIntTypesFixedSize = <NativeType>{ |
| NativeType.kInt8, |
| NativeType.kInt16, |
| NativeType.kInt32, |
| NativeType.kInt64, |
| NativeType.kUint8, |
| NativeType.kUint16, |
| NativeType.kUint32, |
| NativeType.kUint64, |
| }; |
| |
| /// The [NativeType] class names. |
| const Map<NativeType, String> nativeTypeClassNames = <NativeType, String>{ |
| NativeType.kNativeType: 'NativeType', |
| NativeType.kNativeInteger: '_NativeInteger', |
| NativeType.kNativeDouble: '_NativeDouble', |
| NativeType.kPointer: 'Pointer', |
| NativeType.kNativeFunction: 'NativeFunction', |
| NativeType.kInt8: 'Int8', |
| NativeType.kInt16: 'Int16', |
| NativeType.kInt32: 'Int32', |
| NativeType.kInt64: 'Int64', |
| NativeType.kUint8: 'Uint8', |
| NativeType.kUint16: 'Uint16', |
| NativeType.kUint32: 'Uint32', |
| NativeType.kUint64: 'Uint64', |
| NativeType.kFloat: 'Float', |
| NativeType.kDouble: 'Double', |
| NativeType.kVoid: 'Void', |
| NativeType.kOpaque: 'Opaque', |
| NativeType.kStruct: 'Struct', |
| NativeType.kHandle: 'Handle', |
| NativeType.kBool: 'Bool', |
| }; |
| |
| const int UNKNOWN = 0; |
| const int WORD_SIZE = -1; |
| |
| /// The [NativeType] sizes in bytes. |
| const Map<NativeType, int> nativeTypeSizes = <NativeType, int>{ |
| NativeType.kNativeType: UNKNOWN, |
| NativeType.kNativeInteger: UNKNOWN, |
| NativeType.kNativeDouble: UNKNOWN, |
| NativeType.kPointer: WORD_SIZE, |
| NativeType.kNativeFunction: UNKNOWN, |
| NativeType.kInt8: 1, |
| NativeType.kInt16: 2, |
| NativeType.kInt32: 4, |
| NativeType.kInt64: 8, |
| NativeType.kUint8: 1, |
| NativeType.kUint16: 2, |
| NativeType.kUint32: 4, |
| NativeType.kUint64: 8, |
| NativeType.kFloat: 4, |
| NativeType.kDouble: 8, |
| NativeType.kVoid: UNKNOWN, |
| NativeType.kOpaque: UNKNOWN, |
| NativeType.kStruct: UNKNOWN, |
| NativeType.kHandle: WORD_SIZE, |
| NativeType.kBool: 1, |
| }; |
| |
| /// Load and store are rewired to their static type for these types. |
| const List<NativeType> optimizedTypes = [ |
| NativeType.kBool, |
| NativeType.kInt8, |
| NativeType.kInt16, |
| NativeType.kInt32, |
| NativeType.kInt64, |
| NativeType.kUint8, |
| NativeType.kUint16, |
| NativeType.kUint32, |
| NativeType.kUint64, |
| NativeType.kFloat, |
| NativeType.kDouble, |
| NativeType.kPointer, |
| ]; |
| |
| const List<NativeType> unalignedLoadsStores = [ |
| NativeType.kFloat, |
| NativeType.kDouble, |
| ]; |
| |
| const List<String> addressOfExtensionsTypedData = [ |
| 'Float32List', |
| 'Float64List', |
| 'Int16List', |
| 'Int32List', |
| 'Int64List', |
| 'Int8List', |
| 'Uint16List', |
| 'Uint32List', |
| 'Uint64List', |
| 'Uint8List', |
| ]; |
| |
| const List<String> addressOfExtensionsCompound = ['Array', 'Struct', 'Union']; |
| |
| const List<String> addressOfExtensionsPrimitive = ['Bool', 'Double', 'Int']; |
| |
| const List<String> addressOfExtensions = [ |
| ...addressOfExtensionsCompound, |
| ...addressOfExtensionsPrimitive, |
| ...addressOfExtensionsTypedData, |
| ]; |
| |
| enum FfiTypeCheckDirection { |
| // Passing a value from native code to Dart code. |
| nativeToDart, |
| |
| // Passing a value from Dart code to native code. |
| dartToNative, |
| } |
| |
| /// Converting mode of `Handle` in [convertNativeTypeToDartType]. |
| enum HandleToDartTypeConversionMode { |
| /// The variance-based conversion mode, where `Handle` is converted to |
| /// `Never` if it appears in covariant positions of the type being converted, |
| /// or to `Object?` when it appears in contravariant positions of the type. |
| /// This mode is used when the type being converted appears as the subtype of |
| /// a type check. |
| varianceBasedBehaviorForSubtype, |
| |
| /// The variance-based conversion mode, where `Handle` is converted to |
| /// `Object?` if it appears in covariant positions of the type being |
| /// converted, or to `Never` when it appears in contravariant positions of |
| /// the type. This mode is used when the type being converted appears as the |
| /// supertype of a type check. |
| varianceBasedBehaviorForSupertype, |
| } |
| |
| /// [FfiTransformer] contains logic which is shared between |
| /// _FfiUseSiteTransformer and _FfiDefinitionTransformer. |
| class FfiTransformer extends Transformer { |
| final TypeEnvironment env; |
| final CoreTypes coreTypes; |
| final LibraryIndex index; |
| final ClassHierarchy hierarchy; |
| final DiagnosticReporter diagnosticReporter; |
| final ReferenceFromIndex? referenceFromIndex; |
| |
| final Class objectClass; |
| final Class intClass; |
| final Class doubleClass; |
| final Class boolClass; |
| final Class listClass; |
| final Class typeClass; |
| final Procedure unsafeCastMethod; |
| final Procedure nativeEffectMethod; |
| final Class typedDataClass; |
| final Procedure typedDataBufferGetter; |
| final Procedure typedDataOffsetInBytesGetter; |
| final Procedure byteBufferAsUint8List; |
| final Procedure uint8ListFactory; |
| final Class pragmaClass; |
| final Field pragmaName; |
| final Field pragmaOptions; |
| final Procedure listElementAt; |
| final Procedure numAddition; |
| final Procedure numMultiplication; |
| final Procedure objectEquals; |
| final Procedure stateErrorThrowNewFunction; |
| |
| final Library ffiLibrary; |
| final Class allocatorClass; |
| final Class nativeFunctionClass; |
| final Class handleClass; |
| final Class opaqueClass; |
| final Class arrayClass; |
| final Class arraySizeClass; |
| final Field arraySizeDimension1Field; |
| final Field arraySizeDimension2Field; |
| final Field arraySizeDimension3Field; |
| final Field arraySizeDimension4Field; |
| final Field arraySizeDimension5Field; |
| final Field arraySizeDimensionsField; |
| final Field arraySizeVariableDimensionField; |
| final Class pointerClass; |
| final Class compoundClass; |
| final Class structClass; |
| final Class unionClass; |
| final Class abiSpecificIntegerClass; |
| final Class abiSpecificIntegerMappingClass; |
| final Class varArgsClass; |
| final Class nativeFieldWrapperClass1Class; |
| final Class ffiStructLayoutClass; |
| final Field ffiStructLayoutTypesField; |
| final Field ffiStructLayoutPackingField; |
| final Class ffiAbiSpecificMappingClass; |
| final Field ffiAbiSpecificMappingNativeTypesField; |
| final Class ffiInlineArrayClass; |
| final Field ffiInlineArrayElementTypeField; |
| final Field ffiInlineArrayLengthField; |
| final Field ffiInlineArrayVariableLengthField; |
| final Class packedClass; |
| final Field packedMemberAlignmentField; |
| final Procedure allocateMethod; |
| final Procedure allocatorAllocateMethod; |
| final Procedure castMethod; |
| final Procedure offsetByMethod; |
| final Procedure addressGetter; |
| final Procedure structPointerGetRef; |
| final Procedure structPointerSetRef; |
| final Procedure structPointerRefWithFinalizer; |
| final Procedure structPointerRefWithFinalizerTearoff; |
| final Procedure structPointerGetElemAt; |
| final Procedure structPointerSetElemAt; |
| final Procedure structPointerElementAt; |
| final Procedure structPointerPlusOperator; |
| final Procedure structPointerMinusOperator; |
| final Procedure structPointerElementAtTearoff; |
| final Procedure unionPointerGetRef; |
| final Procedure unionPointerSetRef; |
| final Procedure unionPointerRefWithFinalizer; |
| final Procedure unionPointerRefWithFinalizerTearoff; |
| final Procedure unionPointerGetElemAt; |
| final Procedure unionPointerSetElemAt; |
| final Procedure unionPointerElementAt; |
| final Procedure unionPointerPlusOperator; |
| final Procedure unionPointerMinusOperator; |
| final Procedure unionPointerElementAtTearoff; |
| final Procedure uint8PointerAsTypedList; |
| final Constructor arrayListConstructor; |
| final Constructor arrayArrayListConstructor; |
| final Constructor abiSpecificIntegerArrayListConstructor; |
| final Procedure arrayArrayElements; |
| final Procedure structArrayElements; |
| final Procedure unionArrayElements; |
| final Procedure abiSpecificIntegerArrayElements; |
| final Procedure structArrayElemAt; |
| final Procedure unionArrayElemAt; |
| final Procedure arrayArrayElemAt; |
| final Procedure arrayArrayAssignAt; |
| final Procedure abiSpecificIntegerPointerGetValue; |
| final Procedure abiSpecificIntegerPointerSetValue; |
| final Procedure abiSpecificIntegerPointerElemAt; |
| final Procedure abiSpecificIntegerPointerSetElemAt; |
| final Procedure abiSpecificIntegerPointerElementAt; |
| final Procedure abiSpecificIntegerPointerPlusOperator; |
| final Procedure abiSpecificIntegerPointerMinusOperator; |
| final Procedure abiSpecificIntegerPointerElementAtTearoff; |
| final Procedure abiSpecificIntegerArrayElemAt; |
| final Procedure abiSpecificIntegerArraySetElemAt; |
| final Procedure asFunctionMethod; |
| final Procedure ffiCallMethod; |
| final Procedure sizeOfMethod; |
| final Procedure lookupFunctionMethod; |
| final Procedure fromFunctionMethod; |
| final Field compoundTypedDataBaseField; |
| final Field compoundOffsetInBytesField; |
| final Field arraySizeField; |
| final Field arrayVariableLengthField; |
| final Field arrayNestedDimensionsField; |
| final Procedure arrayCheckIndex; |
| final Procedure arrayNestedDimensionsFlattened; |
| final Procedure arrayNestedDimensionsFirst; |
| final Procedure arrayNestedDimensionsRest; |
| final Procedure structCreate; |
| final Procedure unionCreate; |
| final Constructor compoundFromTypedDataBase; |
| final Constructor structFromTypedDataBase; |
| final Constructor unionFromTypedDataBase; |
| final Constructor structFromTypedData; |
| final Constructor unionFromTypedData; |
| final Constructor arrayConstructor; |
| final Procedure fromAddressInternal; |
| final Procedure libraryLookupMethod; |
| final Procedure abiMethod; |
| final Procedure createNativeCallableListenerProcedure; |
| final Procedure nativeCallbackFunctionProcedure; |
| final Procedure nativeAsyncCallbackFunctionProcedure; |
| final Procedure createNativeCallableIsolateLocalProcedure; |
| final Procedure createNativeCallableIsolateGroupBoundProcedure; |
| final Procedure nativeIsolateLocalCallbackFunctionProcedure; |
| final Procedure nativeIsolateGroupBoundCallbackFunctionProcedure; |
| final Procedure nativeIsolateGroupBoundClosureFunctionProcedure; |
| final Map<NativeType, Procedure> loadMethods; |
| final Map<NativeType, Procedure> loadUnalignedMethods; |
| final Map<NativeType, Procedure> storeMethods; |
| final Map<NativeType, Procedure> storeUnalignedMethods; |
| final Procedure loadAbiSpecificIntMethod; |
| final Procedure loadAbiSpecificIntAtIndexMethod; |
| final Procedure storeAbiSpecificIntMethod; |
| final Procedure storeAbiSpecificIntAtIndexMethod; |
| final Procedure abiCurrentMethod; |
| final Map<Constant, Abi> constantAbis; |
| final Class intptrClass; |
| late AbiSpecificNativeTypeCfe intptrNativeTypeCfe; |
| final Procedure memCopy; |
| final Procedure allocationTearoff; |
| final Procedure asFunctionTearoff; |
| final Procedure lookupFunctionTearoff; |
| final Procedure getNativeFieldFunction; |
| final Class finalizableClass; |
| final Procedure reachabilityFenceFunction; |
| final Procedure checkAbiSpecificIntegerMappingFunction; |
| final Class rawRecvPortClass; |
| final Class nativeCallableClass; |
| final Procedure nativeCallableIsolateLocalConstructor; |
| final Procedure nativeCallableIsolateGroupBoundConstructor; |
| final Constructor nativeCallablePrivateIsolateLocalConstructor; |
| final Constructor nativeCallablePrivateIsolateGroupBoundConstructor; |
| final Procedure nativeCallableListenerConstructor; |
| final Constructor nativeCallablePrivateListenerConstructor; |
| final Field nativeCallablePortField; |
| final Field nativeCallablePointerField; |
| final Procedure nativeAddressOf; |
| final Procedure nativePrivateAddressOf; |
| final List<Procedure> addressOfMethods; |
| final List<Procedure> addressOfMethodsCompound; |
| final List<Procedure> addressOfMethodsPrimitive; |
| final List<Procedure> addressOfMethodsTypedData; |
| final Class ffiCallClass; |
| final Field ffiCallIsLeafField; |
| final Field nativeIsLeafField; |
| |
| late final InterfaceType nativeFieldWrapperClass1Type; |
| late final InterfaceType voidType; |
| late final InterfaceType pointerVoidType; |
| // The instantiated to bounds type argument for the Pointer class. |
| late final InterfaceType nativeTypeType; |
| // The Pointer type when instantiated to bounds. |
| late final InterfaceType pointerNativeTypeType; |
| late final InterfaceType uint8Type; |
| late final InterfaceType compoundType; |
| |
| /// Classes corresponding to [NativeType], indexed by [NativeType]. |
| final Map<NativeType, Class> nativeTypesClasses; |
| final Map<Class, NativeType> classNativeTypes; |
| |
| Library? _currentLibrary; |
| Library get currentLibrary => _currentLibrary!; |
| |
| IndexedLibrary? currentLibraryIndex; |
| |
| FfiTransformer( |
| this.index, |
| this.coreTypes, |
| this.hierarchy, |
| this.diagnosticReporter, |
| this.referenceFromIndex, |
| ) : env = TypeEnvironment(coreTypes, hierarchy), |
| objectClass = coreTypes.objectClass, |
| intClass = coreTypes.intClass, |
| doubleClass = coreTypes.doubleClass, |
| boolClass = coreTypes.boolClass, |
| listClass = coreTypes.listClass, |
| typeClass = coreTypes.typeClass, |
| unsafeCastMethod = index.getTopLevelProcedure( |
| 'dart:_internal', |
| 'unsafeCast', |
| ), |
| nativeEffectMethod = index.getTopLevelProcedure( |
| 'dart:_internal', |
| '_nativeEffect', |
| ), |
| typedDataClass = index.getClass('dart:typed_data', 'TypedData'), |
| typedDataBufferGetter = index.getProcedure( |
| 'dart:typed_data', |
| 'TypedData', |
| 'get:buffer', |
| ), |
| typedDataOffsetInBytesGetter = index.getProcedure( |
| 'dart:typed_data', |
| 'TypedData', |
| 'get:offsetInBytes', |
| ), |
| byteBufferAsUint8List = index.getProcedure( |
| 'dart:typed_data', |
| 'ByteBuffer', |
| 'asUint8List', |
| ), |
| uint8ListFactory = index.getProcedure('dart:typed_data', 'Uint8List', ''), |
| pragmaClass = coreTypes.pragmaClass, |
| pragmaName = coreTypes.pragmaName, |
| pragmaOptions = coreTypes.pragmaOptions, |
| listElementAt = coreTypes.index.getProcedure('dart:core', 'List', '[]'), |
| numAddition = coreTypes.index.getProcedure('dart:core', 'num', '+'), |
| numMultiplication = coreTypes.index.getProcedure('dart:core', 'num', '*'), |
| objectEquals = coreTypes.index.getProcedure('dart:core', 'Object', '=='), |
| stateErrorThrowNewFunction = coreTypes.index.getProcedure( |
| 'dart:core', |
| 'StateError', |
| '_throwNew', |
| ), |
| ffiLibrary = index.getLibrary('dart:ffi'), |
| allocatorClass = index.getClass('dart:ffi', 'Allocator'), |
| nativeFunctionClass = index.getClass('dart:ffi', 'NativeFunction'), |
| handleClass = index.getClass('dart:ffi', 'Handle'), |
| opaqueClass = index.getClass('dart:ffi', 'Opaque'), |
| arrayClass = index.getClass('dart:ffi', 'Array'), |
| arraySizeClass = index.getClass('dart:ffi', '_ArraySize'), |
| arraySizeDimension1Field = index.getField( |
| 'dart:ffi', |
| '_ArraySize', |
| 'dimension1', |
| ), |
| arraySizeDimension2Field = index.getField( |
| 'dart:ffi', |
| '_ArraySize', |
| 'dimension2', |
| ), |
| arraySizeDimension3Field = index.getField( |
| 'dart:ffi', |
| '_ArraySize', |
| 'dimension3', |
| ), |
| arraySizeDimension4Field = index.getField( |
| 'dart:ffi', |
| '_ArraySize', |
| 'dimension4', |
| ), |
| arraySizeDimension5Field = index.getField( |
| 'dart:ffi', |
| '_ArraySize', |
| 'dimension5', |
| ), |
| arraySizeDimensionsField = index.getField( |
| 'dart:ffi', |
| '_ArraySize', |
| 'dimensions', |
| ), |
| arraySizeVariableDimensionField = index.getField( |
| 'dart:ffi', |
| '_ArraySize', |
| 'variableDimension', |
| ), |
| pointerClass = index.getClass('dart:ffi', 'Pointer'), |
| compoundClass = index.getClass('dart:ffi', '_Compound'), |
| structClass = index.getClass('dart:ffi', 'Struct'), |
| unionClass = index.getClass('dart:ffi', 'Union'), |
| abiSpecificIntegerClass = index.getClass( |
| 'dart:ffi', |
| 'AbiSpecificInteger', |
| ), |
| abiSpecificIntegerMappingClass = index.getClass( |
| 'dart:ffi', |
| 'AbiSpecificIntegerMapping', |
| ), |
| varArgsClass = index.getClass('dart:ffi', 'VarArgs'), |
| nativeFieldWrapperClass1Class = index.getClass( |
| 'dart:nativewrappers', |
| 'NativeFieldWrapperClass1', |
| ), |
| ffiStructLayoutClass = index.getClass('dart:ffi', '_FfiStructLayout'), |
| ffiStructLayoutTypesField = index.getField( |
| 'dart:ffi', |
| '_FfiStructLayout', |
| 'fieldTypes', |
| ), |
| ffiStructLayoutPackingField = index.getField( |
| 'dart:ffi', |
| '_FfiStructLayout', |
| 'packing', |
| ), |
| ffiAbiSpecificMappingClass = index.getClass( |
| 'dart:ffi', |
| '_FfiAbiSpecificMapping', |
| ), |
| ffiAbiSpecificMappingNativeTypesField = index.getField( |
| 'dart:ffi', |
| '_FfiAbiSpecificMapping', |
| 'nativeTypes', |
| ), |
| ffiInlineArrayClass = index.getClass('dart:ffi', '_FfiInlineArray'), |
| ffiInlineArrayElementTypeField = index.getField( |
| 'dart:ffi', |
| '_FfiInlineArray', |
| 'elementType', |
| ), |
| ffiInlineArrayLengthField = index.getField( |
| 'dart:ffi', |
| '_FfiInlineArray', |
| 'length', |
| ), |
| ffiInlineArrayVariableLengthField = index.getField( |
| 'dart:ffi', |
| '_FfiInlineArray', |
| 'variableLength', |
| ), |
| packedClass = index.getClass('dart:ffi', 'Packed'), |
| packedMemberAlignmentField = index.getField( |
| 'dart:ffi', |
| 'Packed', |
| 'memberAlignment', |
| ), |
| allocateMethod = index.getProcedure('dart:ffi', 'AllocatorAlloc', 'call'), |
| allocatorAllocateMethod = index.getProcedure( |
| 'dart:ffi', |
| 'Allocator', |
| 'allocate', |
| ), |
| castMethod = index.getProcedure('dart:ffi', 'Pointer', 'cast'), |
| offsetByMethod = index.getProcedure('dart:ffi', 'Pointer', '_offsetBy'), |
| addressGetter = index.getProcedure('dart:ffi', 'Pointer', 'get:address'), |
| compoundTypedDataBaseField = index.getField( |
| 'dart:ffi', |
| '_Compound', |
| '_typedDataBase', |
| ), |
| compoundOffsetInBytesField = index.getField( |
| 'dart:ffi', |
| '_Compound', |
| '_offsetInBytes', |
| ), |
| arraySizeField = index.getField('dart:ffi', 'Array', '_size'), |
| arrayVariableLengthField = index.getField( |
| 'dart:ffi', |
| 'Array', |
| '_variableLength', |
| ), |
| arrayNestedDimensionsField = index.getField( |
| 'dart:ffi', |
| 'Array', |
| '_nestedDimensions', |
| ), |
| arrayCheckIndex = index.getProcedure('dart:ffi', 'Array', '_checkIndex'), |
| arrayNestedDimensionsFlattened = index.getProcedure( |
| 'dart:ffi', |
| 'Array', |
| 'get:_nestedDimensionsFlattened', |
| ), |
| arrayNestedDimensionsFirst = index.getProcedure( |
| 'dart:ffi', |
| 'Array', |
| 'get:_nestedDimensionsFirst', |
| ), |
| arrayNestedDimensionsRest = index.getProcedure( |
| 'dart:ffi', |
| 'Array', |
| 'get:_nestedDimensionsRest', |
| ), |
| structCreate = index.getProcedure('dart:ffi', 'Struct', 'create'), |
| unionCreate = index.getProcedure('dart:ffi', 'Union', 'create'), |
| compoundFromTypedDataBase = index.getConstructor( |
| 'dart:ffi', |
| '_Compound', |
| '_fromTypedDataBase', |
| ), |
| structFromTypedDataBase = index.getConstructor( |
| 'dart:ffi', |
| 'Struct', |
| '_fromTypedDataBase', |
| ), |
| unionFromTypedDataBase = index.getConstructor( |
| 'dart:ffi', |
| 'Union', |
| '_fromTypedDataBase', |
| ), |
| structFromTypedData = index.getConstructor( |
| 'dart:ffi', |
| 'Struct', |
| '_fromTypedData', |
| ), |
| unionFromTypedData = index.getConstructor( |
| 'dart:ffi', |
| 'Union', |
| '_fromTypedData', |
| ), |
| arrayConstructor = index.getConstructor('dart:ffi', 'Array', '_'), |
| fromAddressInternal = index.getTopLevelProcedure( |
| 'dart:ffi', |
| '_fromAddress', |
| ), |
| structPointerGetRef = index.getProcedure( |
| 'dart:ffi', |
| 'StructPointer', |
| 'get:ref', |
| ), |
| structPointerSetRef = index.getProcedure( |
| 'dart:ffi', |
| 'StructPointer', |
| 'set:ref', |
| ), |
| structPointerRefWithFinalizer = index.getProcedure( |
| 'dart:ffi', |
| 'StructPointer', |
| 'refWithFinalizer', |
| ), |
| structPointerRefWithFinalizerTearoff = index.getProcedure( |
| 'dart:ffi', |
| 'StructPointer', |
| LibraryIndex.tearoffPrefix + 'refWithFinalizer', |
| ), |
| structPointerGetElemAt = index.getProcedure( |
| 'dart:ffi', |
| 'StructPointer', |
| '[]', |
| ), |
| structPointerSetElemAt = index.getProcedure( |
| 'dart:ffi', |
| 'StructPointer', |
| '[]=', |
| ), |
| structPointerElementAt = index.getProcedure( |
| 'dart:ffi', |
| 'StructPointer', |
| 'elementAt', |
| ), |
| structPointerPlusOperator = index.getProcedure( |
| 'dart:ffi', |
| 'StructPointer', |
| '+', |
| ), |
| structPointerMinusOperator = index.getProcedure( |
| 'dart:ffi', |
| 'StructPointer', |
| '-', |
| ), |
| structPointerElementAtTearoff = index.getProcedure( |
| 'dart:ffi', |
| 'StructPointer', |
| LibraryIndex.tearoffPrefix + 'elementAt', |
| ), |
| unionPointerGetRef = index.getProcedure( |
| 'dart:ffi', |
| 'UnionPointer', |
| 'get:ref', |
| ), |
| unionPointerSetRef = index.getProcedure( |
| 'dart:ffi', |
| 'UnionPointer', |
| 'set:ref', |
| ), |
| unionPointerRefWithFinalizer = index.getProcedure( |
| 'dart:ffi', |
| 'UnionPointer', |
| 'refWithFinalizer', |
| ), |
| unionPointerRefWithFinalizerTearoff = index.getProcedure( |
| 'dart:ffi', |
| 'UnionPointer', |
| LibraryIndex.tearoffPrefix + 'refWithFinalizer', |
| ), |
| unionPointerGetElemAt = index.getProcedure( |
| 'dart:ffi', |
| 'UnionPointer', |
| '[]', |
| ), |
| unionPointerSetElemAt = index.getProcedure( |
| 'dart:ffi', |
| 'UnionPointer', |
| '[]=', |
| ), |
| unionPointerElementAt = index.getProcedure( |
| 'dart:ffi', |
| 'UnionPointer', |
| 'elementAt', |
| ), |
| unionPointerPlusOperator = index.getProcedure( |
| 'dart:ffi', |
| 'UnionPointer', |
| '+', |
| ), |
| unionPointerMinusOperator = index.getProcedure( |
| 'dart:ffi', |
| 'UnionPointer', |
| '-', |
| ), |
| unionPointerElementAtTearoff = index.getProcedure( |
| 'dart:ffi', |
| 'UnionPointer', |
| LibraryIndex.tearoffPrefix + 'elementAt', |
| ), |
| uint8PointerAsTypedList = index.getProcedure( |
| 'dart:ffi', |
| 'Uint8Pointer', |
| 'asTypedList', |
| ), |
| arrayArrayElements = index.getProcedure( |
| 'dart:ffi', |
| 'ArrayArray', |
| 'get:elements', |
| ), |
| structArrayElements = index.getProcedure( |
| 'dart:ffi', |
| 'StructArray', |
| 'get:elements', |
| ), |
| unionArrayElements = index.getProcedure( |
| 'dart:ffi', |
| 'UnionArray', |
| 'get:elements', |
| ), |
| abiSpecificIntegerArrayElements = index.getProcedure( |
| 'dart:ffi', |
| 'AbiSpecificIntegerArray', |
| 'get:elements', |
| ), |
| arrayArrayListConstructor = index.getConstructor( |
| 'dart:ffi', |
| '_ArrayArrayList', |
| '', |
| ), |
| arrayListConstructor = index.getConstructor('dart:ffi', '_ArrayList', ''), |
| abiSpecificIntegerArrayListConstructor = index.getConstructor( |
| 'dart:ffi', |
| '_AbiSpecificIntegerArrayList', |
| '', |
| ), |
| structArrayElemAt = index.getProcedure('dart:ffi', 'StructArray', '[]'), |
| unionArrayElemAt = index.getProcedure('dart:ffi', 'UnionArray', '[]'), |
| arrayArrayElemAt = index.getProcedure('dart:ffi', 'ArrayArray', '[]'), |
| arrayArrayAssignAt = index.getProcedure('dart:ffi', 'ArrayArray', '[]='), |
| abiSpecificIntegerPointerGetValue = index.getProcedure( |
| 'dart:ffi', |
| 'AbiSpecificIntegerPointer', |
| 'get:value', |
| ), |
| abiSpecificIntegerPointerSetValue = index.getProcedure( |
| 'dart:ffi', |
| 'AbiSpecificIntegerPointer', |
| 'set:value', |
| ), |
| abiSpecificIntegerPointerElemAt = index.getProcedure( |
| 'dart:ffi', |
| 'AbiSpecificIntegerPointer', |
| '[]', |
| ), |
| abiSpecificIntegerPointerSetElemAt = index.getProcedure( |
| 'dart:ffi', |
| 'AbiSpecificIntegerPointer', |
| '[]=', |
| ), |
| abiSpecificIntegerPointerElementAt = index.getProcedure( |
| 'dart:ffi', |
| 'AbiSpecificIntegerPointer', |
| 'elementAt', |
| ), |
| abiSpecificIntegerPointerPlusOperator = index.getProcedure( |
| 'dart:ffi', |
| 'AbiSpecificIntegerPointer', |
| '+', |
| ), |
| abiSpecificIntegerPointerMinusOperator = index.getProcedure( |
| 'dart:ffi', |
| 'AbiSpecificIntegerPointer', |
| '-', |
| ), |
| abiSpecificIntegerPointerElementAtTearoff = index.getProcedure( |
| 'dart:ffi', |
| 'AbiSpecificIntegerPointer', |
| LibraryIndex.tearoffPrefix + 'elementAt', |
| ), |
| abiSpecificIntegerArrayElemAt = index.getProcedure( |
| 'dart:ffi', |
| 'AbiSpecificIntegerArray', |
| '[]', |
| ), |
| abiSpecificIntegerArraySetElemAt = index.getProcedure( |
| 'dart:ffi', |
| 'AbiSpecificIntegerArray', |
| '[]=', |
| ), |
| asFunctionMethod = index.getProcedure( |
| 'dart:ffi', |
| 'NativeFunctionPointer', |
| 'asFunction', |
| ), |
| ffiCallMethod = index.getTopLevelProcedure('dart:ffi', '_ffiCall'), |
| sizeOfMethod = index.getTopLevelProcedure('dart:ffi', 'sizeOf'), |
| lookupFunctionMethod = index.getProcedure( |
| 'dart:ffi', |
| 'DynamicLibraryExtension', |
| 'lookupFunction', |
| ), |
| fromFunctionMethod = index.getProcedure( |
| 'dart:ffi', |
| 'Pointer', |
| 'fromFunction', |
| ), |
| libraryLookupMethod = index.getProcedure( |
| 'dart:ffi', |
| 'DynamicLibrary', |
| 'lookup', |
| ), |
| abiMethod = index.getTopLevelProcedure('dart:ffi', '_abi'), |
| createNativeCallableListenerProcedure = index.getTopLevelProcedure( |
| 'dart:ffi', |
| '_createNativeCallableListener', |
| ), |
| createNativeCallableIsolateLocalProcedure = index.getTopLevelProcedure( |
| 'dart:ffi', |
| '_createNativeCallableIsolateLocal', |
| ), |
| createNativeCallableIsolateGroupBoundProcedure = index |
| .getTopLevelProcedure( |
| 'dart:ffi', |
| '_createNativeCallableIsolateGroupBound', |
| ), |
| nativeCallbackFunctionProcedure = index.getTopLevelProcedure( |
| 'dart:ffi', |
| '_nativeCallbackFunction', |
| ), |
| nativeAsyncCallbackFunctionProcedure = index.getTopLevelProcedure( |
| 'dart:ffi', |
| '_nativeAsyncCallbackFunction', |
| ), |
| nativeIsolateLocalCallbackFunctionProcedure = index.getTopLevelProcedure( |
| 'dart:ffi', |
| '_nativeIsolateLocalCallbackFunction', |
| ), |
| nativeIsolateGroupBoundCallbackFunctionProcedure = index |
| .getTopLevelProcedure( |
| 'dart:ffi', |
| '_nativeIsolateGroupBoundCallbackFunction', |
| ), |
| nativeIsolateGroupBoundClosureFunctionProcedure = index |
| .getTopLevelProcedure( |
| 'dart:ffi', |
| '_nativeIsolateGroupBoundClosureFunction', |
| ), |
| nativeTypesClasses = nativeTypeClassNames.map( |
| (nativeType, name) => |
| MapEntry(nativeType, index.getClass('dart:ffi', name)), |
| ), |
| classNativeTypes = nativeTypeClassNames.map( |
| (nativeType, name) => |
| MapEntry(index.getClass('dart:ffi', name), nativeType), |
| ), |
| loadMethods = Map.fromIterable( |
| optimizedTypes, |
| value: (t) { |
| final name = nativeTypeClassNames[t]; |
| return index.getTopLevelProcedure('dart:ffi', "_load$name"); |
| }, |
| ), |
| loadUnalignedMethods = Map.fromIterable( |
| unalignedLoadsStores, |
| value: (t) { |
| final name = nativeTypeClassNames[t]; |
| return index.getTopLevelProcedure( |
| 'dart:ffi', |
| "_load${name}Unaligned", |
| ); |
| }, |
| ), |
| storeMethods = Map.fromIterable( |
| optimizedTypes, |
| value: (t) { |
| final name = nativeTypeClassNames[t]; |
| return index.getTopLevelProcedure('dart:ffi', "_store$name"); |
| }, |
| ), |
| storeUnalignedMethods = Map.fromIterable( |
| unalignedLoadsStores, |
| value: (t) { |
| final name = nativeTypeClassNames[t]; |
| return index.getTopLevelProcedure( |
| 'dart:ffi', |
| "_store${name}Unaligned", |
| ); |
| }, |
| ), |
| loadAbiSpecificIntMethod = index.getTopLevelProcedure( |
| 'dart:ffi', |
| "_loadAbiSpecificInt", |
| ), |
| loadAbiSpecificIntAtIndexMethod = index.getTopLevelProcedure( |
| 'dart:ffi', |
| "_loadAbiSpecificIntAtIndex", |
| ), |
| storeAbiSpecificIntMethod = index.getTopLevelProcedure( |
| 'dart:ffi', |
| "_storeAbiSpecificInt", |
| ), |
| storeAbiSpecificIntAtIndexMethod = index.getTopLevelProcedure( |
| 'dart:ffi', |
| "_storeAbiSpecificIntAtIndex", |
| ), |
| abiCurrentMethod = index.getProcedure('dart:ffi', 'Abi', 'current'), |
| constantAbis = abiNames.map( |
| (abi, name) => MapEntry( |
| (index.getField('dart:ffi', 'Abi', name).initializer |
| as ConstantExpression) |
| .constant, |
| abi, |
| ), |
| ), |
| intptrClass = index.getClass('dart:ffi', 'IntPtr'), |
| memCopy = index.getTopLevelProcedure('dart:ffi', '_memCopy'), |
| allocationTearoff = index.getProcedure( |
| 'dart:ffi', |
| 'AllocatorAlloc', |
| LibraryIndex.tearoffPrefix + 'call', |
| ), |
| asFunctionTearoff = index.getProcedure( |
| 'dart:ffi', |
| 'NativeFunctionPointer', |
| LibraryIndex.tearoffPrefix + 'asFunction', |
| ), |
| lookupFunctionTearoff = index.getProcedure( |
| 'dart:ffi', |
| 'DynamicLibraryExtension', |
| LibraryIndex.tearoffPrefix + 'lookupFunction', |
| ), |
| getNativeFieldFunction = index.getTopLevelProcedure( |
| 'dart:nativewrappers', |
| '_getNativeField', |
| ), |
| finalizableClass = index.getClass('dart:ffi', 'Finalizable'), |
| reachabilityFenceFunction = index.getTopLevelProcedure( |
| 'dart:_internal', |
| 'reachabilityFence', |
| ), |
| checkAbiSpecificIntegerMappingFunction = index.getTopLevelProcedure( |
| 'dart:ffi', |
| "_checkAbiSpecificIntegerMapping", |
| ), |
| rawRecvPortClass = index.getClass('dart:isolate', 'RawReceivePort'), |
| nativeCallableClass = index.getClass('dart:ffi', 'NativeCallable'), |
| nativeCallableIsolateLocalConstructor = index.getProcedure( |
| 'dart:ffi', |
| 'NativeCallable', |
| 'isolateLocal', |
| ), |
| nativeCallableIsolateGroupBoundConstructor = index.getProcedure( |
| 'dart:ffi', |
| 'NativeCallable', |
| 'isolateGroupBound', |
| ), |
| nativeCallablePrivateIsolateLocalConstructor = index.getConstructor( |
| 'dart:ffi', |
| '_NativeCallableIsolateLocal', |
| '', |
| ), |
| nativeCallableListenerConstructor = index.getProcedure( |
| 'dart:ffi', |
| 'NativeCallable', |
| 'listener', |
| ), |
| nativeCallablePrivateListenerConstructor = index.getConstructor( |
| 'dart:ffi', |
| '_NativeCallableListener', |
| '', |
| ), |
| nativeCallablePrivateIsolateGroupBoundConstructor = index.getConstructor( |
| 'dart:ffi', |
| '_NativeCallableIsolateGroupBound', |
| '', |
| ), |
| nativeCallablePortField = index.getField( |
| 'dart:ffi', |
| '_NativeCallableListener', |
| '_port', |
| ), |
| nativeCallablePointerField = index.getField( |
| 'dart:ffi', |
| '_NativeCallableBase', |
| '_pointer', |
| ), |
| nativeAddressOf = |
| index.getMember('dart:ffi', 'Native', 'addressOf') as Procedure, |
| nativePrivateAddressOf = |
| index.getMember('dart:ffi', 'Native', '_addressOf') as Procedure, |
| addressOfMethods = [ |
| for (final name in addressOfExtensions) |
| index.getProcedure('dart:ffi', '${name}Address', 'get:address'), |
| ], |
| addressOfMethodsPrimitive = [ |
| for (final name in addressOfExtensionsPrimitive) |
| index.getProcedure('dart:ffi', '${name}Address', 'get:address'), |
| ], |
| addressOfMethodsCompound = [ |
| for (final name in addressOfExtensionsCompound) |
| index.getProcedure('dart:ffi', '${name}Address', 'get:address'), |
| ], |
| addressOfMethodsTypedData = [ |
| for (final name in addressOfExtensionsTypedData) |
| index.getProcedure('dart:ffi', '${name}Address', 'get:address'), |
| ], |
| ffiCallClass = index.getClass('dart:ffi', '_FfiCall'), |
| ffiCallIsLeafField = index.getField('dart:ffi', '_FfiCall', 'isLeaf'), |
| nativeIsLeafField = index.getField('dart:ffi', 'Native', 'isLeaf') { |
| nativeFieldWrapperClass1Type = nativeFieldWrapperClass1Class.getThisType( |
| coreTypes, |
| Nullability.nonNullable, |
| ); |
| voidType = nativeTypesClasses[NativeType.kVoid]!.getThisType( |
| coreTypes, |
| Nullability.nonNullable, |
| ); |
| pointerVoidType = InterfaceType(pointerClass, Nullability.nonNullable, [ |
| voidType, |
| ]); |
| nativeTypeType = nativeTypesClasses[NativeType.kNativeType]!.getThisType( |
| coreTypes, |
| Nullability.nonNullable, |
| ); |
| pointerNativeTypeType = InterfaceType( |
| pointerClass, |
| Nullability.nonNullable, |
| [nativeTypeType], |
| ); |
| intptrNativeTypeCfe = |
| NativeTypeCfe(this, InterfaceType(intptrClass, Nullability.nonNullable)) |
| as AbiSpecificNativeTypeCfe; |
| uint8Type = InterfaceType( |
| nativeTypesClasses[NativeType.kUint8]!, |
| Nullability.nonNullable, |
| ); |
| compoundType = InterfaceType( |
| compoundClass, |
| Nullability.nonNullable, |
| const <DartType>[], |
| ); |
| } |
| |
| @override |
| TreeNode visitLibrary(Library node) { |
| assert(_currentLibrary == null); |
| _currentLibrary = node; |
| currentLibraryIndex = referenceFromIndex?.lookupLibrary(node); |
| final result = super.visitLibrary(node); |
| _currentLibrary = null; |
| return result; |
| } |
| |
| /// Computes the Dart type corresponding to a ffi.[NativeType], returns null |
| /// if it is not a valid NativeType. |
| /// |
| /// ``` |
| /// [Int8] -> [int] |
| /// [Int16] -> [int] |
| /// [Int32] -> [int] |
| /// [Int64] -> [int] |
| /// [Uint8] -> [int] |
| /// [Uint16] -> [int] |
| /// [Uint32] -> [int] |
| /// [Uint64] -> [int] |
| /// [IntPtr] -> [int] |
| /// T extends [AbiSpecificInteger] -> [int] |
| /// [Double] -> [double] |
| /// [Float] -> [double] |
| /// [Bool] -> [bool] |
| /// [Void] -> [void] |
| /// [Pointer]<T> -> [Pointer]<T> |
| /// T extends [Struct] -> T |
| /// T extends [Union] -> T |
| /// [Handle] -> [Object?]|[Never] |
| /// [NativeFunction]<T1 Function(T2, T3) -> S1 Function(S2, S3) |
| /// where DartRepresentationOf(Tn) -> Sn |
| /// ``` |
| /// |
| /// If [mode] is [HandleToDartTypeConversionMode.oldNativeBehavior], `Handle` |
| /// is converted to `Object?`. Otherwise, `Handle` is converted to either |
| /// `Object?` or `Never`. Since `Handle` represents any possible Dart object, |
| /// it should match any type. To achieve that, in the subtype checks where |
| /// `Handle` appears as the subtype (as indicated by [mode] being |
| /// [HandleToDartTypeConversionMode.varianceBasedBehaviorForSubtype]), it |
| /// should be converted depending on the variance of its position: `Never` for |
| /// covariant positions, and `Object?` for contravariant ones. If `Handle` |
| /// appears as the supertype in the check (as indicated by [mode] being |
| /// [HandleToDartTypeConversionMode.varianceBasedBehaviorForSupertype]), the |
| /// treatment of covariant and contravariant positions is reversed. For more |
| /// details, see https://github.com/dart-lang/sdk/issues/49518. |
| /// |
| /// [isCovariant] is the flag that helps compute the variance of the position |
| /// in [nativeType]. It's updated in the recursive invocations of |
| /// [convertNativeTypeToDartType] and should be omitted in regular call sites. |
| /// [isCovariant] is ignored if [mode] is |
| /// [HandleToDartTypeConversionMode.oldNativeBehavior]. |
| DartType? convertNativeTypeToDartType( |
| DartType nativeType, { |
| bool allowStructAndUnion = false, |
| bool allowHandle = false, |
| bool allowInlineArray = false, |
| bool allowVoid = false, |
| HandleToDartTypeConversionMode mode = |
| HandleToDartTypeConversionMode.varianceBasedBehaviorForSubtype, |
| Variance variance = Variance.invariant, |
| }) { |
| if (nativeType is! InterfaceType) { |
| return null; |
| } |
| final InterfaceType native = nativeType; |
| final Class nativeClass = native.classNode; |
| final NativeType? nativeType_ = getType(nativeClass); |
| |
| if (nativeClass == arrayClass) { |
| if (!allowInlineArray) { |
| return null; |
| } |
| final nested = convertNativeTypeToDartType( |
| nativeType.typeArguments.single, |
| allowInlineArray: true, |
| allowStructAndUnion: true, |
| ); |
| if (nested == null) { |
| return null; |
| } |
| return nativeType; |
| } |
| if (hierarchy.isSubclassOf(nativeClass, abiSpecificIntegerClass)) { |
| if (nativeClass == abiSpecificIntegerClass) { |
| return null; |
| } |
| return coreTypes.intNonNullableRawType; |
| } |
| if (hierarchy.isSubclassOf(nativeClass, compoundClass)) { |
| if (nativeClass == structClass || nativeClass == unionClass) { |
| return null; |
| } |
| return allowStructAndUnion ? nativeType : null; |
| } |
| if (nativeType_ == null) { |
| return null; |
| } |
| if (nativeType_ == NativeType.kPointer) { |
| return nativeType; |
| } |
| if (nativeIntTypesFixedSize.contains(nativeType_)) { |
| return coreTypes.intNonNullableRawType; |
| } |
| if (nativeType_ == NativeType.kFloat || nativeType_ == NativeType.kDouble) { |
| return coreTypes.doubleNonNullableRawType; |
| } |
| if (nativeType_ == NativeType.kBool) { |
| return coreTypes.boolNonNullableRawType; |
| } |
| if (nativeType_ == NativeType.kVoid) { |
| if (!allowVoid) { |
| return null; |
| } |
| return VoidType(); |
| } |
| if (nativeType_ == NativeType.kHandle && allowHandle) { |
| switch (mode) { |
| case HandleToDartTypeConversionMode.varianceBasedBehaviorForSubtype: |
| return switch (variance) { |
| Variance.contravariant => coreTypes.objectNullableRawType, |
| Variance.covariant || |
| Variance.invariant || |
| Variance.unrelated => const NeverType.nonNullable(), |
| }; |
| case HandleToDartTypeConversionMode.varianceBasedBehaviorForSupertype: |
| return switch (variance) { |
| Variance.contravariant => const NeverType.nonNullable(), |
| Variance.covariant || |
| Variance.invariant || |
| Variance.unrelated => coreTypes.objectNullableRawType, |
| }; |
| } |
| } |
| if (nativeType_ != NativeType.kNativeFunction || |
| native.typeArguments[0] is! FunctionType) { |
| return null; |
| } |
| |
| final FunctionType fun = native.typeArguments[0] as FunctionType; |
| if (fun.namedParameters.isNotEmpty) return null; |
| if (fun.positionalParameters.length != fun.requiredParameterCount) { |
| return null; |
| } |
| if (fun.typeParameters.isNotEmpty) return null; |
| |
| final DartType? returnType = convertNativeTypeToDartType( |
| fun.returnType, |
| allowStructAndUnion: true, |
| allowHandle: true, |
| allowVoid: true, |
| mode: mode, |
| variance: variance, |
| ); |
| if (returnType == null) return null; |
| final argumentTypes = <DartType>[]; |
| for (final paramDartType in flattenVarargs(fun).positionalParameters) { |
| argumentTypes.add( |
| convertNativeTypeToDartType( |
| paramDartType, |
| allowStructAndUnion: true, |
| allowHandle: true, |
| mode: mode, |
| variance: variance.combine(Variance.contravariant), |
| ) ?? |
| dummyDartType, |
| ); |
| } |
| if (argumentTypes.contains(dummyDartType)) return null; |
| return FunctionType(argumentTypes, returnType, Nullability.nonNullable); |
| } |
| |
| /// Finds a native type for the given [dartType] if there is only one possible |
| /// native type. |
| /// |
| /// This is impossible for some types (like [int] which needs a specific ffi |
| /// type to denote the width in C). This method returns `null` for those |
| /// types. |
| /// |
| /// For types where this returns a non-null value, this is the inverse of |
| /// [convertNativeTypeToDartType]. |
| DartType? convertDartTypeToNativeType( |
| DartType dartType, { |
| bool allowVoid = false, |
| }) { |
| if (allowVoid && dartType is VoidType) return voidType; |
| |
| if (isArrayType(dartType) || |
| isPointerType(dartType) || |
| isStructOrUnionSubtype(dartType)) { |
| return dartType; |
| } |
| |
| if (dartType is FunctionType) { |
| if (dartType.namedParameters.isNotEmpty) return null; |
| if (dartType.positionalParameters.length != |
| dartType.requiredParameterCount) { |
| return null; |
| } |
| if (dartType.typeParameters.isNotEmpty) return null; |
| |
| final returnType = convertDartTypeToNativeType( |
| dartType.returnType, |
| allowVoid: true, |
| ); |
| if (returnType == null) return null; |
| |
| final argumentTypes = <DartType>[]; |
| for (final paramDartType |
| in flattenVarargs(dartType).positionalParameters) { |
| argumentTypes.add( |
| convertDartTypeToNativeType(paramDartType) ?? dummyDartType, |
| ); |
| } |
| if (argumentTypes.contains(dummyDartType)) return null; |
| return FunctionType(argumentTypes, returnType, Nullability.nonNullable); |
| } |
| |
| return null; |
| } |
| |
| /// Removes the VarArgs from a DartType list. |
| /// |
| /// ``` |
| /// [Int8, Int8] -> [Int8, Int8] |
| /// [Int8, VarArgs<(Int8,)>] -> [Int8, Int8] |
| /// [Int8, VarArgs<(Int8, Int8)>] -> [Int8, Int8, Int8] |
| /// ``` |
| FunctionType flattenVarargs(FunctionType functionTypeWithPossibleVarArgs) { |
| final positionalParameters = |
| functionTypeWithPossibleVarArgs.positionalParameters; |
| if (positionalParameters.isEmpty) { |
| return functionTypeWithPossibleVarArgs; |
| } |
| final lastPositionalParameter = positionalParameters.last; |
| if (lastPositionalParameter is InterfaceType && |
| lastPositionalParameter.classNode == varArgsClass) { |
| final typeArgument = lastPositionalParameter.typeArguments.single; |
| if (typeArgument is! RecordType) { |
| return functionTypeWithPossibleVarArgs; |
| } |
| |
| if (typeArgument.named.isNotEmpty) { |
| // Named record fields are not supported. |
| return functionTypeWithPossibleVarArgs; |
| } |
| |
| final positionalParameters = [ |
| ...functionTypeWithPossibleVarArgs.positionalParameters.sublist( |
| 0, |
| functionTypeWithPossibleVarArgs.positionalParameters.length - 1, |
| ), |
| for (final paramDartType in typeArgument.positional) paramDartType, |
| ]; |
| return FunctionType( |
| positionalParameters, |
| functionTypeWithPossibleVarArgs.returnType, |
| functionTypeWithPossibleVarArgs.declaredNullability, |
| namedParameters: functionTypeWithPossibleVarArgs.namedParameters, |
| typeParameters: functionTypeWithPossibleVarArgs.typeParameters, |
| requiredParameterCount: positionalParameters.length, |
| ); |
| } |
| return functionTypeWithPossibleVarArgs; |
| } |
| |
| /// The [NativeType] corresponding to [c]. Returns `null` for user-defined |
| /// structs. |
| NativeType? getType(Class c) { |
| return classNativeTypes[c]; |
| } |
| |
| InterfaceType _listOfIntType(Nullability elementNullability) => InterfaceType( |
| listClass, |
| Nullability.nonNullable, |
| [coreTypes.intRawType(elementNullability)], |
| ); |
| |
| ConstantExpression intListConstantExpression( |
| List<int?> values, |
| Nullability elementNullability, |
| ) => ConstantExpression( |
| ListConstant(coreTypes.intRawType(elementNullability), [ |
| for (var v in values) |
| if (v != null) IntConstant(v) else NullConstant(), |
| ]), |
| _listOfIntType(elementNullability), |
| ); |
| |
| /// Expression that queries VM internals at runtime to figure out on which ABI |
| /// we are. |
| Expression runtimeBranchOnLayout(Map<Abi, int?> values) { |
| final elementNullability = |
| values.isPartial ? Nullability.nullable : Nullability.nonNullable; |
| final result = InstanceInvocation( |
| InstanceAccessKind.Instance, |
| intListConstantExpression([ |
| for (final abi in Abi.values) values[abi], |
| ], elementNullability), |
| listElementAt.name, |
| Arguments([StaticInvocation(abiMethod, Arguments([]))]), |
| interfaceTarget: listElementAt, |
| functionType: |
| Substitution.fromInterfaceType( |
| _listOfIntType(elementNullability), |
| ).substituteType(listElementAt.getterType) |
| as FunctionType, |
| ); |
| if (values.isPartial) { |
| return checkAbiSpecificIntegerMapping(result); |
| } |
| return result; |
| } |
| |
| Expression checkAbiSpecificIntegerMapping(Expression nullableExpression) => |
| StaticInvocation( |
| checkAbiSpecificIntegerMappingFunction, |
| Arguments( |
| [nullableExpression], |
| types: [InterfaceType(intClass, Nullability.nonNullable)], |
| ), |
| ); |
| |
| bool isPrimitiveType(DartType type) { |
| if (type is InvalidType) { |
| return false; |
| } |
| if (type is NullType) { |
| return false; |
| } |
| if (!env.isSubtypeOf(type, nativeTypeType)) { |
| return false; |
| } |
| if (isPointerType(type)) { |
| return false; |
| } |
| if (type is InterfaceType) { |
| final nativeType = getType(type.classNode); |
| return nativeType != null; |
| } |
| return false; |
| } |
| |
| bool isPointerType(DartType type) { |
| if (type is InvalidType) { |
| return false; |
| } |
| if (type is NullType) { |
| return false; |
| } |
| return env.isSubtypeOf(type, pointerNativeTypeType); |
| } |
| |
| bool isArrayType(DartType type) { |
| if (type is InvalidType) { |
| return false; |
| } |
| if (type is NullType) { |
| return false; |
| } |
| return env.isSubtypeOf( |
| type, |
| InterfaceType(arrayClass, Nullability.nonNullable, [nativeTypeType]), |
| ); |
| } |
| |
| /// Returns the single element type nested type argument of `Array`. |
| /// |
| /// `Array<Array<Array<Int8>>>` -> `Int8`. |
| /// |
| /// `Array<Array<Array<Unknown>>>` -> [InvalidType]. |
| DartType arraySingleElementType(DartType dartType) { |
| if (dartType is! InterfaceType) { |
| return InvalidType(); |
| } |
| InterfaceType elementType = dartType; |
| while (elementType.classNode == arrayClass) { |
| final elementTypeAny = elementType.typeArguments[0]; |
| if (elementTypeAny is! InterfaceType) { |
| return InvalidType(); |
| } |
| elementType = elementTypeAny; |
| } |
| return elementType; |
| } |
| |
| /// Ensures that [node] has an `Array` annotation with valid dimensions |
| /// matching its [type]. |
| /// |
| /// Throws an [FfiStaticTypeError] otherwise. |
| List<int> ensureArraySizeAnnotation( |
| Member node, |
| DartType type, |
| bool allowVariableLength, |
| ) { |
| final sizeAnnotations = getArraySizeAnnotations(node); |
| List<int> dimensions; |
| bool variableLength; |
| var success = true; |
| |
| if (sizeAnnotations.length == 1) { |
| final singleElementType = arraySingleElementType(type); |
| if (singleElementType is! InterfaceType) { |
| assert(singleElementType is InvalidType); |
| throw FfiStaticTypeError(); |
| } else { |
| dimensions = sizeAnnotations.single.$1; |
| variableLength = sizeAnnotations.single.$2; |
| if (arrayDimensions(type) != dimensions.length) { |
| diagnosticReporter.report( |
| templateFfiSizeAnnotationDimensions.withArguments(node.name.text), |
| node.fileOffset, |
| node.name.text.length, |
| node.fileUri, |
| ); |
| } |
| if (variableLength) { |
| if (!allowVariableLength) { |
| diagnosticReporter.report( |
| messageFfiVariableLengthArrayNotLast, |
| node.fileOffset, |
| node.name.text.length, |
| node.fileUri, |
| ); |
| } |
| } |
| for (var i = 0; i < dimensions.length; i++) { |
| // First dimension is variable. |
| if (i == 0 && variableLength) { |
| // Variable dimension can't be negative. |
| if (dimensions[0] < 0) { |
| diagnosticReporter.report( |
| messageNegativeVariableDimension, |
| node.fileOffset, |
| node.name.text.length, |
| node.fileUri, |
| ); |
| } |
| continue; |
| } |
| |
| if (dimensions[i] <= 0) { |
| diagnosticReporter.report( |
| messageNonPositiveArrayDimensions, |
| node.fileOffset, |
| node.name.text.length, |
| node.fileUri, |
| ); |
| success = false; |
| } |
| } |
| } |
| } else { |
| diagnosticReporter.report( |
| templateFfiSizeAnnotation.withArguments(node.name.text), |
| node.fileOffset, |
| node.name.text.length, |
| node.fileUri, |
| ); |
| throw FfiStaticTypeError(); |
| } |
| |
| if (!success) { |
| throw FfiStaticTypeError(); |
| } |
| |
| return dimensions; |
| } |
| |
| Iterable<(List<int>, bool)> getArraySizeAnnotations(Member node) { |
| return node.annotations |
| .whereType<ConstantExpression>() |
| .map((e) => e.constant) |
| .whereType<InstanceConstant>() |
| .where((e) => e.classNode == arraySizeClass) |
| .map(_arraySize); |
| } |
| |
| /// Reads the dimensions from a constant instance of `_ArraySize`. |
| (List<int>, bool) _arraySize(InstanceConstant constant) { |
| final variableDimension = |
| constant.fieldValues[arraySizeVariableDimensionField.fieldReference]; |
| final variableLength = variableDimension is IntConstant; |
| final dimensions = |
| constant.fieldValues[arraySizeDimensionsField.fieldReference]; |
| if (dimensions is ListConstant) { |
| final result = dimensions.entries.whereType<IntConstant>().map( |
| (e) => e.value, |
| ); |
| return ( |
| [if (variableLength) variableDimension.value, ...result], |
| variableLength, |
| ); |
| } |
| final dimensionFields = [ |
| arraySizeDimension1Field, |
| arraySizeDimension2Field, |
| arraySizeDimension3Field, |
| arraySizeDimension4Field, |
| arraySizeDimension5Field, |
| ]; |
| final result = |
| dimensionFields |
| .map((f) => constant.fieldValues[f.fieldReference]) |
| .whereType<IntConstant>() |
| .map((c) => c.value) |
| .toList(); |
| return (result, variableLength); |
| } |
| |
| /// Returns the number of dimensions of `Array`. |
| /// |
| /// `Array<Array<Array<Int8>>>` -> 3. |
| /// |
| /// `Array<Array<Array<Unknown>>>` -> 3. |
| int arrayDimensions(DartType dartType) { |
| DartType elementType = dartType; |
| int dimensions = 0; |
| while (elementType is InterfaceType && |
| elementType.classNode == arrayClass) { |
| elementType = elementType.typeArguments[0]; |
| dimensions++; |
| } |
| return dimensions; |
| } |
| |
| bool isAbiSpecificIntegerSubtype(DartType type) { |
| if (type is InvalidType) { |
| return false; |
| } |
| if (type is NullType) { |
| return false; |
| } |
| if (type is InterfaceType) { |
| if (type.classNode == abiSpecificIntegerClass) { |
| return false; |
| } |
| } |
| return env.isSubtypeOf( |
| type, |
| InterfaceType(abiSpecificIntegerClass, Nullability.nonNullable), |
| ); |
| } |
| |
| bool isStructOrUnionSubtype(DartType type) { |
| if (type is InvalidType) { |
| return false; |
| } |
| if (type is NullType) { |
| return false; |
| } |
| if (type is InterfaceType) { |
| if (type.classNode == structClass || |
| type.classNode == unionClass || |
| type.classNode == arrayClass) { |
| return false; |
| } |
| } |
| return env.isSubtypeOf( |
| type, |
| InterfaceType(compoundClass, Nullability.nonNullable), |
| ); |
| } |
| |
| Expression getCompoundTypedDataBaseField( |
| Expression receiver, |
| int fileOffset, |
| ) { |
| return InstanceGet( |
| InstanceAccessKind.Instance, |
| receiver, |
| compoundTypedDataBaseField.name, |
| interfaceTarget: compoundTypedDataBaseField, |
| resultType: compoundTypedDataBaseField.type, |
| )..fileOffset = fileOffset; |
| } |
| |
| Expression getCompoundOffsetInBytesField( |
| Expression receiver, |
| int fileOffset, |
| ) { |
| return InstanceGet( |
| InstanceAccessKind.Instance, |
| receiver, |
| compoundOffsetInBytesField.name, |
| interfaceTarget: compoundOffsetInBytesField, |
| resultType: compoundOffsetInBytesField.type, |
| )..fileOffset = fileOffset; |
| } |
| |
| Expression add(Expression a, Expression b) { |
| return InstanceInvocation( |
| InstanceAccessKind.Instance, |
| a, |
| numAddition.name, |
| Arguments([b]), |
| interfaceTarget: numAddition, |
| functionType: numAddition.getterType as FunctionType, |
| ); |
| } |
| |
| Expression multiply(Expression a, Expression b) { |
| return InstanceInvocation( |
| InstanceAccessKind.Instance, |
| a, |
| numMultiplication.name, |
| Arguments([b]), |
| interfaceTarget: numMultiplication, |
| functionType: numMultiplication.getterType as FunctionType, |
| ); |
| } |
| |
| MapConstant? getAbiSpecificIntegerMappingAnnotation(Class node) { |
| final annotations = |
| node.annotations |
| .whereType<ConstantExpression>() |
| .map((e) => e.constant) |
| .whereType<InstanceConstant>() |
| .where((e) => e.classNode == abiSpecificIntegerMappingClass) |
| .map( |
| (instanceConstant) => |
| instanceConstant.fieldValues.values.single as MapConstant, |
| ) |
| .toList(); |
| |
| // There can be at most one annotation (checked by `_FfiDefinitionTransformer`) |
| if (annotations.length == 1) { |
| return annotations[0]; |
| } |
| return null; |
| } |
| |
| Expression? inlineSizeOf(InterfaceType nativeType) { |
| final Class nativeClass = nativeType.classNode; |
| final NativeType? nt = getType(nativeClass); |
| if (nt == null) { |
| // User-defined compounds. |
| final Procedure sizeOfGetter = nativeClass.procedures.firstWhere( |
| (function) => function.name == Name('#sizeOf'), |
| ); |
| return StaticGet(sizeOfGetter); |
| } |
| final int size = nativeTypeSizes[nt]!; |
| if (size == WORD_SIZE) { |
| return runtimeBranchOnLayout(wordSize); |
| } |
| if (size != UNKNOWN) { |
| return ConstantExpression( |
| IntConstant(size), |
| InterfaceType(intClass, currentLibrary.nonNullable), |
| ); |
| } |
| // Size unknown. |
| return null; |
| } |
| |
| /// Generates an expression performing an Abi specific integer load or store. |
| /// |
| /// If [value] is provided, it is a store, otherwise a load. |
| /// |
| /// Provide either [index], or [offsetInBytes], or none for an offset of 0. |
| /// |
| /// Generates an expression: |
| /// |
| /// ```dart |
| /// _storeAbiSpecificInt( |
| /// [8, 8, 4][_abi()], |
| /// typedDataBase, |
| /// index * [8, 8, 4][_abi()], |
| /// value, |
| /// ) |
| /// ``` |
| Expression abiSpecificLoadOrStoreExpression( |
| AbiSpecificNativeTypeCfe nativeTypeCfe, { |
| required Expression typedDataBase, |
| Expression? offsetInBytes, |
| Expression? index, |
| Expression? value, |
| required fileOffset, |
| }) { |
| final method = () { |
| if (value != null) { |
| if (index != null) { |
| return storeAbiSpecificIntAtIndexMethod; |
| } |
| return storeAbiSpecificIntMethod; |
| } |
| if (index != null) { |
| return loadAbiSpecificIntAtIndexMethod; |
| } |
| return loadAbiSpecificIntMethod; |
| }(); |
| |
| return StaticInvocation( |
| method, |
| Arguments( |
| [ |
| typedDataBase, |
| offsetInBytes ?? ConstantExpression(IntConstant(0)), |
| if (index != null) index, |
| if (value != null) value, |
| ], |
| types: [InterfaceType(nativeTypeCfe.clazz, Nullability.nonNullable)], |
| ), |
| )..fileOffset = fileOffset; |
| } |
| |
| /// Prevents the struct from being tree-shaken in TFA by invoking its |
| /// constructor in a `_nativeEffect` expression. |
| Expression invokeCompoundConstructor( |
| Expression nestedExpression, |
| Class compoundClass, |
| ) { |
| final constructor = compoundClass.constructors.firstWhere( |
| (c) => c.name == Name("#fromTypedDataBase"), |
| ); |
| return BlockExpression( |
| Block([ |
| ExpressionStatement( |
| StaticInvocation( |
| nativeEffectMethod, |
| Arguments([ |
| ConstructorInvocation( |
| constructor, |
| Arguments([ |
| StaticInvocation( |
| uint8ListFactory, |
| Arguments([ConstantExpression(IntConstant(1))]), |
| )..fileOffset = nestedExpression.fileOffset, |
| ConstantExpression(IntConstant(0)), |
| ]), |
| )..fileOffset = nestedExpression.fileOffset, |
| ]), |
| ), |
| ), |
| ]), |
| nestedExpression, |
| )..fileOffset = nestedExpression.fileOffset; |
| } |
| |
| /// Returns the compound [Class] if a compound is returned, otherwise `null`. |
| Class? findCompoundReturnType(DartType dartSignature) { |
| if (dartSignature is! FunctionType) { |
| return null; |
| } |
| final returnType = dartSignature.returnType; |
| if (returnType is! InterfaceType) { |
| return null; |
| } |
| final clazz = returnType.classNode; |
| if (clazz.superclass == structClass || clazz.superclass == unionClass) { |
| return clazz; |
| } |
| return null; |
| } |
| |
| /// Returns |
| /// - `true` if leaf |
| /// - `false` if not leaf |
| /// - `null` if the expression is not valid (e.g. non-const bool, null) |
| bool? getIsLeafBoolean(StaticInvocation node) { |
| for (final named in node.arguments.named) { |
| if (named.name == 'isLeaf') { |
| final expr = named.value; |
| if (expr is BoolLiteral) { |
| return expr.value; |
| } else if (expr is ConstantExpression) { |
| final constant = expr.constant; |
| if (constant is BoolConstant) { |
| return constant.value; |
| } |
| } |
| // isLeaf is passed some invalid value. |
| return null; |
| } |
| } |
| // isLeaf defaults to false. |
| return false; |
| } |
| |
| void ensureLeafCallDoesNotUseHandles( |
| InterfaceType nativeType, |
| bool isLeaf, { |
| required TreeNode reportErrorOn, |
| }) { |
| // Handles are only disallowed for leaf calls. |
| if (isLeaf == false) { |
| return; |
| } |
| |
| bool error = false; |
| |
| // Check if return type is Handle. |
| final functionType = nativeType.typeArguments[0]; |
| if (functionType is FunctionType) { |
| final returnType = functionType.returnType; |
| if (returnType is InterfaceType) { |
| if (returnType.classNode == handleClass) { |
| diagnosticReporter.report( |
| messageFfiLeafCallMustNotReturnHandle, |
| reportErrorOn.fileOffset, |
| 1, |
| reportErrorOn.location?.file, |
| ); |
| error = true; |
| } |
| } |
| // Check if any of the argument types are Handle. |
| for (DartType param in functionType.positionalParameters) { |
| if ((param as InterfaceType).classNode == handleClass) { |
| diagnosticReporter.report( |
| messageFfiLeafCallMustNotTakeHandle, |
| reportErrorOn.fileOffset, |
| 1, |
| reportErrorOn.location?.file, |
| ); |
| error = true; |
| } |
| } |
| } |
| |
| if (error) { |
| throw FfiStaticTypeError(); |
| } |
| } |
| |
| DartType ensureNativeTypeMatch( |
| FfiTypeCheckDirection direction, |
| DartType nativeType, |
| DartType dartType, |
| TreeNode reportErrorOn, { |
| bool allowHandle = false, |
| bool allowVoid = false, |
| bool allowArray = false, |
| }) { |
| final DartType correspondingDartType; |
| switch (direction) { |
| case FfiTypeCheckDirection.nativeToDart: |
| correspondingDartType = |
| convertNativeTypeToDartType( |
| nativeType, |
| allowStructAndUnion: true, |
| allowHandle: allowHandle, |
| allowInlineArray: allowArray, |
| allowVoid: allowVoid, |
| mode: |
| HandleToDartTypeConversionMode |
| .varianceBasedBehaviorForSubtype, |
| variance: Variance.covariant, |
| )!; |
| if (env.isSubtypeOf(correspondingDartType, dartType)) { |
| // If subtype, manually check the return type is not void. |
| if (correspondingDartType is FunctionType) { |
| if (dartType is FunctionType) { |
| if ((dartType.returnType is VoidType) == |
| (correspondingDartType.returnType is VoidType)) { |
| return correspondingDartType; |
| } |
| // One of the return types is void, the other isn't, report error. |
| } else { |
| // One is a function type, the other isn't, report error. |
| } |
| } else { |
| return correspondingDartType; |
| } |
| } |
| case FfiTypeCheckDirection.dartToNative: |
| correspondingDartType = |
| convertNativeTypeToDartType( |
| nativeType, |
| allowStructAndUnion: true, |
| allowHandle: allowHandle, |
| allowInlineArray: allowArray, |
| allowVoid: allowVoid, |
| mode: |
| HandleToDartTypeConversionMode |
| .varianceBasedBehaviorForSupertype, |
| variance: Variance.covariant, |
| )!; |
| if (env.isSubtypeOf(dartType, correspondingDartType)) { |
| return correspondingDartType; |
| } |
| } |
| diagnosticReporter.report( |
| templateFfiTypeMismatch.withArguments( |
| dartType, |
| correspondingDartType, |
| nativeType, |
| ), |
| reportErrorOn.fileOffset, |
| 1, |
| reportErrorOn.location?.file, |
| ); |
| throw FfiStaticTypeError(); |
| } |
| |
| void ensureNativeTypeValid( |
| DartType nativeType, |
| TreeNode reportErrorOn, { |
| bool allowHandle = false, |
| bool allowStructAndUnion = false, |
| bool allowInlineArray = false, |
| bool allowVoid = false, |
| }) { |
| if (!isNativeTypeValid( |
| nativeType, |
| allowStructAndUnion: allowStructAndUnion, |
| allowHandle: allowHandle, |
| allowInlineArray: allowInlineArray, |
| allowVoid: allowVoid, |
| )) { |
| diagnosticReporter.report( |
| templateFfiTypeInvalid.withArguments(nativeType), |
| reportErrorOn.fileOffset, |
| 1, |
| reportErrorOn.location?.file, |
| ); |
| throw FfiStaticTypeError(); |
| } |
| } |
| |
| /// The Dart type system does not enforce that NativeFunction return and |
| /// parameter types are only NativeTypes, so we need to check this. |
| bool isNativeTypeValid( |
| DartType nativeType, { |
| bool allowStructAndUnion = false, |
| bool allowHandle = false, |
| bool allowInlineArray = false, |
| bool allowVoid = false, |
| }) { |
| return convertNativeTypeToDartType( |
| nativeType, |
| allowStructAndUnion: allowStructAndUnion, |
| allowHandle: allowHandle, |
| allowInlineArray: allowInlineArray, |
| allowVoid: allowVoid, |
| ) != |
| null; |
| } |
| |
| void addPragmaPreferInline(Member node) { |
| assert(node is Procedure || node is Constructor); |
| node.addAnnotation( |
| ConstantExpression( |
| InstanceConstant(pragmaClass.reference, [], { |
| pragmaName.fieldReference: StringConstant("vm:prefer-inline"), |
| pragmaOptions.fieldReference: NullConstant(), |
| }), |
| ), |
| ); |
| } |
| } |
| |
| /// Returns all libraries including the ones from component except for platform |
| /// libraries that are only in component. |
| Set<Library> _getAllRelevantLibraries( |
| Component component, |
| List<Library> libraries, |
| ) { |
| Set<Library> allLibs = {}; |
| allLibs.addAll(libraries); |
| for (Library lib in component.libraries) { |
| // Skip real dart: libraries. dart:core imports dart:ffi, but that doesn't |
| // mean we have to transform anything. |
| if (lib.importUri.isScheme("dart") && !lib.isSynthetic) continue; |
| allLibs.add(lib); |
| } |
| return allLibs; |
| } |
| |
| /// Checks if any library depends on dart:ffi. |
| Library? importsFfi(Component component, List<Library> libraries) { |
| final Uri dartFfiUri = Uri.parse("dart:ffi"); |
| Set<Library> allLibs = _getAllRelevantLibraries(component, libraries); |
| for (Library lib in allLibs) { |
| for (LibraryDependency dependency in lib.dependencies) { |
| Library targetLibrary = dependency.targetLibrary; |
| if (targetLibrary.importUri == dartFfiUri) { |
| return targetLibrary; |
| } |
| } |
| } |
| return null; |
| } |
| |
| /// Calculates the libraries in [libraries] that transitively imports dart:ffi. |
| /// |
| /// Returns null if dart:ffi is not imported. |
| List<Library>? calculateTransitiveImportsOfDartFfiIfUsed( |
| Component component, |
| List<Library> libraries, |
| ) { |
| Set<Library> allLibs = _getAllRelevantLibraries(component, libraries); |
| |
| final Uri dartFfiUri = Uri.parse("dart:ffi"); |
| Library? dartFfi; |
| canFind: |
| for (Library lib in allLibs) { |
| for (LibraryDependency dependency in lib.dependencies) { |
| Library targetLibrary = dependency.targetLibrary; |
| if (targetLibrary.importUri == dartFfiUri) { |
| dartFfi = targetLibrary; |
| break canFind; |
| } |
| } |
| } |
| if (dartFfi == null) return null; |
| |
| kernelGraph.LibraryGraph graph = new kernelGraph.LibraryGraph(allLibs); |
| Set<Library> result = kernelGraph.calculateTransitiveDependenciesOf(graph, { |
| dartFfi, |
| }); |
| return (result..retainAll(libraries)).toList(); |
| } |
| |
| extension on Map<Abi, Object?> { |
| bool get isPartial => |
| [for (final abi in Abi.values) this[abi]].contains(null); |
| } |
| |
| /// Used internally for abnormal control flow to prevent cascading error |
| /// messages. |
| class FfiStaticTypeError implements Exception {} |