Skip to content

[objective_c] Link Foundation in the native support library - #3782

Merged
liamappelbe merged 3 commits into
dart-lang:mainfrom
Gorniv:objective_c-link-foundation
Nov 6, 2026
Merged

liamappelbe merged 3 commits into
dart-lang:mainfrom
Gorniv:objective_c-link-foundation

Conversation

@Gorniv

@Gorniv Gorniv commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Description

The ObjC bindings load classes through their OBJC_CLASS_$_Foo symbols, resolved via the objective_c.dylib handle, and fall back to objc_getClass when that lookup throws (#3344). The native support library is linked with -undefined dynamic_lookup and no frameworks, so it only depends on libSystem:

$ otool -L objective_c.framework/objective_c
	@rpath/objective_c.framework/objective_c
	/usr/lib/libSystem.B.dylib

As a result dlsym can never find a built-in Foundation class through that handle, and the first use of every Foundation class in package:objective_c (NSArray, NSString, ...) throws an ArgumentError that getClass catches before falling back:

ArgumentError (Invalid argument(s): Couldn't resolve native function 'OBJC_CLASS_$_NSArray' in 'package:objective_c/objective_c.dylib' : Failed to lookup symbol 'OBJC_CLASS_$_NSArray': dlsym(0x90466c40, OBJC_CLASS_$_NSArray): symbol not found.

The result is still correct, but a debugger that breaks on all exceptions stops at app startup on any Flutter app that uses path_provider (via path_provider_foundation), with no way to skip just this exception.

This PR links the support library against Foundation, so these symbols resolve through the dylib's dependencies, to the same class pointers objc_getClass returns. The dylib's own classes (DOBJC*) still resolve as before, and the fallback is kept for other frameworks.

Related Issues

Follow-up to #3344 (which fixed #3338). There is no separate issue for this; happy to file one if preferred.

Testing

  • Added a test in test/internal_test.dart that resolves OBJC_CLASS_$_NSArray through package:objective_c/objective_c.dylib and compares it with getClass('NSArray'). It fails on main with the ArgumentError above and passes with this change.
  • Ran the macOS jobs of objective_c.yaml and ffigen.yml locally (macOS 27, Xcode 27.1, Flutter 3.47.9 / Dart 3.13.7):
    • pkgs/objective_c: dart format --set-exit-if-changed, flutter analyze --fatal-infos, the snippet check and dart test (152 tests) pass.
    • example/command_line: dart run lib/main.dart works.
    • example/flutter_app: flutter build macos, flutter build ios --no-codesign and xcodebuild analyze (warnings as errors) succeed. otool -L on the bundled objective_c.framework now lists Foundation for both macOS slices and iOS.
    • pkgs/ffigen: dart test gives the same result with and without this change: 901 passed, 1 failed (verify_bindings_test for sdk_variable_test.dart, a bindings diff from the Xcode 27.1 SDK).

Note: with the Xcode 27.1 SDK, test/generate_code_test.dart regenerates objective_c_bindings_generated.dart with a few new deprecated 27.0.0 annotations. That is unrelated to this change and not included.

PR Checklist

  • I’ve reviewed the contributor guide and applied the relevant portions to this PR.
  • I've run dart tool/ci.dart --all locally and resolved all issues identified. (It doesn't cover pkgs/objective_c or pkgs/ffigen, so I also ran their workflow jobs, see Testing. Everything passes except 4 swift2objc integration tests, whose expected output differs with the local Xcode 27.1 Swift, and the swift2objc apitool missing-export check. This PR doesn't touch swift2objc, which doesn't depend on objective_c.)
  • All existing and new tests are passing. I added new tests to check the change I am making.
  • The PR is actually solving the issue.
  • I have updated CHANGELOG.md for the relevant packages.
  • I have updated the pubspec package version if necessary.

The generated @Native class lookups (eg OBJC_CLASS_$_NSArray) are resolved
through the objective_c.dylib handle. The dylib only linked libSystem, so every
built-in Foundation class lookup threw an ArgumentError that getClass caught
before falling back to objc_getClass. That stops debuggers that break on all
exceptions.
@google-cla

google-cla Bot commented Nov 5, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@Gorniv
Gorniv force-pushed the objective_c-link-foundation branch from 0398ce6 to e20e95b Compare November 5, 2026 18:37

@liamappelbe liamappelbe left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks pretty good. How does this affect the size of the support library?

Comment thread pkgs/objective_c/hook/build.dart Outdated
];
final mFlags = [...cFlags, ...objCFlags];
final linkFlags = cFlags;
// Link Foundation so that the generated `@Native` class lookups (eg

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unnecesary comment

