Skip to content

Commit d1eeff9

Browse files
authored
Support no_std in the wasmparser crate (#1493)
* Add `#![no_std]` support to the `wasmparser` crate This commit adds a `std` feature to the `wasmparser` crate, enables it by default, and then enables building the crate when the feature is disabled. This works by switching imports of `wasmparser` from `std` to either the `core` or `alloc` crates which are available in the `no_std` context. The main change made in this PR is that maps used by `wasmparser`, e.g. `HashMap` and `IndexMap`, are now abstracted with type aliases in `wasmparser` which unconditionally customize a hash algorithm parameter. When the `std` feature is enabled this parameter is implemented using the standard library's default hashing algorithm. When `std` is disabled then this uses the `ahash` crate internally. This commit additionally updates CI to both check that wasmparser builds when the `std` feature is disabled and that it builds on a target without `std`. * Fix rebase conflict
1 parent 7003a7f commit d1eeff9

39 files changed

Lines changed: 309 additions & 130 deletions

.github/workflows/main.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ jobs:
152152
runs-on: ubuntu-latest
153153
steps:
154154
- uses: actions/checkout@v4
155+
- run: rustup target add x86_64-unknown-none
155156
- run: cargo check --benches -p wasm-smith
156157
- run: cargo check --no-default-features
157158
- run: cargo check --no-default-features --features print
@@ -176,6 +177,9 @@ jobs:
176177
- run: cargo check --no-default-features -p wit-parser --features serde
177178
- run: cargo check --no-default-features -p wit-parser --features decoding
178179
- run: cargo check --no-default-features -p wit-parser --features serde,decoding,wat
180+
- run: cargo check --no-default-features -p wasmparser
181+
- run: cargo check --no-default-features -p wasmparser --target x86_64-unknown-none
182+
- run: cargo check --no-default-features -p wasmparser --features std
179183
- run: |
180184
if cargo tree -p wasm-smith --no-default-features -e no-dev | grep wasmparser; then
181185
echo wasm-smith without default features should not depend on wasmparser

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ clap = { version = "4.0.0", features = ["derive"] }
4242
clap_complete = "4.4.7"
4343
criterion = "0.3.3"
4444
env_logger = "0.11"
45-
indexmap = "2.0.0"
45+
indexmap = { version = "2.0.0", default-features = false }
4646
leb128 = "0.2.4"
4747
libfuzzer-sys = "0.4.0"
4848
log = "0.4.17"
@@ -55,10 +55,12 @@ serde_json = { version = "1" }
5555
wasmtime = { version = "20.0.0", default-features = false, features = ['cranelift', 'component-model', 'runtime', 'gc'] }
5656
url = "2.0.0"
5757
pretty_assertions = "1.3.0"
58-
semver = "1.0.0"
58+
semver = { version = "1.0.0", default-features = false }
5959
smallvec = "1.11.1"
6060
libtest-mimic = "0.7.0"
6161
bitflags = "2.5.0"
62+
hashbrown = { version = "0.14.3", default-features = false, features = ['ahash'] }
63+
ahash = { version = "0.8.11", default-features = false }
6264

6365
wasm-compose = { version = "0.205.0", path = "crates/wasm-compose" }
6466
wasm-encoder = { version = "0.205.0", path = "crates/wasm-encoder" }

crates/wasm-compose/src/encoding.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -730,7 +730,7 @@ impl<'a> TypeEncoder<'a> {
730730
index
731731
}
732732

733-
fn flags(encodable: &mut Encodable, names: &IndexSet<KebabString>) -> u32 {
733+
fn flags(encodable: &mut Encodable, names: &wasmparser::map::IndexSet<KebabString>) -> u32 {
734734
let index = encodable.type_count();
735735
encodable
736736
.ty()
@@ -739,7 +739,7 @@ impl<'a> TypeEncoder<'a> {
739739
index
740740
}
741741

742-
fn enum_type(encodable: &mut Encodable, cases: &IndexSet<KebabString>) -> u32 {
742+
fn enum_type(encodable: &mut Encodable, cases: &wasmparser::map::IndexSet<KebabString>) -> u32 {
743743
let index = encodable.type_count();
744744
encodable
745745
.ty()

crates/wasmparser/Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ workspace = true
1919
bitflags = "2.4.1"
2020
indexmap = { workspace = true }
2121
semver = { workspace = true }
22+
hashbrown = { workspace = true }
23+
ahash = { workspace = true }
2224

2325
[dev-dependencies]
2426
anyhow = { workspace = true }
@@ -34,3 +36,7 @@ log.workspace = true
3436
[[bench]]
3537
name = "benchmark"
3638
harness = false
39+
40+
[features]
41+
default = ['std']
42+
std = ['indexmap/std']

crates/wasmparser/LICENSE

Lines changed: 0 additions & 1 deletion
This file was deleted.

crates/wasmparser/src/binary_reader.rs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@
1313
* limitations under the License.
1414
*/
1515

16+
use crate::prelude::*;
1617
use crate::{limits::*, *};
17-
use std::error::Error;
18-
use std::fmt;
19-
use std::marker;
20-
use std::ops::Range;
21-
use std::str;
18+
use core::fmt;
19+
use core::marker;
20+
use core::ops::Range;
21+
use core::str;
2222

2323
pub(crate) const WASM_MAGIC_NUMBER: &[u8; 4] = b"\0asm";
2424

@@ -39,9 +39,10 @@ pub(crate) struct BinaryReaderErrorInner {
3939
}
4040

4141
/// The result for `BinaryReader` operations.
42-
pub type Result<T, E = BinaryReaderError> = std::result::Result<T, E>;
42+
pub type Result<T, E = BinaryReaderError> = core::result::Result<T, E>;
4343

44-
impl Error for BinaryReaderError {}
44+
#[cfg(feature = "std")]
45+
impl std::error::Error for BinaryReaderError {}
4546

4647
impl fmt::Display for BinaryReaderError {
4748
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {

crates/wasmparser/src/lib.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,28 @@
2727
//! the examples documented for [`Parser::parse`] or [`Parser::parse_all`].
2828
2929
#![deny(missing_docs)]
30+
#![no_std]
31+
32+
extern crate alloc;
33+
#[cfg(feature = "std")]
34+
extern crate std;
35+
36+
/// A small "prelude" to use throughout this crate.
37+
///
38+
/// This crate is tagged with `#![no_std]` meaning that we get libcore's prelude
39+
/// by default. This crate also uses `alloc`, however, and common types there
40+
/// like `String`. This custom prelude helps bring those types into scope to
41+
/// avoid having to import each of them manually.
42+
mod prelude {
43+
pub use alloc::borrow::ToOwned;
44+
pub use alloc::boxed::Box;
45+
pub use alloc::format;
46+
pub use alloc::string::{String, ToString};
47+
pub use alloc::vec;
48+
pub use alloc::vec::Vec;
49+
50+
pub use crate::map::{HashMap, HashSet, IndexMap, IndexSet};
51+
}
3052

3153
/// A helper macro to conveniently iterate over all opcodes recognized by this
3254
/// crate. This can be used to work with either the [`Operator`] enumeration or
@@ -770,3 +792,5 @@ mod parser;
770792
mod readers;
771793
mod resources;
772794
mod validator;
795+
796+
pub mod map;

crates/wasmparser/src/map.rs

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
//! Type aliases for maps used by `wasmparser`
2+
//!
3+
//! This module contains type aliases used for [`HashMap`], [`HashSet`],
4+
//! [`IndexMap`], and [`IndexSet`]. Note that these differ from upstream types
5+
//! in the `indexmap` crate and the standard library due to customization of the
6+
//! hash algorithm type parameter.
7+
8+
use core::hash::{BuildHasher, Hasher};
9+
10+
/// Wasmparser-specific type alias for an ordered map.
11+
pub type IndexMap<K, V> = indexmap::IndexMap<K, V, RandomState>;
12+
13+
/// Wasmparser-specific type alias for an ordered set.
14+
pub type IndexSet<K> = indexmap::IndexSet<K, RandomState>;
15+
16+
/// Wasmparser-specific type alias for hash map.
17+
pub type HashMap<K, V> = hashbrown::HashMap<K, V, RandomState>;
18+
19+
/// Wasmparser-specific type alias for hash set.
20+
pub type HashSet<K> = hashbrown::HashSet<K, RandomState>;
21+
22+
/// Wasmparser's hashing state stored per-map.
23+
///
24+
/// This is DoS-resistant when the `std` feature is activated and still somewhat
25+
/// resistant when it's not active but not as secure.
26+
#[derive(Clone, Debug)]
27+
pub struct RandomState(RandomStateImpl);
28+
29+
impl Default for RandomState {
30+
#[inline]
31+
fn default() -> RandomState {
32+
RandomState(RandomStateImpl::default())
33+
}
34+
}
35+
36+
impl BuildHasher for RandomState {
37+
type Hasher = RandomStateHasher;
38+
39+
#[inline]
40+
fn build_hasher(&self) -> RandomStateHasher {
41+
RandomStateHasher(self.0.build_hasher())
42+
}
43+
}
44+
45+
/// Wasmparser's hasher type used with [`RandomState`].
46+
pub struct RandomStateHasher(<RandomStateImpl as BuildHasher>::Hasher);
47+
48+
impl Hasher for RandomStateHasher {
49+
#[inline]
50+
fn finish(&self) -> u64 {
51+
self.0.finish()
52+
}
53+
#[inline]
54+
fn write(&mut self, bytes: &[u8]) {
55+
self.0.write(bytes)
56+
}
57+
#[inline]
58+
fn write_u8(&mut self, i: u8) {
59+
self.0.write_u8(i)
60+
}
61+
#[inline]
62+
fn write_u16(&mut self, i: u16) {
63+
self.0.write_u16(i)
64+
}
65+
#[inline]
66+
fn write_u32(&mut self, i: u32) {
67+
self.0.write_u32(i)
68+
}
69+
#[inline]
70+
fn write_u64(&mut self, i: u64) {
71+
self.0.write_u64(i)
72+
}
73+
#[inline]
74+
fn write_u128(&mut self, i: u128) {
75+
self.0.write_u128(i)
76+
}
77+
#[inline]
78+
fn write_usize(&mut self, i: usize) {
79+
self.0.write_usize(i)
80+
}
81+
#[inline]
82+
fn write_i8(&mut self, i: i8) {
83+
self.0.write_i8(i)
84+
}
85+
#[inline]
86+
fn write_i16(&mut self, i: i16) {
87+
self.0.write_i16(i)
88+
}
89+
#[inline]
90+
fn write_i32(&mut self, i: i32) {
91+
self.0.write_i32(i)
92+
}
93+
#[inline]
94+
fn write_i64(&mut self, i: i64) {
95+
self.0.write_i64(i)
96+
}
97+
#[inline]
98+
fn write_i128(&mut self, i: i128) {
99+
self.0.write_i128(i)
100+
}
101+
#[inline]
102+
fn write_isize(&mut self, i: isize) {
103+
self.0.write_isize(i)
104+
}
105+
}
106+
107+
// When the `std` feature is active reuse the standard library's implementation
108+
// of hash state and hasher.
109+
#[cfg(feature = "std")]
110+
use std::collections::hash_map::RandomState as RandomStateImpl;
111+
112+
// When the `std` feature is NOT active then rely on `ahash::RandomState`. That
113+
// relies on ASLR by default for randomness.
114+
#[derive(Default, Clone, Debug)]
115+
#[cfg(not(feature = "std"))]
116+
struct RandomStateImpl;
117+
118+
#[cfg(not(feature = "std"))]
119+
impl BuildHasher for RandomStateImpl {
120+
type Hasher = ahash::AHasher;
121+
122+
#[inline]
123+
fn build_hasher(&self) -> ahash::AHasher {
124+
ahash::RandomState::new().build_hasher()
125+
}
126+
}

crates/wasmparser/src/parser.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::binary_reader::WASM_MAGIC_NUMBER;
2+
use crate::prelude::*;
23
use crate::CoreTypeSectionReader;
34
use crate::{
45
limits::MAX_WASM_MODULE_SIZE, BinaryReader, BinaryReaderError, ComponentCanonicalSectionReader,
@@ -8,9 +9,9 @@ use crate::{
89
GlobalSectionReader, ImportSectionReader, InstanceSectionReader, MemorySectionReader, Result,
910
SectionLimited, TableSectionReader, TagSectionReader, TypeSectionReader,
1011
};
11-
use std::fmt;
12-
use std::iter;
13-
use std::ops::Range;
12+
use core::fmt;
13+
use core::iter;
14+
use core::ops::Range;
1415

1516
pub(crate) const WASM_MODULE_VERSION: u16 = 0x1;
1617

@@ -952,7 +953,7 @@ impl Parser {
952953
///
953954
/// ```
954955
/// use wasmparser::{Result, Parser, Chunk, Payload::*};
955-
/// use std::ops::Range;
956+
/// use core::ops::Range;
956957
///
957958
/// fn objdump_headers(mut wasm: &[u8]) -> Result<()> {
958959
/// let mut parser = Parser::new(0);

0 commit comments

Comments
 (0)