Skip to content

Commit 2f9ec56

Browse files
committed
Don't use Uint8Array when not necessary
1 parent de63b7f commit 2f9ec56

File tree

3 files changed

+113
-37
lines changed

3 files changed

+113
-37
lines changed

.github/workflows/tests.yml

+31-14
Original file line numberDiff line numberDiff line change
@@ -229,11 +229,27 @@ jobs:
229229
# run: cargo test
230230

231231
web:
232-
name: Web
232+
name: Web ${{ matrix.rust.description }}
233233
runs-on: ubuntu-24.04
234+
strategy:
235+
fail-fast: false
236+
matrix:
237+
rust:
238+
- { version: stable, atomics: false }
239+
- {
240+
description: with Atomics,
241+
atomics: true,
242+
version: nightly,
243+
components: rust-src,
244+
flags: '-Ctarget-feature=+atomics,+bulk-memory',
245+
args: '-Zbuild-std=panic_abort,std',
246+
}
234247
steps:
235248
- uses: actions/checkout@v4
236-
- uses: dtolnay/rust-toolchain@stable
249+
- uses: dtolnay/rust-toolchain@master
250+
with:
251+
toolchain: ${{ matrix.rust.version }}
252+
components: ${{ matrix.rust.components }}
237253
- name: Install precompiled wasm-pack
238254
shell: bash
239255
run: |
@@ -243,35 +259,36 @@ jobs:
243259
wasm-pack --version
244260
- uses: Swatinem/rust-cache@v2
245261
- name: Test (Node)
262+
if: matrix.rust.atomics == false
246263
env:
247-
RUSTFLAGS: -Dwarnings --cfg getrandom_backend="wasm_js"
248-
run: wasm-pack test --node -- --features std
264+
RUSTFLAGS: -Dwarnings --cfg getrandom_backend="wasm_js" ${{ matrix.rust.flags }}
265+
run: wasm-pack test --node -- --features std ${{ matrix.rust.args }}
249266
- name: Test (Firefox)
250267
env:
251268
WASM_BINDGEN_USE_BROWSER: 1
252-
RUSTFLAGS: -Dwarnings --cfg getrandom_backend="wasm_js"
253-
run: wasm-pack test --headless --firefox -- --features std
269+
RUSTFLAGS: -Dwarnings --cfg getrandom_backend="wasm_js" ${{ matrix.rust.flags }}
270+
run: wasm-pack test --headless --firefox -- --features std ${{ matrix.rust.args }}
254271
- name: Test (Chrome)
255272
env:
256273
WASM_BINDGEN_USE_BROWSER: 1
257-
RUSTFLAGS: -Dwarnings --cfg getrandom_backend="wasm_js"
258-
run: wasm-pack test --headless --chrome -- --features std
274+
RUSTFLAGS: -Dwarnings --cfg getrandom_backend="wasm_js" ${{ matrix.rust.flags }}
275+
run: wasm-pack test --headless --chrome -- --features std ${{ matrix.rust.args }}
259276
- name: Test (dedicated worker)
260277
env:
261278
WASM_BINDGEN_USE_DEDICATED_WORKER: 1
262-
RUSTFLAGS: -Dwarnings --cfg getrandom_backend="wasm_js"
263-
run: wasm-pack test --headless --firefox -- --features std
279+
RUSTFLAGS: -Dwarnings --cfg getrandom_backend="wasm_js" ${{ matrix.rust.flags }}
280+
run: wasm-pack test --headless --firefox -- --features std ${{ matrix.rust.args }}
264281
- name: Test (shared worker)
265282
env:
266283
WASM_BINDGEN_USE_SHARED_WORKER: 1
267-
RUSTFLAGS: -Dwarnings --cfg getrandom_backend="wasm_js"
268-
run: wasm-pack test --headless --firefox -- --features std
284+
RUSTFLAGS: -Dwarnings --cfg getrandom_backend="wasm_js" ${{ matrix.rust.flags }}
285+
run: wasm-pack test --headless --firefox -- --features std ${{ matrix.rust.args }}
269286
- name: Test (service worker)
270287
env:
271288
WASM_BINDGEN_USE_SERVICE_WORKER: 1
272-
RUSTFLAGS: -Dwarnings --cfg getrandom_backend="wasm_js"
289+
RUSTFLAGS: -Dwarnings --cfg getrandom_backend="wasm_js" ${{ matrix.rust.flags }}
273290
# Firefox doesn't support module service workers and therefor can't import scripts
274-
run: wasm-pack test --headless --chrome -- --features std
291+
run: wasm-pack test --headless --chrome -- --features std ${{ matrix.rust.args }}
275292

