Skip to content

fix(ffi/objc): define only real C enum constant names as globals - #87

Open
NathanWalker wants to merge 1 commit into
android-react-nativefrom
fix/objc-enum-member-globals
Open

NathanWalker wants to merge 1 commit into
android-react-nativefrom
fix/objc-enum-member-globals

Conversation

@NathanWalker

Copy link
Copy Markdown
Contributor

Replaces the enum part of #77 (item 6 of the "NativeScript bugs" list in ng-native/ng-native#931). This addresses the review point "Enum globals are chosen by a guess based on names … the decision should be made [in the bridge], in one place."

Summary

With init("", { globals: true }), every key of every enum object became a global, including short member names like Center, Fill and CloudKit's Zone. Angular's zoneless scheduler checks typeof Zone, so it crashed at bootstrap.

The bridge couldn't make that decision before: the generator strips enum members down to short names, so the full C names weren't in the metadata at all. Its rebuilt aliases matched the real C name for only about 47% of members in a sample of SDK headers. MKMapTypeStandard and CKErrorZoneBusy were never produced, while invented names like NSComparisonAscending and kNSTextAlignmentCenter were. So the metadata now records what was stripped:

 enum entry (metadata format v2)
   name
+  memberPrefix, memberSuffix            // what transformEnumMemberNames stripped
   members[]: name | mdSectionOffsetEnumMemberCName flag, value
                                         // flag = stored name already is the C name

 C name = flagged ? name : memberPrefix + name + memberSuffix   // exact; the generator fails the build otherwise
EnumMemberAliases.h  aliasesFor(...)  -> aliases, exactly one tagged isCName
  ├─ JSI bridge: enumToObject            (enum object keeps every alias + the C name)
  │              getEnumGlobalNames(enum) -> C names only
  │                ├─ Install.mm global installer       (no JS name heuristics)
  │                └─ installGlobals() in @nativescript/react-native
  └─ napi tree:  registerEnumGlobals / enum getter -> C names only on global
  • Both trees. The napi tree had the same bug and worse: it put every stored short name on the global object at startup, and every alias on first use. It now uses the same helper. The one difference left is each tree's existing list of suffixes stripped from the enum name for convenience keys. Merging the two lists would change existing keys.
  • Old metadata (v1) still loads, but yields no C names, so no enum members become globals. Short names are never installed as a fallback.
  • Size: about +37.6 KB (+0.8%) on the iOS simulator arm64 metadata.
  • NSURLError-style unnamed enums were already stored as full-name constants and are unaffected.

Evidence

Real-behaviour runtime tests in platforms/apple/test/runtime/runner/app/tests/Marshalling/EnumTests.js:

  • full names are globals, including MKMapTypeStandard, CKErrorZoneBusy, CKRecordZoneCapabilityAtomic and CKRecordZoneEncryptionScopePerZone;
  • Center, Zone, Ascending, Restricted, ZoneBusy, NSComparisonAscending and kNSTextAlignmentCenter are not globals.
Run Base This branch
iOS 27 simulator, V8 (node scripts/run-tests-ios.js) 2 enum specs fail (Center in globalThis: true) 11/11 enum specs pass
macOS, napi backend (MACOS_TEST_FFI_BACKEND=napi node scripts/run-tests-macos.js) — 716 tests, 0 failures
  • The full iOS run has 8 other failures, the same on base and branch: existing DBL_MAX double-marshalling specs.
  • Against regenerated iOS 27.1 simulator metadata, all 21,254 rebuilt C names compile as identifiers against the SDK headers.

Not verified: the Hermes, JSC and QuickJS bridge builds, whose only change is one added #include. TypeScript was not type-checked, and nothing was run on a physical device.

Merge Danger

Door: one-way, for the metadata format.

The format version goes from 1 to 2. New runtimes read both versions. An older runtime reading v2 metadata exits with "Invalid metadata header version". Metadata is generated by the same package or runtime that reads it, so mixing only happens with a hand-copied .nsmd.

Blast Radius: enum globals on iOS/macOS

  • Code that used a short member name as a bare global (Center) breaks. It must use NSTextAlignment.Center or NSTextAlignmentCenter.
  • Enum objects gain their C name as a key.
  • For a member with no other aliases, the reverse lookup Enum[value] can now return the C name instead of the short name.

With globals enabled, every key of every enum object became a global,
including short member names such as Center, Fill and CloudKit's Zone.
Libraries that feature-detect a global by name were misled: Angular's
zoneless scheduler checks typeof Zone and crashed at bootstrap.

The metadata only stored the shortened member names, so the bridge could
not tell which alias was the real C constant. Metadata format v2 records,
per enum, the prefix and suffix the generator stripped from its members,
and flags members whose stored name is already the full C name. The
reader rebuilds every member's exact C name from that; v1 metadata still
loads but yields no C names.

A shared alias builder (ffi/objc/shared/EnumMemberAliases.h) tags the C
name among each member's aliases and is used by both the JSI bridge and
the Node-API tree. The bridge exposes getEnumGlobalNames(enumName), and
the shared global installer and installGlobals() in
@nativescript/react-native define only those names. The Node-API tree
defines only C names as globals. Short names and other conveniences stay
on the enum object, which also gains the C name as a key, so constants
like MKMapTypeStandard and CKErrorZoneBusy now resolve.
@coderabbitai

coderabbitai Bot commented Oct 24, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 63f135ac-156e-4cdd-8b2d-e38191254f6b

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@ammarahm-ed

Copy link
Copy Markdown
Collaborator

@DjDeveloperr Can you take a look at this? I think this would break NativeScript core apps that simply using Center instead of NSTextAlignment.Center?

@NathanWalker

Copy link
Copy Markdown
Contributor Author

This shouldn't affect NativeScript core apps. Short member names were never globals in the NativeScript iOS runtime, and core doesn't use them as bare globals:

  • The iOS runtime (NativeScript/ios) resolves globals by top-level metadata name. An enum like NSTextAlignment is a global; its members like Center are only properties on it.
  • The iOS typings (@nativescript/types-ios) declare enums as declare const enum NSTextAlignment { Center = 1, ... }, with no top-level Center. So TypeScript code can't reference a bare Center without a TS2304 error.
  • Core's *.ios.ts sources have no bare Center, Fill, Zone, ScaleAspectFit, etc. Core always qualifies them, e.g. NSTextAlignment.Center in ui/text-base/index.ios.ts and UIViewContentMode.ScaleAspectFit in ui/image/index.ios.ts.
  • NSTextAlignment.Center keeps working. Enum objects keep every alias, and EnumTests.js covers it.

The only code that could break is code that relied on the short names this branch's bridges briefly put on globalThis. That was in the napi tree at startup, and in the JSI bridge with globals: true. Those globals are what crashed Angular's zoneless bootstrap (typeof Zone). The full C names (NSTextAlignmentCenter) are still globals.

@DjDeveloperr

Copy link
Copy Markdown
Collaborator

LGTM as long as it aligns behavior with existing iOS runtime. New runtime is beta and we can make breaking changes like this (especially since it doesn't affect core apps). Good to remove any divergence.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants