Skip to content

Commit

Permalink
Don't allow invalid Unicode scalar values in char (#3866)
Browse files Browse the repository at this point in the history
  • Loading branch information
daxpedda authored Mar 4, 2024
1 parent 807bdb4 commit 8e992dc
Show file tree
Hide file tree
Showing 6 changed files with 51 additions and 3 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@
* Make .wasm output deterministic when using `--reference-types`.
[#3851](https://github.com/rustwasm/wasm-bindgen/pull/3851)

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

--------------------------------------------------------------------------------

## [0.2.91](https://github.com/rustwasm/wasm-bindgen/compare/0.2.90...0.2.91)
Expand Down
22 changes: 19 additions & 3 deletions crates/cli-support/src/js/binding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,11 @@ impl<'a, 'b> JsBuilder<'a, 'b> {
self.prelude(&format!("_assertNonNull({});", arg));
}

fn assert_char(&mut self, arg: &str) {
self.cx.expose_assert_char();
self.prelude(&format!("_assertChar({});", arg));
}

fn assert_optional_bigint(&mut self, arg: &str) {
if !self.cx.config.debug {
return;
Expand Down Expand Up @@ -739,7 +744,11 @@ fn instruction(

Instruction::I32FromStringFirstChar => {
let val = js.pop();
js.push(format!("{}.codePointAt(0)", val));
let i = js.tmp();
js.prelude(&format!("const char{i} = {val}.codePointAt(0);"));
let val = format!("char{i}");
js.assert_char(&val);
js.push(val);
}

Instruction::I32FromExternrefOwned => {
Expand Down Expand Up @@ -816,11 +825,18 @@ fn instruction(

Instruction::I32FromOptionChar => {
let val = js.pop();
let i = js.tmp();
js.cx.expose_is_like_none();
js.push(format!(
"isLikeNone({0}) ? 0xFFFFFF : {0}.codePointAt(0)",
js.prelude(&format!(
"const char{i} = isLikeNone({0}) ? 0xFFFFFF : {0}.codePointAt(0);",
val
));
let val = format!("char{i}");
js.cx.expose_assert_char();
js.prelude(&format!(
"if ({val} !== 0xFFFFFF) {{ _assertChar({val}); }}"
));
js.push(val);
}

Instruction::I32FromOptionEnum { hole } => {
Expand Down
13 changes: 13 additions & 0 deletions crates/cli-support/src/js/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2116,6 +2116,19 @@ impl<'a> Context<'a> {
);
}

fn expose_assert_char(&mut self) {
if !self.should_write_global("assert_char") {
return;
}
self.global(
"
function _assertChar(c) {
if (typeof(c) === 'number' && (c >= 0x110000 || (c >= 0xD800 && c < 0xE000))) throw new Error(`expected a valid Unicode scalar value, found ${c}`);
}
",
);
}

fn expose_make_mut_closure(&mut self) -> Result<(), Error> {
if !self.should_write_global("make_mut_closure") {
return Ok(());
Expand Down
1 change: 1 addition & 0 deletions src/convert/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ impl FromWasmAbi for char {

#[inline]
unsafe fn from_abi(js: u32) -> char {
// SAFETY: Checked in bindings.
char::from_u32_unchecked(js)
}
}
Expand Down
10 changes: 10 additions & 0 deletions tests/wasm/char.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ exports.js_identity = a => a;
exports.js_works = () => {
assert.strictEqual(wasm.letter(), 'a');
assert.strictEqual(wasm.face(), '😀');
assert.strictEqual(wasm.rust_identity(''), '\u0000');
assert.strictEqual(wasm.rust_identity('Ղ'), 'Ղ');
assert.strictEqual(wasm.rust_identity('ҝ'), 'ҝ');
assert.strictEqual(wasm.rust_identity('Δ'), 'Δ');
Expand All @@ -14,4 +15,13 @@ exports.js_works = () => {
assert.strictEqual(wasm.rust_js_identity('㊻'), '㊻');
wasm.rust_letter('a');
wasm.rust_face('😀');

assert.strictEqual(wasm.rust_option_identity(undefined), undefined);
assert.strictEqual(wasm.rust_option_identity(null), undefined);
assert.strictEqual(wasm.rust_option_identity(''), '\u0000');
assert.strictEqual(wasm.rust_option_identity('\u0000'), '\u0000');

assert.throws(() => wasm.rust_identity(55357), /c.codePointAt is not a function/);
assert.throws(() => wasm.rust_identity('\uD83D'), /expected a valid Unicode scalar value, found 55357/);
assert.throws(() => wasm.rust_option_identity('\uD83D'), /expected a valid Unicode scalar value, found 55357/);
};
5 changes: 5 additions & 0 deletions tests/wasm/char.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ pub fn rust_identity(c: char) -> char {
c
}

#[wasm_bindgen]
pub fn rust_option_identity(c: Option<char>) -> Option<char> {
c
}

#[wasm_bindgen]
pub fn rust_js_identity(c: char) -> char {
js_identity(c)
Expand Down

0 comments on commit 8e992dc

Please sign in to comment.