Summary: Pull Request resolved: https://github.com/react/react-native/pull/57975 `HighResDuration::fromDOMHighResTimeStamp` and `HighResTimeStamp::fromDOMHighResTimeStamp` converted milliseconds back to nanoseconds with `static_cast<int64_t>(units * 1e6)`, which truncates toward zero. `toDOMHighResTimeStamp()` divides the nanosecond count by 1e6 (one rounding) and multiplying back by 1e6 rounds again, so the product frequently lands a hair below the original integer (e.g. `537648854729249.97`). Truncation then chops off a whole nanosecond, so `fromDOMHighResTimeStamp(toDOMHighResTimeStamp(x)) != x` for roughly 2% of random `now()` values. That is the source of an intermittent `BridgingTest/highResTimeStampTest` failure, which reported: ``` Expected equality of these values: timestamp Which is: 8-byte object <22-02 00-21 FD-E8 01-00> bridging::fromJs<HighResTimeStamp>( rt, bridging::toJs(rt, timestamp), invoker) Which is: 8-byte object <21-02 00-21 FD-E8 01-00> ``` Round to the nearest nanosecond instead of truncating. Nanosecond values below 2^53 are exactly representable in a double, so the round trip is now exact for every value below 2.25e15 ns (26 days of monotonic clock); beyond that the double's ULP exceeds 0.5 ns and residual error is at most 2 ns, which is a floor of the DOM representation itself. Rounding is also the correct semantic independently of the round trip — truncation gives a systematic downward bias and is asymmetric across zero, which affects the other callers (`RCTHighResTimeStampFromSeconds` for touch timestamps, `RuntimeTargetConsole` `console.timeStamp`, and `PerformanceTracer`). The rounding is `std::llround`, which costs both overloads their `constexpr`: `<cmath>` rounding functions do not become usable in a constant expression until C++23 (P0533), and this header is compiled as C++20 everywhere (`-std=c++20` in `rn_defs.bzl`, `react-native-flags.cmake`, the podspecs and `Package.swift`). Both carry a `TODO` to restore `constexpr` once the C++23 rollout reaches them. Dropping it is safe here. All 14 call sites are plain runtime calls — none is in a constant-expression context, none assigns to a `constexpr` variable or feeds a `static_assert` — and both functions are defined inside the class body, so they stay implicitly `inline` and neither linkage nor ABI changes. `fromDOMHighResTimeStamp` converts a `double` arriving from JS, so constant-evaluating it was never meaningful in the first place. This does narrow the published API surface, so the C++ API snapshots are regenerated: the only change across all nine `.api` files is the `constexpr` keyword dropping off these two declarations, 18 lines in total. `HighResTimeStamp::fromDOMHighResTimeStamp` now delegates to `HighResDuration`'s so there is a single implementation. `highResTimeStampTest` previously asserted on `HighResTimeStamp::now()`, whose magnitude is host-uptime-dependent, so it only tripped the bug on about 2% of runs. It now round-trips five fixed nanosecond values (including the exact value from the failing run), which exercises the bug on every run. No test was skipped, disabled, or loosened. Changelog: [General][Fixed] - Round instead of truncate when converting a `DOMHighResTimeStamp` back to nanoseconds, so `HighResTimeStamp` and `HighResDuration` round trips are exact Reviewed By: javache Differential Revision: D116286868 fbshipit-source-id: 1adafd1b57b1185f51f08fa4dc55d96b88c176cd
scripts/cxx-api
Python build pipeline for React Native's C++ (and Objective-C) API snapshots.
Overview
scripts/cxx-api generates human-readable snapshots of React Native's public C++ API surface. It uses Doxygen to parse C/C++/Objective-C headers and a custom Python parser to produce a simplified, sorted representation of every public symbol.
The pipeline produces one .api snapshot file per configured API view × variant combination:
| Snapshot | Description |
|---|---|
ReactCommonDebugCxx.api |
Platform-independent C++ API (debug) |
ReactCommonReleaseCxx.api |
Platform-independent C++ API (release) |
ReactAndroidDebugCxx.api |
Android-specific C++ API (debug) |
ReactAndroidReleaseCxx.api |
Android-specific C++ API (release) |
ReactAppleDebugCxx.api |
Apple-specific C++/Obj-C API (debug) |
ReactAppleReleaseCxx.api |
Apple-specific C++/Obj-C API (release) |
For each view, debug and release variants are generated with different preprocessor definitions (e.g. REACT_NATIVE_DEBUG vs NDEBUG), since #ifdef guards in the source headers can produce a different public API surface per variant.
Snapshot files are committed to the repo under scripts/cxx-api/api-snapshots/.
Usage
Generate snapshots
Maintainers should run this command whenever making intentional C++ API changes:
python -m scripts.cxx-api.parser
Validate snapshots against committed baseline
This mode generates snapshots to a temporary directory and compares them against the committed .api files. It is designed for CI:
python -m scripts.cxx-api.parser --validate
If any snapshot differs, a unified diff is printed and the process exits with a non-zero status. To fix a failing validation, regenerate the snapshots with python -m scripts.cxx-api.parser and commit the updated .api files.
How it works
The pipeline has two main stages:
1. Doxygen XML generation
Doxygen is configured via a generated config file (built from .doxygen.config.template) with the input directories, exclude patterns, and preprocessor definitions specified in config.yml. It outputs XML describing every symbol found in the headers.
2. Snapshot parsing
The Python parser (parser/) reads the Doxygen XML output and builds a scope tree of the public API surface. The tree is then serialized to a deterministically sorted, human-readable .api text format.
When to use it
The snapshot should be regenerated whenever making intentional changes to the public C++ API surface. This includes additions, removals, and changes to files located in:
xplat/js/react-native-github/xplat/js/react-native-github/ReactCommon/xplat/js/react-native-github/ReactAndroid/xplat/js/react-native-github/ReactApple/xplat/js/react-native-github/Libraries/
Configuration
All API views and their variants are defined in config.yml. Each view specifies:
| Field | Description |
|---|---|
inputs |
Directories to scan for headers |
exclude_patterns |
Glob patterns for files to skip |
definitions |
Preprocessor macros to define |
variants |
Named build variants (e.g. debug/release) with extra definitions |
codegen |
Optional codegen platform (android, ios) to generate TurboModule/Component headers before scanning |
private_directories |
Directories whose headers are scanned (they may be transitively included) but should not contribute public symbols. If any public API entity is defined in a private directory, a warning is printed to help catch accidental API exposure. |
Snapshot format
The .api files use a minimal pseudo-C++ syntax designed for easy diffing:
namespace facebook::react {
class ComponentDescriptor {
public ComponentDescriptor(ComponentDescriptorParameters params);
public ComponentHandle getComponentHandle();
}
enum class AccessibilityRole {
None = 0,
Button = 1,
}
}
- Scopes and members are sorted alphabetically.
- Access specifiers (
public,protected) are preserved. - Template parameters are included.
- Doc comments and source file names are stripped.