Skip to content

Ensure that required symbols are available to FFI even when the final binary is linked with -dead_strip #1748

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Nov 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions pkgs/ffigen/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
6 changes: 5 additions & 1 deletion pkgs/ffigen/example/swift/third_party/swift_api.h
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -40,6 +40,8 @@
#include <string.h>
#endif
#if defined(__cplusplus)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wnon-modular-include-in-framework-module"
#if defined(__arm64e__) && __has_include(<ptrauth.h>)
# include <ptrauth.h>
#else
Expand All @@ -53,6 +55,7 @@
# endif
#pragma clang diagnostic pop
#endif
#pragma clang diagnostic pop
#endif

#if !defined(SWIFT_TYPEDEFS)
Expand Down Expand Up @@ -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")
Expand Down
1 change: 1 addition & 0 deletions pkgs/ffigen/lib/src/code_generator/objc_block.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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')};
Expand Down
2 changes: 1 addition & 1 deletion pkgs/ffigen/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
4 changes: 3 additions & 1 deletion pkgs/objective_c/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down
12 changes: 12 additions & 0 deletions pkgs/objective_c/src/ffi.h
Original file line number Diff line number Diff line change
@@ -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_
18 changes: 10 additions & 8 deletions pkgs/objective_c/src/objective_c.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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_
4 changes: 3 additions & 1 deletion pkgs/objective_c/src/objective_c.m
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
#import <Foundation/NSThread.h>
#import <dispatch/dispatch.h>

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
Expand Down
5 changes: 5 additions & 0 deletions pkgs/objective_c/src/objective_c_bindings_generated.m
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand Down
27 changes: 27 additions & 0 deletions pkgs/objective_c/test/main.c
Original file line number Diff line number Diff line change
@@ -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 <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>

#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;
}
11 changes: 11 additions & 0 deletions pkgs/objective_c/test/setup.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -74,6 +75,9 @@ String _buildObject(String input, List<String> flags) {
void _linkLib(List<String> inputs, String output) =>
_runClang(['-shared', '-undefined', 'dynamic_lookup', ...inputs], output);

void _linkMain(List<String> inputs, String output) =>
_runClang(['-dead_strip', '-fobjc-arc', ...inputs], output);

void main(List<String> arguments) {
final parser = ArgParser();
parser.addFlag('main-thread-dispatcher');
Expand All @@ -96,4 +100,11 @@ void main(List<String> 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}');
}
}
Loading