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