276293
wasi:
277294
name: WASI

.github/workflows/workspace.yml

+4
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ jobs:
4949
env:
5050
RUSTFLAGS: -Dwarnings --cfg getrandom_backend="wasm_js"
5151
run: cargo clippy -Zbuild-std --target wasm32-unknown-unknown
52+
- name: Web WASM with atomics (wasm_js.rs)
53+
env:
54+
RUSTFLAGS: -Dwarnings --cfg getrandom_backend="wasm_js" -Ctarget-feature=+atomics,+bulk-memory
55+
run: cargo clippy -Zbuild-std --target wasm32-unknown-unknown
5256
- name: Linux (linux_android.rs)
5357
env:
5458
RUSTFLAGS: -Dwarnings --cfg getrandom_backend="linux_getrandom"

src/backends/wasm_js.rs

+78-23
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
#[cfg(feature = "std")]
66
extern crate std;
77

8-
use crate::Error;
8+
use crate::{util, Error};
99
use core::mem::MaybeUninit;
1010
#[cfg(feature = "std")]
1111
use std::thread_local;
@@ -15,7 +15,7 @@ pub use crate::util::{inner_u32, inner_u64};
1515
#[cfg(not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none"))))]
1616
compile_error!("`wasm_js` backend can be enabled only for OS-less WASM targets!");
1717

18-
use js_sys::Uint8Array;
18+
use js_sys::{JsString, Uint8Array};
1919
use wasm_bindgen::{prelude::wasm_bindgen, JsValue};
2020

2121
// Size of our temporary Uint8Array buffer used with WebCrypto methods
@@ -26,34 +26,81 @@ pub fn fill_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
2626
CRYPTO.with(|crypto| {
2727
let crypto = crypto.as_ref().ok_or(Error::WEB_CRYPTO)?;
2828

29-
// getRandomValues does not work with all types of WASM memory,
30-
// so we initially write to browser memory to avoid exceptions.
31-
let buf = Uint8Array::new_with_length(CRYPTO_BUFFER_SIZE.into());
32-
for chunk in dest.chunks_mut(CRYPTO_BUFFER_SIZE.into()) {
33-
let chunk_len: u32 = chunk
34-
.len()
35-
.try_into()
36-
.expect("chunk length is bounded by CRYPTO_BUFFER_SIZE");
37-
// The chunk can be smaller than buf's length, so we call to
38-
// JS to create a smaller view of buf without allocation.
39-
let sub_buf = if chunk_len == u32::from(CRYPTO_BUFFER_SIZE) {
40-
buf.clone()
41-
} else {
42-
buf.subarray(0, chunk_len)
43-
};
44-
45-
if crypto.get_random_values(&sub_buf).is_err() {
46-
return Err(Error::WEB_GET_RANDOM_VALUES);
47-
}
29+
if is_sab() {
30+
// getRandomValues does not work with all types of WASM memory,
31+
// so we initially write to browser memory to avoid exceptions.
32+
let buf = Uint8Array::new_with_length(CRYPTO_BUFFER_SIZE.into());
33+
for chunk in dest.chunks_mut(CRYPTO_BUFFER_SIZE.into()) {
34+
let chunk_len: u32 = chunk
35+
.len()
36+
.try_into()
37+
.expect("chunk length is bounded by CRYPTO_BUFFER_SIZE");
38+
// The chunk can be smaller than buf's length, so we call to
39+
// JS to create a smaller view of buf without allocation.
40+
let sub_buf = if chunk_len == u32::from(CRYPTO_BUFFER_SIZE) {
41+
buf.clone()
42+
} else {
43+
buf.subarray(0, chunk_len)
44+
};
45+
46+
if crypto.get_random_values(&sub_buf).is_err() {
47+
return Err(Error::WEB_GET_RANDOM_VALUES);
48+
}
4849

49-
// SAFETY: `sub_buf`'s length is the same length as `chunk`
50-
unsafe { sub_buf.raw_copy_to_ptr(chunk.as_mut_ptr().cast::<u8>()) };
50+
// SAFETY: `sub_buf`'s length is the same length as `chunk`
51+
unsafe { sub_buf.raw_copy_to_ptr(chunk.as_mut_ptr().cast::<u8>()) };
52+
}
53+
} else {
54+
for chunk in dest.chunks_mut(CRYPTO_BUFFER_SIZE.into()) {
55+
// SAFETY: this is only safe because on Wasm the issues with unitialized data don't exist
56+
if crypto
57+
.get_random_values_ref(unsafe { util::slice_assume_init_mut(chunk) })
58+
.is_err()
59+
{
60+
return Err(Error::WEB_GET_RANDOM_VALUES);
61+
}
62+
}
5163
}
5264
Ok(())
5365
})
5466
}
5567

