diff --git a/CHANGELOG.md b/CHANGELOG.md index 13329a7d072..069fea5e1d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,11 @@ daylight saving changes that are not precisely one hour. (No change on the Web which uses the JavaScript `Date` object.) +#### `dart:ffi` + +* Adds the `DynamicLibrary.providesSymbol` function to check whether a symbol + is available in a dynamic library. + #### `dart:io` * BREAKING CHANGE (for pre-migrated null safe code): diff --git a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.strong.expect b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.strong.expect index b92f5afd4ce..385e300b242 100644 --- a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.strong.expect +++ b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.strong.expect @@ -25,4 +25,4 @@ constants { Constructor coverage from constants: org-dartlang-testcase:///ffi_struct_inline_array.dart: -- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:135:9) +- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:136:9) diff --git a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.strong.transformed.expect b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.strong.transformed.expect index d480c5f25df..157a41bf9d7 100644 --- a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.strong.transformed.expect +++ b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.strong.transformed.expect @@ -52,4 +52,4 @@ constants { Constructor coverage from constants: org-dartlang-testcase:///ffi_struct_inline_array.dart: -- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:135:9) +- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:136:9) diff --git a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.weak.expect b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.weak.expect index 792f6e0443d..8e37bb27c00 100644 --- a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.weak.expect +++ b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.weak.expect @@ -25,4 +25,4 @@ constants { Constructor coverage from constants: org-dartlang-testcase:///ffi_struct_inline_array.dart: -- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:135:9) +- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:136:9) diff --git a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.weak.transformed.expect b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.weak.transformed.expect index 10975389ad0..dedc171be06 100644 --- a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.weak.transformed.expect +++ b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.weak.transformed.expect @@ -52,4 +52,4 @@ constants { Constructor coverage from constants: org-dartlang-testcase:///ffi_struct_inline_array.dart: -- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:135:9) +- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:136:9) diff --git a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.strong.expect b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.strong.expect index 7c27f0b8ac2..0275472300c 100644 --- a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.strong.expect +++ b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.strong.expect @@ -33,4 +33,4 @@ constants { Constructor coverage from constants: org-dartlang-testcase:///ffi_struct_inline_array_multi_dimensional.dart: -- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:135:9) +- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:136:9) diff --git a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.strong.transformed.expect b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.strong.transformed.expect index 6978f30f29a..bb7ab61aa68 100644 --- a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.strong.transformed.expect +++ b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.strong.transformed.expect @@ -84,4 +84,4 @@ Extra constant evaluation: evaluated: 110, effectively constant: 2 Constructor coverage from constants: org-dartlang-testcase:///ffi_struct_inline_array_multi_dimensional.dart: -- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:135:9) +- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:136:9) diff --git a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.weak.expect b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.weak.expect index 7831122bbc3..8045c2fb6b0 100644 --- a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.weak.expect +++ b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.weak.expect @@ -33,4 +33,4 @@ constants { Constructor coverage from constants: org-dartlang-testcase:///ffi_struct_inline_array_multi_dimensional.dart: -- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:135:9) +- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:136:9) diff --git a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.weak.transformed.expect b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.weak.transformed.expect index 3cdfa2e96fa..1ba57181b10 100644 --- a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.weak.transformed.expect +++ b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.weak.transformed.expect @@ -84,4 +84,4 @@ Extra constant evaluation: evaluated: 110, effectively constant: 2 Constructor coverage from constants: org-dartlang-testcase:///ffi_struct_inline_array_multi_dimensional.dart: -- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:135:9) +- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:136:9) diff --git a/runtime/lib/ffi_dynamic_library.cc b/runtime/lib/ffi_dynamic_library.cc index 3935a5479ae..eb8e74b5c18 100644 --- a/runtime/lib/ffi_dynamic_library.cc +++ b/runtime/lib/ffi_dynamic_library.cc @@ -99,8 +99,8 @@ static void* ResolveSymbol(void* handle, const char* symbol) { defined(HOST_OS_ANDROID) || defined(HOST_OS_FUCHSIA) dlerror(); // Clear any errors. void* pointer = dlsym(handle, symbol); - if (pointer == nullptr) { - char* error = dlerror(); + char* error = dlerror(); + if (error != nullptr) { const String& msg = String::Handle( String::NewFormatted("Failed to lookup symbol (%s)", error)); Exceptions::ThrowArgumentError(msg); @@ -147,4 +147,32 @@ DEFINE_NATIVE_ENTRY(Ffi_dl_getHandle, 0, 1) { return Integer::NewFromUint64(handle); } +static bool SymbolExists(void* handle, const char* symbol) { +#if defined(HOST_OS_LINUX) || defined(HOST_OS_MACOS) || \ + defined(HOST_OS_ANDROID) || defined(HOST_OS_FUCHSIA) + dlerror(); // Clear previous error, if any. + dlsym(handle, symbol); + // Checking whether dlsym returns a nullptr is not enough, as the value of + // the symbol could actually be NULL. Check the error condition instead. + return dlerror() == nullptr; +#elif defined(HOST_OS_WINDOWS) + return GetProcAddress(reinterpret_cast(handle), symbol) != nullptr; +#else + const Array& args = Array::Handle(Array::New(1)); + args.SetAt(0, + String::Handle(String::New( + "The dart:ffi library is not available on this platform."))); + Exceptions::ThrowByType(Exceptions::kUnsupported, args); +#endif +} + +DEFINE_NATIVE_ENTRY(Ffi_dl_providesSymbol, 0, 2) { + GET_NON_NULL_NATIVE_ARGUMENT(DynamicLibrary, dlib, arguments->NativeArgAt(0)); + GET_NON_NULL_NATIVE_ARGUMENT(String, argSymbolName, + arguments->NativeArgAt(1)); + + void* handle = dlib.GetHandle(); + return Bool::Get(SymbolExists(handle, argSymbolName.ToCString())).ptr(); +} + } // namespace dart diff --git a/runtime/vm/bootstrap_natives.h b/runtime/vm/bootstrap_natives.h index 5965209dca6..5237e608972 100644 --- a/runtime/vm/bootstrap_natives.h +++ b/runtime/vm/bootstrap_natives.h @@ -402,6 +402,7 @@ namespace dart { V(Ffi_dl_open, 1) \ V(Ffi_dl_lookup, 2) \ V(Ffi_dl_getHandle, 1) \ + V(Ffi_dl_providesSymbol, 2) \ V(Ffi_asExternalTypedData, 2) \ V(Ffi_dl_processLibrary, 0) \ V(Ffi_dl_executableLibrary, 0) \ diff --git a/sdk/lib/_internal/vm/lib/ffi_dynamic_library_patch.dart b/sdk/lib/_internal/vm/lib/ffi_dynamic_library_patch.dart index 5482890738b..2966149c6e7 100644 --- a/sdk/lib/_internal/vm/lib/ffi_dynamic_library_patch.dart +++ b/sdk/lib/_internal/vm/lib/ffi_dynamic_library_patch.dart @@ -30,6 +30,9 @@ class DynamicLibrary { Pointer lookup(String symbolName) native "Ffi_dl_lookup"; + @patch + bool providesSymbol(String symbolName) native "Ffi_dl_providesSymbol"; + // TODO(dacoharkes): Expose this to users, or extend Pointer? // https://github.com/dart-lang/sdk/issues/35881 int getHandle() native "Ffi_dl_getHandle"; diff --git a/sdk/lib/ffi/dynamic_library.dart b/sdk/lib/ffi/dynamic_library.dart index e39e53c5816..0a4ee2e56af 100644 --- a/sdk/lib/ffi/dynamic_library.dart +++ b/sdk/lib/ffi/dynamic_library.dart @@ -39,9 +39,15 @@ class DynamicLibrary { /// [dlsym(3)](https://man7.org/linux/man-pages/man3/dlsym.3.html) system /// call. /// - /// The symbol must be provided by the dynamic library. + /// The symbol must be provided by the dynamic library. To check whether + /// the library provides such symbol, use [hasSymbol]. external Pointer lookup(String symbolName); + /// Checks whether this dynamic library provides a symbol with the given + /// name. + @Since('2.14') + external bool providesSymbol(String symbolName); + /// Dynamic libraries are equal if they load the same library. external bool operator ==(Object other); diff --git a/sdk/lib/ffi/ffi.dart b/sdk/lib/ffi/ffi.dart index 26177e801f4..e82e5677b71 100644 --- a/sdk/lib/ffi/ffi.dart +++ b/sdk/lib/ffi/ffi.dart @@ -11,6 +11,7 @@ */ library dart.ffi; +import 'dart:_internal' show Since; import 'dart:isolate'; import 'dart:typed_data'; diff --git a/tests/ffi/has_symbol_test.dart b/tests/ffi/has_symbol_test.dart new file mode 100644 index 00000000000..858de9cdfa0 --- /dev/null +++ b/tests/ffi/has_symbol_test.dart @@ -0,0 +1,34 @@ +// 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. +// +// SharedObjects=ffi_test_functions + +import 'dart:ffi'; +import 'dart:io'; + +import 'package:expect/expect.dart'; + +import 'ffi_test_helpers.dart'; + +void main() { + testHasSymbol(); +} + +void testHasSymbol() { + Expect.isTrue(ffiTestFunctions.providesSymbol('ReturnMaxUint8')); + Expect.isFalse(ffiTestFunctions.providesSymbol('SymbolNotInLibrary')); + + if (Platform.isMacOS || + Platform.isIOS || + Platform.isAndroid || + Platform.isLinux) { + DynamicLibrary p = DynamicLibrary.process(); + Expect.isTrue(p.providesSymbol('dlopen')); + Expect.isFalse(p.providesSymbol('symbol_that_does_not_exist_in_process')); + } + + DynamicLibrary e = DynamicLibrary.executable(); + Expect.isTrue(e.providesSymbol('Dart_Invoke')); + Expect.isFalse(e.providesSymbol('symbol_that_does_not_exist_in_executable')); +} diff --git a/tests/ffi_2/has_symbol_test.dart b/tests/ffi_2/has_symbol_test.dart new file mode 100644 index 00000000000..11c1de10ef0 --- /dev/null +++ b/tests/ffi_2/has_symbol_test.dart @@ -0,0 +1,36 @@ +// 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. +// +// SharedObjects=ffi_test_functions + +// @dart=2.9 + +import 'dart:ffi'; +import 'dart:io'; + +import 'package:expect/expect.dart'; + +import 'ffi_test_helpers.dart'; + +void main() { + testHasSymbol(); +} + +void testHasSymbol() { + Expect.isTrue(ffiTestFunctions.providesSymbol('ReturnMaxUint8')); + Expect.isFalse(ffiTestFunctions.providesSymbol('SymbolNotInLibrary')); + + if (Platform.isMacOS || + Platform.isIOS || + Platform.isAndroid || + Platform.isLinux) { + DynamicLibrary p = DynamicLibrary.process(); + Expect.isTrue(p.providesSymbol('dlopen')); + Expect.isFalse(p.providesSymbol('symbol_that_does_not_exist_in_process')); + } + + DynamicLibrary e = DynamicLibrary.executable(); + Expect.isTrue(e.providesSymbol('Dart_Invoke')); + Expect.isFalse(e.providesSymbol('symbol_that_does_not_exist_in_executable')); +}