diff options
-rwxr-xr-x[-rw-r--r--] | scripts/fuzz_opt.py | 406 | ||||
-rw-r--r-- | scripts/fuzz_shell.js | 2 | ||||
-rw-r--r-- | src/tools/js-wrapper.h | 12 | ||||
-rw-r--r-- | test/passes/emit-js-wrapper=a.js.wast.js | 18 |
4 files changed, 329 insertions, 109 deletions
diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 60552179c..96d35c610 100644..100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -1,3 +1,5 @@ +#!/usr/bin/python3 + ''' Runs random passes and options on random inputs, using wasm-opt. @@ -15,20 +17,21 @@ script covers different options being passed) import os import difflib +import math import subprocess import random import re import shutil import sys import time +import traceback from test import shared +assert sys.version_info.major == 3, 'requires Python 3!' # parameters -NANS = True - # feature options that are always passed to the tools. # exceptions: https://github.com/WebAssembly/binaryen/issues/2195 # simd: known issues with d8 @@ -36,14 +39,15 @@ NANS = True # truncsat: https://github.com/WebAssembly/binaryen/issues/2198 CONSTANT_FEATURE_OPTS = ['--all-features'] -FUZZ_OPTS = [] +INPUT_SIZE_MIN = 1024 +INPUT_SIZE_MEAN = 40 * 1024 +INPUT_SIZE_MAX = 5 * INPUT_SIZE_MEAN -INPUT_SIZE_LIMIT = 150 * 1024 +PRINT_WATS = False # utilities - def in_binaryen(*args): return os.path.join(shared.options.binaryen_root, *args) @@ -53,17 +57,27 @@ def in_bin(tool): def random_size(): - return random.randint(1, INPUT_SIZE_LIMIT) + if random.random() < 0.25: + # sometimes do an exponential distribution, which prefers smaller sizes but may + # also get very high + ret = int(random.expovariate(1.0 / INPUT_SIZE_MEAN)) + # if the result is valid, use it, otherwise do the normal thing + # (don't clamp, which would give us a lot of values on the borders) + if ret >= INPUT_SIZE_MIN and ret <= INPUT_SIZE_MAX: + return ret + + # most of the time do a simple linear range around the mean + return random.randint(INPUT_SIZE_MIN, 2 * INPUT_SIZE_MEAN - INPUT_SIZE_MIN) def run(cmd): print(' '.join(cmd)) - return subprocess.check_output(cmd) + return subprocess.check_output(cmd, text=True) def run_unchecked(cmd): print(' '.join(cmd)) - return subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT).communicate()[0] + return subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True).communicate()[0] def randomize_pass_debug(): @@ -73,6 +87,7 @@ def randomize_pass_debug(): else: os.environ['BINARYEN_PASS_DEBUG'] = '0' del os.environ['BINARYEN_PASS_DEBUG'] + print('randomized pass debug:', os.environ.get('BINARYEN_PASS_DEBUG', '')) def randomize_feature_opts(): @@ -87,13 +102,41 @@ def randomize_feature_opts(): for possible in POSSIBLE_FEATURE_OPTS: if random.random() < 0.5: FEATURE_OPTS.append(possible) - print('feature opts:', ' '.join(FEATURE_OPTS)) + print('randomized feature opts:', ' '.join(FEATURE_OPTS)) + + +FUZZ_OPTS = None +NANS = None +OOB = None +LEGALIZE = None + + +def randomize_fuzz_settings(): + global FUZZ_OPTS, NANS, OOB, LEGALIZE + FUZZ_OPTS = [] + if random.random() < 0.5: + NANS = True + else: + NANS = False + FUZZ_OPTS += ['--no-fuzz-nans'] + if random.random() < 0.5: + OOB = True + else: + OOB = False + FUZZ_OPTS += ['--no-fuzz-oob'] + if random.random() < 0.5: + LEGALIZE = True + FUZZ_OPTS += ['--legalize-js-interface'] + else: + LEGALIZE = False + print('randomized settings (NaNs, OOB, legalize):', NANS, OOB, LEGALIZE) # Test outputs we want to ignore are marked this way. IGNORE = '[binaryen-fuzzer-ignore]' +# compare two strings, strictly def compare(x, y, context): if x != y and x != IGNORE and y != IGNORE: message = ''.join([a + '\n' for a in difflib.unified_diff(x.splitlines(), y.splitlines(), fromfile='expected', tofile='actual')]) @@ -103,6 +146,61 @@ def compare(x, y, context): )) +# numbers are "close enough" if they just differ in printing, as different +# vms may print at different precision levels and verbosity +def numbers_are_close_enough(x, y): + # handle nan comparisons like -nan:0x7ffff0 vs NaN, ignoring the bits + if 'nan' in x.lower() and 'nan' in y.lower(): + return True + # float() on the strings will handle many minor differences, like + # float('1.0') == float('1') , float('inf') == float('Infinity'), etc. + try: + return float(x) == float(y) + except Exception: + pass + # otherwise, try a full eval which can handle i64s too + try: + x = eval(x) + y = eval(y) + return x == y or float(x) == float(y) + except Exception as e: + print('failed to check if numbers are close enough:', e) + return False + + +# compare between vms, which may slightly change how numbers are printed +def compare_between_vms(x, y, context): + x_lines = x.splitlines() + y_lines = y.splitlines() + if len(x_lines) != len(y_lines): + return compare(x, y, context) + + num_lines = len(x_lines) + for i in range(num_lines): + x_line = x_lines[i] + y_line = y_lines[i] + if x_line != y_line: + # this is different, but maybe it's a vm difference we can ignore + LEI_LOGGING = '[LoggingExternalInterface logging' + if x_line.startswith(LEI_LOGGING) and y_line.startswith(LEI_LOGGING): + x_val = x_line[len(LEI_LOGGING) + 1:-1] + y_val = y_line[len(LEI_LOGGING) + 1:-1] + if numbers_are_close_enough(x_val, y_val): + continue + NOTE_RESULT = '[fuzz-exec] note result' + if x_line.startswith(NOTE_RESULT) and y_line.startswith(NOTE_RESULT): + x_val = x_line.split(' ')[-1] + y_val = y_line.split(' ')[-1] + if numbers_are_close_enough(x_val, y_val): + continue + + # this failed to compare. print a custom diff of the relevant lines + MARGIN = 3 + start = max(i - MARGIN, 0) + end = min(i + MARGIN, num_lines) + return compare('\n'.join(x_lines[start:end]), '\n'.join(y_lines[start:end]), context) + + def fix_output(out): # large doubles may print slightly different on different VMs def fix_double(x): @@ -114,10 +212,8 @@ def fix_output(out): x = str(float(x)) return 'f64.const ' + x out = re.sub(r'f64\.const (-?[nanN:abcdefxIity\d+-.]+)', fix_double, out) - # mark traps from wasm-opt as exceptions, even though they didn't run in a vm out = out.replace('[trap ', 'exception: [trap ') - # exceptions may differ when optimizing, but an exception should occur. so ignore their types # also js engines print them out slightly differently return '\n'.join(map(lambda x: ' *exception*' if 'exception' in x else x, out.splitlines())) @@ -136,9 +232,6 @@ def run_vm(cmd): # ignore some vm assertions, if bugs have already been filed known_issues = [ 'local count too large', # ignore this; can be caused by flatten, ssa, etc. passes - 'liftoff-assembler.cc, line 239\n', # https://bugs.chromium.org/p/v8/issues/detail?id=8631 - 'liftoff-assembler.cc, line 245\n', # https://bugs.chromium.org/p/v8/issues/detail?id=8631 - 'liftoff-register.h, line 86\n', # https://bugs.chromium.org/p/v8/issues/detail?id=8632 ] try: return run(cmd) @@ -175,6 +268,13 @@ def run_d8(wasm): # * Totally generic: These receive the input pattern, a wasm generated from it, and a wasm # optimized from that, and can then do anything it wants with those. class TestCaseHandler: + # how frequent this handler will be run. 1 means always run it, 0.5 means half the + # time + frequency = 1 + + def __init__(self): + self.num_runs = 0 + # If the core handle_pair() method is not overridden, it calls handle_single() # on each of the pair. That is useful if you just want the two wasms, and don't # care about their relationship @@ -185,45 +285,71 @@ class TestCaseHandler: def can_run_on_feature_opts(self, feature_opts): return True + def increment_runs(self): + self.num_runs += 1 + + def count_runs(self): + return self.num_runs + # Run VMs and compare results + +class VM: + def __init__(self, name, run, deterministic_nans, requires_legalization): + self.name = name + self.run = run + self.deterministic_nans = deterministic_nans + self.requires_legalization = requires_legalization + + class CompareVMs(TestCaseHandler): + def __init__(self): + super(CompareVMs, self).__init__() + + def run_binaryen_interpreter(wasm): + return run_bynterp(wasm, ['--fuzz-exec-before']) + + def run_v8(wasm): + run([in_bin('wasm-opt'), wasm, '--emit-js-wrapper=' + wasm + '.js'] + FEATURE_OPTS) + return run_vm([shared.V8, wasm + '.js'] + shared.V8_OPTS + ['--', wasm]) + + self.vms = [ + VM('binaryen interpreter', run_binaryen_interpreter, deterministic_nans=True, requires_legalization=False), + VM('d8', run_v8, deterministic_nans=False, requires_legalization=True), + ] + def handle_pair(self, input, before_wasm, after_wasm, opts): - run([in_bin('wasm-opt'), before_wasm, '--emit-js-wrapper=a.js', '--emit-spec-wrapper=a.wat'] + FEATURE_OPTS) - run([in_bin('wasm-opt'), after_wasm, '--emit-js-wrapper=b.js', '--emit-spec-wrapper=b.wat'] + FEATURE_OPTS) - before = self.run_vms('a.js', before_wasm) - after = self.run_vms('b.js', after_wasm) - self.compare_vs(before, after) + before = self.run_vms(before_wasm) + after = self.run_vms(after_wasm) + self.compare_before_and_after(before, after) - def run_vms(self, js, wasm): + def run_vms(self, wasm): results = [] - results.append(fix_output(run_bynterp(wasm, ['--fuzz-exec-before']))) - results.append(fix_output(run_vm([shared.V8, js] + shared.V8_OPTS + ['--', wasm]))) - - # append to add results from VMs - # results += [fix_output(run_vm([shared.V8, js] + shared.V8_OPTS + ['--', wasm]))] - # results += [fix_output(run_vm([os.path.expanduser('~/.jsvu/jsc'), js, '--', wasm]))] - # spec has no mechanism to not halt on a trap. so we just check until the first trap, basically - # run(['../spec/interpreter/wasm', wasm]) - # results += [fix_spec_output(run_unchecked(['../spec/interpreter/wasm', wasm, '-e', open(prefix + 'wat').read()]))] + for vm in self.vms: + results.append(fix_output(vm.run(wasm))) - if len(results) == 0: - results = [0] + # compare between the vms on this specific input - # NaNs are a source of nondeterminism between VMs; don't compare them + # NaNs are a source of nondeterminism between VMs; don't compare them. if not NANS: - first = results[0] + first = None for i in range(len(results)): - compare(first, results[i], 'CompareVMs at ' + str(i)) + # No legalization for JS means we can't compare JS to others, as any + # illegal export will fail immediately. + if LEGALIZE or not vm.requires_legalization: + if first is None: + first = i + else: + compare_between_vms(results[first], results[i], 'CompareVMs between VMs: ' + self.vms[first].name + ' and ' + self.vms[i].name) return results - def compare_vs(self, before, after): + def compare_before_and_after(self, before, after): + # compare each VM to itself on the before and after inputs for i in range(len(before)): - compare(before[i], after[i], 'CompareVMs at ' + str(i)) - # with nans, we can only compare the binaryen interpreter to itself - if NANS: - break + vm = self.vms[i] + if vm.deterministic_nans: + compare(before[i], after[i], 'CompareVMs between before and after: ' + vm.name) def can_run_on_feature_opts(self, feature_opts): return all([x in feature_opts for x in ['--disable-simd', '--disable-reference-types', '--disable-exception-handling']]) @@ -244,40 +370,46 @@ class FuzzExec(TestCaseHandler): ] -# As FuzzExec, but without a separate invocation. This can find internal bugs with generating -# the IR (which might be worked around by writing it and then reading it). -class FuzzExecImmediately(TestCaseHandler): - def handle_pair(self, input, before_wasm, after_wasm, opts): - # fuzz binaryen interpreter itself. separate invocation so result is easily reduceable - run_bynterp(before_wasm, ['--fuzz-exec', '--fuzz-binary'] + opts) - - # Check for determinism - the same command must have the same output. # Note that this doesn't use get_commands() intentionally, since we are testing # for something that autoreduction won't help with anyhow (nondeterminism is very # hard to reduce). class CheckDeterminism(TestCaseHandler): + # not that important + frequency = 0.333 + def handle_pair(self, input, before_wasm, after_wasm, opts): # check for determinism run([in_bin('wasm-opt'), before_wasm, '-o', 'b1.wasm'] + opts) run([in_bin('wasm-opt'), before_wasm, '-o', 'b2.wasm'] + opts) - assert open('b1.wasm').read() == open('b2.wasm').read(), 'output must be deterministic' + assert open('b1.wasm', 'rb').read() == open('b2.wasm', 'rb').read(), 'output must be deterministic' class Wasm2JS(TestCaseHandler): def handle_pair(self, input, before_wasm, after_wasm, opts): - compare(self.run(before_wasm), self.run(after_wasm), 'Wasm2JS') + # always check for compiler crashes. without NaNs we can also compare + # before and after (with NaNs, a reinterpret through memory might end up + # different in JS than wasm) + before = self.run(before_wasm) + after = self.run(after_wasm) + if not NANS: + compare(before, after, 'Wasm2JS') def run(self, wasm): - # TODO: wasm2js does not handle nans precisely, and does not - # handle oob loads etc. with traps, should we use - # FUZZ_OPTS += ['--no-fuzz-nans'] - # FUZZ_OPTS += ['--no-fuzz-oob'] - # ? wrapper = run([in_bin('wasm-opt'), wasm, '--emit-js-wrapper=/dev/stdout'] + FEATURE_OPTS) cmd = [in_bin('wasm2js'), wasm, '--emscripten'] - if random.random() < 0.5: - cmd += ['-O'] + # avoid optimizations if we have nans, as we don't handle them with + # full precision and optimizations can change things + # OOB accesses are also an issue with optimizations, that can turn the + # loaded "undefined" into either 0 (with an |0) or stay undefined + # in optimized code. + if not NANS and not OOB and random.random() < 0.5: + # when optimizing also enable deterministic mode, to avoid things + # like integer divide by zero causing false positives (1 / 0 is + # Infinity without a | 0 , and 0 with one, and the truthiness of + # those differs; we don't want to care about this because it + # would trap in wasm anyhow) + cmd += ['-O', '--deterministic'] main = run(cmd + FEATURE_OPTS) with open(os.path.join(shared.options.binaryen_root, 'scripts', 'wasm2js.js')) as f: glue = f.read() @@ -303,6 +435,14 @@ class Asyncify(TestCaseHandler): before = fix_output(run_d8(before_wasm)) after = fix_output(run_d8(after_wasm)) + try: + compare(before, after, 'Asyncify (before/after)') + except Exception: + # if we failed to just compare the builds before asyncify even runs, + # then it may use NaNs or be sensitive to legalization; ignore it + print('ignoring due to pre-asyncify difference') + return + # TODO: also something that actually does async sleeps in the code, say # on the logging commands? # --remove-unused-module-elements removes the asyncify intrinsics, which are not valid to call @@ -319,12 +459,12 @@ class Asyncify(TestCaseHandler): # emit some status logging from asyncify print(out.splitlines()[-1]) # ignore the output from the new asyncify API calls - the ones with asserts will trap, too - for ignore in ['[fuzz-exec] calling $asyncify_start_unwind\nexception!\n', - '[fuzz-exec] calling $asyncify_start_unwind\n', - '[fuzz-exec] calling $asyncify_start_rewind\nexception!\n', - '[fuzz-exec] calling $asyncify_start_rewind\n', - '[fuzz-exec] calling $asyncify_stop_rewind\n', - '[fuzz-exec] calling $asyncify_stop_unwind\n']: + for ignore in ['[fuzz-exec] calling asyncify_start_unwind\nexception!\n', + '[fuzz-exec] calling asyncify_start_unwind\n', + '[fuzz-exec] calling asyncify_start_rewind\nexception!\n', + '[fuzz-exec] calling asyncify_start_rewind\n', + '[fuzz-exec] calling asyncify_stop_rewind\n', + '[fuzz-exec] calling asyncify_stop_unwind\n']: out = out.replace(ignore, '') out = '\n'.join([l for l in out.splitlines() if 'asyncify: ' not in l]) return fix_output(out) @@ -332,7 +472,6 @@ class Asyncify(TestCaseHandler): before_asyncify = do_asyncify(before_wasm) after_asyncify = do_asyncify(after_wasm) - compare(before, after, 'Asyncify (before/after)') compare(before, before_asyncify, 'Asyncify (before/before_asyncify)') compare(before, after_asyncify, 'Asyncify (before/after_asyncify)') @@ -347,7 +486,6 @@ testcase_handlers = [ CheckDeterminism(), Wasm2JS(), Asyncify(), - FuzzExecImmediately(), ] @@ -355,10 +493,16 @@ testcase_handlers = [ def test_one(random_input, opts): randomize_pass_debug() randomize_feature_opts() - - printed = run([in_bin('wasm-opt'), random_input, '-ttf', '-o', 'a.wasm'] + FUZZ_OPTS + FEATURE_OPTS + ['--print']) - with open('a.printed.wast', 'w') as f: - f.write(printed) + randomize_fuzz_settings() + print() + + generate_command = [in_bin('wasm-opt'), random_input, '-ttf', '-o', 'a.wasm'] + FUZZ_OPTS + FEATURE_OPTS + if PRINT_WATS: + printed = run(generate_command + ['--print']) + with open('a.printed.wast', 'w') as f: + f.write(printed) + else: + run(generate_command) wasm_size = os.stat('a.wasm').st_size bytes = wasm_size print('pre wasm size:', wasm_size) @@ -373,6 +517,8 @@ def test_one(random_input, opts): if testcase_handler.can_run_on_feature_opts(FEATURE_OPTS): if hasattr(testcase_handler, 'get_commands'): print('running testcase handler:', testcase_handler.__class__.__name__) + testcase_handler.increment_runs() + # if the testcase handler supports giving us a list of commands, then we can get those commands # and use them to do useful things like automatic reduction. in this case we give it the input # wasm plus opts and a random seed (if it needs any internal randomness; we want to have the same @@ -425,21 +571,37 @@ def test_one(random_input, opts): # reduce the input to something smaller with the same behavior on the script subprocess.check_call([in_bin('wasm-reduce'), 'input.wasm', '--command=bash tt.sh', '-t', 'a.wasm', '-w', 'reduced.wasm']) print('Finished reduction. See "tt.sh" and "reduced.wasm".') - sys.exit(1) + raise Exception('halting after autoreduction') print('') - # created a second wasm for handlers that want to look at pairs. - printed = run([in_bin('wasm-opt'), 'a.wasm', '-o', 'b.wasm'] + opts + FUZZ_OPTS + FEATURE_OPTS + ['--print']) - with open('b.printed.wast', 'w') as f: - f.write(printed) + # create a second wasm for handlers that want to look at pairs. + generate_command = [in_bin('wasm-opt'), 'a.wasm', '-o', 'b.wasm'] + opts + FUZZ_OPTS + FEATURE_OPTS + if PRINT_WATS: + printed = run(generate_command + ['--print']) + with open('b.printed.wast', 'w') as f: + f.write(printed) + else: + run(generate_command) wasm_size = os.stat('b.wasm').st_size bytes += wasm_size print('post wasm size:', wasm_size) - for testcase_handler in testcase_handlers: - if testcase_handler.can_run_on_feature_opts(FEATURE_OPTS): - if not hasattr(testcase_handler, 'get_commands'): + # run some of the pair handling handlers. we don't run them all so that we get more + # coverage of different wasm files (but we do run all get_commands() using ones, + # earlier, because those are autoreducible and we want to maximize the chance of + # that) + # however, also try our best to pick a handler that can run this + relevant_handlers = [handler for handler in testcase_handlers if not hasattr(handler, 'get_commands') and handler.can_run_on_feature_opts(FEATURE_OPTS)] + NUM_PAIR_HANDLERS = 2 + if len(relevant_handlers) > 0: + chosen_handlers = set(random.choices(relevant_handlers, k=NUM_PAIR_HANDLERS)) + for testcase_handler in chosen_handlers: + assert testcase_handler.can_run_on_feature_opts(FEATURE_OPTS) + assert not hasattr(testcase_handler, 'get_commands') + if random.random() < testcase_handler.frequency: print('running testcase handler:', testcase_handler.__class__.__name__) + testcase_handler.increment_runs() + # let the testcase handler handle this testcase however it wants. in this case we give it # the input and both wasms. testcase_handler.handle_pair(input=random_input, before_wasm='a.wasm', after_wasm='b.wasm', opts=opts + FUZZ_OPTS + FEATURE_OPTS) @@ -510,7 +672,7 @@ opt_choices = [ ] -def get_multiple_opt_choices(): +def randomize_opt_flags(): ret = [] # core opts while 1: @@ -533,9 +695,6 @@ def get_multiple_opt_choices(): # main -if not NANS: - FUZZ_OPTS += ['--no-fuzz-nans'] - # possible feature options that are sometimes passed to the tools. this # contains the list of all possible feature flags we can disable (after # we enable all before that in the constant options) @@ -543,21 +702,82 @@ POSSIBLE_FEATURE_OPTS = run([in_bin('wasm-opt'), '--print-features', '-all', in_ print('POSSIBLE_FEATURE_OPTS:', POSSIBLE_FEATURE_OPTS) if __name__ == '__main__': - print('checking infinite random inputs') - random.seed(time.time() * os.getpid()) - temp = 'input.dat' + # if we are given a seed, run exactly that one testcase. otherwise, + # run new ones until we fail + if len(sys.argv) == 2: + given_seed = int(sys.argv[1]) + print('checking a single given seed', given_seed) + else: + given_seed = None + print('checking infinite random inputs') + seed = time.time() * os.getpid() + raw_input_data = 'input.dat' counter = 0 - bytes = 0 # wasm bytes tested + total_wasm_size = 0 + total_input_size = 0 + total_input_size_squares = 0 start_time = time.time() while True: counter += 1 - f = open(temp, 'w') - size = random_size() + if given_seed is not None: + seed = given_seed + given_seed_passed = True + else: + seed = random.randint(0, 1 << 64) + random.seed(seed) + input_size = random_size() + total_input_size += input_size + total_input_size_squares += input_size ** 2 print('') - print('ITERATION:', counter, 'size:', size, 'speed:', counter / (time.time() - start_time), 'iters/sec, ', bytes / (time.time() - start_time), 'bytes/sec\n') - for x in range(size): - f.write(chr(random.randint(0, 255))) - f.close() - opts = get_multiple_opt_choices() - print('opts:', ' '.join(opts)) - bytes += test_one('input.dat', opts) + mean = float(total_input_size) / counter + mean_of_squares = float(total_input_size_squares) / counter + stddev = math.sqrt(mean_of_squares - (mean ** 2)) + print('ITERATION:', counter, 'seed:', seed, 'size:', input_size, '(mean:', str(mean) + ', stddev:', str(stddev) + ')', 'speed:', counter / (time.time() - start_time), 'iters/sec, ', total_wasm_size / (time.time() - start_time), 'wasm_bytes/sec\n') + with open(raw_input_data, 'wb') as f: + f.write(bytes([random.randint(0, 255) for x in range(input_size)])) + assert os.path.getsize(raw_input_data) == input_size + opts = randomize_opt_flags() + print('randomized opts:', ' '.join(opts)) + try: + total_wasm_size += test_one(raw_input_data, opts) + except KeyboardInterrupt: + print('(stopping by user request)') + break + except Exception as e: + # print the exception manually, so that we can show our message at + # the very end where it won't be missed + ex_type, ex, tb = sys.exc_info() + print('!') + print('-----------------------------------------') + print('Exception:') + traceback.print_tb(tb) + print('-----------------------------------------') + print('!') + for arg in e.args: + print(arg) + if given_seed is not None: + given_seed_passed = False + else: + print('''\ +================================================================================ +You found a bug! Please report it with + + seed: %(seed)d + +and the exact version of Binaryen you found it on, plus the exact Python +version (hopefully deterministic random numbers will be identical). + +(you can run that testcase again with "fuzz_opt.py %(seed)d") +================================================================================ + ''' % {'seed': seed}) + break + if given_seed is not None: + if given_seed_passed: + print('(finished running seed %d without error)' % given_seed) + else: + print('(finished running seed %d, see error above)' % given_seed) + break + + print('\nInvocations so far:') + for testcase_handler in testcase_handlers: + print(' ', testcase_handler.__class__.__name__ + ':', testcase_handler.count_runs()) diff --git a/scripts/fuzz_shell.js b/scripts/fuzz_shell.js index 671a054c4..dbe17098d 100644 --- a/scripts/fuzz_shell.js +++ b/scripts/fuzz_shell.js @@ -196,7 +196,7 @@ sortedExports.forEach(function(e) { Asyncify.check(); if (typeof exports[e] !== 'function') return; try { - console.log('[fuzz-exec] calling $' + e); + console.log('[fuzz-exec] calling ' + e); var result = exports[e](); if (typeof result !== 'undefined') { console.log('[fuzz-exec] note result: $' + e + ' => ' + result); diff --git a/src/tools/js-wrapper.h b/src/tools/js-wrapper.h index a787d4d2c..0a9b5925e 100644 --- a/src/tools/js-wrapper.h +++ b/src/tools/js-wrapper.h @@ -47,13 +47,13 @@ static std::string generateJSWrapper(Module& wasm) { " }\n" "}\n" "function literal(x, type) {\n" - " var ret = type + '.const ';\n" + " var ret = '';\n" " switch (type) {\n" " case 'i32': ret += (x | 0); break;\n" " case 'f32':\n" " case 'f64': {\n" " if (x == 0 && (1 / x) < 0) ret += '-';\n" - " ret += x;\n" + " ret += Number(x).toString();\n" " break;\n" " }\n" " default: throw 'what?';\n" @@ -82,17 +82,17 @@ static std::string generateJSWrapper(Module& wasm) { " },\n" "});\n"; for (auto& exp : wasm.exports) { - auto* func = wasm.getFunctionOrNull(exp->value); - if (!func) { + if (exp->kind != ExternalKind::Function) { continue; // something exported other than a function } + auto* func = wasm.getFunction(exp->value); ret += "if (instance.exports.hangLimitInitializer) " "instance.exports.hangLimitInitializer();\n"; ret += "try {\n"; - ret += std::string(" console.log('[fuzz-exec] calling $") + exp->name.str + + ret += std::string(" console.log('[fuzz-exec] calling ") + exp->name.str + "');\n"; if (func->sig.results != Type::none) { - ret += std::string(" console.log('[fuzz-exec] note result: $") + + ret += std::string(" console.log('[fuzz-exec] note result: ") + exp->name.str + " => ' + literal("; } else { ret += " "; diff --git a/test/passes/emit-js-wrapper=a.js.wast.js b/test/passes/emit-js-wrapper=a.js.wast.js index 2e127d189..60f2449b0 100644 --- a/test/passes/emit-js-wrapper=a.js.wast.js +++ b/test/passes/emit-js-wrapper=a.js.wast.js @@ -21,13 +21,13 @@ if (typeof process === 'object' && typeof require === 'function' /* node.js dete } } function literal(x, type) { - var ret = type + '.const '; + var ret = ''; switch (type) { case 'i32': ret += (x | 0); break; case 'f32': case 'f64': { if (x == 0 && (1 / x) < 0) ret += '-'; - ret += x; + ret += Number(x).toString(); break; } default: throw 'what?'; @@ -48,36 +48,36 @@ var instance = new WebAssembly.Instance(new WebAssembly.Module(binary), { }); if (instance.exports.hangLimitInitializer) instance.exports.hangLimitInitializer(); try { - console.log('[fuzz-exec] calling $add'); - console.log('[fuzz-exec] note result: $add => ' + literal(instance.exports.add(0, 0), 'i32')); + console.log('[fuzz-exec] calling add'); + console.log('[fuzz-exec] note result: add => ' + literal(instance.exports.add(0, 0), 'i32')); } catch (e) { console.log('exception!' /* + e */); } if (instance.exports.hangLimitInitializer) instance.exports.hangLimitInitializer(); try { - console.log('[fuzz-exec] calling $no_return'); + console.log('[fuzz-exec] calling no_return'); instance.exports.no_return(0); } catch (e) { console.log('exception!' /* + e */); } if (instance.exports.hangLimitInitializer) instance.exports.hangLimitInitializer(); try { - console.log('[fuzz-exec] calling $types'); + console.log('[fuzz-exec] calling types'); instance.exports.types(0, 0, 0, 0, 0); } catch (e) { console.log('exception!' /* + e */); } if (instance.exports.hangLimitInitializer) instance.exports.hangLimitInitializer(); try { - console.log('[fuzz-exec] calling $types2'); + console.log('[fuzz-exec] calling types2'); instance.exports.types2(0, 0, 0); } catch (e) { console.log('exception!' /* + e */); } if (instance.exports.hangLimitInitializer) instance.exports.hangLimitInitializer(); try { - console.log('[fuzz-exec] calling $types3'); - console.log('[fuzz-exec] note result: $types3 => ' + literal(instance.exports.types3(0, 0, 0), 'i32')); + console.log('[fuzz-exec] calling types3'); + console.log('[fuzz-exec] note result: types3 => ' + literal(instance.exports.types3(0, 0, 0), 'i32')); } catch (e) { console.log('exception!' /* + e */); } |