@codecov

codecov Bot commented Nov 5, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 85.88%. Comparing base (6287745) to head (fb8c763).

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #3782      +/-   ##
==========================================
- Coverage   85.88%   85.88%   -0.01%     
==========================================
  Files         322      322              
  Lines       20855    20855              
==========================================
- Hits        17912    17911       -1     
- Misses       2943     2944       +1     
Flag Coverage Δ
ffigen 91.36% <ø> (ø)
objective_c 62.83% <ø> (-0.02%) ⬇️
swiftgen 80.41% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
code_assets 95.31% <ø> (ø)
data_assets 91.56% <ø> (ø)
ffi 100.00% <ø> (ø)
ffigen 91.45% <ø> (ø)
hooks 86.40% <ø> (ø)
hooks_runner 90.84% <ø> (ø)
jni 66.47% <ø> (ø)
jni_flutter ∅ <ø> (∅)
jni_util 25.00% <ø> (ø)
jnigen 82.85% <ø> (ø)
json_syntax_generator 42.01% <ø> (ø)
native_test_helpers 85.71% <ø> (ø)
native_toolchain_c 96.24% <ø> (ø)
objective_c 79.55% <ø> (-0.18%) ⬇️
pub_formats ∅ <ø> (∅)
record_use 81.48% <ø> (ø)
snippet_tool 66.23% <ø> (ø)
swift2objc 92.95% <ø> (ø)
swiftgen 80.41% <ø> (ø)
test_case_selector 97.28% <ø> (ø)
see 1 file with indirect coverage changes
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@Gorniv

Gorniv commented Nov 5, 2026

Copy link
Copy Markdown
Contributor Author

Thanks! Removed the comment.

The size doesn't grow; the dylib ends up 16–24 bytes smaller. I built it with the hook's flags (without the test utils), with and without -framework Foundation:

Target Before After
arm64-apple-darwin 242,880 242,856
x86_64-apple-darwin 255,712 255,696
arm64-apple-ios 258,104 258,088
arm64-apple-ios-simulator 243,008 242,984

The three extra LC_LOAD_DYLIB commands (Foundation, CoreFoundation, libobjc) fit into the existing padding at the start of __TEXT, so that segment stays the same size, and the bind info gets slightly shorter (1224 → 1208 bytes on macOS arm64) because the imports bind to those libraries instead of using flat lookups. Those libraries are already loaded in any process that uses this package, so nothing new gets loaded at runtime.

@liamappelbe
liamappelbe merged commit baad15a into dart-lang:main Nov 6, 2026
56 checks passed
copybara-service Bot pushed a commit to dart-lang/sdk that referenced this pull request Nov 12, 2026
Revisions updated by `dart tools/rev_sdk_deps.dart`.

