[Compiler Bug]: Rust backend decodes user text that looks like `__SURROGATE_XXXX__` into a lone surrogate
What kind of issue is this?
- React Compiler core (the JS output is incorrect, or your app works incorrectly after optimization)
Link to repro
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).
Repro steps
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:
- Keep the markers and escape existing marker-like text before encoding (e.g. rewrite a literal
__SURROGATE_prefix into a separate escape marker). Then undo that escape infrom_marker_string,to_marker_stringandrestoreJsonSurrogates. This is a small change, but all four functions have to agree. - Stop sending lone surrogates as text: pass them out of band (for example as a list of positions and code units next to the JSON), or use an encoding that serde can read directly. This removes the ambiguity, but it's a bigger change to the bridge.
Happy to send a PR once there's a preferred approach.
How often does this bug happen?
Every time
What version of React are you using?
main (2b19aec)
What version of React Compiler are you using?
main (2b19aec), Rust backend
Source: facebook/react