[vm/ffi] Deprecate `Pointer<T extends Struct>.ref` generic calls
The analyzer and CFE now report a warning on calls to
`Pointer<T extends Struct>.ref` and `Pointer<T extends Struct>.[]`
where `T` is a generic.
Adapted from https://dart-review.googlesource.com/c/sdk/+/180190 to
only deprecate but not error out on generic calls to `.ref` and `[]`.
Issue: https://github.com/dart-lang/sdk/issues/38721
TEST=tests/ffi/vmspecific_static_checks_test.dart
Change-Id: I81353089d59f093730d63792e9dbcd0b2ff0c432
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/180365
Reviewed-by: Vyacheslav Egorov <vegorov@google.com>
diff --git a/pkg/analyzer/lib/error/error.dart b/pkg/analyzer/lib/error/error.dart
index c9211dd..6130688 100644
--- a/pkg/analyzer/lib/error/error.dart
+++ b/pkg/analyzer/lib/error/error.dart
@@ -473,6 +473,7 @@
FfiCode.MUST_BE_A_NATIVE_FUNCTION_TYPE,
FfiCode.MUST_BE_A_SUBTYPE,
FfiCode.NON_CONSTANT_TYPE_ARGUMENT,
+ FfiCode.NON_CONSTANT_TYPE_ARGUMENT_WARNING,
FfiCode.NON_NATIVE_FUNCTION_TYPE_ARGUMENT_TO_POINTER,
FfiCode.SUBTYPE_OF_FFI_CLASS_IN_EXTENDS,
FfiCode.SUBTYPE_OF_FFI_CLASS_IN_IMPLEMENTS,
diff --git a/pkg/analyzer/lib/src/dart/error/ffi_code.dart b/pkg/analyzer/lib/src/dart/error/ffi_code.dart
index b4d5580..63e796e 100644
--- a/pkg/analyzer/lib/src/dart/error/ffi_code.dart
+++ b/pkg/analyzer/lib/src/dart/error/ffi_code.dart
@@ -179,6 +179,20 @@
/**
* Parameters:
+ * 0: the name of the function, method, or constructor having type arguments
+ */
+ static const FfiCode NON_CONSTANT_TYPE_ARGUMENT_WARNING = FfiCode(
+ name: 'NON_CONSTANT_TYPE_ARGUMENT_WARNING',
+ message:
+ "Support for using non-constant type arguments '{0}' in this FFI API"
+ " is deprecated and will be removed in the next stable version of "
+ "Dart. Rewrite the code to ensure that type arguments are compile "
+ "time constants referring to a valid native type.",
+ correction: "Try changing the type argument to be a constant type.",
+ type: ErrorType.HINT);
+
+ /**
+ * Parameters:
* 0: the type that should be a valid dart:ffi native type.
*/
static const FfiCode NON_NATIVE_FUNCTION_TYPE_ARGUMENT_TO_POINTER = FfiCode(
diff --git a/pkg/analyzer/lib/src/generated/ffi_verifier.dart b/pkg/analyzer/lib/src/generated/ffi_verifier.dart
index 955afa8..fd2b6fe 100644
--- a/pkg/analyzer/lib/src/generated/ffi_verifier.dart
+++ b/pkg/analyzer/lib/src/generated/ffi_verifier.dart
@@ -153,6 +153,19 @@
}
@override
+ void visitIndexExpression(IndexExpression node) {
+ Element element = node.staticElement;
+ Element enclosingElement = element?.enclosingElement;
+ if (enclosingElement is ExtensionElement) {
+ if (_isNativeStructPointerExtension(enclosingElement)) {
+ if (element.name == '[]') {
+ _validateRefIndexed(node);
+ }
+ }
+ }
+ }
+
+ @override
void visitMethodInvocation(MethodInvocation node) {
Element element = node.methodName.staticElement;
if (element is MethodElement) {
@@ -178,6 +191,34 @@
super.visitMethodInvocation(node);
}
+ @override
+ void visitPrefixedIdentifier(PrefixedIdentifier node) {
+ Element element = node.staticElement;
+ Element enclosingElement = element?.enclosingElement;
+ if (enclosingElement is ExtensionElement) {
+ if (_isNativeStructPointerExtension(enclosingElement)) {
+ if (element.name == 'ref') {
+ _validateRefPrefixedIdentifier(node);
+ }
+ }
+ }
+ super.visitPrefixedIdentifier(node);
+ }
+
+ @override
+ void visitPropertyAccess(PropertyAccess node) {
+ Element element = node.propertyName.staticElement;
+ Element enclosingElement = element?.enclosingElement;
+ if (enclosingElement is ExtensionElement) {
+ if (_isNativeStructPointerExtension(enclosingElement)) {
+ if (element.name == 'ref') {
+ _validateRefPropertyAccess(node);
+ }
+ }
+ }
+ super.visitPropertyAccess(node);
+ }
+
/// Return `true` if the given [element] represents the extension
/// `AllocatorAlloc`.
bool _isAllocatorExtension(Element element) =>
@@ -240,6 +281,9 @@
element.name == 'NativeFunctionPointer' &&
element.library.name == _dartFfiLibraryName;
+ bool _isNativeStructPointerExtension(Element element) =>
+ element.name == 'StructPointer' && element.library.name == 'dart.ffi';
+
/// Returns `true` iff [nativeType] is a `ffi.NativeType` type.
bool _isNativeTypeInterfaceType(DartType nativeType) {
if (nativeType is InterfaceType) {
@@ -338,6 +382,7 @@
}
/// Validates that the given [nativeType] is a valid dart:ffi native type.
+ // TODO(https://dartbug.com/44747): Change to named arguments.
bool _isValidFfiNativeType(
DartType nativeType, bool allowVoid, bool allowEmptyStruct) {
if (nativeType is InterfaceType) {
@@ -697,6 +742,35 @@
}
}
+ void _validateRefIndexed(IndexExpression node) {
+ DartType targetType = node.target.staticType;
+ if (!_isValidFfiNativeType(targetType, false, true)) {
+ final AstNode errorNode = node;
+ _errorReporter.reportErrorForNode(
+ FfiCode.NON_CONSTANT_TYPE_ARGUMENT_WARNING, errorNode, ['[]']);
+ }
+ }
+
+ /// Validate the invocation of the extension method
+ /// `Pointer<T extends Struct>.ref`.
+ void _validateRefPrefixedIdentifier(PrefixedIdentifier node) {
+ DartType targetType = node.prefix.staticType;
+ if (!_isValidFfiNativeType(targetType, false, true)) {
+ final AstNode errorNode = node;
+ _errorReporter.reportErrorForNode(
+ FfiCode.NON_CONSTANT_TYPE_ARGUMENT_WARNING, errorNode, ['ref']);
+ }
+ }
+
+ void _validateRefPropertyAccess(PropertyAccess node) {
+ DartType targetType = node.target.staticType;
+ if (!_isValidFfiNativeType(targetType, false, true)) {
+ final AstNode errorNode = node;
+ _errorReporter.reportErrorForNode(
+ FfiCode.NON_CONSTANT_TYPE_ARGUMENT_WARNING, errorNode, ['ref']);
+ }
+ }
+
/// 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) {
diff --git a/pkg/front_end/lib/src/api_unstable/vm.dart b/pkg/front_end/lib/src/api_unstable/vm.dart
index a744355..547bbb2 100644
--- a/pkg/front_end/lib/src/api_unstable/vm.dart
+++ b/pkg/front_end/lib/src/api_unstable/vm.dart
@@ -62,6 +62,7 @@
templateFfiFieldCyclic,
templateFfiFieldInitializer,
templateFfiFieldNoAnnotation,
+ templateFfiNonConstantTypeArgumentWarning,
templateFfiNotStatic,
templateFfiStructGeneric,
templateFfiTypeInvalid,
diff --git a/pkg/front_end/lib/src/fasta/fasta_codes_cfe_generated.dart b/pkg/front_end/lib/src/fasta/fasta_codes_cfe_generated.dart
index a145ee7..0d94cfb 100644
--- a/pkg/front_end/lib/src/fasta/fasta_codes_cfe_generated.dart
+++ b/pkg/front_end/lib/src/fasta/fasta_codes_cfe_generated.dart
@@ -916,6 +916,35 @@
}
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Template<Message Function(DartType _type, bool isNonNullableByDefault)>
+ templateFfiNonConstantTypeArgumentWarning = const Template<
+ Message Function(DartType _type, bool isNonNullableByDefault)>(
+ messageTemplate:
+ r"""Support for using non-constant type arguments '#type' in this FFI API is deprecated and will be removed in the next stable version of Dart. Rewrite the code to ensure that type arguments are compile time constants referring to a valid native type.""",
+ withArguments: _withArgumentsFfiNonConstantTypeArgumentWarning);
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<Message Function(DartType _type, bool isNonNullableByDefault)>
+ codeFfiNonConstantTypeArgumentWarning =
+ const Code<Message Function(DartType _type, bool isNonNullableByDefault)>(
+ "FfiNonConstantTypeArgumentWarning",
+ analyzerCodes: <String>["NON_CONSTANT_TYPE_ARGUMENT_WARNING"],
+ severity: Severity.info);
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+Message _withArgumentsFfiNonConstantTypeArgumentWarning(
+ DartType _type, bool isNonNullableByDefault) {
+ TypeLabeler labeler = new TypeLabeler(isNonNullableByDefault);
+ List<Object> typeParts = labeler.labelType(_type);
+ String type = typeParts.join();
+ return new Message(codeFfiNonConstantTypeArgumentWarning,
+ message:
+ """Support for using non-constant type arguments '${type}' in this FFI API is deprecated and will be removed in the next stable version of Dart. Rewrite the code to ensure that type arguments are compile time constants referring to a valid native type.""" +
+ labeler.originMessages,
+ arguments: {'type': _type});
+}
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Template<
Message Function(
DartType _type,
diff --git a/pkg/front_end/messages.yaml b/pkg/front_end/messages.yaml
index 137317d..2e197e9 100644
--- a/pkg/front_end/messages.yaml
+++ b/pkg/front_end/messages.yaml
@@ -4267,6 +4267,13 @@
#names
external: test/ffi_test.dart
+FfiNonConstantTypeArgumentWarning:
+ # Used by dart:ffi
+ template: "Support for using non-constant type arguments '#type' in this FFI API is deprecated and will be removed in the next stable version of Dart. Rewrite the code to ensure that type arguments are compile time constants referring to a valid native type."
+ analyzerCode: NON_CONSTANT_TYPE_ARGUMENT_WARNING
+ severity: INFO
+ external: test/ffi_test.dart
+
FfiNotStatic:
# Used by dart:ffi
template: "#name expects a static function as parameter. dart:ffi only supports calling static Dart functions from native code."
diff --git a/pkg/front_end/test/spell_checking_list_common.txt b/pkg/front_end/test/spell_checking_list_common.txt
index da673d4..9b60eea 100644
--- a/pkg/front_end/test/spell_checking_list_common.txt
+++ b/pkg/front_end/test/spell_checking_list_common.txt
@@ -2437,6 +2437,7 @@
references
referencing
referred
+referring
refers
refine
refinement
diff --git a/pkg/front_end/test/spell_checking_list_messages.txt b/pkg/front_end/test/spell_checking_list_messages.txt
index d754653..f8c8578 100644
--- a/pkg/front_end/test/spell_checking_list_messages.txt
+++ b/pkg/front_end/test/spell_checking_list_messages.txt
@@ -11,6 +11,7 @@
JS
adjusting
+api
argument(s)
assigning
b
@@ -29,6 +30,7 @@
e.g
enum's
f
+ffi
flutter_runner
futureor
h
diff --git a/pkg/vm/lib/transformations/ffi.dart b/pkg/vm/lib/transformations/ffi.dart
index 17ad5f5..8d1ad62 100644
--- a/pkg/vm/lib/transformations/ffi.dart
+++ b/pkg/vm/lib/transformations/ffi.dart
@@ -219,6 +219,8 @@
final Procedure offsetByMethod;
final Procedure elementAtMethod;
final Procedure addressGetter;
+ final Procedure structPointerRef;
+ final Procedure structPointerElemAt;
final Procedure asFunctionMethod;
final Procedure asFunctionInternal;
final Procedure lookupFunctionMethod;
@@ -282,6 +284,10 @@
index.getMember('dart:ffi', 'Struct', '_fromPointer'),
fromAddressInternal =
index.getTopLevelMember('dart:ffi', '_fromAddress'),
+ structPointerRef =
+ index.getMember('dart:ffi', 'StructPointer', 'get:ref'),
+ structPointerElemAt =
+ index.getMember('dart:ffi', 'StructPointer', '[]'),
asFunctionMethod =
index.getMember('dart:ffi', 'NativeFunctionPointer', 'asFunction'),
asFunctionInternal =
@@ -344,7 +350,9 @@
/// [NativeFunction]<T1 Function(T2, T3) -> S1 Function(S2, S3)
/// where DartRepresentationOf(Tn) -> Sn
DartType convertNativeTypeToDartType(DartType nativeType,
- {bool allowStructs = false, bool allowHandle = false}) {
+ {bool allowStructs = false,
+ bool allowStructItself = false,
+ bool allowHandle = false}) {
if (nativeType is! InterfaceType) {
return null;
}
@@ -353,6 +361,9 @@
final NativeType nativeType_ = getType(nativeClass);
if (hierarchy.isSubclassOf(nativeClass, structClass)) {
+ if (structClass == nativeClass) {
+ return allowStructItself ? nativeType : null;
+ }
return allowStructs ? nativeType : null;
}
if (nativeType_ == null) {
diff --git a/pkg/vm/lib/transformations/ffi_use_sites.dart b/pkg/vm/lib/transformations/ffi_use_sites.dart
index 7bfd6a2..dcf091c 100644
--- a/pkg/vm/lib/transformations/ffi_use_sites.dart
+++ b/pkg/vm/lib/transformations/ffi_use_sites.dart
@@ -13,6 +13,7 @@
templateFfiExpectedExceptionalReturn,
templateFfiExpectedNoExceptionalReturn,
templateFfiExtendsOrImplementsSealedClass,
+ templateFfiNonConstantTypeArgumentWarning,
templateFfiNotStatic,
templateFfiTypeInvalid,
templateFfiTypeMismatch;
@@ -173,7 +174,14 @@
final Member target = node.target;
try {
- if (target == lookupFunctionMethod) {
+ if (target == structPointerRef || target == structPointerElemAt) {
+ final DartType nativeType = node.arguments.types[0];
+
+ _warningNativeTypeValid(nativeType, node, allowStructItself: false);
+
+ // TODO(http://dartbug.com/38721): Replace calls with direct
+ // constructor invocations.
+ } else if (target == lookupFunctionMethod) {
final DartType nativeType = InterfaceType(
nativeFunctionClass, Nullability.legacy, [node.arguments.types[0]]);
final DartType dartType = node.arguments.types[1];
@@ -427,9 +435,11 @@
}
void _ensureNativeTypeValid(DartType nativeType, Expression node,
- {bool allowHandle: false}) {
+ {bool allowHandle: false, bool allowStructItself = true}) {
if (!_nativeTypeValid(nativeType,
- allowStructs: true, allowHandle: allowHandle)) {
+ allowStructs: true,
+ allowStructItself: allowStructItself,
+ allowHandle: allowHandle)) {
diagnosticReporter.report(
templateFfiTypeInvalid.withArguments(
nativeType, currentLibrary.isNonNullableByDefault),
@@ -440,6 +450,22 @@
}
}
+ void _warningNativeTypeValid(DartType nativeType, Expression node,
+ {bool allowHandle: false, bool allowStructItself = true}) {
+ if (!_nativeTypeValid(nativeType,
+ allowStructs: true,
+ allowStructItself: allowStructItself,
+ allowHandle: allowHandle)) {
+ diagnosticReporter.report(
+ templateFfiNonConstantTypeArgumentWarning.withArguments(
+ nativeType, currentLibrary.isNonNullableByDefault),
+ node.fileOffset,
+ 1,
+ node.location.file);
+ throw _FfiStaticTypeError();
+ }
+ }
+
void _ensureNoEmptyStructs(DartType nativeType, Expression node) {
// Error on structs with no fields.
if (nativeType is InterfaceType) {
@@ -466,9 +492,13 @@
/// The Dart type system does not enforce that NativeFunction return and
/// parameter types are only NativeTypes, so we need to check this.
bool _nativeTypeValid(DartType nativeType,
- {bool allowStructs: false, allowHandle: false}) {
+ {bool allowStructs: false,
+ bool allowStructItself = false,
+ bool allowHandle = false}) {
return convertNativeTypeToDartType(nativeType,
- allowStructs: allowStructs, allowHandle: allowHandle) !=
+ allowStructs: allowStructs,
+ allowStructItself: allowStructItself,
+ allowHandle: allowHandle) !=
null;
}
diff --git a/sdk/lib/ffi/ffi.dart b/sdk/lib/ffi/ffi.dart
index bce5feb..426b697 100644
--- a/sdk/lib/ffi/ffi.dart
+++ b/sdk/lib/ffi/ffi.dart
@@ -538,6 +538,10 @@
///
/// The [address] must be aligned according to the struct alignment rules of
/// the platform.
+ ///
+ /// Support for invoking this extension method with non-constant [T] will be
+ /// removed in the next stable version of Dart and it will become mandatory
+ /// to invoke it with a compile-time constant [T].
external T get ref;
/// Creates a reference to access the fields of this struct backed by native
@@ -545,6 +549,10 @@
///
/// The [address] must be aligned according to the struct alignment rules of
/// the platform.
+ ///
+ /// Support for invoking this extension method with non-constant [T] will be
+ /// removed in the next stable version of Dart and it will become mandatory
+ /// to invoke it with a compile-time constant [T].
external T operator [](int index);
}
diff --git a/tests/ffi/vmspecific_static_checks_test.dart b/tests/ffi/vmspecific_static_checks_test.dart
index 45f9611..18bf1b2 100644
--- a/tests/ffi/vmspecific_static_checks_test.dart
+++ b/tests/ffi/vmspecific_static_checks_test.dart
@@ -56,6 +56,7 @@
testEmptyStructFromFunctionReturn();
testAllocateGeneric();
testAllocateNativeType();
+ testRefStruct();
}
typedef Int8UnOp = Int8 Function(Int8);
@@ -601,3 +602,19 @@
void testAllocateNativeType() {
calloc(); //# 1321: compile-time error
}
+
+void testRefStruct() {
+ final myStructPointer = calloc<TestStruct13>();
+ Pointer<Struct> structPointer = myStructPointer;
+ structPointer.ref; //# 1330: ok
+ calloc.free(myStructPointer);
+}
+
+T genericRef<T extends Struct>(Pointer<T> p) => //# 1200: ok
+ p.ref; //# 1200: ok
+
+T genericRef2<T extends Struct>(Pointer<T> p) => //# 1201: ok
+ p.cast<T>().ref; //# 1201: ok
+
+T genericRef3<T extends Struct>(Pointer<T> p) => //# 1202: ok
+ p[0]; //# 1202: ok
diff --git a/tests/ffi_2/vmspecific_static_checks_test.dart b/tests/ffi_2/vmspecific_static_checks_test.dart
index dd15508..1bda258 100644
--- a/tests/ffi_2/vmspecific_static_checks_test.dart
+++ b/tests/ffi_2/vmspecific_static_checks_test.dart
@@ -56,6 +56,7 @@
testEmptyStructFromFunctionReturn();
testAllocateGeneric();
testAllocateNativeType();
+ testRefStruct();
}
typedef Int8UnOp = Int8 Function(Int8);
@@ -599,3 +600,19 @@
void testAllocateNativeType() {
calloc(); //# 1321: compile-time error
}
+
+void testRefStruct() {
+ final myStructPointer = calloc<TestStruct13>();
+ Pointer<Struct> structPointer = myStructPointer;
+ structPointer.ref; //# 1330: ok
+ calloc.free(myStructPointer);
+}
+
+T genericRef<T extends Struct>(Pointer<T> p) => //# 1200: ok
+ p.ref; //# 1200: ok
+
+T genericRef2<T extends Struct>(Pointer<T> p) => //# 1201: ok
+ p.cast<T>().ref; //# 1201: ok
+
+T genericRef3<T extends Struct>(Pointer<T> p) => //# 1202: ok
+ p[0]; //# 1202: ok