core (https://github.com/dart-lang/core/compare/36f57a9..fa0c6b7):
  fa0c6b77  Fri Nov 6 12:56:58 2026 +0100  Michael Thomsen  Release os_detect 2.0.4 (dart-lang/core#1038)

dartdoc (https://github.com/dart-lang/dartdoc/compare/03ec6df..7e2508c):
  7e2508c6  Wed Nov 11 14:52:22 2026 +0200  Sergey G. Grekhov  Add constructor doc comment tests for enums and extension types (dart-lang/dartdoc#4316)
  03dfb467  Mon Nov 9 11:00:32 2026 +0200  Sergey G. Grekhov  `#3330`. Split test for parameters to separate tests (dart-lang/dartdoc#4314)
  23302d53  Mon Nov 9 10:56:34 2026 +0200  Sergey G. Grekhov  Add tests for primary constructors (dart-lang/dartdoc#4313)

ecosystem (https://github.com/dart-lang/ecosystem/compare/54e1673..1d2b5ff):
  1d2b5ff  Tue Nov 10 09:04:24 2026 -0800  Kevin Moore  feat(repo_manage): add --replace-pattern to report changelog (dart-lang/ecosystem#482)

http (https://github.com/dart-lang/http/compare/4646fe6..2bac218):
  2bac218  Fri Nov 6 17:49:23 2026 -0800  dart-ecosystem-bot[bot]  Tidy: package:cronet_http
  cdf967c  Fri Nov 6 15:40:49 2026 -0800  Brian Quinlan  chore(groundkeeper): use stable for flutter (dart-lang/http#2066)
  6582177  Thu Nov 5 13:04:35 2026 -0800  Brian Quinlan  doc: use timeout as an abort example (dart-lang/http#2062)

native (https://github.com/dart-lang/native/compare/6287745..b21ef2d):
  b21ef2da3  Wed Nov 11 16:07:34 2026 +0530  dhairya05  [json_syntax_generator] Pass the parameter name to ArgumentError.value (dart-lang/native#3787)
  20a607e48  Wed Nov 11 07:28:03 2026 -0300  Daco Harkes  [code_assets] [hooks] [hooks_runner] Adopt `.g.dart` for generated files (dart-lang/native#3790)
  cc974ddec  Wed Nov 11 19:59:39 2026 +1100  Liam Appelbe  [objective_c] Fix `isValidObject` bug on iOS simulator (dart-lang/native#3788)
  f7edbdee3  Sun Nov 8 13:18:25 2026 +1100  Liam Appelbe  [jni] Fix possible receiver NPEs (dart-lang/native#3784)
  0ca7d6745  Fri Nov 6 10:20:21 2026 -0300  Daco Harkes  [code_assets] [hooks_runner] [native_toolchain_c] Prepare for release (dart-lang/native#3781)
  baad15ab4  Fri Nov 6 02:56:52 2026 +0100  Kravchenko Igor  [objective_c] Link Foundation in the native support library (dart-lang/native#3782)

shelf (https://github.com/dart-lang/shelf/compare/9946de5..b175e9f):
  b175e9f  Tue Nov 10 09:14:31 2026 +0530  Muhammad Uvesh  shelf_static: ignore invalid If-Modified-Since headers (dart-lang/shelf#557)
  02b5f94  Sat Nov 7 23:09:28 2026 +0530  dhairya05  Fix typo in shelf_router docs (dart-lang/shelf#556)

test (https://github.com/dart-lang/test/compare/2a9e017..3533ae1):
  3533ae1e  Tue Nov 10 15:06:08 2026 -0800  Nate Bosch  Never retry tearDownAll (dart-lang/test#2815)
  5680b0b1  Fri Nov 6 12:52:11 2026 +0100  Sigurd Meldgaard  Add globalSetup and addGlobalTearDown APIs for cross-suite setup and teardown (dart-lang/test#2714)
  b8dc888a  Thu Nov 5 18:11:37 2026 -0800  Nate Bosch  [checks_codegen] Skip private fields and fields referencing private types (dart-lang/test#2782)
  66e062c4  Thu Nov 5 16:05:36 2026 -0800  Nate Bosch  [checks_codegen] Generate full field types with imports (dart-lang/test#2781)

tools (https://github.com/dart-lang/tools/compare/727fd9c..589d844):
  589d844c4  Wed Nov 11 20:27:32 2026 +0300  Hasan Armoush  fix(markdown): combine adjacent Text nodes when the element is last in its list (dart-lang/tools#2598)

Change-Id: I13acaa7378c86f2c33d88f3c4b163acf2b48ff08
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/576820
Commit-Queue: Nate Bosch <[email protected]>
Reviewed-by: Nicholas Shahan <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[ffigen] Auto-load native asset

2 participants