Merge wazero-fuzz into internal/integration_test (#872)
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
This commit is contained in:
33
.github/workflows/commit.yaml
vendored
33
.github/workflows/commit.yaml
vendored
@@ -186,3 +186,36 @@ jobs:
|
||||
cache: true
|
||||
|
||||
- run: make bench.check
|
||||
|
||||
# This ensures that internal/integration_test/fuzz is runnable, and is not intended to
|
||||
# run full-length fuzzing while trying to find low-hanging frontend bugs.
|
||||
fuzz:
|
||||
name: Minimal Fuzzing
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
cache: true
|
||||
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
# Cache corpus and artifacts so that we don't start from scratch but rather with a meaningful corpus
|
||||
# in the subsequent CI jobs.
|
||||
path: |
|
||||
~/.cargo
|
||||
~/.cache/go-build
|
||||
~/go/pkg/mod
|
||||
internal/integration_test/fuzz/target
|
||||
internal/integration_test/fuzz/fuzz/artifacts
|
||||
internal/integration_test/fuzz/fuzz/corpus
|
||||
key: build-fuzzer-${{ runner.os }}-go-${{ env.GO_VERSION }}-${{ hashFiles('**/go.sum', 'Makefile', '**/Cargo.lock', '**/Cargo.toml') }}
|
||||
|
||||
# Fuzzer requires nightly rustc.
|
||||
- run: rustup default nightly
|
||||
- run: cargo install cargo-fuzz
|
||||
# Run fuzzing only for a minute, not a full-length intensive one, but 60 seconds seems enough to find minor "front-end"
|
||||
# bugs which might exist in binary parser, validation, or instantiation phase while not pressuring CI jobs.
|
||||
- run: make fuzz fuzz_timeout_seconds=60
|
||||
|
||||
5
Makefile
5
Makefile
@@ -223,3 +223,8 @@ site: ## Serve website content
|
||||
clean: ## Ensure a clean build
|
||||
@rm -rf dist build coverage.txt
|
||||
@go clean -testcache
|
||||
|
||||
fuzz_timeout_seconds ?= 10
|
||||
.PHONY: fuzz
|
||||
fuzz:
|
||||
@cd internal/integration_test/fuzz && cargo fuzz run basic -- -max_total_time=$(fuzz_timeout_seconds)
|
||||
|
||||
@@ -2,7 +2,7 @@ This directory contains tests which use multiple packages. For example:
|
||||
|
||||
* `bench` contains benchmark tests.
|
||||
* `engine` contains variety of end-to-end tests, mainly to ensure the consistency in the behavior between engines.
|
||||
* `fuzzcases` contains variety of test cases found by [wazero-fuzz](https://github.com/tetratelabs/wazero-fuzz).
|
||||
* `fuzzcases` contains variety of test cases found by the [fuzz](./fuzz) testing.
|
||||
* `post1_0` contains end-to-end tests for features [finished](https://github.com/WebAssembly/proposals/blob/main/finished-proposals.md) after WebAssembly 1.0 (20191205).
|
||||
* `spectest` contains end-to-end tests with the [WebAssembly specification tests](https://github.com/WebAssembly/spec/tree/wg-1.0/test/core).
|
||||
* `vs` tests and benchmarks VS other WebAssembly runtimes.
|
||||
|
||||
1
internal/integration_test/fuzz/.gitignore
vendored
Normal file
1
internal/integration_test/fuzz/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
target
|
||||
172
internal/integration_test/fuzz/Cargo.lock
generated
Normal file
172
internal/integration_test/fuzz/Cargo.lock
generated
Normal file
@@ -0,0 +1,172 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.58"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb07d2053ccdbe10e2af2995a2f116c1330396493dc1269f6a91d0ae82e19704"
|
||||
|
||||
[[package]]
|
||||
name = "arbitrary"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a7924531f38b1970ff630f03eb20a2fde69db5c590c93b0f3482e95dcc5fd60"
|
||||
dependencies = [
|
||||
"derive_arbitrary",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.73"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
|
||||
|
||||
[[package]]
|
||||
name = "derive_arbitrary"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c9a577516173adb681466d517d39bd468293bc2c2a16439375ef0f35bba45f3d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flagset"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cda653ca797810c02f7ca4b804b40b8b95ae046eb989d356bce17919a8c25499"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "607c8a29735385251a339424dd462993c0fed8fa09d378f259377df08c126022"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "leb128"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67"
|
||||
|
||||
[[package]]
|
||||
name = "libfuzzer-sys"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "336244aaeab6a12df46480dc585802aa743a72d66b11937844c61bbca84c991d"
|
||||
dependencies = [
|
||||
"arbitrary",
|
||||
"cc",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.98"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-encoder"
|
||||
version = "0.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d443c5a7daae71697d97ec12ad70b4fe8766d3a0f4db16158ac8b781365892f7"
|
||||
dependencies = [
|
||||
"leb128",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-smith"
|
||||
version = "0.11.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3daf8042376731e1873eae92dd609e1d0781105ffc3ffbc452f7bab719c887e2"
|
||||
dependencies = [
|
||||
"arbitrary",
|
||||
"flagset",
|
||||
"indexmap",
|
||||
"leb128",
|
||||
"wasm-encoder",
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmparser"
|
||||
version = "0.89.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab5d3e08b13876f96dd55608d03cd4883a0545884932d5adf11925876c96daef"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmprinter"
|
||||
version = "0.2.39"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa9e5ee2f56cc8a5da489558114e8c118e5a8416d96aefe63dcf1b5b05b858c6"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wazero-fuzz-fuzz"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"libfuzzer-sys",
|
||||
"wasm-smith",
|
||||
"wasmprinter",
|
||||
]
|
||||
4
internal/integration_test/fuzz/Cargo.toml
Normal file
4
internal/integration_test/fuzz/Cargo.toml
Normal file
@@ -0,0 +1,4 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"fuzz",
|
||||
]
|
||||
59
internal/integration_test/fuzz/README.md
Normal file
59
internal/integration_test/fuzz/README.md
Normal file
@@ -0,0 +1,59 @@
|
||||
Fuzzing infrastructure for wazero engines via [wasm-tools](https://github.com/bytecodealliance/wasm-tools).
|
||||
|
||||
### Dependency
|
||||
|
||||
- [cargo](https://doc.rust-lang.org/cargo/getting-started/installation.html)
|
||||
- Needs to enable nightly (for libFuzzer).
|
||||
- [cargo-fuzz](https://github.com/rust-fuzz/cargo-fuzz)
|
||||
- `cargo install cargo-fuzz`
|
||||
|
||||
### Run Fuzzing
|
||||
|
||||
Currently, we only have one kind of fuzzing named `basic` where we compare the results from the compiler
|
||||
and interpreter engines, and see if there's a diff in them. To run the test, execute the following command:
|
||||
|
||||
```
|
||||
# Running on the host archictecture.
|
||||
cargo fuzz run basic
|
||||
|
||||
# Running on the specified architecture which is handy when developping on M1 Mac.
|
||||
cargo fuzz run basic-x86_64-apple-darwin
|
||||
```
|
||||
|
||||
See `cargo fuzz run --help` for the options. Especially, the following flags are useful:
|
||||
|
||||
- `-jobs=N`: `cargo fuzz run` by default only spawns one worker, so this flag helps do the parallel fuzzing.
|
||||
- usage: `cargo fuzz run basic -- -jobs=5` will run 5 parallel workers to run fuzzing jobs.
|
||||
- `-max_total_time`: the maximum total time in seconds to run the fuzzer.
|
||||
- usage: `cargo fuzz run basic -- -max_total_time=100` will run fuzzing for 100 seconds.
|
||||
- `-timeout` sets the timeout seconds _per fuzzing run_, not the entire job.
|
||||
|
||||
|
||||
### Reproduce errors
|
||||
|
||||
If the fuzzer encounters error, you would get the output like the following:
|
||||
|
||||
```
|
||||
Failed Wasm binary has been written to /Users/mathetake/wazero/internal/integration_test/fuzz/wazerolib/testdata/73c61e218b8547ef35271a22ca95f932dcc102bda9b3a9bdf1976e6ed36da31d.wasm
|
||||
Failed Wasm Text has been written to /Users/mathetake/wazero/internal/integration_test/fuzz/wazerolib/testdata/73c61e218b8547ef35271a22ca95f932dcc102bda9b3a9bdf1976e6ed36da31d.wat
|
||||
To reproduce the failure, execute: WASM_BINARY_PATH=/Users/mathetake/wazero/internal/integration_test/fuzz/wazerolib/testdata/73c61e218b8547ef35271a22ca95f932dcc102bda9b3a9bdf1976e6ed36da31d.wasm go test ./wazerolib/...
|
||||
```
|
||||
|
||||
then you can check the wasm and wat as well as reproduce the error by running
|
||||
```
|
||||
WASM_BINARY_PATH=/Users/mathetake/wazero/internal/integration_test/fuzz/wazerolib/testdata/73c61e218b8547ef35271a22ca95f932dcc102bda9b3a9bdf1976e6ed36da31d.wasm go test ./wazerolib/...
|
||||
```
|
||||
|
||||
|
||||
Also, in the bottom of the output, you can find the message as
|
||||
|
||||
```
|
||||
|
||||
Minimize test case with:
|
||||
|
||||
cargo fuzz tmin basic fuzz/artifacts/basic/crash-d2c1f5307fde6f057454606bcc21d5653be9be8d
|
||||
|
||||
────────────────────────────────────────────────────────────────────────────────
|
||||
```
|
||||
|
||||
and you can use that command to "minimize" the input binary while keeping the same error.
|
||||
4
internal/integration_test/fuzz/fuzz/.gitignore
vendored
Normal file
4
internal/integration_test/fuzz/fuzz/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
target/
|
||||
corpus/
|
||||
artifacts/
|
||||
coverage
|
||||
160
internal/integration_test/fuzz/fuzz/Cargo.lock
generated
Normal file
160
internal/integration_test/fuzz/fuzz/Cargo.lock
generated
Normal file
@@ -0,0 +1,160 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "arbitrary"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a7924531f38b1970ff630f03eb20a2fde69db5c590c93b0f3482e95dcc5fd60"
|
||||
dependencies = [
|
||||
"derive_arbitrary",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.73"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
|
||||
|
||||
[[package]]
|
||||
name = "derive_arbitrary"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c9a577516173adb681466d517d39bd468293bc2c2a16439375ef0f35bba45f3d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flagset"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cda653ca797810c02f7ca4b804b40b8b95ae046eb989d356bce17919a8c25499"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "607c8a29735385251a339424dd462993c0fed8fa09d378f259377df08c126022"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "leb128"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67"
|
||||
|
||||
[[package]]
|
||||
name = "libfuzzer-sys"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "336244aaeab6a12df46480dc585802aa743a72d66b11937844c61bbca84c991d"
|
||||
dependencies = [
|
||||
"arbitrary",
|
||||
"cc",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.98"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-encoder"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f76068e87fe9b837a6bc2ccded66784173eadb828c4168643e9fddf6f9ed2e61"
|
||||
dependencies = [
|
||||
"leb128",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-smith"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b73250e61e41d0e467b78559c7d761841005d724384bb0b78d52ff974acf5520"
|
||||
dependencies = [
|
||||
"arbitrary",
|
||||
"flagset",
|
||||
"indexmap",
|
||||
"leb128",
|
||||
"wasm-encoder",
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmparser"
|
||||
version = "0.87.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c04e207cd2e8ecb6f9bd28a2cf3119b4c6bfeee6fe3a25cc1daf8041d00a875"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wazero-fuzz"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "wazero-fuzz-fuzz"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"libfuzzer-sys",
|
||||
"wasm-smith",
|
||||
"wazero-fuzz",
|
||||
]
|
||||
50
internal/integration_test/fuzz/fuzz/Cargo.toml
Normal file
50
internal/integration_test/fuzz/fuzz/Cargo.toml
Normal file
@@ -0,0 +1,50 @@
|
||||
[package]
|
||||
name = "wazero-fuzz-fuzz"
|
||||
version = "0.0.0"
|
||||
authors = ["Automatically generated"]
|
||||
publish = false
|
||||
edition = "2018"
|
||||
|
||||
[package.metadata]
|
||||
cargo-fuzz = true
|
||||
|
||||
[dependencies]
|
||||
libfuzzer-sys = "0.4.3"
|
||||
wasm-smith = "0.11.4"
|
||||
wasmprinter = "0.2.39"
|
||||
|
||||
[[bin]]
|
||||
name = "basic"
|
||||
path = "fuzz_targets/basic.rs"
|
||||
test = false
|
||||
doc = false
|
||||
|
||||
# Note: having different bin target for each architecture in order to have separate corpus and artifacts and
|
||||
# to run both fuzzing on M1 Mac.
|
||||
[[bin]]
|
||||
name = "basic-x86_64-apple-darwin"
|
||||
path = "fuzz_targets/basic.rs"
|
||||
test = false
|
||||
doc = false
|
||||
target = "x86_64-apple-darwin"
|
||||
|
||||
[[bin]]
|
||||
name = "basic-aarch64-apple-darwin"
|
||||
path = "fuzz_targets/basic.rs"
|
||||
test = false
|
||||
doc = false
|
||||
target = "aarch64-apple-darwin"
|
||||
|
||||
[[bin]]
|
||||
name = "basic-x86_64-unknown-linux-gnu"
|
||||
path = "fuzz_targets/basic.rs"
|
||||
test = false
|
||||
doc = false
|
||||
target = "x86_64-unknown-linux-gnu"
|
||||
|
||||
[[bin]]
|
||||
name = "basic-aarch64-unknown-linux-gnu"
|
||||
path = "fuzz_targets/basic.rs"
|
||||
test = false
|
||||
doc = false
|
||||
target = "aarch64-unknown-linux-gnu"
|
||||
52
internal/integration_test/fuzz/fuzz/build.rs
Normal file
52
internal/integration_test/fuzz/fuzz/build.rs
Normal file
@@ -0,0 +1,52 @@
|
||||
use std::env::var;
|
||||
use std::process;
|
||||
use std::str;
|
||||
|
||||
fn main() {
|
||||
// Get the absolute path to the root of this repository (not cargo target).
|
||||
let wazero_fuzz_dir = format!("{}/..", var("CARGO_MANIFEST_DIR").unwrap());
|
||||
|
||||
let wazero_fuzz_lib_dir = format!("{}/wazerolib", wazero_fuzz_dir.as_str());
|
||||
let library_out_path = format!("{}/libwazero.a", wazero_fuzz_lib_dir);
|
||||
let library_source_path = format!("{}/lib.go", wazero_fuzz_lib_dir);
|
||||
|
||||
// Parse the GOARCH from the --target argument passed to cargo.
|
||||
let goarch = var("TARGET")
|
||||
.map(|target| {
|
||||
if target.contains("aarch64") {
|
||||
"arm64"
|
||||
} else if target.contains("x86") {
|
||||
"amd64"
|
||||
} else {
|
||||
panic!("unsupported target {:?}", target)
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
// Build the wazero library via go build -buildmode c-archive....
|
||||
let mut command = process::Command::new("go");
|
||||
command.current_dir(&wazero_fuzz_lib_dir);
|
||||
command.arg("build");
|
||||
command.args(&["-buildmode", "c-archive"]);
|
||||
command.args(&["-o", library_out_path.as_str()]);
|
||||
command.args(&[library_source_path.as_str()]);
|
||||
command.env("GOARCH", goarch);
|
||||
command.env("CGO_ENABLED", "1");
|
||||
|
||||
let output = command.output().expect("failed to execute process");
|
||||
|
||||
// If the build didn't succeed, exit the process with the stderr from Go's command.
|
||||
if !output.status.success() {
|
||||
panic!(
|
||||
"failed to compile wazero lib: {}\n",
|
||||
str::from_utf8(&output.stderr).unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
// Ensures that we rebuild the library when the source code for wazero file has been changed.
|
||||
println!("cargo:rerun-if-changed={}/../../..", wazero_fuzz_dir);
|
||||
|
||||
// Ensures that the linker can find the wazero library.
|
||||
println!("cargo:rustc-link-search={}", wazero_fuzz_lib_dir);
|
||||
println!("cargo:rustc-link-lib=static=wazero");
|
||||
}
|
||||
75
internal/integration_test/fuzz/fuzz/fuzz_targets/basic.rs
Normal file
75
internal/integration_test/fuzz/fuzz/fuzz_targets/basic.rs
Normal file
@@ -0,0 +1,75 @@
|
||||
#![no_main]
|
||||
|
||||
use libfuzzer_sys::arbitrary::{Result, Unstructured};
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use wasm_smith::SwarmConfig;
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
drop(run(data));
|
||||
});
|
||||
|
||||
fn run(data: &[u8]) -> Result<()> {
|
||||
// Create the random source.
|
||||
let mut u = Unstructured::new(data);
|
||||
|
||||
// Generate the configuration.
|
||||
let mut config: SwarmConfig = u.arbitrary()?;
|
||||
|
||||
// 64-bit memory won't be supported by wazero.
|
||||
config.memory64_enabled = false;
|
||||
// Also, multiple memories are not supported.
|
||||
config.max_memories = 1;
|
||||
// TODO: after having CompiledModule.Imports(), enable this with dummy imports.
|
||||
// See https://github.com/bytecodealliance/wasmtime/blob/f242975c49385edafe4f72dfa5f0ff6aae23eda3/crates/fuzzing/src/oracles/dummy.rs#L6-L20
|
||||
config.max_imports = 0;
|
||||
// If we don't set the limit, we will soon reach the OOM and the fuzzing will be killed by OS.
|
||||
config.max_memory_pages = 10;
|
||||
config.memory_max_size_required = true;
|
||||
// Don't test too large tables.
|
||||
config.max_tables = 2;
|
||||
config.max_table_elements = 1_000;
|
||||
config.table_max_size_required = true;
|
||||
|
||||
// max_instructions is set to 100 by default which seems a little bit smaller.
|
||||
config.max_instructions = 5000;
|
||||
|
||||
// Without canonicalization of NaNs, the results cannot be matched among engines.
|
||||
config.canonicalize_nans = true;
|
||||
|
||||
// Export all the things so that we can invoke them.
|
||||
config.export_everything = true;
|
||||
|
||||
// Ensures that at least one function exists.
|
||||
config.min_funcs = 1;
|
||||
config.max_funcs = config.max_funcs.max(1);
|
||||
|
||||
// Generate the random module via wasm-smith.
|
||||
let mut module = wasm_smith::Module::new(config.clone(), &mut u)?;
|
||||
module.ensure_termination(1000);
|
||||
let module_bytes = module.to_bytes();
|
||||
|
||||
let wat_bytes = wasmprinter::print_bytes(&module_bytes).unwrap();
|
||||
|
||||
// Pass the randomly generated module to the wazero library.
|
||||
unsafe {
|
||||
require_no_diff(
|
||||
module_bytes.as_ptr(),
|
||||
module_bytes.len(),
|
||||
wat_bytes.as_ptr(),
|
||||
wat_bytes.len(),
|
||||
);
|
||||
}
|
||||
|
||||
// We always return Ok as inside require_no_diff, we cause panic if the binary is interesting.
|
||||
Ok(())
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
// require_no_diff is implemented in Go, and accepts the pointer to the binary and its size.
|
||||
fn require_no_diff(
|
||||
binary_ptr: *const u8,
|
||||
binary_size: usize,
|
||||
wat_ptr: *const u8,
|
||||
wat_size: usize,
|
||||
);
|
||||
}
|
||||
7
internal/integration_test/fuzz/go.mod
Normal file
7
internal/integration_test/fuzz/go.mod
Normal file
@@ -0,0 +1,7 @@
|
||||
module github.com/tetratelabs/wazero/internal/integration_test/fuzz
|
||||
|
||||
go 1.18
|
||||
|
||||
require github.com/tetratelabs/wazero v1.0.0-pre.1
|
||||
|
||||
replace github.com/tetratelabs/wazero => ../../../
|
||||
3
internal/integration_test/fuzz/wazerolib/.gitignore
vendored
Normal file
3
internal/integration_test/fuzz/wazerolib/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
testdata/
|
||||
libwazero.a
|
||||
libwazero.h
|
||||
268
internal/integration_test/fuzz/wazerolib/lib.go
Normal file
268
internal/integration_test/fuzz/wazerolib/lib.go
Normal file
@@ -0,0 +1,268 @@
|
||||
package main
|
||||
|
||||
import "C"
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"reflect"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"github.com/tetratelabs/wazero"
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
)
|
||||
|
||||
func main() {}
|
||||
|
||||
// require_no_diff ensures that the behavior is the same between the compiler and the interpreter for any given binary.
|
||||
// And if there's diff, this also saves the problematic binary and wat into testdata directory.
|
||||
//
|
||||
//export require_no_diff
|
||||
func require_no_diff(binaryPtr uintptr, binarySize int, watPtr uintptr, watSize int) {
|
||||
wasmBin := *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{
|
||||
Data: binaryPtr,
|
||||
Len: binarySize,
|
||||
Cap: binarySize,
|
||||
}))
|
||||
|
||||
wat := *(*string)(unsafe.Pointer(&reflect.SliceHeader{
|
||||
Data: watPtr,
|
||||
Len: watSize,
|
||||
Cap: watSize,
|
||||
}))
|
||||
|
||||
// Create two runtimes.
|
||||
|
||||
failed := true
|
||||
defer func() {
|
||||
if failed {
|
||||
// If the test fails, we save the binary and wat into testdata directory.
|
||||
saveFailedBinary(wasmBin, wat)
|
||||
}
|
||||
}()
|
||||
|
||||
requireNoDiff(wasmBin, func(err error) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
})
|
||||
|
||||
failed = false
|
||||
return
|
||||
}
|
||||
|
||||
// requireNoDiff ensures that the behavior is the same between the compiler and the interpreter for any given binary.
|
||||
func requireNoDiff(wasmBin []byte, requireNoError func(err error)) {
|
||||
// Choose the context to use for function calls.
|
||||
ctx := context.Background()
|
||||
|
||||
compiler := wazero.NewRuntimeWithConfig(ctx, wazero.NewRuntimeConfigCompiler())
|
||||
interpreter := wazero.NewRuntimeWithConfig(ctx, wazero.NewRuntimeConfigInterpreter())
|
||||
|
||||
defer compiler.Close(ctx)
|
||||
defer interpreter.Close(ctx)
|
||||
|
||||
compiledCompiled, err := compiler.CompileModule(ctx, wasmBin)
|
||||
requireNoError(err)
|
||||
|
||||
interpreterCompiled, err := interpreter.CompileModule(ctx, wasmBin)
|
||||
requireNoError(err)
|
||||
|
||||
// Instantiate module.
|
||||
compilerMod, compilerInstErr := compiler.InstantiateModule(ctx, compiledCompiled, wazero.NewModuleConfig())
|
||||
interpreterMod, interpreterInstErr := interpreter.InstantiateModule(ctx, interpreterCompiled, wazero.NewModuleConfig())
|
||||
|
||||
okToInvoke, err := ensureInstantiationError(compilerInstErr, interpreterInstErr)
|
||||
requireNoError(err)
|
||||
|
||||
if okToInvoke {
|
||||
err = ensureInvocationResultMatch(compilerMod, interpreterMod, interpreterCompiled.ExportedFunctions())
|
||||
requireNoError(err)
|
||||
}
|
||||
}
|
||||
|
||||
const valueTypeVector = 0x7b
|
||||
|
||||
// ensureInvocationResultMatch invokes all the exported functions from the module, and compare all the results between compiler vs interpreter.
|
||||
func ensureInvocationResultMatch(compiledMod, interpreterMod api.Module, exportedFunctions map[string]api.FunctionDefinition) (err error) {
|
||||
ctx := context.Background()
|
||||
|
||||
outer:
|
||||
for name, def := range exportedFunctions {
|
||||
resultTypes := def.ResultTypes()
|
||||
for _, rt := range resultTypes {
|
||||
switch rt {
|
||||
case api.ValueTypeI32, api.ValueTypeI64, api.ValueTypeF32, api.ValueTypeF64, valueTypeVector:
|
||||
default:
|
||||
// For the sake of simplicity in the assertion, we only invoke the function with the basic types.
|
||||
continue outer
|
||||
}
|
||||
}
|
||||
|
||||
cmpF := compiledMod.ExportedFunction(name)
|
||||
intF := interpreterMod.ExportedFunction(name)
|
||||
|
||||
params := getDummyValues(def.ParamTypes())
|
||||
cmpRes, cmpErr := cmpF.Call(ctx, params...)
|
||||
intRes, intErr := intF.Call(ctx, params...)
|
||||
if errMismatch := ensureInvocationError(cmpErr, intErr); errMismatch != nil {
|
||||
panic(fmt.Sprintf("error mismatch on invoking %s: %v", name, errMismatch))
|
||||
}
|
||||
|
||||
matched := true
|
||||
var typesIndex int
|
||||
for i := 0; i < len(cmpRes); i++ {
|
||||
switch resultTypes[typesIndex] {
|
||||
case api.ValueTypeI32, api.ValueTypeF32:
|
||||
matched = matched && uint32(cmpRes[i]) == uint32(intRes[i])
|
||||
case api.ValueTypeI64, api.ValueTypeF64:
|
||||
matched = matched && cmpRes[i] == intRes[i]
|
||||
case valueTypeVector:
|
||||
matched = matched && cmpRes[i] == intRes[i] && cmpRes[i+1] == intRes[i+1]
|
||||
i++ // We need to advance twice (lower and higher 64bits)
|
||||
}
|
||||
typesIndex++
|
||||
}
|
||||
|
||||
if !matched {
|
||||
err = fmt.Errorf("result mismatch on invoking '%s':\n\tinterpreter got: %v\n\tcompiler got: %v", name, intRes, cmpRes)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// getDummyValues returns a dummy input values for function invocations.
|
||||
func getDummyValues(valueTypes []api.ValueType) (ret []uint64) {
|
||||
for _, vt := range valueTypes {
|
||||
if vt != 0x7b { // v128
|
||||
ret = append(ret, 0)
|
||||
} else {
|
||||
ret = append(ret, 0, 0)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ensureInvocationError ensures that function invocation errors returned by interpreter and compiler match each other's.
|
||||
func ensureInvocationError(compilerErr, interpErr error) error {
|
||||
if compilerErr == nil && interpErr == nil {
|
||||
return nil
|
||||
} else if compilerErr == nil && interpErr != nil {
|
||||
return fmt.Errorf("compiler returned no error, but interpreter got: %w", interpErr)
|
||||
} else if compilerErr != nil && interpErr == nil {
|
||||
return fmt.Errorf("interpreter returned no error, but compiler got: %w", compilerErr)
|
||||
}
|
||||
|
||||
compilerErrMsg, interpErrMsg := compilerErr.Error(), interpErr.Error()
|
||||
if idx := strings.Index(compilerErrMsg, "\n"); idx >= 0 {
|
||||
compilerErrMsg = compilerErrMsg[:strings.Index(compilerErrMsg, "\n")]
|
||||
}
|
||||
if idx := strings.Index(interpErrMsg, "\n"); idx >= 0 {
|
||||
interpErrMsg = interpErrMsg[:strings.Index(interpErrMsg, "\n")]
|
||||
}
|
||||
|
||||
if compilerErrMsg != interpErrMsg {
|
||||
return fmt.Errorf("error mismatch:\n\tinterpreter: %v\n\tcompiler: %v", interpErr, compilerErr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ensureInstantiationError ensures that instantiation errors returned by interpreter and compiler match each other's.
|
||||
func ensureInstantiationError(compilerErr, interpErr error) (okToInvoke bool, err error) {
|
||||
if compilerErr == nil && interpErr == nil {
|
||||
return true, nil
|
||||
} else if compilerErr == nil && interpErr != nil {
|
||||
return false, fmt.Errorf("compiler returned no error, but interpreter got: %w", interpErr)
|
||||
} else if compilerErr != nil && interpErr == nil {
|
||||
return false, fmt.Errorf("interpreter returned no error, but compiler got: %w", compilerErr)
|
||||
}
|
||||
|
||||
compilerErrMsg, interpErrMsg := compilerErr.Error(), interpErr.Error()
|
||||
if idx := strings.Index(compilerErrMsg, "\n"); idx >= 0 {
|
||||
compilerErrMsg = compilerErrMsg[:strings.Index(compilerErrMsg, "\n")]
|
||||
}
|
||||
if idx := strings.Index(interpErrMsg, "\n"); idx >= 0 {
|
||||
interpErrMsg = interpErrMsg[:strings.Index(interpErrMsg, "\n")]
|
||||
}
|
||||
|
||||
if !allowedErrorDuringInstantiation(compilerErrMsg) {
|
||||
return false, fmt.Errorf("invalid error occur with compiler: %v", compilerErr)
|
||||
} else if !allowedErrorDuringInstantiation(interpErrMsg) {
|
||||
return false, fmt.Errorf("invalid error occur with interpreter: %v", interpErrMsg)
|
||||
}
|
||||
|
||||
if compilerErrMsg != interpErrMsg {
|
||||
return false, fmt.Errorf("error mismatch:\n\tinterpreter: %v\n\tcompiler: %v", interpErr, compilerErr)
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// allowedErrorDuringInstantiation checks if the error message is considered sane.
|
||||
func allowedErrorDuringInstantiation(errMsg string) bool {
|
||||
// This happens when data segment causes out of bound, but it is considered as runtime-error in WebAssembly 2.0
|
||||
// which is fine.
|
||||
if strings.HasPrefix(errMsg, "data[") && strings.HasSuffix(errMsg, "]: out of bounds memory access") {
|
||||
return true
|
||||
}
|
||||
|
||||
// Start function failure is neither instantiation nor compilation error, but rather a runtime error, so that is fine.
|
||||
if strings.HasPrefix(errMsg, "start function[") && strings.Contains(errMsg, "failed: wasm error:") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const failedCasesDir = "wazerolib/testdata"
|
||||
|
||||
// saveFailedBinary writes binary and wat into failedCasesDir so that it is easy to reproduce the error.
|
||||
func saveFailedBinary(bin []byte, wat string) {
|
||||
checksum := sha256.Sum256(bin)
|
||||
checkSumStr := hex.EncodeToString(checksum[:])
|
||||
|
||||
dir, err := os.Getwd()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
testDataDir := path.Join(dir, failedCasesDir)
|
||||
binaryPath := path.Join(testDataDir, fmt.Sprintf("%s.wasm", checkSumStr))
|
||||
f, err := os.Create(binaryPath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
defer f.Close()
|
||||
|
||||
_, err = f.Write(bin)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
watPath := path.Join(testDataDir, fmt.Sprintf("%s.wat", checkSumStr))
|
||||
watF, err := os.Create(watPath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
defer watF.Close()
|
||||
|
||||
_, err = watF.Write([]byte(wat))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Printf(`
|
||||
Failed WebAssembly Text:
|
||||
%s
|
||||
|
||||
Failed Wasm binary has been written to %s
|
||||
Failed Wasm Text has been written to %s
|
||||
To reproduce the failure, execute: WASM_BINARY_PATH=%s go test ./wazerolib/...
|
||||
|
||||
|
||||
`, wat, binaryPath, watPath, binaryPath)
|
||||
}
|
||||
20
internal/integration_test/fuzz/wazerolib/lib_test.go
Normal file
20
internal/integration_test/fuzz/wazerolib/lib_test.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/tetratelabs/wazero/internal/testing/require"
|
||||
)
|
||||
|
||||
// TestReRunFailedCase re-runs the failed case specified by WASM_BINARY_NAME in testdata directory.
|
||||
func TestReRunFailedCase(t *testing.T) {
|
||||
binaryPath := os.Getenv("WASM_BINARY_PATH")
|
||||
|
||||
wasmBin, err := os.ReadFile(binaryPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
requireNoDiff(wasmBin, func(err error) { require.NoError(t, err) })
|
||||
}
|
||||
0
internal/integration_test/fuzz/wazerolib/testdata/.keep
vendored
Normal file
0
internal/integration_test/fuzz/wazerolib/testdata/.keep
vendored
Normal file
@@ -1,2 +0,0 @@
|
||||
Here we collect the test cases found by [wazero-fuzz](https://github.com/tetratelabs/wazero-fuzz).
|
||||
The test data in `./testdata` directory is named as `12345.wat` where the numebr corresponds to the PR or issue in this repostiroy.
|
||||
Reference in New Issue
Block a user