1aafe0a6fSNick Fitzgerald# Reducing Test Cases 2aafe0a6fSNick Fitzgerald 3aafe0a6fSNick FitzgeraldWhen reporting a bug, or investing a bug report, in Wasmtime it is easier for 4aafe0a6fSNick Fitzgeraldeveryone involved when there is a test case that reproduces the bug. It is even 5aafe0a6fSNick Fitzgeraldbetter when that test case is as small as possible, so that developers don't 6aafe0a6fSNick Fitzgeraldneed to wade through megabytes of unrelated Wasm that isn't necessary to 7aafe0a6fSNick Fitzgeraldshowcase the bug. The process of taking a large test case and stripping out the 8aafe0a6fSNick Fitzgeraldunnecessary bits is called *test case reduction*. 9aafe0a6fSNick Fitzgerald 10*ac3358b1SChris Fallin[The `wasm-tools shrink` tool](https://github.com/bytecodealliance/wasm-tools) can 11aafe0a6fSNick Fitzgeraldautomatically reduce Wasm test cases when given 12aafe0a6fSNick Fitzgerald 13aafe0a6fSNick Fitzgerald1. the original, unreduced test case, and 14aafe0a6fSNick Fitzgerald2. a predicate script to determine whether the bug reproduces on a given reduced 15aafe0a6fSNick Fitzgerald test case candidate. 16aafe0a6fSNick Fitzgerald 17aafe0a6fSNick FitzgeraldIf the test case causes Wasmtime to segfault, the script can run Wasmtime and 18aafe0a6fSNick Fitzgeraldcheck its exit code. If the test case produces a different result in Wasmtime vs 19aafe0a6fSNick Fitzgeraldanother Wasm engine, the script can run both engines and compare their 20aafe0a6fSNick Fitzgeraldresults. It is also often useful to `grep` through the candidate's WAT 21aafe0a6fSNick Fitzgeralddisassembly to make sure that relevant features and instructions are present. 22aafe0a6fSNick Fitzgerald 235e27f3ebSNick FitzgeraldNote that there are also a few other test-case reducers that can operate on 245e27f3ebSNick FitzgeraldWasm. All of them, including `wasm-shrink`, work fairly similarly at a high 255e27f3ebSNick Fitzgeraldlevel, but often if one reducer gets stuck in a local minimum, another reducer 265e27f3ebSNick Fitzgeraldcan pick up from there and reduce the test case further due to differences in 275e27f3ebSNick Fitzgeraldthe details of their implementations. Therefore, if you find that `wasm-shrink` 285e27f3ebSNick Fitzgeraldisn't very effective on a particular test case, you can try continuing reduction 295e27f3ebSNick Fitzgeraldwith one of the following: 305e27f3ebSNick Fitzgerald 315e27f3ebSNick Fitzgerald* [Binaryen's `wasm-reduce` 325e27f3ebSNick Fitzgerald tool](https://github.com/WebAssembly/binaryen?tab=readme-ov-file#tools) 335e27f3ebSNick Fitzgerald* [`creduce`, which can be effective at reducing Wasm test cases when 345e27f3ebSNick Fitzgerald disassembled into their `.wat` text 355e27f3ebSNick Fitzgerald format](https://github.com/csmith-project/creduce) 365e27f3ebSNick Fitzgerald 37aafe0a6fSNick Fitzgerald## Case Study: [Issue #7779](https://github.com/bytecodealliance/wasmtime/issues/7779) 38aafe0a6fSNick Fitzgerald 39aafe0a6fSNick FitzgeraldA bug was reported involving the `memory.init` instruction. The attached test 40aafe0a6fSNick Fitzgeraldcase was larger than it needed to be and contained a bunch of functions and 41aafe0a6fSNick Fitzgeraldother things that were irrelevant. A perfect use case for `wasm-tools shrink`! 42aafe0a6fSNick Fitzgerald 43aafe0a6fSNick FitzgeraldFirst, we needed a predicate script to identify the reported buggy behavior. The 44aafe0a6fSNick Fitzgeraldscript is given the candidate test case as its first argument and must exit zero 45aafe0a6fSNick Fitzgeraldif the candidate exhibits the bug and non-zero otherwise. 46aafe0a6fSNick Fitzgerald 47aafe0a6fSNick Fitzgerald```bash 48aafe0a6fSNick Fitzgerald#!/usr/bin/env bash 49aafe0a6fSNick Fitzgerald 50aafe0a6fSNick Fitzgerald# Propagate failure: exit non-zero if any individual command exits non-zero. 51aafe0a6fSNick Fitzgeraldset -e 52aafe0a6fSNick Fitzgerald 53aafe0a6fSNick Fitzgerald# Disassembly the candidate into WAT. Make sure the `memory.init` instruction 54aafe0a6fSNick Fitzgerald# is present, since the bug report is about that instruction. Additionally, make 55aafe0a6fSNick Fitzgerald# sure it is referencing the same data segment. 56aafe0a6fSNick Fitzgeraldwasm-tools print $1 | grep -q 'memory.init 2' 57aafe0a6fSNick Fitzgerald 58aafe0a6fSNick Fitzgerald# Make sure that the data segment in question remains unchanged, as mutating its 59aafe0a6fSNick Fitzgerald# length can change the semantics of the `memory.init` instruction. 60aafe0a6fSNick Fitzgeraldwasm-tools print $1 | grep -Eq '\(data \(;2;\) \(i32\.const 32\) "\\01\\02\\03\\04\\05\\06\\07\\08\\ff"\)' 61aafe0a6fSNick Fitzgerald 62aafe0a6fSNick Fitzgerald# Make sure that the `_start` function that contains the `memory.init` is still 63aafe0a6fSNick Fitzgerald# exported, so that running the Wasm will run the `memory.init` instruction. 64aafe0a6fSNick Fitzgeraldwasm-tools print $1 | grep -Eq '\(export "_start" \(func 0\)\)' 65aafe0a6fSNick Fitzgerald 66aafe0a6fSNick Fitzgerald# Run the testcase in Wasmtime and make sure that it traps the same way as the 67aafe0a6fSNick Fitzgerald# original test case. 68aafe0a6fSNick Fitzgeraldcargo run --manifest-path ~/wasmtime/Cargo.toml -- run $1 2>&1 \ 69aafe0a6fSNick Fitzgerald | grep -q 'wasm trap: out of bounds memory access' 70aafe0a6fSNick Fitzgerald``` 71aafe0a6fSNick Fitzgerald 72aafe0a6fSNick FitzgeraldNote that this script is a little fuzzy! It just checks for `memory.init` and a 73aafe0a6fSNick Fitzgeraldparticular trap. That trap can correctly occur according to Wasm semantics when 74aafe0a6fSNick Fitzgerald`memory.init` is given certain inputs! This means we need to double check that 75aafe0a6fSNick Fitzgeraldthe reduced test case actually exhibits a real bug and its inputs haven't been 76aafe0a6fSNick Fitzgeraldtransformed into something that Wasm semantics specify should indeed 77aafe0a6fSNick Fitzgeraldtrap. Sometimes writing very precise predicate scripts is difficult, but we do 78aafe0a6fSNick Fitzgeraldthe best we can and usually it works out fine. 79aafe0a6fSNick Fitzgerald 80aafe0a6fSNick FitzgeraldWith the predicate script in hand, we can automatically reduce the original test 81aafe0a6fSNick Fitzgeraldcase: 82aafe0a6fSNick Fitzgerald 83aafe0a6fSNick Fitzgerald```shell-session 84aafe0a6fSNick Fitzgerald$ wasm-tools shrink predicate.sh test-case.wasm 85aafe0a6fSNick Fitzgerald369 bytes (1.07% smaller) 86aafe0a6fSNick Fitzgerald359 bytes (3.75% smaller) 87aafe0a6fSNick Fitzgerald357 bytes (4.29% smaller) 88aafe0a6fSNick Fitzgerald354 bytes (5.09% smaller) 89aafe0a6fSNick Fitzgerald344 bytes (7.77% smaller) 90aafe0a6fSNick Fitzgerald... 91aafe0a6fSNick Fitzgerald118 bytes (68.36% smaller) 92aafe0a6fSNick Fitzgerald106 bytes (71.58% smaller) 93aafe0a6fSNick Fitzgerald94 bytes (74.80% smaller) 94aafe0a6fSNick Fitzgerald91 bytes (75.60% smaller) 95aafe0a6fSNick Fitzgerald90 bytes (75.87% smaller) 96aafe0a6fSNick Fitzgerald 97aafe0a6fSNick Fitzgeraldtest-case.shrunken.wasm :: 90 bytes (75.87% smaller) 98aafe0a6fSNick Fitzgerald================================================================================ 99aafe0a6fSNick Fitzgerald(module 100aafe0a6fSNick Fitzgerald (type (;0;) (func)) 101aafe0a6fSNick Fitzgerald (func (;0;) (type 0) 102aafe0a6fSNick Fitzgerald (local i32 f32 i64 f64) 103aafe0a6fSNick Fitzgerald i32.const 0 104aafe0a6fSNick Fitzgerald i32.const 9 105aafe0a6fSNick Fitzgerald i32.const 0 106aafe0a6fSNick Fitzgerald memory.init 2 107aafe0a6fSNick Fitzgerald ) 108aafe0a6fSNick Fitzgerald (memory (;0;) 1 5) 109aafe0a6fSNick Fitzgerald (export "_start" (func 0)) 110aafe0a6fSNick Fitzgerald (data (;0;) (i32.const 8) "") 111aafe0a6fSNick Fitzgerald (data (;1;) (i32.const 16) "") 112aafe0a6fSNick Fitzgerald (data (;2;) (i32.const 32) "\01\02\03\04\05\06\07\08\ff") 113aafe0a6fSNick Fitzgerald) 114aafe0a6fSNick Fitzgerald================================================================================ 115aafe0a6fSNick Fitzgerald``` 116aafe0a6fSNick Fitzgerald 117aafe0a6fSNick FitzgeraldIn this case, the arguments to the original `memory.init` instruction haven't 118aafe0a6fSNick Fitzgeraldchanged, and neither has the relevant data segment, so the reduced test case 119aafe0a6fSNick Fitzgeraldshould exhibit the same behavior as the original. 120aafe0a6fSNick Fitzgerald 121aafe0a6fSNick FitzgeraldIn the end, it was [determined that Wasmtime was behaving as 122aafe0a6fSNick Fitzgeraldexpected](https://github.com/bytecodealliance/wasmtime/issues/7779#issuecomment-1894350625), 123aafe0a6fSNick Fitzgeraldbut the presence of the reduced test case makes it much easier to make that 124aafe0a6fSNick Fitzgeralddetermination. 125