Skip to content

Commit 8e992dc

Browse files
authored
Don't allow invalid Unicode scalar values in char (#3866)
1 parent 807bdb4 commit 8e992dc

File tree

6 files changed

+51
-3
lines changed

6 files changed

+51
-3
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@
2626
* Make .wasm output deterministic when using `--reference-types`.
2727
[#3851](https://github.com/rustwasm/wasm-bindgen/pull/3851)
2828

29+
* Don't allow invalid Unicode scalar values in `char`.
30+
[#3866](https://github.com/rustwasm/wasm-bindgen/pull/3866)
31+
2932
--------------------------------------------------------------------------------
3033

3134
## [0.2.91](https://github.com/rustwasm/wasm-bindgen/compare/0.2.90...0.2.91)

crates/cli-support/src/js/binding.rs

+19-3
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,11 @@ impl<'a, 'b> JsBuilder<'a, 'b> {
502502
self.prelude(&format!("_assertNonNull({});", arg));
503503
}
504504

505+
fn assert_char(&mut self, arg: &str) {
506+
self.cx.expose_assert_char();
507+
self.prelude(&format!("_assertChar({});", arg));
508+
}
509+
505510
fn assert_optional_bigint(&mut self, arg: &str) {
506511
if !self.cx.config.debug {
507512
return;
@@ -739,7 +744,11 @@ fn instruction(
739744

740745
Instruction::I32FromStringFirstChar => {
741746
let val = js.pop();
742-
js.push(format!("{}.codePointAt(0)", val));
747+
let i = js.tmp();
748+
js.prelude(&format!("const char{i} = {val}.codePointAt(0);"));
749+
let val = format!("char{i}");
750+
js.assert_char(&val);
751+
js.push(val);
743752
}
744753

745754
Instruction::I32FromExternrefOwned => {
@@ -816,11 +825,18 @@ fn instruction(
816825

817826
Instruction::I32FromOptionChar => {
818827
let val = js.pop();
828+
let i = js.tmp();
819829
js.cx.expose_is_like_none();
820-
js.push(format!(
821-
"isLikeNone({0}) ? 0xFFFFFF : {0}.codePointAt(0)",
830+
js.prelude(&format!(
831+
"const char{i} = isLikeNone({0}) ? 0xFFFFFF : {0}.codePointAt(0);",
822832
val
823833
));
834+
let val = format!("char{i}");
835+
js.cx.expose_assert_char();
836+
js.prelude(&format!(
837+
"if ({val} !== 0xFFFFFF) {{ _assertChar({val}); }}"
838+
));
839+
js.push(val);
824840
}
825841

826842
Instruction::I32FromOptionEnum { hole } => {

crates/cli-support/src/js/mod.rs

+13
Original file line numberDiff line numberDiff line change
@@ -2116,6 +2116,19 @@ impl<'a> Context<'a> {
21162116
);
21172117
}
21182118

2119+
fn expose_assert_char(&mut self) {
2120+
if !self.should_write_global("assert_char") {
2121+
return;
2122+
}
2123+
self.global(
2124+
"
2125+
function _assertChar(c) {
2126+
if (typeof(c) === 'number' && (c >= 0x110000 || (c >= 0xD800 && c < 0xE000))) throw new Error(`expected a valid Unicode scalar value, found ${c}`);
2127+
}
2128+
",
2129+
);
2130+
}
2131+
21192132
fn expose_make_mut_closure(&mut self) -> Result<(), Error> {
21202133
if !self.should_write_global("make_mut_closure") {
21212134
return Ok(());

src/convert/impls.rs

+1
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ impl FromWasmAbi for char {
188188

189189
#[inline]
190190
unsafe fn from_abi(js: u32) -> char {
191+
// SAFETY: Checked in bindings.
191192
char::from_u32_unchecked(js)
192193
}
193194
}

tests/wasm/char.js

+10
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ exports.js_identity = a => a;
66
exports.js_works = () => {
77
assert.strictEqual(wasm.letter(), 'a');
88
assert.strictEqual(wasm.face(), '😀');
9+
assert.strictEqual(wasm.rust_identity(''), '\u0000');
910
assert.strictEqual(wasm.rust_identity('Ղ'), 'Ղ');
1011
assert.strictEqual(wasm.rust_identity('ҝ'), 'ҝ');
1112
assert.strictEqual(wasm.rust_identity('Δ'), 'Δ');
@@ -14,4 +15,13 @@ exports.js_works = () => {
1415
assert.strictEqual(wasm.rust_js_identity('㊻'), '㊻');
1516
wasm.rust_letter('a');
1617
wasm.rust_face('😀');
18+
19+
assert.strictEqual(wasm.rust_option_identity(undefined), undefined);
20+
assert.strictEqual(wasm.rust_option_identity(null), undefined);
21+
assert.strictEqual(wasm.rust_option_identity(''), '\u0000');
22+
assert.strictEqual(wasm.rust_option_identity('\u0000'), '\u0000');
23+
24+
assert.throws(() => wasm.rust_identity(55357), /c.codePointAt is not a function/);
25+
assert.throws(() => wasm.rust_identity('\uD83D'), /expected a valid Unicode scalar value, found 55357/);
26+
assert.throws(() => wasm.rust_option_identity('\uD83D'), /expected a valid Unicode scalar value, found 55357/);
1727
};

tests/wasm/char.rs

+5
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ pub fn rust_identity(c: char) -> char {
1212
c
1313
}
1414

15+
#[wasm_bindgen]
16+
pub fn rust_option_identity(c: Option<char>) -> Option<char> {
17+
c
18+
}
19+
1520
#[wasm_bindgen]
1621
pub fn rust_js_identity(c: char) -> char {
1722
js_identity(c)

0 commit comments

Comments
 (0)