facebook / react · Issue No. 37647
No playground link: this only affects the Rust backend (babel-plugin-react-compiler-rust). The fixture below reproduces it with yarn snap --rust on main (2b19aec).
The Rust bridge encodes lone surrogates in the AST JSON as __SURROGATE_XXXX__ text (sanitizeJsonSurrogates in bridge.ts). The decoders, JsString::from_marker_string on the Rust side and restoreJsonSurrogates on the way back, turn any __SURROGATE_XXXX__ text into a surrogate, including text the user actually wrote. Existing text that looks like a marker is never escaped, so it gets decoded too.
import {Stringify} from 'shared-runtime';
function foo() {
return <Stringify value={['__SURROGATE_D83D__']} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: foo,
params: [],
isComponent: false,
};
yarn snap --rust:
Non-forget (expected):
(kind: ok) <div>{"value":["__SURROGATE_D83D__"]}</div>
Forget:
(kind: ok) <div>{"value":["\ud83d"]}</div>
The string silently becomes a lone surrogate, with no error. The same happens when the text is built from pieces that are folded, e.g. '__SURROGATE_' + 'D83D__' or a template literal. The TS backend is not affected. Only uppercase hex matches the marker pattern.
Any in-band text marker needs an escape for itself, so fixing this means changing the wire format on both sides. Two options I can see:
__SURROGATE_ prefix into a separate escape marker). Then undo that escape in from_marker_string, to_marker_string and restoreJsonSurrogates. This is a small change, but all four functions have to agree.Happy to send a PR once there's a preferred approach.
Every time
main (2b19aec)
main (2b19aec), Rust backend
Relay reads this issue against the repository's contribution signals: the files it is likely to touch, how the maintainers triage work this size, and what the first contribution would exercise.
The full analysis for this issue is still being assembled. Until then, the description above and the thread on GitHub are the most reliable context.