68+
#[cfg(not(target_feature = "atomics"))]
69+
fn is_sab() -> bool {
70+
use js_sys::WebAssembly::Memory;
71+
use js_sys::{Object, SharedArrayBuffer};
72+
use wasm_bindgen::JsCast;
73+
74+
let buffer: Object = wasm_bindgen::memory()
75+
.unchecked_into::<Memory>()
76+
.buffer()
77+
.unchecked_into();
78+
79+
// `crossOriginIsolated` is not available on Node.js and Safari <v15.2.
80+
if let Some(true) = CROSS_ORIGIN_ISOLATED.with(Option::clone) {
81+
buffer.is_instance_of::<SharedArrayBuffer>()
82+
} else {
83+
// `SharedArrayBuffer` is only available with COOP & COEP. But even
84+
// without its possible to create a shared `WebAssembly.Memory`, so we
85+
// check for that via the constructor name.
86+
87+
let constructor_name = buffer.constructor().name();
88+
89+
if SHARED_ARRAY_BUFFER_NAME.with(|sab_name| &constructor_name == sab_name) {
90+
true
91+
} else {
92+
false
93+
}
94+
}
95+
}
96+
97+
#[cfg(target_feature = "atomics")]
98+
fn is_sab() -> bool {
99+
true
100+
}
101+
56102
#[wasm_bindgen]
103+
#[rustfmt::skip]
57104
extern "C" {
58105
// Web Crypto API: Crypto interface (https://www.w3.org/TR/WebCryptoAPI/)
59106
type Crypto;
@@ -63,4 +110,12 @@ extern "C" {
63110
// Crypto.getRandomValues()
64111
#[wasm_bindgen(method, js_name = getRandomValues, catch)]
65112
fn get_random_values(this: &Crypto, buf: &Uint8Array) -> Result<(), JsValue>;
113+
#[wasm_bindgen(method, js_name = getRandomValues, catch)]
114+
fn get_random_values_ref(this: &Crypto, buf: &mut [u8]) -> Result<(), JsValue>;
115+
// Holds the `crossOriginIsolated` (https://developer.mozilla.org/en-US/docs/Web/API/crossOriginIsolated) global property.
116+
#[wasm_bindgen(thread_local_v2, js_namespace = globalThis, js_name = crossOriginIsolated)]
117+
static CROSS_ORIGIN_ISOLATED: Option<bool>;
118+
// Holds the constructor name of the `SharedArrayBuffer` class.
119+
#[wasm_bindgen(thread_local_v2, static_string)]
120+
static SHARED_ARRAY_BUFFER_NAME: JsString = "SharedArrayBuffer";
66121
}

0 commit comments

Comments
 (0)