[ffi] Add DynamicLibrary.openFromAssetId

Adds a new API `DynamicLibrary.openFromAssetId` to `dart:ffi` that
allows opening dynamically loaded libraries using their native asset IDs
instead of physical file paths.

Closes https://github.com/dart-lang/sdk/issues/63295

TEST=./tools/test.py -n vm-aot-linux-debug-x64 'ffi/native_assets/asset_*'
Cq-Include-Trybots: dart/try:vm-aot-linux-debug-arm64-try,vm-aot-linux-debug-x64-try,vm-aot-linux-debug-x64c-try,vm-aot-mac-debug-arm64-try,vm-aot-mac-debug-x64-try,vm-aot-obfuscate-linux-release-x64-try,vm-aot-optimization-level-linux-release-x64-try,vm-aot-win-debug-arm64-try,vm-aot-win-debug-x64-try,vm-aot-win-debug-x64c-try,vm-asan-linux-release-arm64-try,vm-asan-linux-release-x64-try,vm-asan-mac-release-arm64-try,vm-asan-win-release-x64-try,vm-dyn-linux-debug-x64-try,vm-dyn-mac-debug-arm64-try,vm-ffi-dyn-mac-debug-simarm64_arm64-try,vm-msan-linux-release-arm64-try,vm-msan-linux-release-x64-try,vm-tsan-linux-release-arm64-try,vm-tsan-linux-release-x64-try,vm-tsan-mac-release-arm64-try,vm-ubsan-linux-release-arm64-try,vm-ubsan-linux-release-x64-try,vm-ubsan-mac-release-arm64-try,vm-ubsan-win-release-x64-try
R=vegorov@google.com
Change-Id: Ic617cb0906d1688d2d080dae7d1e08ee58b4c8d6

