|
8 | 8 | from . import utils |
9 | 9 |
|
10 | 10 |
|
11 | | -class PreserveFuzzTest(utils.BinaryenTestCase): |
12 | | - def test_against_js(self): |
13 | | - # When --fuzz-against-js is used, the wasm is only going to be fuzzed |
14 | | - # against JS, so the fuzzer mutates the boundary in valid ways, even if |
15 | | - # --fuzz-preserve-imports-exports is set. |
16 | | - # |
17 | | - # Testing this deterministically is too hard (as the fuzzer evolves, it |
18 | | - # will handle random data differently, and the test would constantly get |
19 | | - # out of date). Instead, test randomly, but in a way that the chance of |
20 | | - # a flake is unrealistic. |
21 | | - max_size = 1024 |
| 11 | +# Runs the fuzzer many times and allows checking for specific variety in the |
| 12 | +# output. Calls hooks: |
| 13 | +# |
| 14 | +# self.found_variety() - checks if we found what we are looking for |
| 15 | +# self.process_wat(wat) - receives the current fuzz wat |
| 16 | +# |
| 17 | +class FuzzerVarietyTester: |
| 18 | + # Run until we find what we want. Stop only if we reached a max number |
| 19 | + # of iterations and a timeout. |
| 20 | + max_time = 60 |
| 21 | + min_iters = 200 |
| 22 | + |
| 23 | + # The maximum size of the wasm-generating input |
| 24 | + max_size = 1024 |
| 25 | + |
| 26 | + def __init__(self, initial): |
| 27 | + self.initial = initial |
| 28 | + |
| 29 | + def test(self): |
22 | 30 | temp_dat = tempfile.NamedTemporaryFile(suffix='.dat') |
23 | | - initial = self.input_path('fuzz.wat') |
24 | | - |
25 | | - # The set of all params we see, for the import that is refinable. Ditto |
26 | | - # for export results. |
27 | | - import_params = set() |
28 | | - export_results = set() |
29 | 31 |
|
30 | | - # Run until we find what we want. Stop only if we reached a max number |
31 | | - # of iterations and a timeout. |
32 | | - min_iters = 200 |
33 | 32 | start_time = time.time() |
34 | | - # Locally this succeeds in less than 1 second. Give it a very wide |
35 | | - # margin of error to avoid flakes. |
36 | | - max_time = start_time + 60 |
| 33 | + stop_time = start_time + self.max_time |
37 | 34 |
|
38 | 35 | i = 0 |
39 | 36 | while True: |
40 | 37 | i += 1 |
41 | 38 |
|
42 | | - if self.found_expected(import_params) and self.found_expected(export_results): |
| 39 | + # Stop early if we found what we are looking for. |
| 40 | + if self.found_variety(): |
43 | 41 | print(f"{i} iterations {round(time.time() - start_time, 2)} seconds)") |
44 | | - print(f'proper import_params : {import_params}') |
45 | | - print(f'proper export_results: {export_results}') |
| 42 | + print(f'proper import_params : {self.import_params}') |
| 43 | + print(f'proper export_results: {self.export_results}') |
46 | 44 | return |
47 | 45 |
|
48 | | - if i > min_iters and time.time() > max_time: |
| 46 | + if i > self.min_iters and time.time() > stop_time: |
49 | 47 | raise Exception('looked too long and still failed') |
50 | 48 |
|
51 | 49 | # Generate raw random data |
52 | | - size = random.randint(1, max_size) |
| 50 | + size = random.randint(1, self.max_size) |
53 | 51 | with open(temp_dat.name, 'wb') as f: |
54 | 52 | f.write(bytes([random.randint(0, 255) for x in range(size)])) |
55 | 53 |
|
56 | 54 | # Generate the fuzz testcase from the random data + the initial |
57 | 55 | # contents. |
58 | | - args = ['-ttf', temp_dat.name, '--initial-fuzz=' + initial, '-all'] |
59 | | - args += ['--fuzz-preserve-imports-exports', '--fuzz-against-js'] |
| 56 | + args = ['-ttf', temp_dat.name, '--initial-fuzz=' + self.initial, '-all'] |
| 57 | + args += self.ttf_args |
60 | 58 | args += ['--print'] |
61 | 59 | wat = shared.run_process(shared.WASM_OPT + args, |
62 | 60 | stdout=subprocess.PIPE).stdout |
63 | 61 |
|
64 | | - # The things that begin reffed might end up not reffed, if mutation |
65 | | - # removes the refs. Check for that. |
66 | | - import_reffed_is_reffed = '(ref.func $import-reffed)' in wat |
67 | | - export_reffed_is_reffed = '(ref.func $export-reffed)' in wat |
68 | | - |
69 | | - # Find the params/results that might be refined. |
70 | | - for line in wat.splitlines(): |
71 | | - if line.startswith(' (import "module" "base" (func $import '): |
72 | | - params, results = self.parse_params_results(line) |
73 | | - import_params.add(params) |
74 | | - assert results == '(result eqref)', 'cannot refine import result' |
75 | | - elif line.startswith(' (import "module" "base" (func $import-reffed '): |
76 | | - params, results = self.parse_params_results(line) |
77 | | - if import_reffed_is_reffed: |
78 | | - assert params == '(param i32 anyref)', 'cannot refine reffed stuff' |
79 | | - assert results == '(result eqref)', 'cannot refine import result' |
80 | | - if line.startswith(' (func $export '): |
81 | | - params, results = self.parse_params_results(line) |
82 | | - assert params == '(param $0 i32) (param $1 anyref)', 'cannot refine export params' |
83 | | - export_results.add(results) |
84 | | - if line.startswith(' (func $export-reffed '): |
85 | | - params, results = self.parse_params_results(line) |
86 | | - assert params == '(param $0 i32) (param $1 anyref)', 'cannot refine export params' |
87 | | - if export_reffed_is_reffed: |
88 | | - assert results == '(result eqref)', 'cannot refine reffed stuff' |
| 62 | + self.process_wat(wat) |
| 63 | + |
| 64 | + |
| 65 | +class FuzzAgainstJSVarietyTester(FuzzerVarietyTester): |
| 66 | + # When --fuzz-against-js is used, the wasm is only going to be fuzzed |
| 67 | + # against JS, so the fuzzer mutates the boundary in valid ways, even if |
| 68 | + # --fuzz-preserve-imports-exports is set. |
| 69 | + # |
| 70 | + # Testing this deterministically is too hard (as the fuzzer evolves, it |
| 71 | + # will handle random data differently, and the test would constantly get |
| 72 | + # out of date). Instead, test randomly, but in a way that the chance of |
| 73 | + # a flake is unrealistic. |
| 74 | + ttf_args = ['--fuzz-preserve-imports-exports', '--fuzz-against-js'] |
| 75 | + |
| 76 | + def __init__(self, initial): |
| 77 | + super().__init__(initial) |
| 78 | + |
| 79 | + # The set of all params we see, for the import that is refinable. Ditto |
| 80 | + # for export results. |
| 81 | + self.import_params = set() |
| 82 | + self.export_results = set() |
| 83 | + |
| 84 | + def found_variety(self): |
| 85 | + return self.found_expected(self.import_params) and self.found_expected(self.export_results) |
| 86 | + |
| 87 | + def process_wat(self, wat): |
| 88 | + # The things that begin reffed might end up not reffed, if mutation |
| 89 | + # removes the refs. Check for that. |
| 90 | + import_reffed_is_reffed = '(ref.func $import-reffed)' in wat |
| 91 | + export_reffed_is_reffed = '(ref.func $export-reffed)' in wat |
| 92 | + |
| 93 | + # Find the params/results that might be refined. |
| 94 | + for line in wat.splitlines(): |
| 95 | + if line.startswith(' (import "module" "base" (func $import '): |
| 96 | + params, results = self.parse_params_results(line) |
| 97 | + self.import_params.add(params) |
| 98 | + assert results == '(result eqref)', 'cannot refine import result' |
| 99 | + elif line.startswith(' (import "module" "base" (func $import-reffed '): |
| 100 | + params, results = self.parse_params_results(line) |
| 101 | + if import_reffed_is_reffed: |
| 102 | + assert params == '(param i32 anyref)', 'cannot refine reffed stuff' |
| 103 | + assert results == '(result eqref)', 'cannot refine import result' |
| 104 | + if line.startswith(' (func $export '): |
| 105 | + params, results = self.parse_params_results(line) |
| 106 | + assert params == '(param $0 i32) (param $1 anyref)', 'cannot refine export params' |
| 107 | + self.export_results.add(results) |
| 108 | + if line.startswith(' (func $export-reffed '): |
| 109 | + params, results = self.parse_params_results(line) |
| 110 | + assert params == '(param $0 i32) (param $1 anyref)', 'cannot refine export params' |
| 111 | + if export_reffed_is_reffed: |
| 112 | + assert results == '(result eqref)', 'cannot refine reffed stuff' |
89 | 113 |
|
90 | 114 | # Given the types we saw for params or results, look in detail for the |
91 | 115 | # things we expect to see. |
@@ -161,3 +185,9 @@ def get(what, line): |
161 | 185 | return ret |
162 | 186 |
|
163 | 187 | return get('(param', line), get('(result', line) |
| 188 | + |
| 189 | + |
| 190 | +class PreserveFuzzTest(utils.BinaryenTestCase): |
| 191 | + def test_against_js(self): |
| 192 | + FuzzAgainstJSVarietyTester(self.input_path('fuzz.wat')).test() |
| 193 | + |
0 commit comments