From e5bb28bc4d12b7a9346a1ea76f9915d2bc2436be Mon Sep 17 00:00:00 2001 From: Daco Harkes Date: Wed, 10 May 2023 11:38:57 +0000 Subject: [PATCH] [vm/ffi] `Pointer.asTypedList` `finalizer` TEST=tests/ffi/external_typed_data_finalizer_test.dart Closes: https://github.com/dart-lang/sdk/issues/50507 CoreLibraryReviewExempt: https://github.com/dart-lang/sdk/issues/52261 Change-Id: I1a82dcca15961b28c0de64637970fe38a39286e5 Cq-Include-Trybots: luci.dart.try:vm-asan-linux-release-x64-try,vm-aot-asan-linux-release-x64-try,vm-ffi-android-debug-arm-try,vm-ffi-qemu-linux-release-arm-try,vm-win-debug-x64-try,vm-win-debug-x64c-try,vm-aot-win-debug-x64c-try,vm-mac-debug-arm64-try,vm-mac-debug-x64-try,vm-aot-mac-release-x64-try Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/301001 Commit-Queue: Daco Harkes Reviewed-by: Slava Egorov --- .../ffi_test/ffi_test_functions_vmspecific.cc | 37 ++++++ runtime/lib/ffi.cc | 23 ++++ runtime/tools/ffi/sdk_lib_ffi_generator.dart | 72 ++++++++--- runtime/vm/bootstrap_natives.cc | 1 + runtime/vm/bootstrap_natives.h | 6 +- runtime/vm/kernel_loader.cc | 3 +- .../vm/lib/ffi_native_finalizer_patch.dart | 43 ++++++- sdk/lib/_internal/vm/lib/ffi_patch.dart | 120 +++++++++++++++--- sdk/lib/_internal/vm/lib/internal_patch.dart | 5 +- sdk/lib/ffi/ffi.dart | 100 +++++++++++++-- sdk/lib/ffi/native_finalizer.dart | 8 ++ .../external_typed_data_finalizer_test.dart | 62 +++++++++ tests/lib/mirrors/invocation_fuzz_test.dart | 3 + tests/lib_2/mirrors/invocation_fuzz_test.dart | 3 + 14 files changed, 433 insertions(+), 53 deletions(-) create mode 100644 tests/ffi/external_typed_data_finalizer_test.dart diff --git a/runtime/bin/ffi_test/ffi_test_functions_vmspecific.cc b/runtime/bin/ffi_test/ffi_test_functions_vmspecific.cc index 584f7ed9907a..afa389d30e64 100644 --- a/runtime/bin/ffi_test/ffi_test_functions_vmspecific.cc +++ b/runtime/bin/ffi_test/ffi_test_functions_vmspecific.cc @@ -8,6 +8,7 @@ #include #include #include +#include #include "platform/globals.h" #include "platform/memory_sanitizer.h" @@ -1316,4 +1317,40 @@ DART_EXPORT bool IsNull(Dart_Handle object) { return Dart_IsNull(object); } +namespace { +struct RefCountedResource { + void* resource; + intptr_t refcount; +}; +} // namespace + +// We only ever have one ref counted resource in our test, use global lock. +std::mutex ref_counted_resource_mutex; + +DART_EXPORT RefCountedResource* AllocateRefcountedResource() { + auto peer = + static_cast(malloc(sizeof(RefCountedResource))); + auto resource = malloc(128); + peer->resource = resource; + peer->refcount = 0; // We're not going to count the reference here. + return peer; +} + +DART_EXPORT void IncreaseRefcount(RefCountedResource* peer) { + ref_counted_resource_mutex.lock(); + peer->refcount++; + ref_counted_resource_mutex.unlock(); +} + +// And delete if zero. +DART_EXPORT void DecreaseRefcount(RefCountedResource* peer) { + ref_counted_resource_mutex.lock(); + peer->refcount--; + if (peer->refcount <= 0) { + free(peer->resource); + free(peer); + } + ref_counted_resource_mutex.unlock(); +} + } // namespace dart diff --git a/runtime/lib/ffi.cc b/runtime/lib/ffi.cc index 4893b7c12c81..68eeeae4fbb6 100644 --- a/runtime/lib/ffi.cc +++ b/runtime/lib/ffi.cc @@ -189,4 +189,27 @@ DEFINE_FFI_NATIVE_ENTRY(FinalizerEntry_SetExternalSize, } }; +namespace { +struct AsTypedListFinalizerData { + void (*callback)(void*); + void* token; +}; +} // namespace + +DEFINE_FFI_NATIVE_ENTRY(Pointer_asTypedListFinalizerAllocateData, void*, ()) { + return malloc(sizeof(AsTypedListFinalizerData)); +}; + +void AsTypedListFinalizerCallback(void* peer) { + const auto* data = reinterpret_cast(peer); + data->callback(data->token); + free(peer); +} + +DEFINE_FFI_NATIVE_ENTRY(Pointer_asTypedListFinalizerCallbackPointer, + void*, + ()) { + return reinterpret_cast(&AsTypedListFinalizerCallback); +}; + } // namespace dart diff --git a/runtime/tools/ffi/sdk_lib_ffi_generator.dart b/runtime/tools/ffi/sdk_lib_ffi_generator.dart index 27eda6f0a48d..e8919dfc5ec3 100644 --- a/runtime/tools/ffi/sdk_lib_ffi_generator.dart +++ b/runtime/tools/ffi/sdk_lib_ffi_generator.dart @@ -39,30 +39,51 @@ main(List arguments) { final args = argParser().parse(arguments); Uri path = Uri.parse(args['path']); - generate(path, "ffi.g.dart", generatePublicExtension); - generate(path, "ffi_patch.g.dart", generatePatchExtension); + update(Uri.file('sdk/lib/ffi/ffi.dart'), generatePublicExtension); + update(Uri.file('sdk/lib/_internal/vm/lib/ffi_patch.dart'), + generatePatchExtension); } -void generate(Uri path, String fileName, - Function(StringBuffer, Config, String) generator) { +void update(Uri fileName, Function(StringBuffer, Config, String) generator) { + final file = File.fromUri(fileName); + if (!file.existsSync()) { + print('$fileName does not exist, run from the root of the SDK.'); + return; + } + + final fileContents = file.readAsStringSync(); + final split1 = fileContents.split(header); + if (split1.length != 2) { + print('$fileName has unexpected contents.'); + print(split1.length); + return; + } + final split2 = split1[1].split(footer); + if (split2.length != 2) { + print('$fileName has unexpected contents 2.'); + print(split2.length); + return; + } + final buffer = StringBuffer(); - generateHeader(buffer); + buffer.write(split1[0]); + buffer.write(header); configuration.forEach((Config c) => generator(buffer, c, "Pointer")); configuration.forEach((Config c) => generator(buffer, c, "Array")); - generateFooter(buffer); + buffer.write(footer); + buffer.write(split2[1]); - final fullPath = path.resolve(fileName).path; - File(fullPath).writeAsStringSync(buffer.toString()); - final fmtResult = Process.runSync(dartPath().path, ["format", fullPath]); + file.writeAsStringSync(buffer.toString()); + final fmtResult = + Process.runSync(dartPath().path, ["format", fileName.toFilePath()]); if (fmtResult.exitCode != 0) { throw Exception( "Formatting failed:\n${fmtResult.stdout}\n${fmtResult.stderr}\n"); } - print("Generated $fullPath."); + print("Updated $fileName."); } -void generateHeader(StringBuffer buffer) { - const header = """ +const header = """ // // The following code is generated, do not edit by hand. // @@ -71,6 +92,7 @@ void generateHeader(StringBuffer buffer) { """; +void generateHeader(StringBuffer buffer) { buffer.write(header); } @@ -173,7 +195,15 @@ void generatePublicExtension( /// /// The user has to ensure the memory range is accessible while using the /// returned list. -$alignment external $typedListType asTypedList(int length); + /// + /// If provided, [finalizer] will be run on the pointer once the typed list + /// is GCed. If provided, [token] will be passed to [finalizer], otherwise + /// the this pointer itself will be passed. +$alignment external $typedListType asTypedList( + int length, { + @Since('3.1') Pointer? finalizer, + @Since('3.1') Pointer? token, + }); """; if (container == "Pointer") { @@ -228,12 +258,20 @@ void generatePatchExtension( ? "" : """ @patch - $typedListType asTypedList(int length) { + $typedListType asTypedList( + int length, { + Pointer? finalizer, + Pointer? token, + }) { ArgumentError.checkNotNull(this, "Pointer<$nativeType>"); ArgumentError.checkNotNull(length, "length"); _checkExternalTypedDataLength(length, $elementSize); _checkPointerAlignment(address, $elementSize); - return _asExternalTypedData$nativeType(this, length); + final result = _asExternalTypedData$nativeType(this, length); + if (finalizer != null) { + _attachAsTypedListFinalizer(finalizer, result, token ?? this, $sizeTimes length); + } + return result; } """; @@ -279,13 +317,13 @@ extension ${nativeType}Array on Array<$nativeType> { } } -void generateFooter(StringBuffer buffer) { - final footer = """ +final footer = """ // // End of generated code. // """; +void generateFooter(StringBuffer buffer) { buffer.write(footer); } diff --git a/runtime/vm/bootstrap_natives.cc b/runtime/vm/bootstrap_natives.cc index 63d1e1689eca..c63d4f1883d3 100644 --- a/runtime/vm/bootstrap_natives.cc +++ b/runtime/vm/bootstrap_natives.cc @@ -127,6 +127,7 @@ void Bootstrap::SetupNativeResolver() { ASSERT(!library.IsNull()); library.set_native_entry_resolver(resolver); library.set_native_entry_symbol_resolver(symbol_resolver); + library.set_ffi_native_resolver(ffi_native_resolver); library = Library::InternalLibrary(); ASSERT(!library.IsNull()); diff --git a/runtime/vm/bootstrap_natives.h b/runtime/vm/bootstrap_natives.h index 433e7d444646..cf772df9cf87 100644 --- a/runtime/vm/bootstrap_natives.h +++ b/runtime/vm/bootstrap_natives.h @@ -466,7 +466,9 @@ namespace dart { V(VariableMirror_type, 2) #define BOOTSTRAP_FFI_NATIVE_LIST(V) \ - V(FinalizerEntry_SetExternalSize, void, (Dart_Handle, intptr_t)) + V(FinalizerEntry_SetExternalSize, void, (Dart_Handle, intptr_t)) \ + V(Pointer_asTypedListFinalizerAllocateData, void*, ()) \ + V(Pointer_asTypedListFinalizerCallbackPointer, void*, ()) class BootstrapNatives : public AllStatic { public: @@ -474,7 +476,7 @@ class BootstrapNatives : public AllStatic { int argument_count, bool* auto_setup_scope); - // For use with @FfiNative. + // For use with @Native. static void* LookupFfiNative(const char* name, uintptr_t argument_count); static const uint8_t* Symbol(Dart_NativeFunction nf); diff --git a/runtime/vm/kernel_loader.cc b/runtime/vm/kernel_loader.cc index 6ea615220068..4a2d1df1a635 100644 --- a/runtime/vm/kernel_loader.cc +++ b/runtime/vm/kernel_loader.cc @@ -1207,7 +1207,8 @@ void KernelLoader::LoadLibraryImportsAndExports(Library* library, if (!Api::IsFfiEnabled() && target_library.url() == Symbols::DartFfi().ptr() && library->url() != Symbols::DartCore().ptr() && - library->url() != Symbols::DartInternal().ptr()) { + library->url() != Symbols::DartInternal().ptr() && + library->url() != Symbols::DartFfi().ptr()) { H.ReportError( "import of dart:ffi is not supported in the current Dart runtime"); } diff --git a/sdk/lib/_internal/vm/lib/ffi_native_finalizer_patch.dart b/sdk/lib/_internal/vm/lib/ffi_native_finalizer_patch.dart index d2c387c0d994..e9483d5ecfab 100644 --- a/sdk/lib/_internal/vm/lib/ffi_native_finalizer_patch.dart +++ b/sdk/lib/_internal/vm/lib/ffi_native_finalizer_patch.dart @@ -5,6 +5,7 @@ // All imports must be in all FFI patch files to not depend on the order // the patches are applied. import 'dart:_internal'; +import 'dart:ffi'; import 'dart:isolate'; import 'dart:typed_data'; @@ -38,7 +39,7 @@ final class _NativeFinalizer extends FinalizerBase implements NativeFinalizer { } void attach( - Finalizable value, + Object value, Pointer token, { Object? detach, int? externalSize, @@ -102,3 +103,43 @@ final class _NativeFinalizer extends FinalizerBase implements NativeFinalizer { finalizer._removeEntries(); } } + +@Native Function()>( + symbol: 'Pointer_asTypedListFinalizerCallbackPointer') +external Pointer + _asTypedListFinalizerCallbackPointer(); + +final Pointer _asTypedListFinalizerCallback = + _asTypedListFinalizerCallbackPointer(); + +final _asTypedListFinalizer = _NativeFinalizer(_asTypedListFinalizerCallback); + +final class _AsTypedListFinalizerData extends Struct { + external Pointer callback; + external Pointer token; +} + +@patch +void _attachAsTypedListFinalizer( + Pointer callback, + Object typedList, + Pointer pointer, + int? externalSize, +) { + final data = _allocateData(); + data.ref.callback = callback; + data.ref.token = pointer.cast(); + _asTypedListFinalizer.attach( + typedList, + data.cast(), + externalSize: externalSize, + ); +} + +// Ensure we use the `malloc` that corresponds to the `free` used inside +// `_asTypedListFinalizerCallback` in the VM. +@Native Function()>( + symbol: 'Pointer_asTypedListFinalizerAllocateData', + isLeaf: true, +) +external Pointer<_AsTypedListFinalizerData> _allocateData(); diff --git a/sdk/lib/_internal/vm/lib/ffi_patch.dart b/sdk/lib/_internal/vm/lib/ffi_patch.dart index f06582e95536..5f362b132de9 100644 --- a/sdk/lib/_internal/vm/lib/ffi_patch.dart +++ b/sdk/lib/_internal/vm/lib/ffi_patch.dart @@ -451,12 +451,20 @@ extension Int8Pointer on Pointer { Pointer elementAt(int index) => Pointer.fromAddress(address + index); @patch - Int8List asTypedList(int length) { + Int8List asTypedList( + int length, { + Pointer? finalizer, + Pointer? token, + }) { ArgumentError.checkNotNull(this, "Pointer"); ArgumentError.checkNotNull(length, "length"); _checkExternalTypedDataLength(length, 1); _checkPointerAlignment(address, 1); - return _asExternalTypedDataInt8(this, length); + final result = _asExternalTypedDataInt8(this, length); + if (finalizer != null) { + _attachAsTypedListFinalizer(finalizer, result, token ?? this, length); + } + return result; } } @@ -478,12 +486,20 @@ extension Int16Pointer on Pointer { Pointer.fromAddress(address + 2 * index); @patch - Int16List asTypedList(int length) { + Int16List asTypedList( + int length, { + Pointer? finalizer, + Pointer? token, + }) { ArgumentError.checkNotNull(this, "Pointer"); ArgumentError.checkNotNull(length, "length"); _checkExternalTypedDataLength(length, 2); _checkPointerAlignment(address, 2); - return _asExternalTypedDataInt16(this, length); + final result = _asExternalTypedDataInt16(this, length); + if (finalizer != null) { + _attachAsTypedListFinalizer(finalizer, result, token ?? this, 2 * length); + } + return result; } } @@ -505,12 +521,20 @@ extension Int32Pointer on Pointer { Pointer.fromAddress(address + 4 * index); @patch - Int32List asTypedList(int length) { + Int32List asTypedList( + int length, { + Pointer? finalizer, + Pointer? token, + }) { ArgumentError.checkNotNull(this, "Pointer"); ArgumentError.checkNotNull(length, "length"); _checkExternalTypedDataLength(length, 4); _checkPointerAlignment(address, 4); - return _asExternalTypedDataInt32(this, length); + final result = _asExternalTypedDataInt32(this, length); + if (finalizer != null) { + _attachAsTypedListFinalizer(finalizer, result, token ?? this, 4 * length); + } + return result; } } @@ -532,12 +556,20 @@ extension Int64Pointer on Pointer { Pointer.fromAddress(address + 8 * index); @patch - Int64List asTypedList(int length) { + Int64List asTypedList( + int length, { + Pointer? finalizer, + Pointer? token, + }) { ArgumentError.checkNotNull(this, "Pointer"); ArgumentError.checkNotNull(length, "length"); _checkExternalTypedDataLength(length, 8); _checkPointerAlignment(address, 8); - return _asExternalTypedDataInt64(this, length); + final result = _asExternalTypedDataInt64(this, length); + if (finalizer != null) { + _attachAsTypedListFinalizer(finalizer, result, token ?? this, 8 * length); + } + return result; } } @@ -558,12 +590,20 @@ extension Uint8Pointer on Pointer { Pointer elementAt(int index) => Pointer.fromAddress(address + index); @patch - Uint8List asTypedList(int length) { + Uint8List asTypedList( + int length, { + Pointer? finalizer, + Pointer? token, + }) { ArgumentError.checkNotNull(this, "Pointer"); ArgumentError.checkNotNull(length, "length"); _checkExternalTypedDataLength(length, 1); _checkPointerAlignment(address, 1); - return _asExternalTypedDataUint8(this, length); + final result = _asExternalTypedDataUint8(this, length); + if (finalizer != null) { + _attachAsTypedListFinalizer(finalizer, result, token ?? this, length); + } + return result; } } @@ -585,12 +625,20 @@ extension Uint16Pointer on Pointer { Pointer.fromAddress(address + 2 * index); @patch - Uint16List asTypedList(int length) { + Uint16List asTypedList( + int length, { + Pointer? finalizer, + Pointer? token, + }) { ArgumentError.checkNotNull(this, "Pointer"); ArgumentError.checkNotNull(length, "length"); _checkExternalTypedDataLength(length, 2); _checkPointerAlignment(address, 2); - return _asExternalTypedDataUint16(this, length); + final result = _asExternalTypedDataUint16(this, length); + if (finalizer != null) { + _attachAsTypedListFinalizer(finalizer, result, token ?? this, 2 * length); + } + return result; } } @@ -612,12 +660,20 @@ extension Uint32Pointer on Pointer { Pointer.fromAddress(address + 4 * index); @patch - Uint32List asTypedList(int length) { + Uint32List asTypedList( + int length, { + Pointer? finalizer, + Pointer? token, + }) { ArgumentError.checkNotNull(this, "Pointer"); ArgumentError.checkNotNull(length, "length"); _checkExternalTypedDataLength(length, 4); _checkPointerAlignment(address, 4); - return _asExternalTypedDataUint32(this, length); + final result = _asExternalTypedDataUint32(this, length); + if (finalizer != null) { + _attachAsTypedListFinalizer(finalizer, result, token ?? this, 4 * length); + } + return result; } } @@ -639,12 +695,20 @@ extension Uint64Pointer on Pointer { Pointer.fromAddress(address + 8 * index); @patch - Uint64List asTypedList(int length) { + Uint64List asTypedList( + int length, { + Pointer? finalizer, + Pointer? token, + }) { ArgumentError.checkNotNull(this, "Pointer"); ArgumentError.checkNotNull(length, "length"); _checkExternalTypedDataLength(length, 8); _checkPointerAlignment(address, 8); - return _asExternalTypedDataUint64(this, length); + final result = _asExternalTypedDataUint64(this, length); + if (finalizer != null) { + _attachAsTypedListFinalizer(finalizer, result, token ?? this, 8 * length); + } + return result; } } @@ -666,12 +730,20 @@ extension FloatPointer on Pointer { Pointer.fromAddress(address + 4 * index); @patch - Float32List asTypedList(int length) { + Float32List asTypedList( + int length, { + Pointer? finalizer, + Pointer? token, + }) { ArgumentError.checkNotNull(this, "Pointer"); ArgumentError.checkNotNull(length, "length"); _checkExternalTypedDataLength(length, 4); _checkPointerAlignment(address, 4); - return _asExternalTypedDataFloat(this, length); + final result = _asExternalTypedDataFloat(this, length); + if (finalizer != null) { + _attachAsTypedListFinalizer(finalizer, result, token ?? this, 4 * length); + } + return result; } } @@ -693,12 +765,20 @@ extension DoublePointer on Pointer { Pointer.fromAddress(address + 8 * index); @patch - Float64List asTypedList(int length) { + Float64List asTypedList( + int length, { + Pointer? finalizer, + Pointer? token, + }) { ArgumentError.checkNotNull(this, "Pointer"); ArgumentError.checkNotNull(length, "length"); _checkExternalTypedDataLength(length, 8); _checkPointerAlignment(address, 8); - return _asExternalTypedDataDouble(this, length); + final result = _asExternalTypedDataDouble(this, length); + if (finalizer != null) { + _attachAsTypedListFinalizer(finalizer, result, token ?? this, 8 * length); + } + return result; } } diff --git a/sdk/lib/_internal/vm/lib/internal_patch.dart b/sdk/lib/_internal/vm/lib/internal_patch.dart index 301287b079b7..7b03d9fda232 100644 --- a/sdk/lib/_internal/vm/lib/internal_patch.dart +++ b/sdk/lib/_internal/vm/lib/internal_patch.dart @@ -9,7 +9,7 @@ import "dart:async" show Timer; import "dart:core" hide Symbol; -import "dart:ffi" show Pointer, Struct, Union, IntPtr, Handle, Void, FfiNative; +import "dart:ffi" show Pointer, Struct, Union, IntPtr, Handle, Void, Native; import "dart:isolate" show SendPort; import "dart:typed_data" show Int32List, Uint8List; @@ -425,7 +425,8 @@ class FinalizerEntry { external int get externalSize; /// Update the external size. - @FfiNative('FinalizerEntry_SetExternalSize') + @Native( + symbol: 'FinalizerEntry_SetExternalSize') external void setExternalSize(int externalSize); } diff --git a/sdk/lib/ffi/ffi.dart b/sdk/lib/ffi/ffi.dart index 2ddc8caab445..5041e999c3b3 100644 --- a/sdk/lib/ffi/ffi.dart +++ b/sdk/lib/ffi/ffi.dart @@ -193,7 +193,15 @@ extension Int8Pointer on Pointer { /// /// The user has to ensure the memory range is accessible while using the /// returned list. - external Int8List asTypedList(int length); + /// + /// If provided, [finalizer] will be run on the pointer once the typed list + /// is GCed. If provided, [token] will be passed to [finalizer], otherwise + /// the this pointer itself will be passed. + external Int8List asTypedList( + int length, { + @Since('3.1') Pointer? finalizer, + @Since('3.1') Pointer? token, + }); } /// Extension on [Pointer] specialized for the type argument [Int16]. @@ -235,8 +243,16 @@ extension Int16Pointer on Pointer { /// The user has to ensure the memory range is accessible while using the /// returned list. /// + /// If provided, [finalizer] will be run on the pointer once the typed list + /// is GCed. If provided, [token] will be passed to [finalizer], otherwise + /// the this pointer itself will be passed. + /// /// The [address] must be 2-byte aligned. - external Int16List asTypedList(int length); + external Int16List asTypedList( + int length, { + @Since('3.1') Pointer? finalizer, + @Since('3.1') Pointer? token, + }); } /// Extension on [Pointer] specialized for the type argument [Int32]. @@ -278,8 +294,16 @@ extension Int32Pointer on Pointer { /// The user has to ensure the memory range is accessible while using the /// returned list. /// + /// If provided, [finalizer] will be run on the pointer once the typed list + /// is GCed. If provided, [token] will be passed to [finalizer], otherwise + /// the this pointer itself will be passed. + /// /// The [address] must be 4-byte aligned. - external Int32List asTypedList(int length); + external Int32List asTypedList( + int length, { + @Since('3.1') Pointer? finalizer, + @Since('3.1') Pointer? token, + }); } /// Extension on [Pointer] specialized for the type argument [Int64]. @@ -312,8 +336,16 @@ extension Int64Pointer on Pointer { /// The user has to ensure the memory range is accessible while using the /// returned list. /// + /// If provided, [finalizer] will be run on the pointer once the typed list + /// is GCed. If provided, [token] will be passed to [finalizer], otherwise + /// the this pointer itself will be passed. + /// /// The [address] must be 8-byte aligned. - external Int64List asTypedList(int length); + external Int64List asTypedList( + int length, { + @Since('3.1') Pointer? finalizer, + @Since('3.1') Pointer? token, + }); } /// Extension on [Pointer] specialized for the type argument [Uint8]. @@ -348,7 +380,15 @@ extension Uint8Pointer on Pointer { /// /// The user has to ensure the memory range is accessible while using the /// returned list. - external Uint8List asTypedList(int length); + /// + /// If provided, [finalizer] will be run on the pointer once the typed list + /// is GCed. If provided, [token] will be passed to [finalizer], otherwise + /// the this pointer itself will be passed. + external Uint8List asTypedList( + int length, { + @Since('3.1') Pointer? finalizer, + @Since('3.1') Pointer? token, + }); } /// Extension on [Pointer] specialized for the type argument [Uint16]. @@ -390,8 +430,16 @@ extension Uint16Pointer on Pointer { /// The user has to ensure the memory range is accessible while using the /// returned list. /// + /// If provided, [finalizer] will be run on the pointer once the typed list + /// is GCed. If provided, [token] will be passed to [finalizer], otherwise + /// the this pointer itself will be passed. + /// /// The [address] must be 2-byte aligned. - external Uint16List asTypedList(int length); + external Uint16List asTypedList( + int length, { + @Since('3.1') Pointer? finalizer, + @Since('3.1') Pointer? token, + }); } /// Extension on [Pointer] specialized for the type argument [Uint32]. @@ -433,8 +481,16 @@ extension Uint32Pointer on Pointer { /// The user has to ensure the memory range is accessible while using the /// returned list. /// + /// If provided, [finalizer] will be run on the pointer once the typed list + /// is GCed. If provided, [token] will be passed to [finalizer], otherwise + /// the this pointer itself will be passed. + /// /// The [address] must be 4-byte aligned. - external Uint32List asTypedList(int length); + external Uint32List asTypedList( + int length, { + @Since('3.1') Pointer? finalizer, + @Since('3.1') Pointer? token, + }); } /// Extension on [Pointer] specialized for the type argument [Uint64]. @@ -467,8 +523,16 @@ extension Uint64Pointer on Pointer { /// The user has to ensure the memory range is accessible while using the /// returned list. /// + /// If provided, [finalizer] will be run on the pointer once the typed list + /// is GCed. If provided, [token] will be passed to [finalizer], otherwise + /// the this pointer itself will be passed. + /// /// The [address] must be 8-byte aligned. - external Uint64List asTypedList(int length); + external Uint64List asTypedList( + int length, { + @Since('3.1') Pointer? finalizer, + @Since('3.1') Pointer? token, + }); } /// Extension on [Pointer] specialized for the type argument [Float]. @@ -510,8 +574,16 @@ extension FloatPointer on Pointer { /// The user has to ensure the memory range is accessible while using the /// returned list. /// + /// If provided, [finalizer] will be run on the pointer once the typed list + /// is GCed. If provided, [token] will be passed to [finalizer], otherwise + /// the this pointer itself will be passed. + /// /// The [address] must be 4-byte aligned. - external Float32List asTypedList(int length); + external Float32List asTypedList( + int length, { + @Since('3.1') Pointer? finalizer, + @Since('3.1') Pointer? token, + }); } /// Extension on [Pointer] specialized for the type argument [Double]. @@ -544,8 +616,16 @@ extension DoublePointer on Pointer { /// The user has to ensure the memory range is accessible while using the /// returned list. /// + /// If provided, [finalizer] will be run on the pointer once the typed list + /// is GCed. If provided, [token] will be passed to [finalizer], otherwise + /// the this pointer itself will be passed. + /// /// The [address] must be 8-byte aligned. - external Float64List asTypedList(int length); + external Float64List asTypedList( + int length, { + @Since('3.1') Pointer? finalizer, + @Since('3.1') Pointer? token, + }); } /// Extension on [Pointer] specialized for the type argument [Bool]. diff --git a/sdk/lib/ffi/native_finalizer.dart b/sdk/lib/ffi/native_finalizer.dart index bba05b1ef610..d1bd464fffd8 100644 --- a/sdk/lib/ffi/native_finalizer.dart +++ b/sdk/lib/ffi/native_finalizer.dart @@ -387,3 +387,11 @@ abstract final class NativeFinalizer { /// object become inaccessible. void detach(Object detach); } + +// To make dart2wasm compile without patch file. +external void _attachAsTypedListFinalizer( + Pointer callback, + Object typedList, + Pointer pointer, + int? externalSize, +); diff --git a/tests/ffi/external_typed_data_finalizer_test.dart b/tests/ffi/external_typed_data_finalizer_test.dart new file mode 100644 index 000000000000..1575fc938263 --- /dev/null +++ b/tests/ffi/external_typed_data_finalizer_test.dart @@ -0,0 +1,62 @@ +// Copyright (c) 2023, 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. + +// SharedObjects=ffi_test_functions + +import 'dart:ffi'; + +import 'dylib_utils.dart'; + +main() { + // Force dlopen so @Native lookups in DynamicLibrary.process() succeed. + dlopenGlobalPlatformSpecific('ffi_test_functions'); + + testAsTypedList(); + testRefcounted(); +} + +void testAsTypedList() { + const length = 10; + final ptr = calloc(length, sizeOf()).cast(); + final typedList = ptr.asTypedList(length, finalizer: freePointer); + print(typedList); +} + +@Native Function(IntPtr num, IntPtr size)>(isLeaf: true) +external Pointer calloc(int num, int size); + +final freePointer = DynamicLibrary.process() + .lookup)>>('free'); + +void testRefcounted() { + final peer = allocateRefcountedResource(); + final resource = peer.ref.resource; + final typedList1 = resource.asTypedList(128, + finalizer: decreaseRefcountPointer.cast(), token: peer.cast()); + increaseRefcount(peer); + print(typedList1); + final typedList2 = resource.asTypedList(128, + finalizer: decreaseRefcountPointer.cast(), token: peer.cast()); + increaseRefcount(peer); + print(typedList2); +} + +@Native Function()>( + symbol: 'AllocateRefcountedResource', isLeaf: true) +external Pointer allocateRefcountedResource(); + +@Native)>( + symbol: 'IncreaseRefcount', isLeaf: true) +external void increaseRefcount(Pointer peer); + +final decreaseRefcountPointer = DynamicLibrary.process() + .lookup)>>( + 'DecreaseRefcount'); + +final class RefCountedResource extends Struct { + external Pointer resource; + + @IntPtr() + external int refcount; +} diff --git a/tests/lib/mirrors/invocation_fuzz_test.dart b/tests/lib/mirrors/invocation_fuzz_test.dart index c51e1fde8b8e..c060639a57d9 100644 --- a/tests/lib/mirrors/invocation_fuzz_test.dart +++ b/tests/lib/mirrors/invocation_fuzz_test.dart @@ -42,6 +42,9 @@ var denylist = [ // Don't try to invoke FFI Natives on simulator. // TODO(http://dartbug.com/48365): Support FFI in simulators. 'dart._internal.FinalizerEntry.setExternalSize', + + // Don't instantiate structs with bogus memory. + 'dart.ffi._AsTypedListFinalizerData', ]; bool isDenylisted(Symbol qualifiedSymbol) { diff --git a/tests/lib_2/mirrors/invocation_fuzz_test.dart b/tests/lib_2/mirrors/invocation_fuzz_test.dart index 038ad39af270..7b1ae886e73d 100644 --- a/tests/lib_2/mirrors/invocation_fuzz_test.dart +++ b/tests/lib_2/mirrors/invocation_fuzz_test.dart @@ -44,6 +44,9 @@ var denylist = [ // Don't try to invoke FFI Natives on simulator. // TODO(http://dartbug.com/48365): Support FFI in simulators. 'dart._internal.FinalizerEntry.setExternalSize', + + // Don't instantiate structs with bogus memory. + 'dart.ffi._AsTypedListFinalizerData', ]; bool isDenylisted(Symbol qualifiedSymbol) {