diff --git a/pkgs/ffigen/CHANGELOG.md b/pkgs/ffigen/CHANGELOG.md index 9ef02a34bb..fbb1224a8c 100644 --- a/pkgs/ffigen/CHANGELOG.md +++ b/pkgs/ffigen/CHANGELOG.md @@ -1,3 +1,8 @@ +## 16.0.1-wip + +- Ensure that required symbols are available to FFI even when the final binary + is linked with `-dead_strip`. + ## 16.0.0 - Ensure all protocols referenced in bindings are available at runtime. diff --git a/pkgs/ffigen/example/swift/third_party/swift_api.h b/pkgs/ffigen/example/swift/third_party/swift_api.h index 2c6185a0c4..41849f3214 100644 --- a/pkgs/ffigen/example/swift/third_party/swift_api.h +++ b/pkgs/ffigen/example/swift/third_party/swift_api.h @@ -1,4 +1,4 @@ -// Generated by Apple Swift version 5.9 (swiftlang-5.9.0.128.108 clang-1500.0.40.1) +// Generated by Apple Swift version 6.0.2 effective-5.10 (swiftlang-6.0.2.1.2 clang-1600.0.26.4) #ifndef SWIFT_MODULE_SWIFT_H #define SWIFT_MODULE_SWIFT_H #pragma clang diagnostic push @@ -40,6 +40,8 @@ #include #endif #if defined(__cplusplus) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnon-modular-include-in-framework-module" #if defined(__arm64e__) && __has_include() # include #else @@ -53,6 +55,7 @@ # endif #pragma clang diagnostic pop #endif +#pragma clang diagnostic pop #endif #if !defined(SWIFT_TYPEDEFS) @@ -287,6 +290,7 @@ typedef unsigned int swift_uint4 __attribute__((__ext_vector_type__(4))); #pragma clang diagnostic ignored "-Wunknown-pragmas" #pragma clang diagnostic ignored "-Wnullability" #pragma clang diagnostic ignored "-Wdollar-in-identifier-extension" +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" #if __has_attribute(external_source_symbol) # pragma push_macro("any") diff --git a/pkgs/ffigen/lib/src/code_generator/objc_block.dart b/pkgs/ffigen/lib/src/code_generator/objc_block.dart index f466c623d4..d22958ce56 100644 --- a/pkgs/ffigen/lib/src/code_generator/objc_block.dart +++ b/pkgs/ffigen/lib/src/code_generator/objc_block.dart @@ -322,6 +322,7 @@ ref.pointer.ref.invoke.cast<$natTrampFnType>().asFunction<$trampFuncFfiDartType> s.write(''' typedef $blockTypedef; +__attribute__((visibility("default"))) __attribute__((used)) $blockName $fnName($blockName block) NS_RETURNS_RETAINED { return ^void($argStr) { ${generateRetain('block')}; diff --git a/pkgs/ffigen/pubspec.yaml b/pkgs/ffigen/pubspec.yaml index d4150f9c9d..0ae4f98b9a 100644 --- a/pkgs/ffigen/pubspec.yaml +++ b/pkgs/ffigen/pubspec.yaml @@ -3,7 +3,7 @@ # BSD-style license that can be found in the LICENSE file. name: ffigen -version: 16.0.0 +version: 16.0.1-wip description: > Generator for FFI bindings, using LibClang to parse C, Objective-C, and Swift files. diff --git a/pkgs/objective_c/CHANGELOG.md b/pkgs/objective_c/CHANGELOG.md index 3607b54ccc..e3b4b38996 100644 --- a/pkgs/objective_c/CHANGELOG.md +++ b/pkgs/objective_c/CHANGELOG.md @@ -1,6 +1,8 @@ ## 4.0.1-wip -- Reduces the changes of duplicate symbols by adding a `DOBJC_` prefix. +- Reduces the chances of duplicate symbols by adding a `DOBJC_` prefix. +- Ensure that required symbols are available to FFI even when the final binary + is linked with `-dead_strip`. ## 4.0.0 diff --git a/pkgs/objective_c/src/ffi.h b/pkgs/objective_c/src/ffi.h new file mode 100644 index 0000000000..716965763d --- /dev/null +++ b/pkgs/objective_c/src/ffi.h @@ -0,0 +1,12 @@ +// Copyright (c) 2024, 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. + +#ifndef OBJECTIVE_C_SRC_FFI_H_ +#define OBJECTIVE_C_SRC_FFI_H_ + +// Ensure that an FFI function, that will appear unused by the linker, will +// not be removed. +#define FFI_EXPORT __attribute__((visibility("default"))) __attribute__((used)) + +#endif // OBJECTIVE_C_SRC_FFI_H_ diff --git a/pkgs/objective_c/src/objective_c.h b/pkgs/objective_c/src/objective_c.h index 8d64c9ba1c..c2fefcb104 100644 --- a/pkgs/objective_c/src/objective_c.h +++ b/pkgs/objective_c/src/objective_c.h @@ -5,28 +5,30 @@ #ifndef OBJECTIVE_C_SRC_OBJECTIVE_C_H_ #define OBJECTIVE_C_SRC_OBJECTIVE_C_H_ +#include "ffi.h" #include "include/dart_api_dl.h" #include "objective_c_runtime.h" // Dispose helper for ObjC blocks that wrap a Dart closure. -void DOBJC_disposeObjCBlockWithClosure(ObjCBlockImpl* block); +FFI_EXPORT void DOBJC_disposeObjCBlockWithClosure(ObjCBlockImpl *block); // Returns whether the block is valid and live. The pointer must point to // readable memory, or be null. May (rarely) return false positives. -bool DOBJC_isValidBlock(ObjCBlockImpl* block); +FFI_EXPORT bool DOBJC_isValidBlock(ObjCBlockImpl *block); // Returns a new Dart_FinalizableHandle that will clean up the object when the // Dart owner is garbage collected. -Dart_FinalizableHandle DOBJC_newFinalizableHandle( - Dart_Handle owner, ObjCObject *object); +FFI_EXPORT Dart_FinalizableHandle +DOBJC_newFinalizableHandle(Dart_Handle owner, ObjCObject *object); // Delete a finalizable handle. Doesn't run the finalization callback, so // doesn't clean up the assocated pointer. -void DOBJC_deleteFinalizableHandle(Dart_FinalizableHandle handle, Dart_Handle owner); +FFI_EXPORT void DOBJC_deleteFinalizableHandle(Dart_FinalizableHandle handle, + Dart_Handle owner); // Returns a newly allocated bool* (initialized to false) that will be deleted // by a Dart_FinalizableHandle when the owner is garbage collected. -bool* DOBJC_newFinalizableBool(Dart_Handle owner); +FFI_EXPORT bool *DOBJC_newFinalizableBool(Dart_Handle owner); // Runs fn(arg) on the main thread. If runOnMainThread is already running on the // main thread, fn(arg) is invoked synchronously. Otherwise it is dispatched to @@ -35,6 +37,6 @@ bool* DOBJC_newFinalizableBool(Dart_Handle owner); // This assumes that the main thread is executing its queue. If not, #define // NO_MAIN_THREAD_DISPATCH to disable this, and run fn(arg) synchronously. The // flutter runner does execute the main dispatch queue, but the Dart VM doesn't. -void DOBJC_runOnMainThread(void (*fn)(void*), void* arg); +FFI_EXPORT void DOBJC_runOnMainThread(void (*fn)(void *), void *arg); -#endif // OBJECTIVE_C_SRC_OBJECTIVE_C_H_ +#endif // OBJECTIVE_C_SRC_OBJECTIVE_C_H_ diff --git a/pkgs/objective_c/src/objective_c.m b/pkgs/objective_c/src/objective_c.m index b3256e09f8..ca3035c5a3 100644 --- a/pkgs/objective_c/src/objective_c.m +++ b/pkgs/objective_c/src/objective_c.m @@ -5,7 +5,9 @@ #import #import -void DOBJC_runOnMainThread(void (*fn)(void*), void* arg) { +#include "ffi.h" + +FFI_EXPORT void DOBJC_runOnMainThread(void (*fn)(void *), void *arg) { #ifdef NO_MAIN_THREAD_DISPATCH fn(arg); #else diff --git a/pkgs/objective_c/src/objective_c_bindings_generated.m b/pkgs/objective_c/src/objective_c_bindings_generated.m index 478c8ffd64..80ce85af1a 100644 --- a/pkgs/objective_c/src/objective_c_bindings_generated.m +++ b/pkgs/objective_c/src/objective_c_bindings_generated.m @@ -13,6 +13,7 @@ Protocol* _ObjectiveCBindings_NSStreamDelegate() { return @protocol(NSStreamDelegate); } typedef void (^_ListenerTrampoline)(id arg0, id arg1, id arg2); +__attribute__((visibility("default"))) __attribute__((used)) _ListenerTrampoline _ObjectiveCBindings_wrapListenerBlock_1j2nt86(_ListenerTrampoline block) NS_RETURNS_RETAINED { return ^void(id arg0, id arg1, id arg2) { objc_retainBlock(block); @@ -21,6 +22,7 @@ _ListenerTrampoline _ObjectiveCBindings_wrapListenerBlock_1j2nt86(_ListenerTramp } typedef void (^_ListenerTrampoline1)(void * arg0); +__attribute__((visibility("default"))) __attribute__((used)) _ListenerTrampoline1 _ObjectiveCBindings_wrapListenerBlock_ovsamd(_ListenerTrampoline1 block) NS_RETURNS_RETAINED { return ^void(void * arg0) { objc_retainBlock(block); @@ -29,6 +31,7 @@ _ListenerTrampoline1 _ObjectiveCBindings_wrapListenerBlock_ovsamd(_ListenerTramp } typedef void (^_ListenerTrampoline2)(void * arg0, id arg1); +__attribute__((visibility("default"))) __attribute__((used)) _ListenerTrampoline2 _ObjectiveCBindings_wrapListenerBlock_wjovn7(_ListenerTrampoline2 block) NS_RETURNS_RETAINED { return ^void(void * arg0, id arg1) { objc_retainBlock(block); @@ -37,6 +40,7 @@ _ListenerTrampoline2 _ObjectiveCBindings_wrapListenerBlock_wjovn7(_ListenerTramp } typedef void (^_ListenerTrampoline3)(void * arg0, id arg1, NSStreamEvent arg2); +__attribute__((visibility("default"))) __attribute__((used)) _ListenerTrampoline3 _ObjectiveCBindings_wrapListenerBlock_18d6mda(_ListenerTrampoline3 block) NS_RETURNS_RETAINED { return ^void(void * arg0, id arg1, NSStreamEvent arg2) { objc_retainBlock(block); @@ -45,6 +49,7 @@ _ListenerTrampoline3 _ObjectiveCBindings_wrapListenerBlock_18d6mda(_ListenerTram } typedef void (^_ListenerTrampoline4)(id arg0, id arg1); +__attribute__((visibility("default"))) __attribute__((used)) _ListenerTrampoline4 _ObjectiveCBindings_wrapListenerBlock_wjvic9(_ListenerTrampoline4 block) NS_RETURNS_RETAINED { return ^void(id arg0, id arg1) { objc_retainBlock(block); diff --git a/pkgs/objective_c/test/main.c b/pkgs/objective_c/test/main.c new file mode 100644 index 0000000000..88bd241be0 --- /dev/null +++ b/pkgs/objective_c/test/main.c @@ -0,0 +1,27 @@ +// Copyright (c) 2024, 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. + +// Verify that the required symbols were not stripped from the binary. + +#include +#include +#include + +#define ASSERT_SYMBOL(s) \ + if (dlsym(RTLD_DEFAULT, s) == 0) { \ + fprintf(stderr, "Missing symbol: %s\n", s); \ + exit(1); \ + } + +int main() { + ASSERT_SYMBOL("DOBJC_disposeObjCBlockWithClosure"); // objective_c.c + ASSERT_SYMBOL("DOBJC_runOnMainThread"); + + ASSERT_SYMBOL("DOBJC_disposeObjCBlockWithClosure"); // objective_c.c + ASSERT_SYMBOL("DOBJC_runOnMainThread"); // objective_c.m + ASSERT_SYMBOL("OBJC_CLASS_$_DOBJCDartProxy"); // proxy.m + // objective_c_bindings_generated.m + ASSERT_SYMBOL("_ObjectiveCBindings_wrapListenerBlock_ovsamd"); + return 0; +} diff --git a/pkgs/objective_c/test/setup.dart b/pkgs/objective_c/test/setup.dart index c9dcc83bb7..4c63b4d6fb 100644 --- a/pkgs/objective_c/test/setup.dart +++ b/pkgs/objective_c/test/setup.dart @@ -19,6 +19,7 @@ final cFiles = [ 'src/include/dart_api_dl.c', 'test/util.c', ].map(_resolve); +final cMain = _resolve('test/main.c'); final objCFiles = [ 'src/input_stream_adapter.m', 'src/objective_c.m', @@ -74,6 +75,9 @@ String _buildObject(String input, List flags) { void _linkLib(List inputs, String output) => _runClang(['-shared', '-undefined', 'dynamic_lookup', ...inputs], output); +void _linkMain(List inputs, String output) => + _runClang(['-dead_strip', '-fobjc-arc', ...inputs], output); + void main(List arguments) { final parser = ArgParser(); parser.addFlag('main-thread-dispatcher'); @@ -96,4 +100,11 @@ void main(List arguments) { lib.lookup('OBJC_CLASS_\$_DOBJCDartProxy'); // proxy.m // objective_c_bindings_generated.m lib.lookup('_ObjectiveCBindings_wrapListenerBlock_ovsamd'); + + // Sanity check that the executable can find FFI symbols. + _linkMain([...objFiles, cMain], '$cMain.exe'); + final result = Process.runSync('$cMain.exe', []); + if (result.exitCode != 0) { + throw Exception('Missing symbols from executable:\n${result.stderr}'); + } }