diff --git a/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart b/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart index b0cbf2f72be..55570890663 100644 --- a/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart +++ b/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart @@ -6647,6 +6647,18 @@ const MessageCode messageFfiNativeFieldType = const MessageCode( r"""Unsupported type for native fields. Native fields only support pointers, compounds and numeric types.""", ); +// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. +const Code codeFfiNativeFunctionMissingType = + messageFfiNativeFunctionMissingType; + +// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. +const MessageCode messageFfiNativeFunctionMissingType = const MessageCode( + "FfiNativeFunctionMissingType", + analyzerCodes: ["NATIVE_FUNCTION_MISSING_TYPE"], + problemMessage: + r"""The native type of this function couldn't be inferred so it must be specified in the annotation.""", +); + // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. const Code codeFfiNativeMustBeExternal = messageFfiNativeMustBeExternal; diff --git a/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml b/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml index 9460639c448..7f7b4a1ac18 100644 --- a/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml +++ b/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml @@ -1792,6 +1792,8 @@ FfiCode.NATIVE_FIELD_MISSING_TYPE: status: needsEvaluation FfiCode.NATIVE_FIELD_NOT_STATIC: status: needsEvaluation +FfiCode.NATIVE_FUNCTION_MISSING_TYPE: + status: needsEvaluation FfiCode.NEGATIVE_VARIABLE_DIMENSION: status: noFix FfiCode.NON_CONSTANT_TYPE_ARGUMENT: diff --git a/pkg/analyzer/lib/src/dart/error/ffi_code.g.dart b/pkg/analyzer/lib/src/dart/error/ffi_code.g.dart index 255d1f5e1c9..fce374ce324 100644 --- a/pkg/analyzer/lib/src/dart/error/ffi_code.g.dart +++ b/pkg/analyzer/lib/src/dart/error/ffi_code.g.dart @@ -387,6 +387,16 @@ class FfiCode extends ErrorCode { hasPublishedDocs: true, ); + /// No parameters + static const FfiCode NATIVE_FUNCTION_MISSING_TYPE = FfiCode( + 'NATIVE_FUNCTION_MISSING_TYPE', + "The native type of this function couldn't be inferred so it must be " + "specified in the annotation.", + correctionMessage: + "Try adding a type parameter extending `NativeType` to the `@Native` " + "annotation.", + ); + /// No parameters. static const FfiCode NEGATIVE_VARIABLE_DIMENSION = FfiCode( 'NEGATIVE_VARIABLE_DIMENSION', diff --git a/pkg/analyzer/lib/src/error/error_code_values.g.dart b/pkg/analyzer/lib/src/error/error_code_values.g.dart index aea94adfd97..f55471afb0a 100644 --- a/pkg/analyzer/lib/src/error/error_code_values.g.dart +++ b/pkg/analyzer/lib/src/error/error_code_values.g.dart @@ -632,6 +632,7 @@ const List errorCodeValues = [ FfiCode.NATIVE_FIELD_INVALID_TYPE, FfiCode.NATIVE_FIELD_MISSING_TYPE, FfiCode.NATIVE_FIELD_NOT_STATIC, + FfiCode.NATIVE_FUNCTION_MISSING_TYPE, FfiCode.NEGATIVE_VARIABLE_DIMENSION, FfiCode.NON_CONSTANT_TYPE_ARGUMENT, FfiCode.NON_NATIVE_FUNCTION_TYPE_ARGUMENT_TO_POINTER, diff --git a/pkg/analyzer/lib/src/generated/ffi_verifier.dart b/pkg/analyzer/lib/src/generated/ffi_verifier.dart index e4a9a53f58b..88b3aa248cd 100644 --- a/pkg/analyzer/lib/src/generated/ffi_verifier.dart +++ b/pkg/analyzer/lib/src/generated/ffi_verifier.dart @@ -110,6 +110,9 @@ class FfiVerifier extends RecursiveAstVisitor { /// Subclass of `Struct` or `Union` we are currently visiting, or `null`. ClassDeclaration? compound; + /// The `Void` type from `dart:ffi`, or `null` if unresolved. + InterfaceType? ffiVoidType; + /// Initialize a newly created verifier. FfiVerifier(this.typeSystem, this._errorReporter, {required this.strictCasts}); @@ -493,8 +496,36 @@ class FfiVerifier extends RecursiveAstVisitor { ); } } else { - if (declarationElement is MethodElement2 || - declarationElement is TopLevelFunctionElement) { + if (declarationElement + case TopLevelFunctionElement() || MethodElement2()) { + declarationElement = declarationElement as ExecutableElement2; + var dartSignature = declarationElement.type; + + if (declarationElement.isStatic && ffiSignature is DynamicType) { + // No type argument was given on the @Native annotation, so we try + // to infer the native type from the Dart signature. + if (dartSignature.returnType is VoidType) { + // The Dart signature has a `void` return type, so we create a new + // `FunctionType` with FFI's `Void` as the return type. + dartSignature = FunctionTypeImpl.v2( + typeParameters: dartSignature.typeParameters, + formalParameters: dartSignature.formalParameters, + returnType: ffiVoidType ??= annotationType.element3.library2 + .getClass2('Void')! + .thisType, + nullabilitySuffix: dartSignature.nullabilitySuffix, + ); + } + _checkFfiNativeFunction( + errorNode, + declarationElement, + dartSignature, + annotationValue, + formalParameters, + ); + return; + } + // Function annotated with something that isn't a function type. _errorReporter.atToken( errorNode, @@ -512,9 +543,6 @@ class FfiVerifier extends RecursiveAstVisitor { ); } } - - if (ffiSignature is FunctionType && - declarationElement is ExecutableElement2) {} } } @@ -683,11 +711,20 @@ class FfiVerifier extends RecursiveAstVisitor { nullabilitySuffix: ffiSignature.nullabilitySuffix, ); if (!_isValidFfiNativeFunctionType(nativeType)) { - _errorReporter.atToken( - errorToken, - FfiCode.MUST_BE_A_NATIVE_FUNCTION_TYPE, - arguments: [nativeType, 'Native'], - ); + var nativeTypeIsOmitted = (annotationValue.type! as InterfaceType) + .typeArguments[0] is DynamicType; + if (nativeTypeIsOmitted) { + _errorReporter.atToken( + errorToken, + FfiCode.NATIVE_FUNCTION_MISSING_TYPE, + ); + } else { + _errorReporter.atToken( + errorToken, + FfiCode.MUST_BE_A_NATIVE_FUNCTION_TYPE, + arguments: [nativeType, 'Native'], + ); + } return; } if (!_validateCompatibleFunctionTypes( @@ -1707,15 +1744,8 @@ class FfiVerifier extends RecursiveAstVisitor { // When referencing a function, the target type must be a // `NativeFunction` so that `T` matches the type from the // annotation. - if (!targetType.isNativeFunction) { - _errorReporter.atNode( - node, - FfiCode.MUST_BE_A_NATIVE_FUNCTION_TYPE, - arguments: [targetType, _nativeAddressOf], - ); - } else { - var targetFunctionType = - (targetType as InterfaceType).typeArguments[0]; + if (targetType case InterfaceType(isNativeFunction: true)) { + var targetFunctionType = targetType.typeArguments[0]; if (!typeSystem.isEqualTo(nativeType, targetFunctionType)) { _errorReporter.atNode( node, @@ -1723,30 +1753,62 @@ class FfiVerifier extends RecursiveAstVisitor { arguments: [nativeType, targetFunctionType, _nativeAddressOf], ); } - } - } else { - // A native field is being referenced, this doesn't require a - // NativeFunction wrapper. However, we can't read the native type - // from the annotation directly because it might be inferred if none - // was given. - if (nativeType is DynamicType) { - var staticType = argument.staticType; - if (staticType != null) { - var canonical = _canonicalFfiTypeForDartType(staticType); - - if (canonical != null) { - nativeType = canonical; - } - } - } - - if (!typeSystem.isEqualTo(nativeType, targetType)) { + } else { _errorReporter.atNode( node, - FfiCode.MUST_BE_A_SUBTYPE, - arguments: [nativeType, targetType, _nativeAddressOf], + FfiCode.MUST_BE_A_NATIVE_FUNCTION_TYPE, + arguments: [targetType, _nativeAddressOf], ); } + } else { + if (argument.staticType case var staticType? + when nativeType is DynamicType) { + // No type argument was given on the @Native annotation, so we try + // to infer the native type from the Dart signature. + if (staticType is FunctionType) { + if (staticType.returnType is VoidType) { + // The Dart signature has a `void` return type, so we create a + // new `FunctionType` with FFI's `Void` as the return type. + staticType = FunctionTypeImpl.v2( + typeParameters: staticType.typeParameters, + formalParameters: staticType.formalParameters, + returnType: ffiVoidType ??= annotationType.element3.library2 + .getClass2('Void')! + .thisType, + nullabilitySuffix: staticType.nullabilitySuffix, + ); + } + + if (targetType case InterfaceType(isNativeFunction: true)) { + var targetFunctionType = targetType.typeArguments[0]; + if (!typeSystem.isEqualTo(staticType, targetFunctionType)) { + _errorReporter.atNode( + node, + FfiCode.MUST_BE_A_SUBTYPE, + arguments: [ + staticType, + targetFunctionType, + _nativeAddressOf + ], + ); + } + } else { + _errorReporter.atNode( + node, + FfiCode.MUST_BE_A_NATIVE_FUNCTION_TYPE, + arguments: [targetType, _nativeAddressOf], + ); + } + } else { + if (!typeSystem.isEqualTo(staticType, targetType)) { + _errorReporter.atNode( + node, + FfiCode.MUST_BE_A_SUBTYPE, + arguments: [staticType, targetType, _nativeAddressOf], + ); + } + } + } } validTarget = true; diff --git a/pkg/analyzer/messages.yaml b/pkg/analyzer/messages.yaml index 7233f80d7a0..66e6a80069a 100644 --- a/pkg/analyzer/messages.yaml +++ b/pkg/analyzer/messages.yaml @@ -19212,6 +19212,49 @@ FfiCode: external int f; } ``` + NATIVE_FUNCTION_MISSING_TYPE: + problemMessage: The native type of this function couldn't be inferred so it must be specified in the annotation. + correctionMessage: Try adding a type parameter extending `NativeType` to the `@Native` annotation. + hasPublishedDocs: false + comment: No parameters + documentation: |- + #### Description + + The analyzer produces this diagnostic when a `@Native`-annotated function + requires a type hint on the annotation to infer the native function type. + + Dart types like `int` and `double` have multiple possible native + representations. Since the native type needs to be known at compile time + to generate correct bindings and call instructions for the function, an + explicit type must be given. + + For more information about FFI, see [C interop using dart:ffi][ffi]. + + #### Example + + The following code produces this diagnostic because the function `f()` has + the return type `int`, but doesn't have an explicit type parameter on the + `Native` annotation: + + ```dart + import 'dart:ffi'; + + @Native() + external int [!f!](); + ``` + + #### Common fixes + + Add the corresponding type to the annotation. For instance, if `f()` was + declared to return an `int32_t` in C, the Dart function should be declared + as: + + ```dart + import 'dart:ffi'; + + @Native() + external int f(); + ``` COMPOUND_IMPLEMENTS_FINALIZABLE: problemMessage: "The class '{0}' can't implement Finalizable." correctionMessage: "Try removing the implements clause from '{0}'." diff --git a/pkg/analyzer/test/src/diagnostics/ffi_native_test.dart b/pkg/analyzer/test/src/diagnostics/ffi_native_test.dart index 2cc05aa8c1f..ff0b1f166e7 100644 --- a/pkg/analyzer/test/src/diagnostics/ffi_native_test.dart +++ b/pkg/analyzer/test/src/diagnostics/ffi_native_test.dart @@ -77,6 +77,21 @@ void main() { await assertErrorsInCode(r''' import 'dart:ffi'; +@Native() +external void foo(); + +void main() { + print(Native.addressOf(foo)); +} +''', [ + error(FfiCode.MUST_BE_A_NATIVE_FUNCTION_TYPE, 74, 21), + ]); + } + + test_invalid_MissingType3() async { + await assertErrorsInCode(r''' +import 'dart:ffi'; + @Native() external Pointer global; @@ -116,6 +131,19 @@ void main() => print(Native.addressOf(foo)); ]); } + test_invalid_NotAPreciseType2() async { + await assertErrorsInCode(r''' +import 'dart:ffi'; + +@Native() +external void foo(); + +void main() => print(Native.addressOf(foo)); +''', [ + error(FfiCode.MUST_BE_A_SUBTYPE, 73, 37), + ]); + } + test_invalid_String() async { await assertErrorsInCode(r''' import 'dart:ffi'; @@ -133,11 +161,15 @@ import 'dart:ffi'; @Native() external void foo(); +@Native() +external void foo2(); + @Native() external Pointer global; void main() { print(Native.addressOf>(foo)); + print(Native.addressOf>(foo2)); print(Native.addressOf>(global)); } '''); @@ -235,7 +267,7 @@ import 'dart:ffi'; @Native() external int foo(); ''', [ - error(FfiCode.MUST_BE_A_NATIVE_FUNCTION_TYPE, 43, 3), + error(FfiCode.NATIVE_FUNCTION_MISSING_TYPE, 43, 3), ]); } @@ -570,7 +602,7 @@ import 'dart:ffi'; @Native() external int foo(); ''', [ - error(FfiCode.MUST_BE_A_NATIVE_FUNCTION_TYPE, 43, 3), + error(FfiCode.NATIVE_FUNCTION_MISSING_TYPE, 43, 3), ]); } @@ -583,7 +615,7 @@ const a = Native(); @a external int foo(); ''', [ - error(FfiCode.MUST_BE_A_NATIVE_FUNCTION_TYPE, 57, 3), + error(FfiCode.NATIVE_FUNCTION_MISSING_TYPE, 57, 3), ]); } @@ -614,6 +646,234 @@ external int foo(); ]); } + test_InferPointerReturnNoParameters() async { + await assertNoErrorsInCode(r''' +import 'dart:ffi'; + +@Native() +external Pointer foo(); +'''); + } + + test_InferPointerReturnPointerParameter() async { + await assertNoErrorsInCode(r''' +import 'dart:ffi'; + +@Native() +external Pointer foo(Pointer x); +'''); + } + + test_InferPointerReturnStructParameter() async { + await assertNoErrorsInCode(r''' +import 'dart:ffi'; + +@Native() +external Pointer foo(MyStruct x); + +final class MyStruct extends Struct { + @Int8() + external int value; +} +'''); + } + + test_InferPointerReturnUnionParameter() async { + await assertNoErrorsInCode(r''' +import 'dart:ffi'; + +@Native() +external Pointer foo(MyUnion x); + +final class MyUnion extends Union { + @Int8() + external int a; + @Int8() + external int b; +} +'''); + } + + test_InferStructReturnNoParameters() async { + await assertNoErrorsInCode(r''' +import 'dart:ffi'; + +@Native() +external MyStruct foo(); + +final class MyStruct extends Struct { + @Int8() + external int value; +} +'''); + } + + test_InferStructReturnPointerParameter() async { + await assertNoErrorsInCode(r''' +import 'dart:ffi'; + +@Native() +external MyStruct foo(Pointer x); + +final class MyStruct extends Struct { + @Int8() + external int value; +} +'''); + } + + test_InferStructReturnStructParameter() async { + await assertNoErrorsInCode(r''' +import 'dart:ffi'; + +@Native() +external MyStruct foo(MyStruct x); + +final class MyStruct extends Struct { + @Int8() + external int value; +} +'''); + } + + test_InferStructReturnUnionParameter() async { + await assertNoErrorsInCode(r''' +import 'dart:ffi'; + +@Native() +external MyStruct foo(MyUnion x); + +final class MyStruct extends Struct { + @Int8() + external int value; +} + +final class MyUnion extends Union { + @Int8() + external int a; + @Int8() + external int b; +} +'''); + } + + test_InferUnionReturnNoParameters() async { + await assertNoErrorsInCode(r''' +import 'dart:ffi'; + +@Native() +external MyUnion foo(); + +final class MyUnion extends Union { + @Int8() + external int a; + @Int8() + external int b; +} +'''); + } + + test_InferUnionReturnPointerParameter() async { + await assertNoErrorsInCode(r''' +import 'dart:ffi'; + +@Native() +external MyUnion foo(Pointer x); + +final class MyUnion extends Union { + @Int8() + external int a; + @Int8() + external int b; +} +'''); + } + + test_InferUnionReturnStructParameter() async { + await assertNoErrorsInCode(r''' +import 'dart:ffi'; + +@Native() +external MyUnion foo(MyStruct x); + +final class MyStruct extends Struct { + @Int8() + external int value; +} + +final class MyUnion extends Union { + @Int8() + external int a; + @Int8() + external int b; +} +'''); + } + + test_InferUnionReturnUnionParameter() async { + await assertNoErrorsInCode(r''' +import 'dart:ffi'; + +@Native() +external MyUnion foo(MyUnion x); + +final class MyUnion extends Union { + @Int8() + external int a; + @Int8() + external int b; +} +'''); + } + + test_InferVoidReturnNoParameters() async { + await assertNoErrorsInCode(r''' +import 'dart:ffi'; + +@Native() +external void foo(); +'''); + } + + test_InferVoidReturnPointerParameter() async { + await assertNoErrorsInCode(r''' +import 'dart:ffi'; + +@Native() +external void foo(Pointer x); +'''); + } + + test_InferVoidReturnStructParameter() async { + await assertNoErrorsInCode(r''' +import 'dart:ffi'; + +@Native() +external void foo(MyStruct x); + +final class MyStruct extends Struct { + @Int8() + external int value; +} +'''); + } + + test_InferVoidReturnUnionParameter() async { + await assertNoErrorsInCode(r''' +import 'dart:ffi'; + +@Native() +external void foo(MyUnion x); + +final class MyUnion extends Union { + @Int8() + external int a; + @Int8() + external int b; +} +'''); + } + test_NativeCanUseHandles() async { await assertErrorsInCode(r''' import 'dart:ffi'; diff --git a/pkg/analyzer/tool/diagnostics/diagnostics.md b/pkg/analyzer/tool/diagnostics/diagnostics.md index 0ce095dc3d5..8d206a82775 100644 --- a/pkg/analyzer/tool/diagnostics/diagnostics.md +++ b/pkg/analyzer/tool/diagnostics/diagnostics.md @@ -14424,6 +14424,49 @@ final class C extends Struct { } ``` +### native_function_missing_type + +_The native type of this function couldn't be inferred so it must be specified +in the annotation._ + +#### Description + +The analyzer produces this diagnostic when a `@Native`-annotated function +requires a type hint on the annotation to infer the native function type. + +Dart types like `int` and `double` have multiple possible native +representations. Since the native type needs to be known at compile time +to generate correct bindings and call instructions for the function, an +explicit type must be given. + +For more information about FFI, see [C interop using dart:ffi][ffi]. + +#### Example + +The following code produces this diagnostic because the function `f()` has +the return type `int`, but doesn't have an explicit type parameter on the +`Native` annotation: + +```dart +import 'dart:ffi'; + +@Native() +external int [!f!](); +``` + +#### Common fixes + +Add the corresponding type to the annotation. For instance, if `f()` was +declared to return an `int32_t` in C, the Dart function should be declared +as: + +```dart +import 'dart:ffi'; + +@Native() +external int f(); +``` + ### negative_variable_dimension _The variable dimension of a variable-length array must be non-negative._ diff --git a/pkg/front_end/messages.yaml b/pkg/front_end/messages.yaml index bf10cfd98fa..71ddca54116 100644 --- a/pkg/front_end/messages.yaml +++ b/pkg/front_end/messages.yaml @@ -5276,6 +5276,12 @@ FfiNativeFieldType: analyzerCode: NATIVE_FIELD_INVALID_TYPE external: test/ffi_test.dart +FfiNativeFunctionMissingType: + # Used by dart:ffi + problemMessage: "The native type of this function couldn't be inferred so it must be specified in the annotation." + analyzerCode: NATIVE_FUNCTION_MISSING_TYPE + external: test/ffi_test.dart + FfiAddressOfMustBeNative: # Used by dart:ffi problemMessage: "Argument to 'Native.addressOf' must be annotated with @Native." diff --git a/pkg/vm/lib/modular/transformations/ffi/common.dart b/pkg/vm/lib/modular/transformations/ffi/common.dart index 8165e05f39f..1c8c1ed2f48 100644 --- a/pkg/vm/lib/modular/transformations/ffi/common.dart +++ b/pkg/vm/lib/modular/transformations/ffi/common.dart @@ -843,14 +843,42 @@ class FfiTransformer extends Transformer { /// /// For types where this returns a non-null value, this is the inverse of /// [convertNativeTypeToDartType]. - DartType? convertDartTypeToNativeType(DartType dartType) { - if (isPointerType(dartType) || - isStructOrUnionSubtype(dartType) || - isArrayType(dartType)) { + DartType? convertDartTypeToNativeType( + DartType dartType, { + bool allowVoid = false, + }) { + if (allowVoid && dartType is VoidType) return voidType; + + if (isArrayType(dartType) || + isPointerType(dartType) || + isStructOrUnionSubtype(dartType)) { return dartType; - } else { - return null; } + + 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 = []; + 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. @@ -1468,7 +1496,7 @@ class FfiTransformer extends Transformer { bool allowInlineArray = false, bool allowVoid = false, }) { - if (!_nativeTypeValid(nativeType, + if (!isNativeTypeValid(nativeType, allowStructAndUnion: allowStructAndUnion, allowHandle: allowHandle, allowInlineArray: allowInlineArray, @@ -1484,7 +1512,7 @@ class FfiTransformer extends Transformer { /// The Dart type system does not enforce that NativeFunction return and /// parameter types are only NativeTypes, so we need to check this. - bool _nativeTypeValid( + bool isNativeTypeValid( DartType nativeType, { bool allowStructAndUnion = false, bool allowHandle = false, diff --git a/pkg/vm/lib/modular/transformations/ffi/native.dart b/pkg/vm/lib/modular/transformations/ffi/native.dart index 5dee1ed02e0..e228a1213f0 100644 --- a/pkg/vm/lib/modular/transformations/ffi/native.dart +++ b/pkg/vm/lib/modular/transformations/ffi/native.dart @@ -11,6 +11,7 @@ import 'package:front_end/src/codes/cfe_codes.dart' messageFfiNativeFieldMissingType, messageFfiNativeFieldMustBeStatic, messageFfiNativeFieldType, + messageFfiNativeFunctionMissingType, messageFfiNativeMustBeExternal, messageFfiNativeOnlyNativeFieldWrapperClassCanBePointer, templateCantHaveNamedParameters, @@ -793,6 +794,43 @@ class FfiNativeTransformer extends FfiTransformer { return (ffiType, NativeTypeCfe.withoutLayout(this, ffiType)); } + DartType _validateOrInferNativeFunctionType( + Procedure node, + DartType nativeType, + FunctionType dartType, + ) { + if (nativeType is DynamicType) { + // No type argument was given on the @Native annotation, so we try to + // infer the native type from the Dart signature. + final inferred = convertDartTypeToNativeType(dartType, allowVoid: true); + if (inferred != null) { + nativeType = inferred; + } + + final nativeFunctionType = InterfaceType( + nativeFunctionClass, + Nullability.nonNullable, + [nativeType], + ); + + if (!isNativeTypeValid( + nativeFunctionType, + allowHandle: true, + allowStructAndUnion: true, + )) { + diagnosticReporter.report( + messageFfiNativeFunctionMissingType, + node.fileOffset, + 1, + node.location?.file, + ); + throw FfiStaticTypeError(); + } + } + + return nativeType; + } + @override TreeNode visitField(Field node) { final nativeAnnotation = tryGetNativeAnnotationOrWarnOnDuplicates(node); @@ -829,6 +867,18 @@ class FfiNativeTransformer extends FfiTransformer { final ffiConstant = ffiNativeAnnotation.constant as InstanceConstant; var nativeType = ffiConstant.typeArguments[0]; + if (node.kind == ProcedureKind.Method) { + final dartType = + node.function.computeFunctionType(Nullability.nonNullable); + try { + nativeType = + _validateOrInferNativeFunctionType(node, nativeType, dartType); + } on FfiStaticTypeError { + // We've already reported an error. + return node; + } + } + final nativeName = _resolveNativeSymbolName(node, ffiConstant); final overriddenAssetName = _assetNameFromAnnotation(ffiConstant); final isLeaf = _isLeaf(ffiConstant); @@ -843,14 +893,13 @@ class FfiNativeTransformer extends FfiTransformer { // We've already reported an error. return node; } - final ffiFunctionType = ffiConstant.typeArguments[0] as FunctionType; if (!node.isStatic) { - return _transformInstanceMethod(node, ffiFunctionType, nativeName, + return _transformInstanceMethod(node, nativeType, nativeName, overriddenAssetName, isLeaf, ffiNativeAnnotation.fileOffset); } - return _transformStaticFunction(node, ffiFunctionType, nativeName, + return _transformStaticFunction(node, nativeType, nativeName, overriddenAssetName, isLeaf, ffiNativeAnnotation.fileOffset); } else if (node.kind == ProcedureKind.Getter || node.kind == ProcedureKind.Setter) { diff --git a/pkg/vm/testcases/transformations/ffi/ffinative.dart b/pkg/vm/testcases/transformations/ffi/ffinative.dart index 313780cca52..2b04048f701 100644 --- a/pkg/vm/testcases/transformations/ffi/ffinative.dart +++ b/pkg/vm/testcases/transformations/ffi/ffinative.dart @@ -11,6 +11,12 @@ import 'dart:ffi'; import 'dart:nativewrappers'; +@Native() +external void returnVoid(); + +@Native() +external Pointer returnPointer(Pointer x); + @Native(symbol: 'ReturnIntPtr') external int returnIntPtr(int x); @@ -69,5 +75,7 @@ void main() { final b = NativeClassy().myField; NativeClassy().myField = !b; + Native.addressOf>(returnVoid); + Native.addressOf>(returnPointer); Native.addressOf>(returnIntPtr); } diff --git a/pkg/vm/testcases/transformations/ffi/ffinative.dart.aot.expect b/pkg/vm/testcases/transformations/ffi/ffinative.dart.aot.expect index 8eb300f29da..b46e9cad3d4 100644 --- a/pkg/vm/testcases/transformations/ffi/ffinative.dart.aot.expect +++ b/pkg/vm/testcases/transformations/ffi/ffinative.dart.aot.expect @@ -199,6 +199,8 @@ static method main() → void { [@vm.direct-call.metadata=#lib::NativeClassy.blah] [@vm.inferred-type.metadata=!? (skip check)] new self::NativeClassy::•().{self::NativeClassy::blah}(){() → core::bool}; final core::bool b = [@vm.direct-call.metadata=#lib::NativeClassy.myField] [@vm.inferred-type.metadata=dart.core::bool] new self::NativeClassy::•().{self::NativeClassy::myField}{core::bool}; [@vm.direct-call.metadata=#lib::NativeClassy.myField] [@vm.inferred-type.metadata=!? (skip check)] new self::NativeClassy::•().{self::NativeClassy::myField} = !b; + ffi::Native::_addressOf>(#C48); + ffi::Native::_addressOf) → ffi::Pointer>>(#C50); ffi::Native::_addressOf>(#C5); } constants { @@ -248,4 +250,8 @@ constants { #C44 = ffi::Native<(ffi::IntPtr) → ffi::IntPtr> {symbol:#C2, assetId:#C3, isLeaf:#C28} #C45 = core::pragma {name:#C1, options:#C44} #C46 = core::pragma {name:#C7, options:#C44} + #C47 = "returnVoid" + #C48 = ffi::Native<() → ffi::Void> {symbol:#C47, assetId:#C3, isLeaf:#C4} + #C49 = "returnPointer" + #C50 = ffi::Native<(ffi::Pointer) → ffi::Pointer> {symbol:#C49, assetId:#C3, isLeaf:#C4} } diff --git a/pkg/vm/testcases/transformations/ffi/ffinative.dart.expect b/pkg/vm/testcases/transformations/ffi/ffinative.dart.expect index ab86316b7bb..5d77cdcb754 100644 --- a/pkg/vm/testcases/transformations/ffi/ffinative.dart.expect +++ b/pkg/vm/testcases/transformations/ffi/ffinative.dart.expect @@ -144,14 +144,20 @@ class NativeClassy extends nat::NativeFieldWrapperClass1 { @#C42 external static method _myField$Setter$FfiNative(ffi::Pointer #t0, core::bool #t1) → void; } +@#C45 +@#C46 +external static method returnVoid() → void; +@#C49 +@#C50 +external static method returnPointer(ffi::Pointer x) → ffi::Pointer; @#C6 @#C8 external static method returnIntPtr(core::int x) → core::int; -@#C44 -@#C45 +@#C52 +@#C53 external static method returnIntPtrLeaf(core::int x) → core::int; -@#C48 -@#C49 +@#C56 +@#C57 external static method returnNativeIntPtrLeaf(core::int x) → core::int; static method main() → void { self::returnIntPtr(13); @@ -166,6 +172,8 @@ static method main() → void { new self::NativeClassy::•().{self::NativeClassy::blah}(){() → core::bool}; final core::bool b = new self::NativeClassy::•().{self::NativeClassy::myField}{core::bool}; new self::NativeClassy::•().{self::NativeClassy::myField} = !b; + ffi::Native::_addressOf>(#C44); + ffi::Native::_addressOf) → ffi::Pointer>>(#C48); ffi::Native::_addressOf>(#C5); } constants { @@ -211,11 +219,19 @@ constants { #C40 = core::pragma {name:#C7, options:#C27} #C41 = core::pragma {name:#C7, options:#C30} #C42 = core::pragma {name:#C7, options:#C32} - #C43 = ffi::Native<(ffi::IntPtr) → ffi::IntPtr> {symbol:#C2, assetId:#C3, isLeaf:#C29} - #C44 = core::pragma {name:#C1, options:#C43} - #C45 = core::pragma {name:#C7, options:#C43} - #C46 = "returnNativeIntPtrLeaf" - #C47 = ffi::Native<(ffi::IntPtr) → ffi::IntPtr> {symbol:#C46, assetId:#C3, isLeaf:#C29} - #C48 = core::pragma {name:#C1, options:#C47} - #C49 = core::pragma {name:#C7, options:#C47} + #C43 = "returnVoid" + #C44 = ffi::Native<() → ffi::Void> {symbol:#C43, assetId:#C3, isLeaf:#C4} + #C45 = core::pragma {name:#C1, options:#C44} + #C46 = core::pragma {name:#C7, options:#C44} + #C47 = "returnPointer" + #C48 = ffi::Native<(ffi::Pointer) → ffi::Pointer> {symbol:#C47, assetId:#C3, isLeaf:#C4} + #C49 = core::pragma {name:#C1, options:#C48} + #C50 = core::pragma {name:#C7, options:#C48} + #C51 = ffi::Native<(ffi::IntPtr) → ffi::IntPtr> {symbol:#C2, assetId:#C3, isLeaf:#C29} + #C52 = core::pragma {name:#C1, options:#C51} + #C53 = core::pragma {name:#C7, options:#C51} + #C54 = "returnNativeIntPtrLeaf" + #C55 = ffi::Native<(ffi::IntPtr) → ffi::IntPtr> {symbol:#C54, assetId:#C3, isLeaf:#C29} + #C56 = core::pragma {name:#C1, options:#C55} + #C57 = core::pragma {name:#C7, options:#C55} } diff --git a/sdk/lib/ffi/ffi.dart b/sdk/lib/ffi/ffi.dart index 10bfda296bb..c0f8d7b350d 100644 --- a/sdk/lib/ffi/ffi.dart +++ b/sdk/lib/ffi/ffi.dart @@ -2374,20 +2374,30 @@ abstract final class NativeApi { /// annotated function or variable in Dart. This can be overridden with the /// [symbol] parameter on the annotation. /// -/// If this annotation is used on a function, then the type argument [T] to the -/// [Native] annotation must be a function type representing the native -/// function's parameter and return types. The parameter and return types must -/// be subtypes of [NativeType]. +/// When used on a function, [T] must be a function type that represents the +/// native function's parameter and return types. The parameter and return types +/// must be subtypes of [NativeType]. /// -/// If this annotation is used on an external variable, then the type argument -/// [T] must be a compatible native type. For example, an [int] field can be -/// annotated with [Int32]. -/// If the type argument to `@Native` is omitted, it defaults to the Dart type -/// of the annotated declaration, which *must* then be a native type too. -/// This will never work for function declarations, but can apply to variables -/// whose type is some of the types of this library, such as [Pointer]. -/// For native global variables that cannot be re-assigned, a final variable in -/// Dart or a getter can be used to prevent assignments to the native field. +/// When used on a variable, [T] must be a compatible native type. For example, +/// an [int] field can be annotated with [Int32]. +/// +/// If the type argument [T] is omitted in the `@Native` annotation, it is +/// inferred from the static type of the declaration, which must meet the +/// following constraints: +/// +/// For function or method declarations: +/// - The return type must be one of the following: +/// - [Pointer] +/// - `void` +/// - Subtype of compound types, such as [Struct] or [Union] +/// - The parameter types must be subtypes of compound types or [Pointer] +/// +/// For variable declarations, the type can be any of the following: +/// - [Pointer] +/// - Subtype of compound types, such as [Struct] or [Union] +/// +/// For native global variables that cannot be reassigned, a `final` variable in +/// Dart or a getter can be used to prevent modifications to the native field. /// /// Example: /// @@ -2395,6 +2405,9 @@ abstract final class NativeApi { /// @Native() /// external int sum(int a, int b); /// +/// @Native() +/// external void free(Pointer p); +/// /// @Native() /// external int aGlobalInt; /// diff --git a/tests/ffi/native_assets/asset_absolute_test.dart b/tests/ffi/native_assets/asset_absolute_test.dart index 5c3e7c62251..d3669f29e55 100644 --- a/tests/ffi/native_assets/asset_absolute_test.dart +++ b/tests/ffi/native_assets/asset_absolute_test.dart @@ -113,7 +113,7 @@ final class Coord extends Struct { @Native() external Coord globalStruct; -@Native() +@Native() external Coord GetGlobalStruct(); @Native() diff --git a/tests/ffi/native_assets/asset_library_annotation_test.dart b/tests/ffi/native_assets/asset_library_annotation_test.dart index 2a996232d57..2ffb3f2ec90 100644 --- a/tests/ffi/native_assets/asset_library_annotation_test.dart +++ b/tests/ffi/native_assets/asset_library_annotation_test.dart @@ -111,7 +111,7 @@ final class Coord extends Struct { @Native() external Coord globalStruct; -@Native() +@Native() external Coord GetGlobalStruct(); @Native() diff --git a/tests/ffi/native_assets/asset_process_test.dart b/tests/ffi/native_assets/asset_process_test.dart index 9242eef6290..f6c6850be8a 100644 --- a/tests/ffi/native_assets/asset_process_test.dart +++ b/tests/ffi/native_assets/asset_process_test.dart @@ -79,25 +79,25 @@ Future runTests() async { @Native(symbol: 'malloc') external Pointer posixMalloc(int size); -@Native(symbol: 'free') +@Native(symbol: 'free') external void posixFree(Pointer pointer); @Native(symbol: 'CoTaskMemAlloc') external Pointer winCoTaskMemAlloc(int cb); -@Native(symbol: 'CoTaskMemFree') +@Native(symbol: 'CoTaskMemFree') external void winCoTaskMemFree(Pointer pv); @Native() external Pointer malloc(int size); -@Native(assetId: asset2Name) +@Native(assetId: asset2Name) external void free(Pointer pointer); @Native() external Pointer CoTaskMemAlloc(int cb); -@Native(assetId: asset2Name) +@Native(assetId: asset2Name) external void CoTaskMemFree(Pointer pv); void testProcessOrSystem() { diff --git a/tests/ffi/native_assets/asset_relative_test.dart b/tests/ffi/native_assets/asset_relative_test.dart index d7586e5a95b..70427912155 100644 --- a/tests/ffi/native_assets/asset_relative_test.dart +++ b/tests/ffi/native_assets/asset_relative_test.dart @@ -193,7 +193,7 @@ final class Coord extends Struct { @Native() external Coord globalStruct; -@Native() +@Native() external Coord GetGlobalStruct(); @Native() diff --git a/tests/ffi/native_assets/asset_system_test.dart b/tests/ffi/native_assets/asset_system_test.dart index dc9ec63d967..e421e62dd53 100644 --- a/tests/ffi/native_assets/asset_system_test.dart +++ b/tests/ffi/native_assets/asset_system_test.dart @@ -81,13 +81,13 @@ Future runTests() async { @Native() external Pointer malloc(int size); -@Native(assetId: asset2Name) +@Native(assetId: asset2Name) external void free(Pointer pointer); @Native() external Pointer CoTaskMemAlloc(int cb); -@Native(assetId: asset2Name) +@Native(assetId: asset2Name) external void CoTaskMemFree(Pointer pv); void testProcessOrSystem() { diff --git a/tests/ffi/native_assets/process_test.dart b/tests/ffi/native_assets/process_test.dart index f734146b251..aadcd0ec9d9 100644 --- a/tests/ffi/native_assets/process_test.dart +++ b/tests/ffi/native_assets/process_test.dart @@ -28,13 +28,13 @@ external Pointer posixMalloc(int size); @Native(symbol: 'calloc') external Pointer posixCalloc(int num, int size); -@Native(symbol: 'free') +@Native(symbol: 'free') external void posixFree(Pointer pointer); @Native(symbol: 'CoTaskMemAlloc') external Pointer winCoTaskMemAlloc(int cb); -@Native(symbol: 'CoTaskMemFree') +@Native(symbol: 'CoTaskMemFree') external void winCoTaskMemFree(Pointer pv); class _MallocAllocator implements Allocator { @@ -90,5 +90,5 @@ void testFailure() { } } -@Native(symbol: 'symbol_is_not_defined_29903211') +@Native(symbol: 'symbol_is_not_defined_29903211') external void symbolIsNotDefined(); diff --git a/tests/ffi/static_checks/regress_51913_test.dart b/tests/ffi/static_checks/regress_51913_test.dart deleted file mode 100644 index b2dd94fc77a..00000000000 --- a/tests/ffi/static_checks/regress_51913_test.dart +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -// Formatting can break multitests, so don't format them. -// dart format off - -import 'dart:ffi'; - -// Should have been `@Native` or something. -@Native() //# 1: compile-time error -external void foo(); //# 1: compile-time error - -void main() { - print('something'); -} diff --git a/tests/ffi/static_checks/vmspecific_static_checks_native_test.dart b/tests/ffi/static_checks/vmspecific_static_checks_native_test.dart index 438bf0bbdb8..9f7517a3e43 100644 --- a/tests/ffi/static_checks/vmspecific_static_checks_native_test.dart +++ b/tests/ffi/static_checks/vmspecific_static_checks_native_test.dart @@ -27,6 +27,89 @@ final class MyStruct extends Struct { external Pointer next; } +final class MyUnion extends Union { + @Int8() + external int a; + @Int8() + external int b; +} + +@Native() +external void validInference(); + +@Native() +external void validInference2(Pointer x); + +@Native() +external void validInference3(MyStruct x); + +@Native() +external void validInference4(MyUnion x); + +@Native() +external MyStruct validInference5(); + +@Native() +external MyStruct validInference6(Pointer x); + +@Native() +external MyStruct validInference7(MyStruct x); + +@Native() +external MyStruct validInference8(MyUnion x); + +@Native() +external MyUnion validInference9(); + +@Native() +external MyUnion validInference10(Pointer x); + +@Native() +external MyUnion validInference11(MyStruct x); + +@Native() +external MyUnion validInference12(MyUnion x); + +@Native() +external Pointer validInference13(); + +@Native() +external Pointer validInference14(Pointer x); + +@Native() +external Pointer validInference15(MyStruct x); + +@Native() +external Pointer validInference16(MyUnion x); + +@Native() +external void invalidNoInference(int x); +// ^^^^^^^^^^^^^^^^^^ +// [analyzer] COMPILE_TIME_ERROR.NATIVE_FUNCTION_MISSING_TYPE +// ^ +// [cfe] The native type of this function couldn't be inferred so it must be specified in the annotation. + +@Native() +external MyStruct invalidNoInference2(int x); +// ^^^^^^^^^^^^^^^^^^^ +// [analyzer] COMPILE_TIME_ERROR.NATIVE_FUNCTION_MISSING_TYPE +// ^ +// [cfe] The native type of this function couldn't be inferred so it must be specified in the annotation. + +@Native() +external MyUnion invalidNoInference3(int x); +// ^^^^^^^^^^^^^^^^^^^ +// [analyzer] COMPILE_TIME_ERROR.NATIVE_FUNCTION_MISSING_TYPE +// ^ +// [cfe] The native type of this function couldn't be inferred so it must be specified in the annotation. + +@Native() +external Pointer invalidNoInference4(int x); +// ^^^^^^^^^^^^^^^^^^^ +// [analyzer] COMPILE_TIME_ERROR.NATIVE_FUNCTION_MISSING_TYPE +// ^ +// [cfe] The native type of this function couldn't be inferred so it must be specified in the annotation. + typedef ComplexNativeFunction = MyStruct Function(Long, Double, MyStruct); const native = Native(); @@ -68,7 +151,7 @@ external Pointer myStructPtrInvalid; // [cfe] Expected type 'Pointer' to be 'MyStruct', which is the Dart type corresponding to 'MyStruct'. @Native() -external int invalidNoInferrence; +external int invalidNoInference5; // ^^^^^^^^^^^^^^^^^^^ // [analyzer] COMPILE_TIME_ERROR.NATIVE_FIELD_MISSING_TYPE // [cfe] The native type of this field could not be inferred and must be specified in the annotation. @@ -171,18 +254,35 @@ void addressOf() { // ^ // [cfe] Expected type 'NativeType' to be a valid and instantiated subtype of 'NativeType'. + Native.addressOf(validInference); +//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +// [analyzer] COMPILE_TIME_ERROR.MUST_BE_A_NATIVE_FUNCTION_TYPE + // ^ + // [cfe] Expected type 'NativeType' to be a valid and instantiated subtype of 'NativeType'. + Native.addressOf(_valid); //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // [analyzer] COMPILE_TIME_ERROR.MUST_BE_A_SUBTYPE // ^ // [cfe] Expected type 'NativeFunction' to be a valid and instantiated subtype of 'NativeType'. + Native.addressOf(validInference); +//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +// [analyzer] COMPILE_TIME_ERROR.MUST_BE_A_SUBTYPE + // ^ + // [cfe] Expected type 'NativeFunction' to be a valid and instantiated subtype of 'NativeType'. + Native.addressOf>(_valid); //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // [analyzer] COMPILE_TIME_ERROR.MUST_BE_A_SUBTYPE // ^ // [cfe] Expected type 'void Function()' to be 'void Function(int)', which is the Dart type corresponding to 'NativeFunction'. + Native.addressOf>(validInference); + Native.addressOf>(validInference2); + Native.addressOf>(validInference5); + Native.addressOf>(validInference9); + Native.addressOf>(validInference13); Native.addressOf>(validNative); Native.addressOf(myStruct0); diff --git a/tools/find_builders.dart b/tools/find_builders.dart index 7ddb178fa8b..5d9b53e73f4 100755 --- a/tools/find_builders.dart +++ b/tools/find_builders.dart @@ -8,7 +8,7 @@ // Usage: // // ``` -// $ tools/find_builders.dart ffi/regress_51504_test ffi/regress_51913_test +// $ tools/find_builders.dart ffi/regress_51504_test ffi/regress_52298_test // Cq-Include-Trybots: dart/try:vm-kernel-linux-debug-x64,... // ``` @@ -27,10 +27,11 @@ Future main(List args) async { for (final testName in testNames) ...await _testGetConfigurations(testName), }); final configurationBuilders = await _configurationBuilders(); - final builders = _filterBuilders( - {for (final config in configurations) configurationBuilders[config]!}, - ).toList() - ..sort(); + final builders = + _filterBuilders({ + for (final config in configurations) configurationBuilders[config]!, + }).toList() + ..sort(); final gerritTryList = builders.map((b) => '$b-try').join(','); print('Cq-Include-Trybots: dart/try:$gerritTryList'); @@ -47,7 +48,7 @@ Future> _testGetConfigurations(String testName) async { final object = jsonDecode(response) as Map; return [ for (final result in ((object['results'] as List)).cast()) - result['configuration'] + result['configuration'], ]; } @@ -105,7 +106,8 @@ Stream> _configurationDocuments() async* { final response = await _get(requestUrl); final object = jsonDecode(response) as Map; yield* Stream.fromIterable( - (object['documents'] as List).cast>()); + (object['documents'] as List).cast>(), + ); nextPageToken = object['nextPageToken']; } while (nextPageToken != null); @@ -114,12 +116,11 @@ Stream> _configurationDocuments() async* { Future> _configurationBuilders() async { return { await for (final document in _configurationDocuments()) - if (document - case { - 'name': String fullName, - 'fields': {'builder': {'stringValue': String builder}} - }) - fullName.split('/').last: builder + if (document case { + 'name': String fullName, + 'fields': {'builder': {'stringValue': String builder}}, + }) + fullName.split('/').last: builder, }; }