| #!/usr/bin/env python3 | |
| # | |
| # Copyright 2016 WebAssembly Community Group participants | |
| # | |
| # Licensed under the Apache License, Version 2.0 (the "License"); | |
| # you may not use this file except in compliance with the License. | |
| # You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, software | |
| # distributed under the License is distributed on an "AS IS" BASIS, | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| # See the License for the specific language governing permissions and | |
| # limitations under the License. | |
| ''' | |
| This fuzzes passes, by starting with a working program, then running | |
| random passes on the wast, and seeing if they break something | |
| Usage: Provide a base filename for a runnable program, e.g. a.out.js. | |
| Then we will modify a.out.wast. Note that the program must | |
| be built to run using that wast (BINARYEN_METHOD=interpret-s-expr) | |
| Other parameters after the first are used when calling the program. | |
| ''' | |
| from __future__ import print_function | |
| import os | |
| import random | |
| import shutil | |
| import subprocess | |
| import sys | |
| PASSES = [ | |
| "duplicate-function-elimination", | |
| "dce", | |
| "remove-unused-brs", | |
| "remove-unused-names", | |
| "optimize-instructions", | |
| "precompute", | |
| "simplify-locals", | |
| "vacuum", | |
| "coalesce-locals", | |
| "reorder-locals", | |
| "merge-blocks", | |
| "remove-unused-functions", | |
| ] | |
| # main | |
| base = sys.argv[1] | |
| wast = base[:-3] + '.wast' | |
| print('>>> base program:', base, ', wast:', wast) | |
| args = sys.argv[2:] | |
| def run(): | |
| if os.path.exists(wast): | |
| print('>>> running using a wast of size', os.stat(wast).st_size) | |
| cmd = ['mozjs', base] + args | |
| try: | |
| return subprocess.check_output(cmd, stderr=subprocess.STDOUT) | |
| except Exception as e: | |
| print(">>> !!! ", e, " !!!") | |
| original_wast = None | |
| try: | |
| # get normal output | |
| normal = run() | |
| print('>>> normal output:\n', normal) | |
| assert normal, 'must be output' | |
| # ensure we actually use the wast | |
| original_wast = wast + '.original.wast' | |
| shutil.move(wast, original_wast) | |
| assert run() != normal, 'running without the wast must fail' | |
| # ensure a bad pass makes it fail | |
| def apply_passes(passes): | |
| wasm_opt = os.path.join('bin', 'wasm-opt') | |
| subprocess.check_call([wasm_opt, original_wast] + passes + ['-o', wast]) | |
| apply_passes(['--remove-imports']) | |
| assert run() != normal, 'running after a breaking pass must fail' | |
| # loop, looking for failures | |
| def simplify(passes): | |
| # passes is known to fail, try to simplify down by removing | |
| more = True | |
| while more: | |
| more = False | |
| print('>>> trying to reduce:', ' '.join(passes), ' [' + str(len(passes)) + ']') | |
| for i in range(len(passes)): | |
| smaller = passes[:i] + passes[i + 1:] | |
| print('>>>>>> try to reduce to:', ' '.join(smaller), ' [' + str(len(smaller)) + ']') | |
| try: | |
| apply_passes(smaller) | |
| assert run() == normal | |
| except Exception: | |
| # this failed too, so it's a good reduction | |
| passes = smaller | |
| print('>>> reduction successful') | |
| more = True | |
| break | |
| print('>>> reduced to:', ' '.join(passes)) | |
| tested = set() | |
| def pick_passes(): | |
| ret = [] | |
| while 1: | |
| str_ret = str(ret) | |
| if random.random() < 0.1 and str_ret not in tested: | |
| tested.add(str_ret) | |
| return ret | |
| ret.append('--' + random.choice(PASSES)) | |
| counter = 0 | |
| while 1: | |
| passes = pick_passes() | |
| print('>>> [' + str(counter) + '] testing:', ' '.join(passes)) | |
| counter += 1 | |
| try: | |
| apply_passes(passes) | |
| except Exception as e: | |
| print(e) | |
| simplify(passes) | |
| break | |
| seen = run() | |
| if seen != normal: | |
| print('>>> bad output:\n', seen) | |
| simplify(passes) | |
| break | |
| finally: | |
| if original_wast: | |
| shutil.move(original_wast, wast) |