| // 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. |
| |
| import 'package:analyzer/dart/ast/ast.dart'; |
| import 'package:analyzer/dart/ast/token.dart'; |
| import 'package:analyzer/dart/ast/visitor.dart'; |
| import 'package:analyzer/dart/element/element.dart'; |
| import 'package:analyzer/dart/element/type.dart'; |
| import 'package:analyzer/error/listener.dart'; |
| import 'package:analyzer/src/dart/ast/extensions.dart'; |
| import 'package:analyzer/src/dart/element/element.dart'; |
| import 'package:analyzer/src/dart/element/type.dart'; |
| import 'package:analyzer/src/dart/element/type_system.dart'; |
| import 'package:analyzer/src/dart/error/ffi_code.dart'; |
| |
| /// A visitor used to find problems with the way the `dart:ffi` APIs are being |
| /// used. See 'pkg/vm/lib/transformations/ffi_checks.md' for the specification |
| /// of the desired hints. |
| class FfiVerifier extends RecursiveAstVisitor<void> { |
| static const _abiSpecificIntegerClassName = 'AbiSpecificInteger'; |
| static const _abiSpecificIntegerMappingClassName = |
| 'AbiSpecificIntegerMapping'; |
| static const _allocatorClassName = 'Allocator'; |
| static const _allocateExtensionMethodName = 'call'; |
| static const _allocatorExtensionName = 'AllocatorAlloc'; |
| static const _arrayClassName = 'Array'; |
| static const _dartFfiLibraryName = 'dart.ffi'; |
| static const _finalizableClassName = 'Finalizable'; |
| static const _isLeafParamName = 'isLeaf'; |
| static const _opaqueClassName = 'Opaque'; |
| |
| static const Set<String> _primitiveIntegerNativeTypesFixedSize = { |
| 'Int8', |
| 'Int16', |
| 'Int32', |
| 'Int64', |
| 'Uint8', |
| 'Uint16', |
| 'Uint32', |
| 'Uint64', |
| }; |
| static const Set<String> _primitiveIntegerNativeTypes = { |
| ..._primitiveIntegerNativeTypesFixedSize, |
| 'IntPtr' |
| }; |
| |
| static const Set<String> _primitiveDoubleNativeTypes = { |
| 'Float', |
| 'Double', |
| }; |
| |
| static const _primitiveBoolNativeType = 'Bool'; |
| |
| static const _structClassName = 'Struct'; |
| |
| static const _unionClassName = 'Union'; |
| |
| /// The type system used to check types. |
| final TypeSystemImpl typeSystem; |
| |
| /// The error reporter used to report errors. |
| final ErrorReporter _errorReporter; |
| |
| /// A flag indicating whether we are currently visiting inside a subclass of |
| /// `Struct`. |
| bool inCompound = false; |
| |
| /// Subclass of `Struct` or `Union` we are currently visiting, or `null`. |
| ClassDeclaration? compound; |
| |
| /// Initialize a newly created verifier. |
| FfiVerifier(this.typeSystem, this._errorReporter); |
| |
| @override |
| void visitClassDeclaration(ClassDeclaration node) { |
| inCompound = false; |
| compound = null; |
| // Only the Allocator, Opaque and Struct class may be extended. |
| var extendsClause = node.extendsClause; |
| if (extendsClause != null) { |
| final NamedType superclass = extendsClause.superclass; |
| final ffiClass = superclass.ffiClass; |
| if (ffiClass != null) { |
| final className = ffiClass.name; |
| if (className == _structClassName || className == _unionClassName) { |
| inCompound = true; |
| compound = node; |
| if (node.declaredElement!.isEmptyStruct) { |
| _errorReporter.reportErrorForToken(FfiCode.EMPTY_STRUCT, node.name2, |
| [node.name2.lexeme, className]); |
| } |
| if (className == _structClassName) { |
| _validatePackedAnnotation(node.metadata); |
| } |
| } else if (className == _abiSpecificIntegerClassName) { |
| _validateAbiSpecificIntegerAnnotation(node); |
| _validateAbiSpecificIntegerMappingAnnotation( |
| node.name2, node.metadata); |
| } else if (className != _allocatorClassName && |
| className != _opaqueClassName && |
| className != _abiSpecificIntegerClassName) { |
| _errorReporter.reportErrorForNode( |
| FfiCode.SUBTYPE_OF_FFI_CLASS_IN_EXTENDS, |
| superclass.name, |
| [node.name2.lexeme, superclass.name.name]); |
| } |
| } else if (superclass.isCompoundSubtype || |
| superclass.isAbiSpecificIntegerSubtype) { |
| _errorReporter.reportErrorForNode( |
| FfiCode.SUBTYPE_OF_STRUCT_CLASS_IN_EXTENDS, |
| superclass, |
| [node.name2.lexeme, superclass.name.name]); |
| } |
| } |
| |
| // No classes from the FFI may be explicitly implemented. |
| void checkSupertype(NamedType typename, FfiCode subtypeOfFfiCode, |
| FfiCode subtypeOfStructCode) { |
| final superName = typename.name.staticElement?.name; |
| if (superName == _allocatorClassName || |
| superName == _finalizableClassName) { |
| return; |
| } |
| if (typename.ffiClass != null) { |
| _errorReporter.reportErrorForNode(subtypeOfFfiCode, typename, |
| [node.name2.lexeme, typename.name.toSource()]); |
| } else if (typename.isCompoundSubtype || |
| typename.isAbiSpecificIntegerSubtype) { |
| _errorReporter.reportErrorForNode(subtypeOfStructCode, typename, |
| [node.name2.lexeme, typename.name.toSource()]); |
| } |
| } |
| |
| var implementsClause = node.implementsClause; |
| if (implementsClause != null) { |
| for (NamedType type in implementsClause.interfaces) { |
| checkSupertype(type, FfiCode.SUBTYPE_OF_FFI_CLASS_IN_IMPLEMENTS, |
| FfiCode.SUBTYPE_OF_STRUCT_CLASS_IN_IMPLEMENTS); |
| } |
| } |
| var withClause = node.withClause; |
| if (withClause != null) { |
| for (NamedType type in withClause.mixinTypes) { |
| checkSupertype(type, FfiCode.SUBTYPE_OF_FFI_CLASS_IN_WITH, |
| FfiCode.SUBTYPE_OF_STRUCT_CLASS_IN_WITH); |
| } |
| } |
| |
| if (inCompound) { |
| if (node.declaredElement!.typeParameters.isNotEmpty) { |
| _errorReporter.reportErrorForToken( |
| FfiCode.GENERIC_STRUCT_SUBCLASS, node.name2, [node.name2.lexeme]); |
| } |
| final implementsClause = node.implementsClause; |
| if (implementsClause != null) { |
| final compoundType = node.declaredElement!.thisType; |
| final structType = compoundType.superclass!; |
| final ffiLibrary = structType.element2.library; |
| final finalizableElement = ffiLibrary.getType(_finalizableClassName)!; |
| final finalizableType = finalizableElement.thisType; |
| if (typeSystem.isSubtypeOf(compoundType, finalizableType)) { |
| _errorReporter.reportErrorForToken( |
| FfiCode.COMPOUND_IMPLEMENTS_FINALIZABLE, |
| node.name2, |
| [node.name2.lexeme]); |
| } |
| } |
| } |
| super.visitClassDeclaration(node); |
| } |
| |
| @override |
| void visitConstructorFieldInitializer(ConstructorFieldInitializer node) { |
| if (!typeSystem.isNonNullableByDefault && inCompound) { |
| _errorReporter.reportErrorForNode( |
| FfiCode.FIELD_INITIALIZER_IN_STRUCT, |
| node, |
| ); |
| } |
| super.visitConstructorFieldInitializer(node); |
| } |
| |
| @override |
| void visitFieldDeclaration(FieldDeclaration node) { |
| if (inCompound) { |
| _validateFieldsInCompound(node); |
| } |
| super.visitFieldDeclaration(node); |
| } |
| |
| @override |
| void visitFunctionDeclaration(FunctionDeclaration node) { |
| _checkFfiNative( |
| errorNode: node, |
| annotations: node.metadata, |
| declarationElement: node.declaredElement as ExecutableElement, |
| formalParameterList: node.functionExpression.parameters, |
| ); |
| super.visitFunctionDeclaration(node); |
| } |
| |
| @override |
| void visitFunctionExpressionInvocation(FunctionExpressionInvocation node) { |
| var element = node.staticElement; |
| if (element is MethodElement) { |
| var enclosingElement = element.enclosingElement3; |
| if (enclosingElement.isAllocatorExtension && |
| element.name == _allocateExtensionMethodName) { |
| _validateAllocate(node); |
| } |
| } |
| super.visitFunctionExpressionInvocation(node); |
| } |
| |
| @override |
| void visitIndexExpression(IndexExpression node) { |
| var element = node.staticElement; |
| if (element is MethodElement) { |
| var enclosingElement = element.enclosingElement3; |
| if (enclosingElement.isNativeStructPointerExtension || |
| enclosingElement.isNativeStructArrayExtension) { |
| if (element.name == '[]') { |
| _validateRefIndexed(node); |
| } |
| } |
| } |
| } |
| |
| @override |
| void visitInstanceCreationExpression(InstanceCreationExpression node) { |
| var constructor = node.constructorName.staticElement; |
| var class_ = constructor?.enclosingElement3; |
| if (class_.isStructSubclass || class_.isUnionSubclass) { |
| _errorReporter.reportErrorForNode( |
| FfiCode.CREATION_OF_STRUCT_OR_UNION, |
| node.constructorName, |
| ); |
| } |
| |
| super.visitInstanceCreationExpression(node); |
| } |
| |
| @override |
| void visitMethodDeclaration(MethodDeclaration node) { |
| _checkFfiNative( |
| errorNode: node, |
| annotations: node.metadata, |
| declarationElement: node.declaredElement as ExecutableElement, |
| formalParameterList: node.parameters); |
| super.visitMethodDeclaration(node); |
| } |
| |
| @override |
| void visitMethodInvocation(MethodInvocation node) { |
| var element = node.methodName.staticElement; |
| if (element is MethodElement) { |
| Element enclosingElement = element.enclosingElement3; |
| if (enclosingElement.isPointer) { |
| if (element.name == 'fromFunction') { |
| _validateFromFunction(node, element); |
| } else if (element.name == 'elementAt') { |
| _validateElementAt(node); |
| } |
| } else if (enclosingElement.isNativeFunctionPointerExtension) { |
| if (element.name == 'asFunction') { |
| _validateAsFunction(node, element); |
| } |
| } else if (enclosingElement.isDynamicLibraryExtension) { |
| if (element.name == 'lookupFunction') { |
| _validateLookupFunction(node); |
| } |
| } |
| } else if (element is FunctionElement) { |
| var enclosingElement = element.enclosingElement3; |
| if (enclosingElement is CompilationUnitElement) { |
| if (element.library.name == 'dart.ffi') { |
| if (element.name == 'sizeOf') { |
| _validateSizeOf(node); |
| } |
| } |
| } |
| } |
| super.visitMethodInvocation(node); |
| } |
| |
| @override |
| void visitPrefixedIdentifier(PrefixedIdentifier node) { |
| var element = node.staticElement; |
| if (element != null) { |
| var enclosingElement = element.enclosingElement3; |
| if (enclosingElement.isNativeStructPointerExtension) { |
| if (element.name == 'ref') { |
| _validateRefPrefixedIdentifier(node); |
| } |
| } |
| } |
| super.visitPrefixedIdentifier(node); |
| } |
| |
| @override |
| void visitPropertyAccess(PropertyAccess node) { |
| var element = node.propertyName.staticElement; |
| if (element != null) { |
| var enclosingElement = element.enclosingElement3; |
| if (enclosingElement.isNativeStructPointerExtension) { |
| if (element.name == 'ref') { |
| _validateRefPropertyAccess(node); |
| } |
| } |
| } |
| super.visitPropertyAccess(node); |
| } |
| |
| void _checkFfiNative({ |
| required Declaration errorNode, |
| required List<Annotation> annotations, |
| required ExecutableElement declarationElement, |
| required FormalParameterList? formalParameterList, |
| }) { |
| final formalParameters = |
| formalParameterList?.parameters ?? <FormalParameter>[]; |
| |
| for (Annotation annotation in annotations) { |
| if (!annotation.isFfiNative) { |
| continue; |
| } |
| |
| final typeArguments = annotation.typeArguments?.arguments; |
| final arguments = annotation.arguments?.arguments; |
| if (typeArguments == null || arguments == null) { |
| continue; |
| } |
| |
| final ffiSignature = typeArguments[0].type! as FunctionType; |
| |
| // Leaf call FFI Natives can't use Handles. |
| _validateFfiLeafCallUsesNoHandles(arguments, ffiSignature, errorNode); |
| |
| if (!declarationElement.isExternal) { |
| _errorReporter.reportErrorForNode( |
| FfiCode.FFI_NATIVE_MUST_BE_EXTERNAL, errorNode); |
| } |
| |
| var ffiParameterTypes = ffiSignature.normalParameterTypes; |
| var ffiParameters = ffiSignature.parameters; |
| |
| if ((declarationElement is MethodElement || |
| declarationElement is PropertyAccessorElementImpl) && |
| !declarationElement.isStatic) { |
| // Instance methods must have the receiver as an extra parameter in the |
| // FfiNative annotation. |
| if (formalParameters.length + 1 != ffiSignature.parameters.length) { |
| _errorReporter.reportErrorForNode( |
| FfiCode.FFI_NATIVE_UNEXPECTED_NUMBER_OF_PARAMETERS_WITH_RECEIVER, |
| errorNode, |
| [formalParameters.length + 1, ffiSignature.parameters.length]); |
| return; |
| } |
| |
| // Receiver can only be Pointer if the class extends |
| // NativeFieldWrapperClass1. |
| if (ffiSignature.normalParameterTypes[0].isPointer) { |
| final cls = declarationElement.enclosingElement3 as ClassElement; |
| if (!_extendsNativeFieldWrapperClass1(cls.thisType)) { |
| _errorReporter.reportErrorForNode( |
| FfiCode |
| .FFI_NATIVE_ONLY_CLASSES_EXTENDING_NATIVEFIELDWRAPPERCLASS1_CAN_BE_POINTER, |
| errorNode); |
| } |
| } |
| |
| ffiParameterTypes = ffiParameterTypes.sublist(1); |
| ffiParameters = ffiParameters.sublist(1); |
| } else { |
| // Number of parameters in the FfiNative annotation must match the |
| // annotated declaration. |
| if (formalParameters.length != ffiSignature.parameters.length) { |
| _errorReporter.reportErrorForNode( |
| FfiCode.FFI_NATIVE_UNEXPECTED_NUMBER_OF_PARAMETERS, |
| errorNode, |
| [ffiSignature.parameters.length, formalParameters.length]); |
| return; |
| } |
| } |
| |
| // Arguments can only be Pointer if the class extends |
| // NativeFieldWrapperClass1. |
| for (var i = 0; i < formalParameters.length; i++) { |
| if (ffiParameterTypes[i].isPointer) { |
| final type = formalParameters[i].declaredElement!.type; |
| if (!_extendsNativeFieldWrapperClass1(type as InterfaceType)) { |
| _errorReporter.reportErrorForNode( |
| FfiCode |
| .FFI_NATIVE_ONLY_CLASSES_EXTENDING_NATIVEFIELDWRAPPERCLASS1_CAN_BE_POINTER, |
| errorNode); |
| } |
| } |
| } |
| |
| final dartType = declarationElement.type; |
| final nativeType = FunctionTypeImpl( |
| typeFormals: ffiSignature.typeFormals, |
| parameters: ffiParameters, |
| returnType: ffiSignature.returnType, |
| nullabilitySuffix: ffiSignature.nullabilitySuffix, |
| ); |
| if (!_isValidFfiNativeFunctionType(nativeType)) { |
| _errorReporter.reportErrorForNode( |
| FfiCode.MUST_BE_A_NATIVE_FUNCTION_TYPE, |
| errorNode, |
| [nativeType, 'FfiNative']); |
| return; |
| } |
| if (!_validateCompatibleFunctionTypes(dartType, nativeType, |
| nativeFieldWrappersAsPointer: true, allowStricterReturn: true)) { |
| _errorReporter.reportErrorForNode(FfiCode.MUST_BE_A_SUBTYPE, errorNode, |
| [nativeType, dartType, 'FfiNative']); |
| return; |
| } |
| } |
| } |
| |
| bool _extendsNativeFieldWrapperClass1(InterfaceType? type) { |
| while (type != null) { |
| if (type.getDisplayString(withNullability: false) == |
| 'NativeFieldWrapperClass1') { |
| return true; |
| } |
| final element = type.element2; |
| if (element is ClassElement) { |
| type = element.supertype; |
| } else { |
| break; |
| } |
| } |
| return false; |
| } |
| |
| bool _isConst(Expression expr) { |
| if (expr is Literal) { |
| return true; |
| } |
| if (expr is Identifier) { |
| final staticElm = expr.staticElement; |
| if (staticElm is ConstVariableElement) { |
| return true; |
| } |
| if (staticElm is PropertyAccessorElementImpl) { |
| if (staticElm.variable is ConstVariableElement) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| /// Returns `true` if [nativeType] is a C type that has a size. |
| bool _isSized(DartType nativeType) { |
| switch (_primitiveNativeType(nativeType)) { |
| case _PrimitiveDartType.double: |
| return true; |
| case _PrimitiveDartType.int: |
| return true; |
| case _PrimitiveDartType.bool: |
| return true; |
| case _PrimitiveDartType.void_: |
| return false; |
| case _PrimitiveDartType.handle: |
| return false; |
| case _PrimitiveDartType.none: |
| break; |
| } |
| if (nativeType.isCompoundSubtype) { |
| return true; |
| } |
| if (nativeType.isPointer) { |
| return true; |
| } |
| if (nativeType.isArray) { |
| return true; |
| } |
| if (nativeType.isAbiSpecificIntegerSubtype) { |
| return true; |
| } |
| return false; |
| } |
| |
| /// Validates that the given type is a valid dart:ffi native function |
| /// signature. |
| bool _isValidFfiNativeFunctionType(DartType nativeType) { |
| if (nativeType is FunctionType && !nativeType.isDartCoreFunction) { |
| if (nativeType.namedParameterTypes.isNotEmpty || |
| nativeType.optionalParameterTypes.isNotEmpty) { |
| return false; |
| } |
| if (!_isValidFfiNativeType(nativeType.returnType, |
| allowVoid: true, allowEmptyStruct: false, allowHandle: true)) { |
| return false; |
| } |
| |
| for (final DartType typeArg in nativeType.normalParameterTypes) { |
| if (!_isValidFfiNativeType(typeArg, |
| allowVoid: false, allowEmptyStruct: false, allowHandle: true)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| /// Validates that the given [nativeType] is a valid dart:ffi native type. |
| bool _isValidFfiNativeType(DartType? nativeType, |
| {bool allowVoid = false, |
| bool allowEmptyStruct = false, |
| bool allowArray = false, |
| bool allowHandle = false}) { |
| if (nativeType is InterfaceType) { |
| final primitiveType = _primitiveNativeType(nativeType); |
| switch (primitiveType) { |
| case _PrimitiveDartType.void_: |
| return allowVoid; |
| case _PrimitiveDartType.handle: |
| return allowHandle; |
| case _PrimitiveDartType.double: |
| case _PrimitiveDartType.int: |
| case _PrimitiveDartType.bool: |
| return true; |
| case _PrimitiveDartType.none: |
| // These are the cases below. |
| break; |
| } |
| if (nativeType.isNativeFunction) { |
| return _isValidFfiNativeFunctionType(nativeType.typeArguments.single); |
| } |
| if (nativeType.isPointer) { |
| final nativeArgumentType = nativeType.typeArguments.single; |
| return _isValidFfiNativeType(nativeArgumentType, |
| allowVoid: true, allowEmptyStruct: true, allowHandle: true) || |
| nativeArgumentType.isCompoundSubtype || |
| nativeArgumentType.isNativeType; |
| } |
| if (nativeType.isCompoundSubtype) { |
| if (!allowEmptyStruct) { |
| if (nativeType.element2.isEmptyStruct) { |
| // TODO(dartbug.com/36780): This results in an error message not |
| // mentioning empty structs at all. |
| return false; |
| } |
| } |
| return true; |
| } |
| if (nativeType.isOpaqueSubtype) { |
| return true; |
| } |
| if (nativeType.isAbiSpecificIntegerSubtype) { |
| return true; |
| } |
| if (allowArray && nativeType.isArray) { |
| return _isValidFfiNativeType(nativeType.typeArguments.single, |
| allowVoid: false, allowEmptyStruct: false); |
| } |
| } else if (nativeType is FunctionType) { |
| return _isValidFfiNativeFunctionType(nativeType); |
| } |
| return false; |
| } |
| |
| /// Get the const bool value of [expr] if it exists. |
| /// Return null if it isn't a const bool. |
| bool? _maybeGetBoolConstValue(Expression expr) { |
| if (expr is BooleanLiteral) { |
| return expr.value; |
| } |
| if (expr is Identifier) { |
| final staticElm = expr.staticElement; |
| if (staticElm is ConstVariableElement) { |
| return staticElm.computeConstantValue()?.toBoolValue(); |
| } |
| if (staticElm is PropertyAccessorElementImpl) { |
| final v = staticElm.variable; |
| if (v is ConstVariableElement) { |
| return v.computeConstantValue()?.toBoolValue(); |
| } |
| } |
| } |
| return null; |
| } |
| |
| _PrimitiveDartType _primitiveNativeType(DartType nativeType) { |
| if (nativeType is InterfaceType) { |
| final element = nativeType.element2; |
| if (element.isFfiClass) { |
| final String name = element.name; |
| if (_primitiveIntegerNativeTypes.contains(name)) { |
| return _PrimitiveDartType.int; |
| } |
| if (_primitiveDoubleNativeTypes.contains(name)) { |
| return _PrimitiveDartType.double; |
| } |
| if (name == _primitiveBoolNativeType) { |
| return _PrimitiveDartType.bool; |
| } |
| if (name == 'Void') { |
| return _PrimitiveDartType.void_; |
| } |
| if (name == 'Handle') { |
| return _PrimitiveDartType.handle; |
| } |
| } |
| } |
| return _PrimitiveDartType.none; |
| } |
| |
| /// Return an indication of the Dart type associated with the [annotation]. |
| _PrimitiveDartType _typeForAnnotation(Annotation annotation) { |
| var element = annotation.element; |
| if (element is ConstructorElement) { |
| String name = element.enclosingElement3.name; |
| if (_primitiveIntegerNativeTypes.contains(name)) { |
| return _PrimitiveDartType.int; |
| } else if (_primitiveDoubleNativeTypes.contains(name)) { |
| return _PrimitiveDartType.double; |
| } else if (_primitiveBoolNativeType == name) { |
| return _PrimitiveDartType.bool; |
| } |
| if (element.type.returnType.isAbiSpecificIntegerSubtype) { |
| return _PrimitiveDartType.int; |
| } |
| } |
| return _PrimitiveDartType.none; |
| } |
| |
| void _validateAbiSpecificIntegerAnnotation(ClassDeclaration node) { |
| if ((node.typeParameters?.length ?? 0) != 0 || |
| node.members.length != 1 || |
| node.members.single is! ConstructorDeclaration || |
| (node.members.single as ConstructorDeclaration).constKeyword == null) { |
| _errorReporter.reportErrorForToken( |
| FfiCode.ABI_SPECIFIC_INTEGER_INVALID, node.name2); |
| } |
| } |
| |
| /// Validate that the [annotations] include at most one mapping annotation. |
| void _validateAbiSpecificIntegerMappingAnnotation( |
| Token errorToken, NodeList<Annotation> annotations) { |
| final ffiPackedAnnotations = annotations |
| .where((annotation) => annotation.isAbiSpecificIntegerMapping) |
| .toList(); |
| |
| if (ffiPackedAnnotations.isEmpty) { |
| _errorReporter.reportErrorForToken( |
| FfiCode.ABI_SPECIFIC_INTEGER_MAPPING_MISSING, errorToken); |
| return; |
| } |
| |
| if (ffiPackedAnnotations.length > 1) { |
| final extraAnnotations = ffiPackedAnnotations.skip(1); |
| for (final annotation in extraAnnotations) { |
| _errorReporter.reportErrorForNode( |
| FfiCode.ABI_SPECIFIC_INTEGER_MAPPING_EXTRA, annotation.name); |
| } |
| } |
| |
| var annotation = ffiPackedAnnotations.first; |
| |
| final arguments = annotation.arguments?.arguments; |
| if (arguments == null) { |
| return; |
| } |
| |
| for (final argument in arguments) { |
| if (argument is SetOrMapLiteral) { |
| for (final element in argument.elements) { |
| if (element is MapLiteralEntry) { |
| final valueType = element.value.staticType; |
| if (valueType is InterfaceType) { |
| final name = valueType.element2.name; |
| if (!_primitiveIntegerNativeTypesFixedSize.contains(name)) { |
| _errorReporter.reportErrorForNode( |
| FfiCode.ABI_SPECIFIC_INTEGER_MAPPING_UNSUPPORTED, |
| element.value, |
| [name], |
| ); |
| } |
| } |
| } |
| } |
| return; |
| } |
| } |
| final annotationConstant = |
| annotation.elementAnnotation?.computeConstantValue(); |
| final mappingValues = annotationConstant?.getField('mapping')?.toMapValue(); |
| if (mappingValues == null) { |
| return; |
| } |
| for (final nativeType in mappingValues.values) { |
| final type = nativeType?.type; |
| if (type is InterfaceType) { |
| final nativeTypeName = type.element2.name; |
| if (!_primitiveIntegerNativeTypesFixedSize.contains(nativeTypeName)) { |
| _errorReporter.reportErrorForNode( |
| FfiCode.ABI_SPECIFIC_INTEGER_MAPPING_UNSUPPORTED, |
| arguments.first, |
| [nativeTypeName], |
| ); |
| } |
| } |
| } |
| } |
| |
| void _validateAllocate(FunctionExpressionInvocation node) { |
| final typeArgumentTypes = node.typeArgumentTypes; |
| if (typeArgumentTypes == null || typeArgumentTypes.length != 1) { |
| return; |
| } |
| final DartType dartType = typeArgumentTypes[0]; |
| if (!_isValidFfiNativeType(dartType, |
| allowVoid: true, allowEmptyStruct: true)) { |
| final AstNode errorNode = node; |
| _errorReporter.reportErrorForNode( |
| FfiCode.NON_CONSTANT_TYPE_ARGUMENT, |
| errorNode, |
| ['$_allocatorExtensionName.$_allocateExtensionMethodName']); |
| } |
| } |
| |
| /// Validate that the [annotations] include exactly one annotation that |
| /// satisfies the [requiredTypes]. If an error is produced that cannot be |
| /// associated with an annotation, associate it with the [errorNode]. |
| void _validateAnnotations(TypeAnnotation errorNode, |
| NodeList<Annotation> annotations, _PrimitiveDartType requiredType) { |
| bool requiredFound = false; |
| List<Annotation> extraAnnotations = []; |
| for (Annotation annotation in annotations) { |
| if (annotation.element.ffiClass != null || |
| annotation.element?.enclosingElement3.isAbiSpecificIntegerSubclass == |
| true) { |
| if (requiredFound) { |
| extraAnnotations.add(annotation); |
| } else { |
| _PrimitiveDartType foundType = _typeForAnnotation(annotation); |
| if (foundType == requiredType) { |
| requiredFound = true; |
| } else { |
| extraAnnotations.add(annotation); |
| } |
| } |
| } |
| } |
| if (extraAnnotations.isNotEmpty) { |
| if (!requiredFound) { |
| Annotation invalidAnnotation = extraAnnotations.removeAt(0); |
| _errorReporter.reportErrorForNode( |
| FfiCode.MISMATCHED_ANNOTATION_ON_STRUCT_FIELD, invalidAnnotation); |
| } |
| for (Annotation extraAnnotation in extraAnnotations) { |
| _errorReporter.reportErrorForNode( |
| FfiCode.EXTRA_ANNOTATION_ON_STRUCT_FIELD, extraAnnotation); |
| } |
| } else if (!requiredFound) { |
| _errorReporter.reportErrorForNode( |
| FfiCode.MISSING_ANNOTATION_ON_STRUCT_FIELD, |
| errorNode, |
| [errorNode.type!, compound!.extendsClause!.superclass.name.name]); |
| } |
| } |
| |
| /// Validate the invocation of the instance method |
| /// `Pointer<T>.asFunction<F>()`. |
| void _validateAsFunction(MethodInvocation node, MethodElement element) { |
| var typeArguments = node.typeArguments?.arguments; |
| if (typeArguments != null && typeArguments.length == 1) { |
| if (_validateTypeArgument(typeArguments[0], 'asFunction')) { |
| return; |
| } |
| } |
| var target = node.realTarget!; |
| var targetType = target.staticType; |
| if (targetType is InterfaceType && targetType.isPointer) { |
| final DartType T = targetType.typeArguments[0]; |
| if (!T.isNativeFunction) { |
| return; |
| } |
| final DartType pointerTypeArg = (T as InterfaceType).typeArguments.single; |
| if (pointerTypeArg is TypeParameterType) { |
| _errorReporter.reportErrorForNode( |
| FfiCode.NON_CONSTANT_TYPE_ARGUMENT, target, ['asFunction']); |
| return; |
| } |
| if (!_isValidFfiNativeFunctionType(pointerTypeArg)) { |
| final AstNode errorNode = |
| typeArguments != null ? typeArguments[0] : node; |
| _errorReporter.reportErrorForNode( |
| FfiCode.NON_NATIVE_FUNCTION_TYPE_ARGUMENT_TO_POINTER, |
| errorNode, |
| [T]); |
| return; |
| } |
| |
| final DartType TPrime = T.typeArguments[0]; |
| final DartType F = node.typeArgumentTypes![0]; |
| if (!_validateCompatibleFunctionTypes(F, TPrime)) { |
| _errorReporter.reportErrorForNode( |
| FfiCode.MUST_BE_A_SUBTYPE, node, [TPrime, F, 'asFunction']); |
| } |
| _validateFfiLeafCallUsesNoHandles( |
| node.argumentList.arguments, TPrime, node); |
| } |
| _validateIsLeafIsConst(node); |
| } |
| |
| /// Validates that the given [nativeType] is, when native types are converted |
| /// to their Dart equivalent, a subtype of [dartType]. |
| bool _validateCompatibleFunctionTypes( |
| DartType dartType, |
| DartType nativeType, { |
| bool nativeFieldWrappersAsPointer = false, |
| bool allowStricterReturn = false, |
| }) { |
| // We require both to be valid function types. |
| if (dartType is! FunctionType || |
| dartType.isDartCoreFunction || |
| nativeType is! FunctionType || |
| nativeType.isDartCoreFunction) { |
| return false; |
| } |
| |
| // We disallow any optional parameters. |
| final int parameterCount = dartType.normalParameterTypes.length; |
| if (parameterCount != nativeType.normalParameterTypes.length) { |
| return false; |
| } |
| // We disallow generic function types. |
| if (dartType.typeFormals.isNotEmpty || nativeType.typeFormals.isNotEmpty) { |
| return false; |
| } |
| if (dartType.namedParameterTypes.isNotEmpty || |
| dartType.optionalParameterTypes.isNotEmpty || |
| nativeType.namedParameterTypes.isNotEmpty || |
| nativeType.optionalParameterTypes.isNotEmpty) { |
| return false; |
| } |
| |
| // Validate that the return types are compatible. |
| if (!_validateCompatibleNativeType( |
| dartType.returnType, nativeType.returnType)) { |
| // TODO(http://dartbug.com/49518): Fix inconsistency between `FfiNative` |
| // and `asFunction`. |
| if (!allowStricterReturn) { |
| return false; |
| } else if (!_validateCompatibleNativeType( |
| dartType.returnType, nativeType.returnType, |
| checkCovariance: true)) { |
| return false; |
| } |
| } |
| |
| // Validate that the parameter types are compatible. |
| for (int i = 0; i < parameterCount; ++i) { |
| if (!_validateCompatibleNativeType( |
| dartType.normalParameterTypes[i], |
| nativeType.normalParameterTypes[i], |
| checkCovariance: true, |
| nativeFieldWrappersAsPointer: nativeFieldWrappersAsPointer, |
| )) { |
| return false; |
| } |
| } |
| |
| // Signatures have same number of parameters and the types match. |
| return true; |
| } |
| |
| /// Validates that, if we convert [nativeType] to it's corresponding |
| /// [dartType] the latter is a subtype of the former if |
| /// [checkCovariance]. |
| bool _validateCompatibleNativeType( |
| DartType dartType, |
| DartType nativeType, { |
| bool checkCovariance = false, |
| bool nativeFieldWrappersAsPointer = false, |
| }) { |
| final nativeReturnType = _primitiveNativeType(nativeType); |
| if (nativeReturnType == _PrimitiveDartType.int || |
| (nativeType is InterfaceType && |
| nativeType.superclass?.element2.name == |
| _abiSpecificIntegerClassName)) { |
| return dartType.isDartCoreInt; |
| } else if (nativeReturnType == _PrimitiveDartType.double) { |
| return dartType.isDartCoreDouble; |
| } else if (nativeReturnType == _PrimitiveDartType.bool) { |
| return dartType.isDartCoreBool; |
| } else if (nativeReturnType == _PrimitiveDartType.void_) { |
| return dartType.isVoid; |
| } else if (dartType.isVoid) { |
| // Don't allow other native subtypes if the Dart return type is void. |
| return nativeReturnType == _PrimitiveDartType.void_; |
| } else if (nativeReturnType == _PrimitiveDartType.handle) { |
| InterfaceType objectType = typeSystem.objectStar; |
| return checkCovariance |
| ? /* everything is subtype of objectStar */ true |
| : typeSystem.isSubtypeOf(objectType, dartType); |
| } else if (dartType is InterfaceType && nativeType is InterfaceType) { |
| if (nativeFieldWrappersAsPointer && |
| _extendsNativeFieldWrapperClass1(dartType)) { |
| // Must be `Pointer<Void>`, `Handle` already checked above. |
| return nativeType.isPointer && |
| _primitiveNativeType(nativeType.typeArguments.single) == |
| _PrimitiveDartType.void_; |
| } |
| return checkCovariance |
| ? typeSystem.isSubtypeOf(dartType, nativeType) |
| : typeSystem.isSubtypeOf(nativeType, dartType); |
| } else { |
| // If the [nativeType] is not a primitive int/double type then it has to |
| // be a Pointer type atm. |
| return false; |
| } |
| } |
| |
| void _validateElementAt(MethodInvocation node) { |
| var targetType = node.realTarget?.staticType; |
| if (targetType is InterfaceType && targetType.isPointer) { |
| final DartType T = targetType.typeArguments[0]; |
| |
| if (!_isValidFfiNativeType(T, allowVoid: true, allowEmptyStruct: true)) { |
| final AstNode errorNode = node; |
| _errorReporter.reportErrorForNode( |
| FfiCode.NON_CONSTANT_TYPE_ARGUMENT, errorNode, ['elementAt']); |
| } |
| } |
| } |
| |
| void _validateFfiLeafCallUsesNoHandles( |
| NodeList<Expression> args, DartType nativeType, AstNode errorNode) { |
| if (args.isEmpty) { |
| return; |
| } |
| for (final arg in args) { |
| if (arg is! NamedExpression || arg.element?.name != _isLeafParamName) { |
| continue; |
| } |
| // Handles are ok for regular (non-leaf) calls. Check `isLeaf:true`. |
| final bool? isLeaf = _maybeGetBoolConstValue(arg.expression); |
| if (isLeaf != null && isLeaf) { |
| if (nativeType is FunctionType) { |
| if (_primitiveNativeType(nativeType.returnType) == |
| _PrimitiveDartType.handle) { |
| _errorReporter.reportErrorForNode( |
| FfiCode.LEAF_CALL_MUST_NOT_RETURN_HANDLE, errorNode); |
| } |
| for (final param in nativeType.normalParameterTypes) { |
| if (_primitiveNativeType(param) == _PrimitiveDartType.handle) { |
| _errorReporter.reportErrorForNode( |
| FfiCode.LEAF_CALL_MUST_NOT_TAKE_HANDLE, errorNode); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| /// Validate that the fields declared by the given [node] meet the |
| /// requirements for fields within a struct or union class. |
| void _validateFieldsInCompound(FieldDeclaration node) { |
| if (node.isStatic) { |
| return; |
| } |
| |
| VariableDeclarationList fields = node.fields; |
| NodeList<Annotation> annotations = node.metadata; |
| |
| if (typeSystem.isNonNullableByDefault) { |
| if (node.externalKeyword == null) { |
| _errorReporter.reportErrorForToken( |
| FfiCode.FIELD_MUST_BE_EXTERNAL_IN_STRUCT, |
| fields.variables[0].name2, |
| ); |
| } |
| } |
| |
| var fieldType = fields.type; |
| if (fieldType == null) { |
| _errorReporter.reportErrorForToken( |
| FfiCode.MISSING_FIELD_TYPE_IN_STRUCT, fields.variables[0].name2); |
| } else { |
| DartType declaredType = fieldType.typeOrThrow; |
| if (declaredType.isDartCoreInt) { |
| _validateAnnotations(fieldType, annotations, _PrimitiveDartType.int); |
| } else if (declaredType.isDartCoreDouble) { |
| _validateAnnotations(fieldType, annotations, _PrimitiveDartType.double); |
| } else if (declaredType.isDartCoreBool) { |
| _validateAnnotations(fieldType, annotations, _PrimitiveDartType.bool); |
| } else if (declaredType.isPointer) { |
| _validateNoAnnotations(annotations); |
| } else if (declaredType.isArray) { |
| final typeArg = (declaredType as InterfaceType).typeArguments.single; |
| if (!_isSized(typeArg)) { |
| AstNode errorNode = fieldType; |
| if (fieldType is NamedType) { |
| var typeArguments = fieldType.typeArguments?.arguments; |
| if (typeArguments != null && typeArguments.isNotEmpty) { |
| errorNode = typeArguments[0]; |
| } |
| } |
| _errorReporter.reportErrorForNode(FfiCode.NON_SIZED_TYPE_ARGUMENT, |
| errorNode, [_arrayClassName, typeArg]); |
| } |
| final arrayDimensions = declaredType.arrayDimensions; |
| _validateSizeOfAnnotation(fieldType, annotations, arrayDimensions); |
| } else if (declaredType.isCompoundSubtype) { |
| final clazz = (declaredType as InterfaceType).element2; |
| if (clazz is ClassElement && clazz.isEmptyStruct) { |
| _errorReporter.reportErrorForNode(FfiCode.EMPTY_STRUCT, node, [ |
| clazz.name, |
| clazz.supertype!.getDisplayString(withNullability: false) |
| ]); |
| } |
| } else { |
| _errorReporter.reportErrorForNode(FfiCode.INVALID_FIELD_TYPE_IN_STRUCT, |
| fieldType, [fieldType.toSource()]); |
| } |
| } |
| |
| if (!typeSystem.isNonNullableByDefault) { |
| for (VariableDeclaration field in fields.variables) { |
| if (field.initializer != null) { |
| _errorReporter.reportErrorForToken( |
| FfiCode.FIELD_IN_STRUCT_WITH_INITIALIZER, |
| field.name2, |
| ); |
| } |
| } |
| } |
| } |
| |
| /// Validate the invocation of the static method |
| /// `Pointer<T>.fromFunction(f, e)`. |
| void _validateFromFunction(MethodInvocation node, MethodElement element) { |
| final int argCount = node.argumentList.arguments.length; |
| if (argCount < 1 || argCount > 2) { |
| // There are other diagnostics reported against the invocation and the |
| // diagnostics generated below might be inaccurate, so don't report them. |
| return; |
| } |
| |
| final DartType T = node.typeArgumentTypes![0]; |
| if (!_isValidFfiNativeFunctionType(T)) { |
| AstNode errorNode = node.methodName; |
| var typeArgument = node.typeArguments?.arguments[0]; |
| if (typeArgument != null) { |
| errorNode = typeArgument; |
| } |
| _errorReporter.reportErrorForNode(FfiCode.MUST_BE_A_NATIVE_FUNCTION_TYPE, |
| errorNode, [T, 'fromFunction']); |
| return; |
| } |
| |
| Expression f = node.argumentList.arguments[0]; |
| DartType FT = f.typeOrThrow; |
| if (!_validateCompatibleFunctionTypes(FT, T)) { |
| _errorReporter.reportErrorForNode( |
| FfiCode.MUST_BE_A_SUBTYPE, f, [FT, T, 'fromFunction']); |
| return; |
| } |
| |
| // TODO(brianwilkerson) Validate that `f` is a top-level function. |
| final DartType R = (T as FunctionType).returnType; |
| if ((FT as FunctionType).returnType.isVoid || |
| R.isPointer || |
| R.isHandle || |
| R.isCompoundSubtype) { |
| if (argCount != 1) { |
| _errorReporter.reportErrorForNode( |
| FfiCode.INVALID_EXCEPTION_VALUE, node.argumentList.arguments[1]); |
| } |
| } else if (argCount != 2) { |
| _errorReporter.reportErrorForNode( |
| FfiCode.MISSING_EXCEPTION_VALUE, node.methodName); |
| } else { |
| Expression e = node.argumentList.arguments[1]; |
| var eType = e.typeOrThrow; |
| if (!_validateCompatibleNativeType(eType, R, checkCovariance: true)) { |
| _errorReporter.reportErrorForNode( |
| FfiCode.MUST_BE_A_SUBTYPE, e, [eType, R, 'fromFunction']); |
| } |
| if (!_isConst(e)) { |
| _errorReporter.reportErrorForNode( |
| FfiCode.ARGUMENT_MUST_BE_A_CONSTANT, e, ['exceptionalReturn']); |
| } |
| } |
| } |
| |
| /// Ensure `isLeaf` is const as we need the value at compile time to know |
| /// which trampoline to generate. |
| void _validateIsLeafIsConst(MethodInvocation node) { |
| final args = node.argumentList.arguments; |
| if (args.isNotEmpty) { |
| for (final arg in args) { |
| if (arg is NamedExpression) { |
| if (arg.element?.name == _isLeafParamName) { |
| if (!_isConst(arg.expression)) { |
| _errorReporter.reportErrorForNode( |
| FfiCode.ARGUMENT_MUST_BE_A_CONSTANT, |
| arg.expression, |
| [_isLeafParamName]); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| /// Validate the invocation of the instance method |
| /// `DynamicLibrary.lookupFunction<S, F>()`. |
| void _validateLookupFunction(MethodInvocation node) { |
| final typeArguments = node.typeArguments?.arguments; |
| if (typeArguments == null || typeArguments.length != 2) { |
| // There are other diagnostics reported against the invocation and the |
| // diagnostics generated below might be inaccurate, so don't report them. |
| return; |
| } |
| |
| final List<DartType> argTypes = node.typeArgumentTypes!; |
| final DartType S = argTypes[0]; |
| final DartType F = argTypes[1]; |
| if (!_isValidFfiNativeFunctionType(S)) { |
| final AstNode errorNode = typeArguments[0]; |
| _errorReporter.reportErrorForNode(FfiCode.MUST_BE_A_NATIVE_FUNCTION_TYPE, |
| errorNode, [S, 'lookupFunction']); |
| return; |
| } |
| if (!_validateCompatibleFunctionTypes(F, S)) { |
| final AstNode errorNode = typeArguments[1]; |
| _errorReporter.reportErrorForNode( |
| FfiCode.MUST_BE_A_SUBTYPE, errorNode, [S, F, 'lookupFunction']); |
| } |
| _validateIsLeafIsConst(node); |
| _validateFfiLeafCallUsesNoHandles( |
| node.argumentList.arguments, S, typeArguments[0]); |
| } |
| |
| /// Validate that none of the [annotations] are from `dart:ffi`. |
| void _validateNoAnnotations(NodeList<Annotation> annotations) { |
| for (Annotation annotation in annotations) { |
| if (annotation.element.ffiClass != null) { |
| _errorReporter.reportErrorForNode( |
| FfiCode.ANNOTATION_ON_POINTER_FIELD, annotation); |
| } |
| } |
| } |
| |
| /// Validate that the [annotations] include at most one packed annotation. |
| void _validatePackedAnnotation(NodeList<Annotation> annotations) { |
| final ffiPackedAnnotations = |
| annotations.where((annotation) => annotation.isPacked).toList(); |
| |
| if (ffiPackedAnnotations.isEmpty) { |
| return; |
| } |
| |
| if (ffiPackedAnnotations.length > 1) { |
| final extraAnnotations = ffiPackedAnnotations.skip(1); |
| for (final annotation in extraAnnotations) { |
| _errorReporter.reportErrorForNode( |
| FfiCode.PACKED_ANNOTATION, annotation); |
| } |
| } |
| |
| // Check number of dimensions. |
| final annotation = ffiPackedAnnotations.first; |
| final value = annotation.elementAnnotation?.packedMemberAlignment; |
| if (![1, 2, 4, 8, 16].contains(value)) { |
| AstNode errorNode = annotation; |
| var arguments = annotation.arguments?.arguments; |
| if (arguments != null && arguments.isNotEmpty) { |
| errorNode = arguments[0]; |
| } |
| _errorReporter.reportErrorForNode( |
| FfiCode.PACKED_ANNOTATION_ALIGNMENT, errorNode); |
| } |
| } |
| |
| void _validateRefIndexed(IndexExpression node) { |
| var targetType = node.realTarget.staticType; |
| if (!_isValidFfiNativeType(targetType, |
| allowVoid: false, allowEmptyStruct: true, allowArray: true)) { |
| final AstNode errorNode = node; |
| _errorReporter.reportErrorForNode( |
| FfiCode.NON_CONSTANT_TYPE_ARGUMENT, errorNode, ['[]']); |
| } |
| } |
| |
| /// Validate the invocation of the extension method |
| /// `Pointer<T extends Struct>.ref`. |
| void _validateRefPrefixedIdentifier(PrefixedIdentifier node) { |
| var targetType = node.prefix.typeOrThrow; |
| if (!_isValidFfiNativeType(targetType, |
| allowVoid: false, allowEmptyStruct: true)) { |
| final AstNode errorNode = node; |
| _errorReporter.reportErrorForNode( |
| FfiCode.NON_CONSTANT_TYPE_ARGUMENT, errorNode, ['ref']); |
| } |
| } |
| |
| void _validateRefPropertyAccess(PropertyAccess node) { |
| var targetType = node.realTarget.staticType; |
| if (!_isValidFfiNativeType(targetType, |
| allowVoid: false, allowEmptyStruct: true)) { |
| final AstNode errorNode = node; |
| _errorReporter.reportErrorForNode( |
| FfiCode.NON_CONSTANT_TYPE_ARGUMENT, errorNode, ['ref']); |
| } |
| } |
| |
| void _validateSizeOf(MethodInvocation node) { |
| final typeArgumentTypes = node.typeArgumentTypes; |
| if (typeArgumentTypes == null || typeArgumentTypes.length != 1) { |
| return; |
| } |
| final DartType T = typeArgumentTypes[0]; |
| if (!_isValidFfiNativeType(T, allowVoid: true, allowEmptyStruct: true)) { |
| final AstNode errorNode = node; |
| _errorReporter.reportErrorForNode( |
| FfiCode.NON_CONSTANT_TYPE_ARGUMENT, errorNode, ['sizeOf']); |
| } |
| } |
| |
| /// Validate that the [annotations] include exactly one size annotation. If |
| /// an error is produced that cannot be associated with an annotation, |
| /// associate it with the [errorNode]. |
| void _validateSizeOfAnnotation(AstNode errorNode, |
| NodeList<Annotation> annotations, int arrayDimensions) { |
| final ffiSizeAnnotations = |
| annotations.where((annotation) => annotation.isArray).toList(); |
| |
| if (ffiSizeAnnotations.isEmpty) { |
| _errorReporter.reportErrorForNode( |
| FfiCode.MISSING_SIZE_ANNOTATION_CARRAY, errorNode); |
| return; |
| } |
| |
| if (ffiSizeAnnotations.length > 1) { |
| final extraAnnotations = ffiSizeAnnotations.skip(1); |
| for (final annotation in extraAnnotations) { |
| _errorReporter.reportErrorForNode( |
| FfiCode.EXTRA_SIZE_ANNOTATION_CARRAY, annotation); |
| } |
| } |
| |
| // Check number of dimensions. |
| final annotation = ffiSizeAnnotations.first; |
| final dimensions = annotation.elementAnnotation?.arraySizeDimensions ?? []; |
| final annotationDimensions = dimensions.length; |
| if (annotationDimensions != arrayDimensions) { |
| _errorReporter.reportErrorForNode( |
| FfiCode.SIZE_ANNOTATION_DIMENSIONS, annotation); |
| } |
| |
| // Check dimensions are positive |
| List<AstNode>? getArgumentNodes() { |
| var arguments = annotation.arguments?.arguments; |
| if (arguments != null && arguments.length == 1) { |
| var firstArgument = arguments[0]; |
| if (firstArgument is ListLiteral) { |
| return firstArgument.elements; |
| } |
| } |
| return arguments; |
| } |
| |
| for (int i = 0; i < dimensions.length; i++) { |
| if (dimensions[i] <= 0) { |
| AstNode errorNode = annotation; |
| var argumentNodes = getArgumentNodes(); |
| if (argumentNodes != null && argumentNodes.isNotEmpty) { |
| errorNode = argumentNodes[i]; |
| } |
| _errorReporter.reportErrorForNode( |
| FfiCode.NON_POSITIVE_ARRAY_DIMENSION, errorNode); |
| } |
| } |
| } |
| |
| /// Validate that the given [typeArgument] has a constant value. Return `true` |
| /// if a diagnostic was produced because it isn't constant. |
| bool _validateTypeArgument(TypeAnnotation typeArgument, String functionName) { |
| if (typeArgument.type is TypeParameterType) { |
| _errorReporter.reportErrorForNode( |
| FfiCode.NON_CONSTANT_TYPE_ARGUMENT, typeArgument, [functionName]); |
| return true; |
| } |
| return false; |
| } |
| } |
| |
| enum _PrimitiveDartType { |
| double, |
| int, |
| bool, |
| void_, |
| handle, |
| none, |
| } |
| |
| extension on Annotation { |
| bool get isAbiSpecificIntegerMapping { |
| final element = this.element; |
| return element is ConstructorElement && |
| element.ffiClass != null && |
| element.enclosingElement3.name == |
| FfiVerifier._abiSpecificIntegerMappingClassName; |
| } |
| |
| bool get isArray { |
| final element = this.element; |
| return element is ConstructorElement && |
| element.ffiClass != null && |
| element.enclosingElement3.name == 'Array'; |
| } |
| |
| bool get isFfiNative { |
| final element = this.element; |
| return element is ConstructorElement && |
| element.ffiClass != null && |
| element.enclosingElement3.name == 'FfiNative'; |
| } |
| |
| bool get isPacked { |
| final element = this.element; |
| return element is ConstructorElement && |
| element.ffiClass != null && |
| element.enclosingElement3.name == 'Packed'; |
| } |
| } |
| |
| extension on ElementAnnotation { |
| List<int> get arraySizeDimensions { |
| assert(isArray); |
| final value = computeConstantValue(); |
| |
| // Element of `@Array.multi([1, 2, 3])`. |
| final listField = value?.getField('dimensions'); |
| if (listField != null) { |
| final listValues = listField |
| .toListValue() |
| ?.map((dartValue) => dartValue.toIntValue()) |
| .whereType<int>() |
| .toList(); |
| if (listValues != null) { |
| return listValues; |
| } |
| } |
| |
| // Element of `@Array(1, 2, 3)`. |
| const dimensionFieldNames = [ |
| 'dimension1', |
| 'dimension2', |
| 'dimension3', |
| 'dimension4', |
| 'dimension5', |
| ]; |
| var result = <int>[]; |
| for (final dimensionFieldName in dimensionFieldNames) { |
| final dimensionValue = value?.getField(dimensionFieldName)?.toIntValue(); |
| if (dimensionValue != null) { |
| result.add(dimensionValue); |
| } |
| } |
| return result; |
| } |
| |
| bool get isArray { |
| final element = this.element; |
| return element is ConstructorElement && |
| element.ffiClass != null && |
| element.enclosingElement3.name == 'Array'; |
| // Note: this is 'Array' instead of '_ArraySize' because it finds the |
| // forwarding factory instead of the forwarded constructor. |
| } |
| |
| bool get isPacked { |
| final element = this.element; |
| return element is ConstructorElement && |
| element.ffiClass != null && |
| element.enclosingElement3.name == 'Packed'; |
| } |
| |
| int? get packedMemberAlignment { |
| assert(isPacked); |
| final value = computeConstantValue(); |
| return value?.getField('memberAlignment')?.toIntValue(); |
| } |
| } |
| |
| extension on Element? { |
| /// If this is a class element from `dart:ffi`, return it. |
| ClassElement? get ffiClass { |
| var element = this; |
| if (element is ConstructorElement) { |
| element = element.enclosingElement3; |
| } |
| if (element is ClassElement && element.isFfiClass) { |
| return element; |
| } |
| return null; |
| } |
| |
| /// Return `true` if this represents the class `AbiSpecificInteger`. |
| bool get isAbiSpecificInteger { |
| final element = this; |
| return element is ClassElement && |
| element.name == FfiVerifier._abiSpecificIntegerClassName && |
| element.isFfiClass; |
| } |
| |
| /// Return `true` if this represents a subclass of the class |
| /// `AbiSpecificInteger`. |
| bool get isAbiSpecificIntegerSubclass { |
| final element = this; |
| return element is ClassElement && element.supertype.isAbiSpecificInteger; |
| } |
| |
| /// Return `true` if this represents the extension `AllocatorAlloc`. |
| bool get isAllocatorExtension { |
| final element = this; |
| return element is ExtensionElement && |
| element.name == FfiVerifier._allocatorExtensionName && |
| element.isFfiExtension; |
| } |
| |
| /// Return `true` if this represents the extension `DynamicLibraryExtension`. |
| bool get isDynamicLibraryExtension { |
| final element = this; |
| return element is ExtensionElement && |
| element.name == 'DynamicLibraryExtension' && |
| element.isFfiExtension; |
| } |
| |
| bool get isNativeFunctionPointerExtension { |
| final element = this; |
| return element is ExtensionElement && |
| element.name == 'NativeFunctionPointer' && |
| element.isFfiExtension; |
| } |
| |
| bool get isNativeStructArrayExtension { |
| final element = this; |
| return element is ExtensionElement && |
| element.name == 'StructArray' && |
| element.isFfiExtension; |
| } |
| |
| bool get isNativeStructPointerExtension { |
| final element = this; |
| return element is ExtensionElement && |
| element.name == 'StructPointer' && |
| element.isFfiExtension; |
| } |
| |
| /// Return `true` if this represents the class `Pointer`. |
| bool get isPointer { |
| final element = this; |
| return element is ClassElement && |
| element.name == 'Pointer' && |
| element.isFfiClass; |
| } |
| |
| /// Return `true` if this represents the class `Struct`. |
| bool get isStruct { |
| final element = this; |
| return element is ClassElement && |
| element.name == 'Struct' && |
| element.isFfiClass; |
| } |
| |
| /// Return `true` if this represents a subclass of the class `Struct`. |
| bool get isStructSubclass { |
| final element = this; |
| return element is ClassElement && element.supertype.isStruct; |
| } |
| |
| /// Return `true` if this represents the class `Union`. |
| bool get isUnion { |
| final element = this; |
| return element is ClassElement && |
| element.name == 'Union' && |
| element.isFfiClass; |
| } |
| |
| /// Return `true` if this represents a subclass of the class `Struct`. |
| bool get isUnionSubclass { |
| final element = this; |
| return element is ClassElement && element.supertype.isUnion; |
| } |
| } |
| |
| extension on InterfaceElement { |
| bool get isEmptyStruct { |
| for (final field in fields) { |
| final declaredType = field.type; |
| if (declaredType.isDartCoreInt) { |
| return false; |
| } else if (declaredType.isDartCoreDouble) { |
| return false; |
| } else if (declaredType.isDartCoreBool) { |
| return false; |
| } else if (declaredType.isPointer) { |
| return false; |
| } else if (declaredType.isCompoundSubtype) { |
| return false; |
| } else if (declaredType.isArray) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool get isFfiClass { |
| return library.name == FfiVerifier._dartFfiLibraryName; |
| } |
| } |
| |
| extension on ExtensionElement { |
| bool get isFfiExtension { |
| return library.name == FfiVerifier._dartFfiLibraryName; |
| } |
| } |
| |
| extension on DartType? { |
| bool get isAbiSpecificInteger { |
| final self = this; |
| return self is InterfaceType && self.element2.isAbiSpecificInteger; |
| } |
| |
| bool get isStruct { |
| final self = this; |
| return self is InterfaceType && self.element2.isStruct; |
| } |
| |
| bool get isUnion { |
| final self = this; |
| return self is InterfaceType && self.element2.isUnion; |
| } |
| } |
| |
| extension on DartType { |
| int get arrayDimensions { |
| DartType iterator = this; |
| int dimensions = 0; |
| while (iterator is InterfaceType && |
| iterator.element2.name == FfiVerifier._arrayClassName && |
| iterator.element2.isFfiClass) { |
| dimensions++; |
| iterator = iterator.typeArguments.single; |
| } |
| return dimensions; |
| } |
| |
| bool get isAbiSpecificInteger { |
| final self = this; |
| if (self is InterfaceType) { |
| final element = self.element2; |
| final name = element.name; |
| return name == FfiVerifier._abiSpecificIntegerClassName && |
| element.isFfiClass; |
| } |
| return false; |
| } |
| |
| /// Returns `true` iff this is an Abi-specific integer type, |
| /// i.e. a subtype of `AbiSpecificInteger`. |
| bool get isAbiSpecificIntegerSubtype { |
| final self = this; |
| if (self is InterfaceType) { |
| final superType = self.element2.supertype; |
| if (superType != null) { |
| final superClassElement = superType.element2; |
| return superClassElement.name == |
| FfiVerifier._abiSpecificIntegerClassName && |
| superClassElement.isFfiClass; |
| } |
| } |
| return false; |
| } |
| |
| /// Return `true` if this represents the class `Array`. |
| bool get isArray { |
| final self = this; |
| if (self is InterfaceType) { |
| final element = self.element2; |
| return element.name == FfiVerifier._arrayClassName && element.isFfiClass; |
| } |
| return false; |
| } |
| |
| bool get isCompound { |
| final self = this; |
| if (self is InterfaceType) { |
| final element = self.element2; |
| final name = element.name; |
| return (name == FfiVerifier._structClassName || |
| name == FfiVerifier._unionClassName) && |
| element.isFfiClass; |
| } |
| return false; |
| } |
| |
| /// Returns `true` if this is a struct type, i.e. a subtype of `Struct`. |
| bool get isCompoundSubtype { |
| final self = this; |
| if (self is InterfaceType) { |
| final superType = self.element2.supertype; |
| if (superType != null) { |
| return superType.isCompound; |
| } |
| } |
| return false; |
| } |
| |
| bool get isHandle { |
| final self = this; |
| if (self is InterfaceType) { |
| final element = self.element2; |
| return element.name == 'Handle' && element.isFfiClass; |
| } |
| return false; |
| } |
| |
| /// Returns `true` iff this is a `ffi.NativeFunction<???>` type. |
| bool get isNativeFunction { |
| final self = this; |
| if (self is InterfaceType) { |
| final element = self.element2; |
| return element.name == 'NativeFunction' && element.isFfiClass; |
| } |
| return false; |
| } |
| |
| /// Returns `true` iff this is a `ffi.NativeType` type. |
| bool get isNativeType { |
| final self = this; |
| if (self is InterfaceType) { |
| final element = self.element2; |
| return element.name == 'NativeType' && element.isFfiClass; |
| } |
| return false; |
| } |
| |
| /// Returns `true` iff this is a opaque type, i.e. a subtype of `Opaque`. |
| bool get isOpaqueSubtype { |
| final self = this; |
| if (self is InterfaceType) { |
| final superType = self.element2.supertype; |
| if (superType != null) { |
| final superClassElement = superType.element2; |
| return superClassElement.name == FfiVerifier._opaqueClassName && |
| superClassElement.isFfiClass; |
| } |
| } |
| return false; |
| } |
| |
| bool get isPointer { |
| final self = this; |
| return self is InterfaceType && self.element2.isPointer; |
| } |
| } |
| |
| extension on NamedType { |
| /// If this is a name of class from `dart:ffi`, return it. |
| ClassElement? get ffiClass { |
| return name.staticElement.ffiClass; |
| } |
| |
| /// Return `true` if this represents a subtype of `Struct` or `Union`. |
| bool get isAbiSpecificIntegerSubtype { |
| var element = name.staticElement; |
| if (element is ClassElement) { |
| return element.allSupertypes.any((e) => e.isAbiSpecificInteger); |
| } |
| return false; |
| } |
| |
| /// Return `true` if this represents a subtype of `Struct` or `Union`. |
| bool get isCompoundSubtype { |
| var element = name.staticElement; |
| if (element is ClassElement) { |
| return element.allSupertypes.any((e) => e.isCompound); |
| } |
| return false; |
| } |
| } |