CoreLibraryReviewExempt: VM-only
Change-Id: Ic617cb0906d1688d2d080dae7d1e08ee58b4c8d6
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/507180
Auto-Submit: Shikhar Soni <shikharsoni@google.com>
Reviewed-by: Daco Harkes <dacoharkes@google.com>
Commit-Queue: Daco Harkes <dacoharkes@google.com>
This commit is contained in:
Shikhar Soni
2026-06-10 09:49:30 -07:00
committed by dart-scoped@luci-project-accounts.iam.gserviceaccount.com
parent 088614d11b
commit 93ca44b048
13 changed files with 183 additions and 43 deletions
@@ -30,13 +30,13 @@ void main(List<String> args, Object? message) async {
return await selfInvokingTest(
doOnOuterInvocation: selfInvokes,
doOnProcessInvocation: () async {
await runTests();
await testIsolateSpawn(runTests);
await runTests(args[1]);
await testIsolateSpawn(() => runTests(args[1]));
await testIsolateSpawnUri(spawnUri: Platform.script, arguments: args);
},
doOnSpawnUriInvocation: () async {
await runTests();
await testIsolateSpawn(runTests);
await runTests(args[1]);
await testIsolateSpawn(() => runTests(args[1]));
},
)(args, message);
}
@@ -50,22 +50,24 @@ Future<void> selfInvokes() async {
await invokeSelf(
selfSourceUri: selfSourceUri,
runtime: Runtime.jit,
arguments: [runTestsArg],
arguments: [runTestsArg, selfSourceUri.toString()],
nativeAssetsYaml: nativeAssetsYaml,
);
await invokeSelf(
selfSourceUri: selfSourceUri,
runtime: Runtime.aot,
arguments: [runTestsArg],
arguments: [runTestsArg, selfSourceUri.toString()],
nativeAssetsYaml: nativeAssetsYaml,
protobufAwareTreeshaking: true,
);
}
Future<void> runTests() async {
Future<void> runTests(String assetId) async {
testFfiTestfunctionsDll();
testOpenFfiTestFunctionsAsset(assetId);
testFfiTestFieldsDll();
testNonExistingFunction();
testOpenFromAssetIdNotFound();
}
@Native<Int32 Function(Int32, Int32)>()
@@ -29,13 +29,13 @@ main(List<String> args, Object? message) async {
return await selfInvokingTest(
doOnOuterInvocation: selfInvokes,
doOnProcessInvocation: () async {
await runTests();
await testIsolateSpawn(runTests);
await runTests(args[1]);
await testIsolateSpawn(() => runTests(args[1]));
await testIsolateSpawnUri(spawnUri: Platform.script, arguments: args);
},
doOnSpawnUriInvocation: () async {
await runTests();
await testIsolateSpawn(runTests);
await runTests(args[1]);
await testIsolateSpawn(() => runTests(args[1]));
},
)(args, message);
}
@@ -49,21 +49,23 @@ Future<void> selfInvokes() async {
await invokeSelf(
selfSourceUri: selfSourceUri,
runtime: Runtime.jit,
arguments: [runTestsArg],
arguments: [runTestsArg, selfSourceUri.toString()],
nativeAssetsYaml: nativeAssetsYaml,
protobufAwareTreeshaking: true,
);
await invokeSelf(
selfSourceUri: selfSourceUri,
runtime: Runtime.aot,
arguments: [runTestsArg],
arguments: [runTestsArg, selfSourceUri.toString()],
nativeAssetsYaml: nativeAssetsYaml,
);
}
Future<void> runTests() async {
Future<void> runTests(String assetId) async {
await testExecutable();
await testOpenExecutableAsset(assetId);
testNonExistingFunction();
testOpenFromAssetIdNotFound();
}
typedef _PostInteger = Bool Function(Int64 port, Int64 message);
@@ -63,7 +63,9 @@ Future<void> selfInvokes() async {
Future<void> runTests() async {
testFfiTestfunctionsDll();
testOpenFfiTestFunctionsAsset(assetName);
testFfiTestFieldsDll();
testOpenFromAssetIdNotFound();
}
@Native<Int32 Function(Int32, Int32)>()
@@ -28,13 +28,13 @@ main(List<String> args, Object? message) async {
return await selfInvokingTest(
doOnOuterInvocation: selfInvokes,
doOnProcessInvocation: () async {
await runTests();
await testIsolateSpawn(runTests);
await runTests(args[1]);
await testIsolateSpawn(() => runTests(args[1]));
await testIsolateSpawnUri(spawnUri: Platform.script, arguments: args);
},
doOnSpawnUriInvocation: () async {
await runTests();
await testIsolateSpawn(runTests);
await runTests(args[1]);
await testIsolateSpawn(() => runTests(args[1]));
},
)(args, message);
}
@@ -52,28 +52,30 @@ Future<void> selfInvokes() async {
await invokeSelf(
selfSourceUri: selfSourceUri,
runtime: Runtime.jit,
arguments: [runTestsArg],
arguments: [runTestsArg, selfSourceUri.toString()],
nativeAssetsYaml: nativeAssetsYaml,
);
await invokeSelf(
selfSourceUri: selfSourceUri,
runtime: Runtime.appjit,
arguments: [runTestsArg],
arguments: [runTestsArg, selfSourceUri.toString()],
nativeAssetsYaml: nativeAssetsYaml,
protobufAwareTreeshaking: true,
);
await invokeSelf(
selfSourceUri: selfSourceUri,
runtime: Runtime.aot,
arguments: [runTestsArg],
arguments: [runTestsArg, selfSourceUri.toString()],
nativeAssetsYaml: nativeAssetsYaml,
);
}
Future<void> runTests() async {
Future<void> runTests(String assetId) async {
testProcessOrSystem();
testOpenProcessOrSystemAsset(assetId);
testProcessOrSystemViaAddressOf();
testNonExistingFunction();
testOpenFromAssetIdNotFound();
}
@Native<Pointer Function(IntPtr)>(symbol: 'malloc')
@@ -31,13 +31,13 @@ void main(List<String> args, Object? message) async {
return await selfInvokingTest(
doOnOuterInvocation: selfInvokes,
doOnProcessInvocation: () async {
await runTests();
await testIsolateSpawn(runTests);
await runTests(args[1]);
await testIsolateSpawn(() => runTests(args[1]));
await testIsolateSpawnUri(spawnUri: Platform.script, arguments: args);
},
doOnSpawnUriInvocation: () async {
await runTests();
await testIsolateSpawn(runTests);
await runTests(args[1]);
await testIsolateSpawn(() => runTests(args[1]));
},
)(args, message);
}
@@ -49,7 +49,7 @@ Future<void> selfInvokes() async {
runtime: Runtime.jit,
kernelCombine: KernelCombine.concatenation,
relativePath: RelativePath.same,
arguments: [runTestsArg],
arguments: [runTestsArg, selfSourceUri.toString()],
useSymlink: true,
protobufAwareTreeshaking: true,
);
@@ -57,7 +57,7 @@ Future<void> selfInvokes() async {
selfSourceUri: selfSourceUri,
runtime: Runtime.jit,
relativePath: RelativePath.down,
arguments: [runTestsArg],
arguments: [runTestsArg, selfSourceUri.toString()],
useSymlink: true,
protobufAwareTreeshaking: false,
);
@@ -69,7 +69,7 @@ Future<void> selfInvokes() async {
? AotCompile.assembly
: AotCompile.elf,
relativePath: RelativePath.up,
arguments: [runTestsArg],
arguments: [runTestsArg, selfSourceUri.toString()],
useSymlink: true,
protobufAwareTreeshaking: true,
);
@@ -140,9 +140,11 @@ Future<void> invokeSelf({
});
}
Future<void> runTests() async {
Future<void> runTests(String assetId) async {
testFfiTestfunctionsDll();
testOpenFfiTestFunctionsAsset(assetId);
testNonExistingFunction();
testOpenFromAssetIdNotFound();
testFfiTestFieldsDll();
}
@@ -28,13 +28,13 @@ main(List<String> args, Object? message) async {
return await selfInvokingTest(
doOnOuterInvocation: selfInvokes,
doOnProcessInvocation: () async {
await runTests();
await testIsolateSpawn(runTests);
await runTests(args[1]);
await testIsolateSpawn(() => runTests(args[1]));
await testIsolateSpawnUri(spawnUri: Platform.script, arguments: args);
},
doOnSpawnUriInvocation: () async {
await runTests();
await testIsolateSpawn(runTests);
await runTests(args[1]);
await testIsolateSpawn(() => runTests(args[1]));
},
)(args, message);
}
@@ -60,22 +60,24 @@ Future<void> selfInvokes() async {
await invokeSelf(
selfSourceUri: selfSourceUri,
runtime: Runtime.jit,
arguments: [runTestsArg],
arguments: [runTestsArg, selfSourceUri.toString()],
nativeAssetsYaml: nativeAssetsYaml,
);
await invokeSelf(
selfSourceUri: selfSourceUri,
runtime: Runtime.aot,
arguments: [runTestsArg],
arguments: [runTestsArg, selfSourceUri.toString()],
nativeAssetsYaml: nativeAssetsYaml,
protobufAwareTreeshaking: true,
);
}
Future<void> runTests() async {
Future<void> runTests(String assetId) async {
testProcessOrSystem();
testOpenProcessOrSystemAsset(assetId);
testProcessOrSystemViaAddressOf();
testNonExistingFunction();
testOpenFromAssetIdNotFound();
}
@Native<Pointer Function(IntPtr)>()
+66
View File
@@ -5,6 +5,7 @@
// This file should be standalone (ignoring packages) because it is copied
// with // OtherResources and used for compiling snapshots.
import 'dart:async';
import 'dart:convert';
import 'dart:ffi';
import 'dart:io';
@@ -608,3 +609,68 @@ void testNonExistingFunction() {
addressOfError.message,
);
}
void testOpenFfiTestFunctionsAsset(String assetId) {
final sumPlus42 = DynamicLibrary.openFromAssetId(assetId)
.lookupFunction<Int32 Function(Int32, Int32), int Function(int, int)>(
'SumPlus42',
);
Expect.equals(2 + 3 + 42, sumPlus42(2, 3));
}
void testOpenProcessOrSystemAsset(String assetId) {
final library = DynamicLibrary.openFromAssetId(assetId);
if (Platform.isWindows) {
final memAlloc = library
.lookupFunction<Pointer Function(Size), Pointer Function(int)>(
'CoTaskMemAlloc',
);
final memFree = library
.lookupFunction<Void Function(Pointer), void Function(Pointer)>(
'CoTaskMemFree',
);
final pointer = memAlloc(8);
Expect.notEquals(nullptr, pointer);
memFree(pointer);
} else {
final malloc = library
.lookupFunction<Pointer Function(IntPtr), Pointer Function(int)>(
'malloc',
);
final free = library
.lookupFunction<Void Function(Pointer), void Function(Pointer)>('free');
final pointer = malloc(8);
Expect.notEquals(nullptr, pointer);
free(pointer);
}
}
Future<void> testOpenExecutableAsset(String assetId) async {
final postInteger = DynamicLibrary.openFromAssetId(assetId)
.lookupFunction<Bool Function(Int64, Int64), bool Function(int, int)>(
'Dart_PostInteger',
);
const int message = 1337 * 42;
final completer = Completer();
final receivePort = ReceivePort()
..listen((receivedMessage) => completer.complete(receivedMessage));
final success = postInteger(receivePort.sendPort.nativePort, message);
Expect.isTrue(success);
final postedMessage = await completer.future;
Expect.equals(message, postedMessage);
receivePort.close();
}
void testOpenFromAssetIdNotFound() {
final argumentError = Expect.throws<ArgumentError>(() {
DynamicLibrary.openFromAssetId(doesNotExistName);
});
Expect.contains(doesNotExistName, argumentError.message);
Expect.contains('No asset with id', argumentError.message);
}