summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xscripts/fuzz_opt.py201
-rw-r--r--src/ir/names.h4
-rw-r--r--src/tools/fuzzing.h243
-rw-r--r--src/tools/wasm-opt.cpp22
-rw-r--r--test/passes/fuzz_metrics_noprint.bin.txt50
-rw-r--r--test/passes/translate-to-fuzz_all-features.txt1540
-rw-r--r--test/unit/input/hello_world.wat11
-rw-r--r--test/unit/input/random_data.txt1
-rw-r--r--test/unit/test_initial_fuzz.py35
9 files changed, 1289 insertions, 818 deletions
diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py
index 0a0fd679b..3e4e42ab9 100755
--- a/scripts/fuzz_opt.py
+++ b/scripts/fuzz_opt.py
@@ -28,6 +28,8 @@ import time
import traceback
from test import shared
+from test import support
+
assert sys.version_info.major == 3, 'requires Python 3!'
@@ -68,9 +70,10 @@ def random_size():
return random.randint(INPUT_SIZE_MIN, 2 * INPUT_SIZE_MEAN - INPUT_SIZE_MIN)
-def run(cmd):
- print(' '.join(cmd))
- return subprocess.check_output(cmd, text=True)
+def run(cmd, stderr=None, silent=False):
+ if not silent:
+ print(' '.join(cmd))
+ return subprocess.check_output(cmd, stderr=stderr, text=True)
def run_unchecked(cmd):
@@ -115,15 +118,22 @@ def randomize_feature_opts():
print('randomized feature opts:', ' '.join(FEATURE_OPTS))
-FUZZ_OPTS = None
-NANS = None
-OOB = None
-LEGALIZE = None
ORIGINAL_V8_OPTS = shared.V8_OPTS[:]
def randomize_fuzz_settings():
- global FUZZ_OPTS, NANS, OOB, LEGALIZE
+ # a list of the optimizations to run on the wasm
+ global FUZZ_OPTS
+
+ # a boolean whether NaN values are allowed, or we de-NaN them
+ global NANS
+
+ # a boolean whether out of bounds operations are allowed, or we bounds-enforce them
+ global OOB
+
+ # a boolean whether we legalize the wasm for JS
+ global LEGALIZE
+
FUZZ_OPTS = []
if random.random() < 0.5:
NANS = True
@@ -159,6 +169,76 @@ def randomize_fuzz_settings():
print('randomized settings (NaNs, OOB, legalize, extra V8_OPTS):', NANS, OOB, LEGALIZE, extra_v8_opts)
+def pick_initial_contents():
+ # if we use an initial wasm file's contents as the basis for the
+ # fuzzing, then that filename, or None if we start entirely from scratch
+ global INITIAL_CONTENTS
+
+ INITIAL_CONTENTS = None
+ # half the time don't use any initial contents
+ if random.random() < 0.5:
+ return
+ test_name = random.choice(all_tests)
+ print('initial contents:', test_name)
+ assert os.path.exists(test_name)
+ # tests that check validation errors are not helpful for us
+ if '.fail.' in test_name:
+ print('initial contents is just a .fail test')
+ return
+ if os.path.basename(test_name) in [
+ # contains too many segments to run in a wasm VM
+ 'limit-segments_disable-bulk-memory.wast',
+ # https://github.com/WebAssembly/binaryen/issues/3203
+ 'simd.wast',
+ # corner cases of escaping of names is not interesting
+ 'names.wast',
+ # huge amount of locals that make it extremely slow
+ 'too_much_for_liveness.wasm'
+ ]:
+ print('initial contents is disallowed')
+ return
+
+ if test_name.endswith('.wast'):
+ # this can contain multiple modules, pick one
+ split_parts = support.split_wast(test_name)
+ if len(split_parts) > 1:
+ index = random.randint(0, len(split_parts) - 1)
+ chosen = split_parts[index]
+ module, asserts = chosen
+ if not module:
+ # there is no module in this choice (just asserts), ignore it
+ print('initial contents has no module')
+ return
+ test_name = 'initial.wat'
+ with open(test_name, 'w') as f:
+ f.write(module)
+ print(' picked submodule %d from multi-module wast' % index)
+
+ global FEATURE_OPTS
+ FEATURE_OPTS += [
+ # has not been enabled in the fuzzer yet
+ '--disable-exception-handling',
+ # has not been fuzzed in general yet
+ '--disable-memory64',
+ # DWARF is incompatible with multivalue atm; it's more important to
+ # fuzz multivalue since we aren't actually fuzzing DWARF here
+ '--strip-dwarf',
+ ]
+
+ # the given wasm may not work with the chosen feature opts. for example, if
+ # we pick atomics.wast but want to run with --disable-atomics, then we'd
+ # error. test the wasm.
+ try:
+ run([in_bin('wasm-opt'), test_name] + FEATURE_OPTS,
+ stderr=subprocess.PIPE,
+ silent=True)
+ except Exception:
+ print('(initial contents not valid for features, ignoring)')
+ return
+
+ INITIAL_CONTENTS = test_name
+
+
# Test outputs we want to ignore are marked this way.
IGNORE = '[binaryen-fuzzer-ignore]'
@@ -334,19 +414,6 @@ class TestCaseHandler:
return self.num_runs
-# Run VMs and compare results
-
-class VM:
- def __init__(self, name, run, can_compare_to_self, can_compare_to_others):
- self.name = name
- self.run = run
- self.can_compare_to_self = can_compare_to_self
- self.can_compare_to_others = can_compare_to_others
-
- def can_run(self, wasm):
- return True
-
-
# Fuzz the interpreter with --fuzz-exec.
class FuzzExec(TestCaseHandler):
frequency = 1
@@ -361,23 +428,45 @@ class CompareVMs(TestCaseHandler):
def __init__(self):
super(CompareVMs, self).__init__()
- def byn_run(wasm):
- return run_bynterp(wasm, ['--fuzz-exec-before'])
+ class BinaryenInterpreter:
+ name = 'binaryen interpreter'
+
+ def run(self, wasm):
+ return run_bynterp(wasm, ['--fuzz-exec-before'])
+
+ def can_run(self, wasm):
+ return True
+
+ def can_compare_to_self(self):
+ return True
+
+ def can_compare_to_others(self):
+ return True
+
+ class D8:
+ name = 'd8'
- def v8_run(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])
+ def run(self, 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])
- def yes():
- return True
+ def can_run(self, wasm):
+ # INITIAL_CONTENT is disallowed because some initial spec testcases
+ # have names that require mangling, see
+ # https://github.com/WebAssembly/binaryen/pull/3216
+ return not INITIAL_CONTENTS
- def if_legal_and_no_nans():
- return LEGALIZE and not NANS
+ def can_compare_to_self(self):
+ # With nans, VM differences can confuse us, so only very simple VMs
+ # can compare to themselves after opts in that case.
+ return not NANS
- def if_no_nans():
- return not NANS
+ def can_compare_to_others(self):
+ # If not legalized, the JS will fail immediately, so no point to
+ # compare to others.
+ return LEGALIZE and not NANS
- class Wasm2C(VM):
+ class Wasm2C:
name = 'wasm2c'
def __init__(self):
@@ -467,14 +556,7 @@ class CompareVMs(TestCaseHandler):
# NaNs can differ from wasm VMs
return not NANS
- self.vms = [
- VM('binaryen interpreter', byn_run, can_compare_to_self=yes, can_compare_to_others=yes),
- # with nans, VM differences can confuse us, so only very simple VMs can compare to themselves after opts in that case.
- # if not legalized, the JS will fail immediately, so no point to compare to others
- VM('d8', v8_run, can_compare_to_self=if_no_nans, can_compare_to_others=if_legal_and_no_nans),
- Wasm2C(),
- Wasm2C2Wasm(),
- ]
+ self.vms = [BinaryenInterpreter(), D8(), Wasm2C(), Wasm2C2Wasm()]
def handle_pair(self, input, before_wasm, after_wasm, opts):
before = self.run_vms(before_wasm)
@@ -638,6 +720,13 @@ class Wasm2JS(TestCaseHandler):
return run_vm([shared.NODEJS, js_file, 'a.wasm'])
def can_run_on_feature_opts(self, feature_opts):
+ # TODO: properly handle memory growth. right now the wasm2js handler
+ # uses --emscripten which assumes the Memory is created before, and
+ # wasm2js.js just starts with a size of 1 and no limit. We should switch
+ # to non-emscripten mode or adding memory information, or check
+ # specifically for growth here
+ if INITIAL_CONTENTS:
+ return False
return all([x in feature_opts for x in ['--disable-exception-handling', '--disable-simd', '--disable-threads', '--disable-bulk-memory', '--disable-nontrapping-float-to-int', '--disable-tail-call', '--disable-sign-ext', '--disable-reference-types', '--disable-multivalue', '--disable-gc']])
@@ -705,11 +794,25 @@ testcase_handlers = [
]
+test_suffixes = ['*.wasm', '*.wast', '*.wat']
+core_tests = shared.get_tests(shared.get_test_dir('.'), test_suffixes)
+passes_tests = shared.get_tests(shared.get_test_dir('passes'), test_suffixes)
+spec_tests = shared.get_tests(shared.get_test_dir('spec'), test_suffixes)
+wasm2js_tests = shared.get_tests(shared.get_test_dir('wasm2js'), test_suffixes)
+lld_tests = shared.get_tests(shared.get_test_dir('lld'), test_suffixes)
+unit_tests = shared.get_tests(shared.get_test_dir(os.path.join('unit', 'input')), test_suffixes)
+all_tests = core_tests + passes_tests + spec_tests + wasm2js_tests + lld_tests + unit_tests
+
+
# Do one test, given an input file for -ttf and some optimizations to run
-def test_one(random_input, opts, given_wasm):
+def test_one(random_input, given_wasm):
randomize_pass_debug()
randomize_feature_opts()
randomize_fuzz_settings()
+ pick_initial_contents()
+
+ opts = randomize_opt_flags()
+ print('randomized opts:', ' '.join(opts))
print()
if given_wasm:
@@ -722,6 +825,8 @@ def test_one(random_input, opts, given_wasm):
# emit the target features section so that reduction can work later,
# without needing to specify the features
generate_command = [in_bin('wasm-opt'), random_input, '-ttf', '-o', 'a.wasm', '--emit-target-features'] + FUZZ_OPTS + FEATURE_OPTS
+ if INITIAL_CONTENTS:
+ generate_command += ['--initial-fuzz=' + INITIAL_CONTENTS]
if PRINT_WATS:
printed = run(generate_command + ['--print'])
with open('a.printed.wast', 'w') as f:
@@ -843,10 +948,16 @@ def randomize_opt_flags():
# core opts
while 1:
choice = random.choice(opt_choices)
- if '--flatten' in choice:
+ if '--flatten' in choice or '-O4' in choice:
if has_flatten:
print('avoiding multiple --flatten in a single command, due to exponential overhead')
continue
+ if '--disable-exception-handling' not in FEATURE_OPTS:
+ print('avoiding --flatten due to exception catching which does not support it yet')
+ continue
+ if INITIAL_CONTENTS and os.path.getsize(INITIAL_CONTENTS) > 2000:
+ print('avoiding --flatten due using a large amount of initial contents, which may blow up')
+ continue
else:
has_flatten = True
flag_groups.append(choice)
@@ -928,15 +1039,13 @@ if __name__ == '__main__':
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))
# remove the generated wasm file, so that we can tell if the fuzzer
# fails to create one
if os.path.exists('a.wasm'):
os.remove('a.wasm')
# run an iteration of the fuzzer
try:
- total_wasm_size += test_one(raw_input_data, opts, given_wasm)
+ total_wasm_size += test_one(raw_input_data, given_wasm)
except KeyboardInterrupt:
print('(stopping by user request)')
break
diff --git a/src/ir/names.h b/src/ir/names.h
index 3d49a4a36..a725c515a 100644
--- a/src/ir/names.h
+++ b/src/ir/names.h
@@ -72,6 +72,10 @@ inline Name getValidFunctionName(Module& module, Name root) {
return getValidName(
module, root, [&](Name test) { return !module.getFunctionOrNull(test); });
}
+inline Name getValidEventName(Module& module, Name root) {
+ return getValidName(
+ module, root, [&](Name test) { return !module.getEventOrNull(test); });
+}
} // namespace Names
diff --git a/src/tools/fuzzing.h b/src/tools/fuzzing.h
index 709ebe1e8..f4c29be55 100644
--- a/src/tools/fuzzing.h
+++ b/src/tools/fuzzing.h
@@ -31,6 +31,7 @@ high chance for set at start of loop
#include <ir/find_all.h>
#include <ir/literal-utils.h>
#include <ir/manipulation.h>
+#include <ir/names.h>
#include <ir/utils.h>
#include <support/file.h>
#include <tools/optimization-options.h>
@@ -188,6 +189,9 @@ public:
void setAllowOOB(bool allowOOB_) { allowOOB = allowOOB_; }
void build() {
+ if (HANG_LIMIT > 0) {
+ prepareHangLimitSupport();
+ }
if (allowMemory) {
setupMemory();
}
@@ -196,6 +200,7 @@ public:
if (wasm.features.hasExceptionHandling()) {
setupEvents();
}
+ modifyInitialFunctions();
addImportLoggingSupport();
// keep adding functions until we run out of input
while (!finishedInput) {
@@ -205,6 +210,9 @@ public:
if (HANG_LIMIT > 0) {
addHangLimitSupport();
}
+ if (allowMemory) {
+ finalizeMemory();
+ }
finalizeTable();
}
@@ -241,7 +249,7 @@ private:
// the memory that we use, a small portion so that we have a good chance of
// looking at writes (we also look outside of this region with small
// probability) this should be a power of 2
- static const int USABLE_MEMORY = 16;
+ const Address USABLE_MEMORY = 16;
// the number of runtime iterations (function calls, loop backbranches) we
// allow before we stop execution with a trap, to prevent hangs. 0 means
@@ -406,21 +414,44 @@ private:
wasm.addExport(
builder.makeExport(hasher->name, hasher->name, ExternalKind::Function));
// Export memory so JS fuzzing can use it
- wasm.addExport(builder.makeExport("memory", "0", ExternalKind::Memory));
+ if (!wasm.getExportOrNull("memory")) {
+ wasm.addExport(builder.makeExport("memory", "0", ExternalKind::Memory));
+ }
}
void setupTable() {
wasm.table.exists = true;
+ wasm.table.initial = wasm.table.max = 0;
wasm.table.segments.emplace_back(builder.makeConst(int32_t(0)));
}
std::map<Type, std::vector<Name>> globalsByType;
void setupGlobals() {
+ // If there were initial wasm contents, there may be imported globals. That
+ // would be a problem in the fuzzer harness as we'd error if we do not
+ // provide them (and provide the proper type, etc.).
+ // Avoid that, so that all the standard fuzzing infrastructure can always
+ // run the wasm.
+ for (auto& global : wasm.globals) {
+ if (global->imported()) {
+ // Remove import info from imported globals, and give them a simple
+ // initializer.
+ global->module = global->base = Name();
+ global->init = makeConst(global->type);
+ } else {
+ // If the initialization referred to an imported global, it no longer
+ // can point to the same global after we make it a non-imported global
+ // (as wasm doesn't allow that - you can only use an imported one).
+ if (global->init->is<GlobalGet>()) {
+ global->init = makeConst(global->type);
+ }
+ }
+ }
for (size_t index = upTo(MAX_GLOBALS); index > 0; --index) {
auto type = getConcreteType();
auto* global =
- builder.makeGlobal(std::string("global$") + std::to_string(index),
+ builder.makeGlobal(Names::getValidGlobalName(wasm, "global$"),
type,
makeConst(type),
Builder::Mutable);
@@ -433,20 +464,87 @@ private:
Index num = upTo(3);
for (size_t i = 0; i < num; i++) {
auto* event =
- builder.makeEvent(std::string("event$") + std::to_string(i),
+ builder.makeEvent(Names::getValidEventName(wasm, "event$"),
WASM_EVENT_ATTRIBUTE_EXCEPTION,
Signature(getControlFlowType(), Type::none));
wasm.addEvent(event);
}
}
+ void finalizeMemory() {
+ for (auto& segment : wasm.memory.segments) {
+ Address maxOffset = segment.data.size();
+ if (!segment.isPassive) {
+ if (auto* offset = segment.offset->dynCast<GlobalGet>()) {
+ // Using a non-imported global in a segment offset is not valid in
+ // wasm. This can occur due to us making what used to be an imported
+ // global, in initial contents, be not imported any more. To fix that,
+ // replace such invalid things with a constant.
+ // Note that it is still possible in theory to have imported globals
+ // here, as we only do the above for initial contents. While the
+ // fuzzer doesn't do so as of the time of this comment, do a check
+ // for full generality, so that this code essentially does "if this
+ // is invalid wasm, fix it up."
+ if (!wasm.getGlobal(offset->name)->imported()) {
+ // TODO: It would be better to avoid segment overlap so that
+ // MemoryPacking can run.
+ segment.offset =
+ builder.makeConst(Literal::makeFromInt32(0, Type::i32));
+ }
+ }
+ if (auto* offset = segment.offset->dynCast<Const>()) {
+ maxOffset = maxOffset + offset->value.getInteger();
+ }
+ }
+ wasm.memory.initial = std::max(
+ wasm.memory.initial,
+ Address((maxOffset + Memory::kPageSize - 1) / Memory::kPageSize));
+ }
+ wasm.memory.initial = std::max(wasm.memory.initial, USABLE_MEMORY);
+ // Avoid an unlimited memory size, which would make fuzzing very difficult
+ // as different VMs will run out of system memory in different ways.
+ if (wasm.memory.max == Memory::kUnlimitedSize) {
+ wasm.memory.max = wasm.memory.initial;
+ }
+ if (wasm.memory.max <= wasm.memory.initial) {
+ // To allow growth to work (which a testcase may assume), try to make the
+ // maximum larger than the initial.
+ // TODO: scan the wasm for grow instructions?
+ wasm.memory.max =
+ std::min(Address(wasm.memory.initial + 1), Address(Memory::kMaxSize32));
+ }
+ // Avoid an imported memory (which the fuzz harness would need to handle).
+ wasm.memory.module = wasm.memory.base = Name();
+ }
+
void finalizeTable() {
- wasm.table.initial = wasm.table.segments[0].data.size();
+ for (auto& segment : wasm.table.segments) {
+ // If the offset is a global that was imported (which is ok) but no
+ // longer is (not ok) we need to change that.
+ if (auto* offset = segment.offset->dynCast<GlobalGet>()) {
+ if (!wasm.getGlobal(offset->name)->imported()) {
+ // TODO: the segments must not overlap...
+ segment.offset =
+ builder.makeConst(Literal::makeFromInt32(0, Type::i32));
+ }
+ }
+ Address maxOffset = segment.data.size();
+ if (auto* offset = segment.offset->dynCast<Const>()) {
+ maxOffset = maxOffset + offset->value.getInteger();
+ }
+ wasm.table.initial = std::max(wasm.table.initial, maxOffset);
+ }
wasm.table.max =
oneIn(2) ? Address(Table::kUnlimitedSize) : wasm.table.initial;
+ // Avoid an imported table (which the fuzz harness would need to handle).
+ wasm.table.module = wasm.table.base = Name();
}
- const Name HANG_LIMIT_GLOBAL = "hangLimit";
+ Name HANG_LIMIT_GLOBAL;
+
+ void prepareHangLimitSupport() {
+ HANG_LIMIT_GLOBAL = Names::getValidGlobalName(wasm, "hangLimit");
+ }
void addHangLimitSupport() {
auto* glob = builder.makeGlobal(HANG_LIMIT_GLOBAL,
@@ -455,15 +553,22 @@ private:
Builder::Mutable);
wasm.addGlobal(glob);
+ Name exportName = "hangLimitInitializer";
+ auto funcName = Names::getValidFunctionName(wasm, exportName);
auto* func = new Function;
- func->name = "hangLimitInitializer";
+ func->name = funcName;
func->sig = Signature(Type::none, Type::none);
- func->body =
- builder.makeGlobalSet(glob->name, builder.makeConst(int32_t(HANG_LIMIT)));
+ func->body = builder.makeGlobalSet(HANG_LIMIT_GLOBAL,
+ builder.makeConst(int32_t(HANG_LIMIT)));
wasm.addFunction(func);
+ if (wasm.getExportOrNull(exportName)) {
+ // We must export our actual hang limit function - remove anything
+ // previously existing.
+ wasm.removeExport(exportName);
+ }
auto* export_ = new Export;
- export_->name = func->name;
+ export_->name = exportName;
export_->value = func->name;
export_->kind = ExternalKind::Function;
wasm.addExport(export_);
@@ -507,11 +612,28 @@ private:
std::map<Type, std::vector<Index>>
typeLocals; // type => list of locals with that type
+ void prepareToCreateFunctionContents(Function* func_) {
+ func = func_;
+ labelIndex = 0;
+ assert(breakableStack.empty());
+ assert(hangStack.empty());
+ }
+
+ void finishCreatingFunctionContents() {
+ if (HANG_LIMIT > 0) {
+ addHangLimitChecks(func);
+ }
+ typeLocals.clear();
+ assert(breakableStack.empty());
+ assert(hangStack.empty());
+ }
+
+ Index numAddedFunctions = 0;
+
Function* addFunction() {
LOGGING_PERCENT = upToSquared(100);
- Index num = wasm.functions.size();
func = new Function;
- func->name = std::string("func_") + std::to_string(num);
+ func->name = Names::getValidFunctionName(wasm, "func");
assert(typeLocals.empty());
Index numParams = upToSquared(MAX_PARAMS);
std::vector<Type> params;
@@ -528,9 +650,7 @@ private:
typeLocals[type].push_back(params.size() + func->vars.size());
func->vars.push_back(type);
}
- labelIndex = 0;
- assert(breakableStack.empty());
- assert(hangStack.empty());
+ prepareToCreateFunctionContents(func);
// with small chance, make the body unreachable
auto bodyType = func->sig.results;
if (oneIn(10)) {
@@ -558,15 +678,11 @@ private:
fixLabels(func);
}
// Add hang limit checks after all other operations on the function body.
- if (HANG_LIMIT > 0) {
- addHangLimitChecks(func);
- }
- assert(breakableStack.empty());
- assert(hangStack.empty());
wasm.addFunction(func);
// export some, but not all (to allow inlining etc.). make sure to
// export at least one, though, to keep each testcase interesting
- if (num == 0 || oneIn(2)) {
+ if ((numAddedFunctions == 0 || oneIn(2)) &&
+ !wasm.getExportOrNull(func->name)) {
auto* export_ = new Export;
export_->name = func->name;
export_->value = func->name;
@@ -578,7 +694,8 @@ private:
wasm.table.segments[0].data.push_back(func->name);
}
// cleanup
- typeLocals.clear();
+ finishCreatingFunctionContents();
+ numAddedFunctions++;
return func;
}
@@ -783,10 +900,86 @@ private:
ReFinalize().walkFunctionInModule(func, &wasm);
}
+ void modifyInitialFunctions() {
+ if (wasm.functions.empty()) {
+ return;
+ }
+ // Pick a chance to fuzz the contents of a function.
+ const int RESOLUTION = 10;
+ auto chance = upTo(RESOLUTION + 1);
+ for (auto& ref : wasm.functions) {
+ auto* func = ref.get();
+ prepareToCreateFunctionContents(func);
+ if (func->imported()) {
+ // We can't allow extra imports, as the fuzzing infrastructure wouldn't
+ // know what to provide.
+ func->module = func->base = Name();
+ func->body = make(func->sig.results);
+ }
+ // Optionally, fuzz the function contents.
+ if (upTo(RESOLUTION) >= chance) {
+ dropToLog(func);
+ // TODO add some locals? and the rest of addFunction's operations?
+ // TODO: interposition, replace initial a(b) with a(RANDOM_THING(b))
+ // TODO: if we add OOB checks after creation, then we can do it on
+ // initial contents too, and it may be nice to *not* run these
+ // passes, like we don't run them on new functions. But, we may
+ // still want to run them some of the time, at least, so that we
+ // check variations on initial testcases even at the risk of OOB.
+ recombine(func);
+ mutate(func);
+ fixLabels(func);
+ }
+ // Note that even if we don't fuzz the contents we still need to call
+ // finish so that we add hang limit protection and other general things.
+ finishCreatingFunctionContents();
+ }
+ // Remove a start function - the fuzzing harness expects code to run only
+ // from exports.
+ wasm.start = Name();
+ }
+
+ // Initial wasm contents may have come from a test that uses the drop pattern:
+ //
+ // (drop ..something interesting..)
+ //
+ // The dropped interesting thing is optimized to some other interesting thing
+ // by a pass, and we verify it is the expected one. But this does not use the
+ // value in a way the fuzzer can notice. Replace some drops with a logging
+ // operation instead.
+ void dropToLog(Function* func) {
+ // Don't always do this.
+ if (oneIn(2)) {
+ return;
+ }
+ struct Modder : public PostWalker<Modder> {
+ Module& wasm;
+ TranslateToFuzzReader& parent;
+
+ Modder(Module& wasm, TranslateToFuzzReader& parent)
+ : wasm(wasm), parent(parent) {}
+
+ void visitDrop(Drop* curr) {
+ if (parent.isLoggableType(curr->value->type) && parent.oneIn(2)) {
+ replaceCurrent(parent.builder.makeCall(std::string("log-") +
+ curr->value->type.toString(),
+ {curr->value},
+ Type::none));
+ }
+ }
+ };
+ Modder modder(wasm, *this);
+ modder.walk(func->body);
+ }
+
// the fuzzer external interface sends in zeros (simpler to compare
// across invocations from JS or wasm-opt etc.). Add invocations in
// the wasm, so they run everywhere
void addInvocations(Function* func) {
+ Name name = func->name.str + std::string("_invoker");
+ if (wasm.getFunctionOrNull(name) || wasm.getExportOrNull(name)) {
+ return;
+ }
std::vector<Expression*> invocations;
while (oneIn(2) && !finishedInput) {
std::vector<Expression*> args;
@@ -808,13 +1001,13 @@ private:
return;
}
auto* invoker = new Function;
- invoker->name = func->name.str + std::string("_invoker");
+ invoker->name = name;
invoker->sig = Signature(Type::none, Type::none);
invoker->body = builder.makeBlock(invocations);
wasm.addFunction(invoker);
auto* export_ = new Export;
- export_->name = invoker->name;
- export_->value = invoker->name;
+ export_->name = name;
+ export_->value = name;
export_->kind = ExternalKind::Function;
wasm.addExport(export_);
}
diff --git a/src/tools/wasm-opt.cpp b/src/tools/wasm-opt.cpp
index ecf4f1c7c..56a26ccfb 100644
--- a/src/tools/wasm-opt.cpp
+++ b/src/tools/wasm-opt.cpp
@@ -81,6 +81,7 @@ int main(int argc, const char* argv[]) {
bool fuzzExecAfter = false;
std::string extraFuzzCommand;
bool translateToFuzz = false;
+ std::string initialFuzz;
bool fuzzPasses = false;
bool fuzzMemory = true;
bool fuzzOOB = true;
@@ -141,6 +142,13 @@ int main(int argc, const char* argv[]) {
"fuzzing",
Options::Arguments::Zero,
[&](Options* o, const std::string& arguments) { translateToFuzz = true; })
+ .add("--initial-fuzz",
+ "-if",
+ "Initial wasm content in translate-to-fuzz (-ttf) mode",
+ Options::Arguments::One,
+ [&initialFuzz](Options* o, const std::string& argument) {
+ initialFuzz = argument;
+ })
.add("--fuzz-passes",
"-fp",
"Pick a random set of passes to run, useful for fuzzing. this depends "
@@ -224,7 +232,13 @@ int main(int argc, const char* argv[]) {
Fatal() << message;
};
- if (!translateToFuzz) {
+ // In normal (non-translate-to-fuzz) mode we read the input file. In
+ // translate-to-fuzz mode the input file is the random data, and used later
+ // down in TranslateToFuzzReader, but there is also an optional initial fuzz
+ // file that if it exists we read it, then add more fuzz on top.
+ if (!translateToFuzz || initialFuzz.size()) {
+ std::string inputFile =
+ translateToFuzz ? initialFuzz : options.extra["infile"];
ModuleReader reader;
// Enable DWARF parsing if we were asked for debug info, and were not
// asked to remove it.
@@ -232,7 +246,7 @@ int main(int argc, const char* argv[]) {
!willRemoveDebugInfo(options.passes));
reader.setProfile(options.profile);
try {
- reader.read(options.extra["infile"], wasm, inputSourceMapFilename);
+ reader.read(inputFile, wasm, inputSourceMapFilename);
} catch (ParseException& p) {
p.dump(std::cerr);
std::cerr << '\n';
@@ -253,8 +267,8 @@ int main(int argc, const char* argv[]) {
exitOnInvalidWasm("error validating input");
}
}
- } else {
- // translate-to-fuzz
+ }
+ if (translateToFuzz) {
options.applyFeatures(wasm);
TranslateToFuzzReader reader(wasm, options.extra["infile"]);
if (fuzzPasses) {
diff --git a/test/passes/fuzz_metrics_noprint.bin.txt b/test/passes/fuzz_metrics_noprint.bin.txt
index da8acec57..ad654067f 100644
--- a/test/passes/fuzz_metrics_noprint.bin.txt
+++ b/test/passes/fuzz_metrics_noprint.bin.txt
@@ -1,30 +1,30 @@
total
[events] : 0
- [exports] : 45
- [funcs] : 72
+ [exports] : 35
+ [funcs] : 47
[globals] : 7
[imports] : 4
[memory-data] : 4
- [table-data] : 30
- [total] : 5321
- [vars] : 256
- binary : 416
- block : 755
- break : 178
- call : 234
- call_indirect : 49
- const : 970
- drop : 58
- global.get : 461
- global.set : 201
- if : 313
- load : 98
- local.get : 412
- local.set : 301
- loop : 119
- nop : 80
- return : 209
- select : 46
- store : 43
- unary : 375
- unreachable : 3
+ [table-data] : 16
+ [total] : 6048
+ [vars] : 137
+ binary : 458
+ block : 871
+ break : 230
+ call : 240
+ call_indirect : 52
+ const : 1081
+ drop : 40
+ global.get : 480
+ global.set : 204
+ if : 356
+ load : 108
+ local.get : 527
+ local.set : 351
+ loop : 155
+ nop : 120
+ return : 233
+ select : 51
+ store : 58
+ unary : 432
+ unreachable : 1
diff --git a/test/passes/translate-to-fuzz_all-features.txt b/test/passes/translate-to-fuzz_all-features.txt
index 3d4182cba..5a9d9dc7b 100644
--- a/test/passes/translate-to-fuzz_all-features.txt
+++ b/test/passes/translate-to-fuzz_all-features.txt
@@ -1,35 +1,31 @@
(module
(type $none_=>_none (func))
- (type $none_=>_i32 (func (result i32)))
- (type $i64_=>_none (func (param i64)))
(type $i32_=>_none (func (param i32)))
+ (type $i64_=>_none (func (param i64)))
(type $f32_=>_none (func (param f32)))
(type $f64_=>_none (func (param f64)))
(type $v128_=>_none (func (param v128)))
+ (type $funcref_=>_none (func (param funcref)))
(type $exnref_=>_none (func (param exnref)))
- (type $funcref_f64_=>_i32 (func (param funcref f64) (result i32)))
- (type $none_=>_f32 (func (result f32)))
- (type $eqref_i32_=>_funcref (func (param eqref i32) (result funcref)))
- (type $i32_i32_f32_exnref_=>_externref (func (param i32 i32 f32 exnref) (result externref)))
- (type $externref_f64_f32_eqref_i31ref_anyref_=>_externref (func (param externref f64 f32 eqref i31ref anyref) (result externref)))
- (type $exnref_f32_i31ref_externref_funcref_i31ref_i64_=>_exnref (func (param exnref f32 i31ref externref funcref i31ref i64) (result exnref)))
- (type $none_=>_eqref_i31ref_i64_v128_eqref (func (result eqref i31ref i64 v128 eqref)))
- (type $v128_i31ref_=>_eqref_i31ref_i64_v128_eqref (func (param v128 i31ref) (result eqref i31ref i64 v128 eqref)))
- (type $none_=>_i31ref (func (result i31ref)))
+ (type $eqref_=>_none (func (param eqref)))
+ (type $none_=>_i32 (func (result i32)))
+ (type $anyref_anyref_externref_eqref_v128_=>_i64 (func (param anyref anyref externref eqref v128) (result i64)))
+ (type $i31ref_v128_=>_exnref (func (param i31ref v128) (result exnref)))
+ (type $i31ref_externref_v128_f32_f32_=>_eqref (func (param i31ref externref v128 f32 f32) (result eqref)))
(import "fuzzing-support" "log-i32" (func $log-i32 (param i32)))
(import "fuzzing-support" "log-i64" (func $log-i64 (param i64)))
(import "fuzzing-support" "log-f32" (func $log-f32 (param f32)))
(import "fuzzing-support" "log-f64" (func $log-f64 (param f64)))
(import "fuzzing-support" "log-v128" (func $log-v128 (param v128)))
(import "fuzzing-support" "log-exnref" (func $log-exnref (param exnref)))
- (memory $0 (shared 1 1))
+ (memory $0 (shared 16 17))
(data (i32.const 0) "N\0fN\f5\f9\b1\ff\fa\eb\e5\fe\a7\ec\fb\fc\f4\a6\e4\ea\f0\ae\e3")
- (table $0 5 5 funcref)
- (elem (i32.const 0) $func_9 $func_9 $func_9 $func_10 $func_14)
- (global $global$5 (mut eqref) (ref.null eq))
- (global $global$4 (mut i32) (i32.const 470177031))
- (global $global$3 (mut f64) (f64.const 2147483647))
- (global $global$2 (mut (eqref f32 eqref funcref funcref i64)) (tuple.make
+ (table $0 3 funcref)
+ (elem (i32.const 0) $func_1 $func_1 $func_1)
+ (global $global$ (mut eqref) (ref.null eq))
+ (global $global$_0 (mut i32) (i32.const 470177031))
+ (global $global$_1 (mut f64) (f64.const 2147483647))
+ (global $global$_2 (mut (eqref f32 eqref funcref funcref i64)) (tuple.make
(ref.null eq)
(f32.const -2147483648)
(ref.null eq)
@@ -37,257 +33,274 @@
(ref.null func)
(i64.const -32)
))
- (global $global$1 (mut f32) (f32.const -32769))
+ (global $global$_3 (mut f32) (f32.const -32769))
(global $hangLimit (mut i32) (i32.const 10))
(export "hashMemory" (func $hashMemory))
(export "memory" (memory $0))
- (export "func_7_invoker" (func $func_7_invoker))
- (export "func_9" (func $func_9))
- (export "func_11_invoker" (func $func_11_invoker))
- (export "func_14" (func $func_14))
- (export "func_18" (func $func_18))
- (export "func_19" (func $func_19))
+ (export "func" (func $func))
+ (export "func_invoker" (func $func_invoker))
+ (export "func_1_invoker" (func $func_1_invoker))
+ (export "func_2" (func $func_2))
+ (export "func_2_invoker" (func $func_2_invoker))
+ (export "func_3" (func $func_3))
(export "hangLimitInitializer" (func $hangLimitInitializer))
(func $hashMemory (result i32)
(local $0 i32)
- (local.set $0
- (i32.const 5381)
- )
- (local.set $0
- (i32.xor
- (i32.add
- (i32.shl
- (local.get $0)
- (i32.const 5)
- )
- (local.get $0)
+ (block
+ (if
+ (i32.eqz
+ (global.get $hangLimit)
)
- (i32.load8_u
- (i32.const 0)
+ (return
+ (i32.const 69)
+ )
+ )
+ (global.set $hangLimit
+ (i32.sub
+ (global.get $hangLimit)
+ (i32.const 1)
)
)
)
- (local.set $0
- (i32.xor
- (i32.add
- (i32.shl
+ (block (result i32)
+ (local.set $0
+ (i32.const 5381)
+ )
+ (local.set $0
+ (i32.xor
+ (i32.add
+ (i32.shl
+ (local.get $0)
+ (i32.const 5)
+ )
(local.get $0)
- (i32.const 5)
)
- (local.get $0)
- )
- (i32.load8_u offset=1
- (i32.const 0)
+ (i32.load8_u
+ (i32.const 0)
+ )
)
)
- )
- (local.set $0
- (i32.xor
- (i32.add
- (i32.shl
+ (local.set $0
+ (i32.xor
+ (i32.add
+ (i32.shl
+ (local.get $0)
+ (i32.const 5)
+ )
(local.get $0)
- (i32.const 5)
)
- (local.get $0)
- )
- (i32.load8_u offset=2
- (i32.const 0)
+ (i32.load8_u offset=1
+ (i32.const 0)
+ )
)
)
- )
- (local.set $0
- (i32.xor
- (i32.add
- (i32.shl
+ (local.set $0
+ (i32.xor
+ (i32.add
+ (i32.shl
+ (local.get $0)
+ (i32.const 5)
+ )
(local.get $0)
- (i32.const 5)
)
- (local.get $0)
- )
- (i32.load8_u offset=3
- (i32.const 0)
+ (i32.load8_u offset=2
+ (i32.const 0)
+ )
)
)
- )
- (local.set $0
- (i32.xor
- (i32.add
- (i32.shl
+ (local.set $0
+ (i32.xor
+ (i32.add
+ (i32.shl
+ (local.get $0)
+ (i32.const 5)
+ )
(local.get $0)
- (i32.const 5)
)
- (local.get $0)
- )
- (i32.load8_u offset=4
- (i32.const 0)
+ (i32.load8_u offset=3
+ (i32.const 0)
+ )
)
)
- )
- (local.set $0
- (i32.xor
- (i32.add
- (i32.shl
+ (local.set $0
+ (i32.xor
+ (i32.add
+ (i32.shl
+ (local.get $0)
+ (i32.const 5)
+ )
(local.get $0)
- (i32.const 5)
)
- (local.get $0)
- )
- (i32.load8_u offset=5
- (i32.const 0)
+ (i32.load8_u offset=4
+ (i32.const 0)
+ )
)
)
- )
- (local.set $0
- (i32.xor
- (i32.add
- (i32.shl
+ (local.set $0
+ (i32.xor
+ (i32.add
+ (i32.shl
+ (local.get $0)
+ (i32.const 5)
+ )
(local.get $0)
- (i32.const 5)
)
- (local.get $0)
- )
- (i32.load8_u offset=6
- (i32.const 0)
+ (i32.load8_u offset=5
+ (i32.const 0)
+ )
)
)
- )
- (local.set $0
- (i32.xor
- (i32.add
- (i32.shl
+ (local.set $0
+ (i32.xor
+ (i32.add
+ (i32.shl
+ (local.get $0)
+ (i32.const 5)
+ )
(local.get $0)
- (i32.const 5)
)
- (local.get $0)
- )
- (i32.load8_u offset=7
- (i32.const 0)
+ (i32.load8_u offset=6
+ (i32.const 0)
+ )
)
)
- )
- (local.set $0
- (i32.xor
- (i32.add
- (i32.shl
+ (local.set $0
+ (i32.xor
+ (i32.add
+ (i32.shl
+ (local.get $0)
+ (i32.const 5)
+ )
(local.get $0)
- (i32.const 5)
)
- (local.get $0)
- )
- (i32.load8_u offset=8
- (i32.const 0)
+ (i32.load8_u offset=7
+ (i32.const 0)
+ )
)
)
- )
- (local.set $0
- (i32.xor
- (i32.add
- (i32.shl
+ (local.set $0
+ (i32.xor
+ (i32.add
+ (i32.shl
+ (local.get $0)
+ (i32.const 5)
+ )
(local.get $0)
- (i32.const 5)
)
- (local.get $0)
- )
- (i32.load8_u offset=9
- (i32.const 0)
+ (i32.load8_u offset=8
+ (i32.const 0)
+ )
)
)
- )
- (local.set $0
- (i32.xor
- (i32.add
- (i32.shl
+ (local.set $0
+ (i32.xor
+ (i32.add
+ (i32.shl
+ (local.get $0)
+ (i32.const 5)
+ )
(local.get $0)
- (i32.const 5)
)
- (local.get $0)
- )
- (i32.load8_u offset=10
- (i32.const 0)
+ (i32.load8_u offset=9
+ (i32.const 0)
+ )
)
)
- )
- (local.set $0
- (i32.xor
- (i32.add
- (i32.shl
+ (local.set $0
+ (i32.xor
+ (i32.add
+ (i32.shl
+ (local.get $0)
+ (i32.const 5)
+ )
(local.get $0)
- (i32.const 5)
)
- (local.get $0)
- )
- (i32.load8_u offset=11
- (i32.const 0)
+ (i32.load8_u offset=10
+ (i32.const 0)
+ )
)
)
- )
- (local.set $0
- (i32.xor
- (i32.add
- (i32.shl
+ (local.set $0
+ (i32.xor
+ (i32.add
+ (i32.shl
+ (local.get $0)
+ (i32.const 5)
+ )
(local.get $0)
- (i32.const 5)
)
- (local.get $0)
- )
- (i32.load8_u offset=12
- (i32.const 0)
+ (i32.load8_u offset=11
+ (i32.const 0)
+ )
)
)
- )
- (local.set $0
- (i32.xor
- (i32.add
- (i32.shl
+ (local.set $0
+ (i32.xor
+ (i32.add
+ (i32.shl
+ (local.get $0)
+ (i32.const 5)
+ )
(local.get $0)
- (i32.const 5)
)
- (local.get $0)
- )
- (i32.load8_u offset=13
- (i32.const 0)
+ (i32.load8_u offset=12
+ (i32.const 0)
+ )
)
)
- )
- (local.set $0
- (i32.xor
- (i32.add
- (i32.shl
+ (local.set $0
+ (i32.xor
+ (i32.add
+ (i32.shl
+ (local.get $0)
+ (i32.const 5)
+ )
(local.get $0)
- (i32.const 5)
)
- (local.get $0)
- )
- (i32.load8_u offset=14
- (i32.const 0)
+ (i32.load8_u offset=13
+ (i32.const 0)
+ )
)
)
- )
- (local.set $0
- (i32.xor
- (i32.add
- (i32.shl
+ (local.set $0
+ (i32.xor
+ (i32.add
+ (i32.shl
+ (local.get $0)
+ (i32.const 5)
+ )
(local.get $0)
- (i32.const 5)
)
- (local.get $0)
+ (i32.load8_u offset=14
+ (i32.const 0)
+ )
)
- (i32.load8_u offset=15
- (i32.const 0)
+ )
+ (local.set $0
+ (i32.xor
+ (i32.add
+ (i32.shl
+ (local.get $0)
+ (i32.const 5)
+ )
+ (local.get $0)
+ )
+ (i32.load8_u offset=15
+ (i32.const 0)
+ )
)
)
+ (local.get $0)
)
- (local.get $0)
)
- (func $func_7 (param $0 i32) (param $1 i32) (param $2 f32) (param $3 exnref) (result externref)
- (local $4 i32)
+ (func $func (param $0 i31ref) (param $1 v128) (result exnref)
(block
(if
(i32.eqz
(global.get $hangLimit)
)
(return
- (ref.null extern)
+ (ref.null exn)
)
)
(global.set $hangLimit
@@ -297,34 +310,30 @@
)
)
)
- (ref.null extern)
+ (ref.null exn)
)
- (func $func_7_invoker
+ (func $func_invoker
(drop
- (call $func_7
- (i32.const -127)
- (i32.const -268435456)
- (f32.const 1179405440)
- (ref.null exn)
+ (call $func
+ (i31.new
+ (i32.const 1633240409)
+ )
+ (v128.const i32x4 0xcf800000 0x4c816020 0x3e1a1a1a 0x00000000)
)
)
- (call $log-i32
- (call $hashMemory)
- )
)
- (func $func_9 (result f32)
- (local $0 anyref)
- (local $1 f64)
- (local $2 (anyref anyref))
- (local $3 eqref)
- (local $4 externref)
+ (func $func_0 (param $0 anyref) (param $1 anyref) (param $2 externref) (param $3 eqref) (param $4 v128) (result i64)
+ (local $5 eqref)
+ (local $6 (funcref externref i32 f64 f32 i64))
+ (local $7 (f32 funcref eqref i32 i31ref i32))
+ (local $8 v128)
(block
(if
(i32.eqz
(global.get $hangLimit)
)
(return
- (f32.const 1.4949444621624858e-31)
+ (i64.const 2147483647)
)
)
(global.set $hangLimit
@@ -334,35 +343,30 @@
)
)
)
- (block $label$0
+ (block $label$0 (result i64)
(nop)
- (block $label$1
- (nop)
- (return
- (f32.const 7.396028525772014e-24)
+ (br_if $label$0
+ (tuple.extract 1
+ (tuple.make
+ (f64.const -255)
+ (i64.const -127)
+ (ref.null func)
+ )
)
+ (i32.const -24)
)
)
)
- (func $func_10 (param $0 externref) (param $1 f64) (param $2 f32) (param $3 eqref) (param $4 i31ref) (param $5 anyref) (result externref)
- (local $6 i64)
- (local $7 (anyref exnref f32 f64 f64))
- (local $8 eqref)
- (local $9 exnref)
- (local $10 i64)
- (local $11 f64)
- (local $12 f32)
- (local $13 v128)
- (local $14 exnref)
- (local $15 (funcref funcref v128 i31ref v128))
- (local $16 anyref)
+ (func $func_1 (param $0 i31ref) (param $1 externref) (param $2 v128) (param $3 f32) (param $4 f32) (result eqref)
+ (local $5 v128)
+ (local $6 (exnref externref i64 i32 f64 funcref))
(block
(if
(i32.eqz
(global.get $hangLimit)
)
(return
- (ref.null extern)
+ (ref.null eq)
)
)
(global.set $hangLimit
@@ -372,121 +376,75 @@
)
)
)
- (ref.null extern)
+ (ref.null eq)
)
- (func $func_11 (param $0 exnref) (param $1 f32) (param $2 i31ref) (param $3 externref) (param $4 funcref) (param $5 i31ref) (param $6 i64) (result exnref)
- (local $7 (i32 i64 anyref externref externref eqref))
- (block
- (if
- (i32.eqz
- (global.get $hangLimit)
- )
- (return
- (ref.null exn)
- )
- )
- (global.set $hangLimit
- (i32.sub
- (global.get $hangLimit)
- (i32.const 1)
+ (func $func_1_invoker
+ (drop
+ (call $func_1
+ (i31.new
+ (i32.const 16777216)
)
+ (ref.null extern)
+ (v128.const i32x4 0x5d455846 0xcf800000 0x42aa0000 0x46b2a800)
+ (f32.const -17592186044416)
+ (f32.const -4294967296)
)
)
- (ref.null exn)
- )
- (func $func_11_invoker
+ (call $log-i32
+ (call $hashMemory)
+ )
(drop
- (call $func_11
- (ref.null exn)
- (f32.const -1022.1400146484375)
+ (call $func_1
(i31.new
- (i32.const -32766)
+ (i32.const 73)
)
(ref.null extern)
- (ref.null func)
- (i31.new
- (i32.const -65535)
- )
- (i64.const 3)
+ (v128.const i32x4 0xffff8001 0xffffffff 0x184c764c 0x10105676)
+ (f32.const -9223372036854775808)
+ (f32.const 129)
)
)
(call $log-i32
(call $hashMemory)
)
- )
- (func $func_13 (result i31ref)
- (local $0 i31ref)
- (local $1 (funcref f32 anyref f32 externref))
- (local $2 f64)
- (local $3 f64)
- (local $4 (i32 v128))
- (local $5 (anyref i64 v128 eqref funcref exnref))
- (local $6 i32)
- (block
- (if
- (i32.eqz
- (global.get $hangLimit)
- )
- (return
- (i31.new
- (i32.const -28)
- )
- )
- )
- (global.set $hangLimit
- (i32.sub
- (global.get $hangLimit)
- (i32.const 1)
+ (drop
+ (call $func_1
+ (i31.new
+ (i32.const -4096)
)
+ (ref.null extern)
+ (v128.const i32x4 0x00000000 0x00000020 0x00000001 0xffc00000)
+ (f32.const -nan:0x7fffbd)
+ (f32.const -255.8040008544922)
)
)
- (i31.new
- (i32.const 64)
+ (call $log-i32
+ (call $hashMemory)
)
- )
- (func $func_14 (result i32)
- (local $0 i32)
- (local $1 funcref)
- (local $2 (anyref externref))
- (local $3 funcref)
- (local $4 i64)
- (local $5 externref)
- (local $6 (exnref f64))
- (local $7 (anyref f64 f64))
- (local $8 (i64 i32 eqref exnref))
- (local $9 (v128 i64 funcref i32 anyref anyref))
- (local $10 (i32 eqref f64 funcref))
- (local $11 eqref)
- (block
- (if
- (i32.eqz
- (global.get $hangLimit)
- )
- (return
- (local.get $0)
- )
- )
- (global.set $hangLimit
- (i32.sub
- (global.get $hangLimit)
- (i32.const 1)
+ (drop
+ (call $func_1
+ (i31.new
+ (i32.const 1936946035)
)
+ (ref.null extern)
+ (v128.const i32x4 0xff010001 0x4e07ffff 0x00060002 0xff00001f)
+ (f32.const 6918)
+ (f32.const 4.921484278772694e-25)
)
)
- (block $label$0 (result i32)
- (nop)
- (local.get $0)
+ (call $log-i32
+ (call $hashMemory)
)
)
- (func $func_15 (result i32)
+ (func $func_2 (param $0 funcref)
+ (local $1 exnref)
+ (local $2 (f64 externref f32 f32 v128 eqref))
(block
(if
(i32.eqz
(global.get $hangLimit)
)
- (return
- (i32.const 32768)
- )
+ (return)
)
(global.set $hangLimit
(i32.sub
@@ -495,89 +453,26 @@
)
)
)
- (global.get $global$4)
- )
- (func $func_16 (param $0 eqref) (param $1 i32) (result funcref)
- (local $2 v128)
- (local $3 funcref)
- (local $4 eqref)
- (block
- (if
- (i32.eqz
- (global.get $hangLimit)
- )
- (return
- (local.get $3)
- )
- )
- (global.set $hangLimit
- (i32.sub
- (global.get $hangLimit)
- (i32.const 1)
+ (block $label$0
+ (f32.store offset=3 align=2
+ (i32.and
+ (i32.const 15)
+ (i32.const 15)
)
+ (f32.const 66)
)
+ (return)
)
- (loop $label$1 (result funcref)
- (block
- (if
- (i32.eqz
- (global.get $hangLimit)
- )
- (return
- (local.get $3)
- )
- )
- (global.set $hangLimit
- (i32.sub
- (global.get $hangLimit)
- (i32.const 1)
- )
- )
- )
- (block (result funcref)
- (block $label$2
- (br_if $label$2
- (i32.eqz
- (block $label$3
- (global.set $global$4
- (local.tee $1
- (local.get $1)
- )
- )
- (block $label$4
- (nop)
- (br_if $label$1
- (i32.eqz
- (i31.get_u
- (i31.new
- (i32.const -90)
- )
- )
- )
- )
- )
- (br $label$1)
- )
- )
- )
- (memory.init 0
- (i32.and
- (local.get $1)
- (i32.const 15)
- )
- (i32.const 16)
- (i32.const 3)
- )
- )
- (br_if $label$1
- (i32.const 131071)
- )
- (local.get $3)
- )
+ )
+ (func $func_2_invoker
+ (call $func_2
+ (ref.null func)
)
)
- (func $func_17 (param $0 i64)
- (local $1 externref)
+ (func $func_3 (param $0 eqref)
+ (local $1 eqref)
+ (local $2 funcref)
+ (local $3 funcref)
(block
(if
(i32.eqz
@@ -593,249 +488,214 @@
)
)
(block $label$0
- (call $log-i32
- (call $hashMemory)
- )
+ (atomic.fence)
(if
- (i32.eqz
- (f64.gt
- (f64.const 103)
- (f64.max
- (f64.const 8388607.124)
- (if
- (i32.eqz
- (global.get $global$4)
- )
- (block $label$1
- (call $log-v128
- (f64x2.replace_lane 0
- (f32x4.neg
- (i8x16.shr_u
- (v128.load offset=3
- (i32.const 65535)
+ (i64.gt_s
+ (i64.const 379)
+ (i64.add
+ (i32.atomic.load offset=22
+ (i32.and
+ (i32.atomic.load offset=22
+ (i32.and
+ (select
+ (block $label$1 (result i32)
+ (block $label$2
+ (memory.init 0
+ (i32.and
+ (ref.eq
+ (ref.null eq)
+ (local.get $0)
+ )
+ (i32.const 15)
+ )
+ (i32.const 8)
+ (i32.const 9)
)
- (loop $label$2 (result i32)
- (block
- (if
- (i32.eqz
- (global.get $hangLimit)
- )
- (return)
+ (if
+ (i32.eqz
+ (i32.const -75)
+ )
+ (block $label$3
+ (nop)
+ (nop)
+ )
+ (nop)
+ )
+ )
+ (i32.const 1903327608)
+ )
+ (select
+ (loop $label$6 (result i32)
+ (block
+ (if
+ (i32.eqz
+ (global.get $hangLimit)
)
- (global.set $hangLimit
- (i32.sub
- (global.get $hangLimit)
- (i32.const 1)
- )
+ (return)
+ )
+ (global.set $hangLimit
+ (i32.sub
+ (global.get $hangLimit)
+ (i32.const 1)
)
)
- (block (result i32)
- (block $label$3
- (call $log-exnref
- (ref.null exn)
+ )
+ (block $label$7 (result i32)
+ (loop $label$8
+ (block
+ (if
+ (i32.eqz
+ (global.get $hangLimit)
+ )
+ (return)
)
- (call $log-i32
- (i32.atomic.rmw8.sub_u offset=22
- (i32.and
- (i32.const 6)
- (i32.const 15)
- )
- (i32.const 1852667194)
+ (global.set $hangLimit
+ (i32.sub
+ (global.get $hangLimit)
+ (i32.const 1)
)
)
)
- (br_if $label$2
- (tuple.extract 0
- (tuple.make
+ (block
+ (nop)
+ (br_if $label$8
+ (i32.eqz
(i32.const -2147483648)
- (i31.new
- (i32.const -32767)
- )
)
)
+ (nop)
)
- (i32.const -2147483648)
)
+ (i32.const 76)
)
)
- )
- (f64.const -nan:0xffffffffffff3)
- )
- )
- (br $label$0)
- )
- (block $label$4
- (br_if $label$0
- (i32.eqz
- (tuple.extract 2
- (block $label$5
- (call $log-i32
- (call $hashMemory)
+ (global.get $global$_0)
+ (if
+ (i32.const -96)
+ (block $label$4
+ (f32.store offset=3
+ (i32.and
+ (i32.const 4124)
+ (i32.const 15)
+ )
+ (f32.const -8192.1474609375)
+ )
+ (return)
+ )
+ (block $label$5
+ (memory.copy
+ (i32.and
+ (i32.const 32768)
+ (i32.const 15)
+ )
+ (i32.and
+ (i32.const 2087520520)
+ (i32.const 15)
+ )
+ (i32.const -131072)
+ )
+ (return)
)
- (br $label$0)
)
)
+ (i32.const 85)
)
+ (i32.const 15)
)
- (br $label$0)
)
+ (i32.const 15)
)
)
+ (i64.const 125)
)
)
- (block $label$6
- (call $log-i32
- (call $hashMemory)
- )
- (call $log-i32
- (call $hashMemory)
- )
- )
- (if
- (i32.eqz
- (i32.or
- (i32.const 65535)
- (i32.const 8)
- )
- )
- (call $log-i32
- (i32.const 608321884)
- )
- (atomic.fence)
- )
- )
- )
- )
- (func $func_18 (param $0 v128) (param $1 i31ref) (result eqref i31ref i64 v128 eqref)
- (local $2 i64)
- (local $3 v128)
- (local $4 f32)
- (block
- (if
- (i32.eqz
- (global.get $hangLimit)
- )
- (return
- (tuple.make
- (ref.null eq)
- (i31.new
- (i32.const -2147483648)
- )
- (i64.const 369041285507055655)
- (v128.const i32x4 0xffffffd1 0xffffffff 0x25312936 0x5455263f)
- (ref.null eq)
- )
- )
- )
- (global.set $hangLimit
- (i32.sub
- (global.get $hangLimit)
- (i32.const 1)
- )
- )
- )
- (tuple.make
- (ref.null eq)
- (local.get $1)
- (i64.const -32766)
- (v128.bitselect
- (local.get $3)
- (block $label$2
- (atomic.fence)
- (return
- (tuple.make
- (ref.null eq)
- (i31.new
- (i32.const 19521)
+ (block $label$9
+ (loop $label$10
+ (block
+ (if
+ (i32.eqz
+ (global.get $hangLimit)
+ )
+ (return)
+ )
+ (global.set $hangLimit
+ (i32.sub
+ (global.get $hangLimit)
+ (i32.const 1)
+ )
)
- (i64.const -32766)
- (v128.const i32x4 0x4f800000 0x3e116873 0x46ca0800 0x54000000)
- (ref.null eq)
- )
- )
- )
- (tuple.extract 2
- (tuple.make
- (i31.new
- (i32.const 1684216173)
)
- (ref.null eq)
- (v128.const i32x4 0xffa20004 0x00000000 0x2b25ffa6 0x005b0080)
- (ref.null eq)
- )
- )
- )
- (global.get $global$5)
- )
- )
- (func $func_19 (param $0 funcref) (param $1 f64) (result i32)
- (local $2 (funcref f32 exnref exnref externref))
- (local $3 externref)
- (local $4 v128)
- (local $5 f64)
- (local $6 i32)
- (local $7 (f64 i31ref eqref eqref))
- (local $8 anyref)
- (local $9 (eqref i31ref i32 i31ref anyref))
- (local $10 eqref)
- (local $11 exnref)
- (local $12 externref)
- (local $13 externref)
- (local $14 externref)
- (local $15 i31ref)
- (block
- (if
- (i32.eqz
- (global.get $hangLimit)
- )
- (return
- (i32.const -131072)
- )
- )
- (global.set $hangLimit
- (i32.sub
- (global.get $hangLimit)
- (i32.const 1)
- )
- )
- )
- (select
- (i32.atomic.load8_u offset=22
- (i32.and
- (local.get $6)
- (i32.const 15)
- )
- )
- (local.tee $6
- (select
- (i16x8.extract_lane_s 4
- (v128.const i32x4 0xfffffff8 0x00008001 0xffffffa0 0x180b1217)
- )
- (if (result i32)
- (if (result i32)
- (i32.eqz
- (i32.const 524287)
+ (block
+ (local.set $1
+ (tuple.extract 2
+ (tuple.make
+ (f32.const 353703200)
+ (f32.const 31868)
+ (ref.null eq)
+ (v128.const i32x4 0x56565656 0xff7fffff 0x41000000 0x41300000)
+ (i32.const 135290122)
+ (i31.new
+ (i32.const 1396855106)
+ )
+ )
+ )
+ )
+ (br_if $label$10
+ (i32.eqz
+ (i32.const -2147483646)
+ )
)
- (block $label$1
- (memory.init 0
+ (i32.atomic.store offset=22
+ (i32.and
+ (i32.load16_u offset=22
+ (i32.and
+ (i32.const 521404930)
+ (i32.const 15)
+ )
+ )
+ (i32.const 15)
+ )
+ (i32.atomic.load offset=3
(i32.and
- (f32.ge
- (block $label$2 (result f32)
- (call $log-i32
- (call $hashMemory)
- )
- (if (result f32)
- (if (result i32)
- (i32.eqz
- (loop $label$3 (result i32)
+ (atomic.notify offset=4
+ (i32.and
+ (if (result i32)
+ (i32.eqz
+ (i32.const 1)
+ )
+ (block $label$11
+ (br $label$10)
+ )
+ (block $label$12 (result i32)
+ (i32.store16 offset=2
+ (i32.and
+ (i32.const 65535)
+ (i32.const 15)
+ )
+ (select
+ (i32.const -2147483648)
+ (i32.const 16)
+ (i32.const 8)
+ )
+ )
+ (i64.le_u
+ (if (result i64)
+ (i32.const 1464489052)
+ (i64.atomic.load8_u offset=2
+ (i32.and
+ (global.get $global$_0)
+ (i32.const 15)
+ )
+ )
+ (i64.const 942112219034230640)
+ )
+ (loop $label$13 (result i64)
(block
(if
(i32.eqz
(global.get $hangLimit)
)
- (return
- (i32.const -83)
- )
+ (return)
)
(global.set $hangLimit
(i32.sub
@@ -844,170 +704,414 @@
)
)
)
- (block (result i32)
- (br_if $label$3
- (local.get $6)
+ (block (result i64)
+ (if
+ (i32.eqz
+ (i32.const 134217728)
+ )
+ (block $label$14
+ (nop)
+ (f64.store offset=22 align=4
+ (i32.and
+ (loop $label$15 (result i32)
+ (block
+ (if
+ (i32.eqz
+ (global.get $hangLimit)
+ )
+ (return)
+ )
+ (global.set $hangLimit
+ (i32.sub
+ (global.get $hangLimit)
+ (i32.const 1)
+ )
+ )
+ )
+ (block (result i32)
+ (nop)
+ (br_if $label$15
+ (i32.const 101189382)
+ )
+ (i32.const -127)
+ )
+ )
+ (i32.const 15)
+ )
+ (select
+ (f64.const -nan:0xfffffffffffa1)
+ (f64.const -0.394)
+ (i32.const -127)
+ )
+ )
+ )
+ (block $label$16
+ (f32.store offset=4 align=1
+ (i32.and
+ (block $label$17
+ (nop)
+ (br $label$16)
+ )
+ (i32.const 15)
+ )
+ (f32.const -2048)
+ )
+ (loop $label$18
+ (block
+ (if
+ (i32.eqz
+ (global.get $hangLimit)
+ )
+ (return)
+ )
+ (global.set $hangLimit
+ (i32.sub
+ (global.get $hangLimit)
+ (i32.const 1)
+ )
+ )
+ )
+ (block
+ (block $label$19
+ (local.set $2
+ (ref.null func)
+ )
+ (nop)
+ )
+ (br_if $label$18
+ (i32.eqz
+ (i32.const 1246300445)
+ )
+ )
+ (drop
+ (tuple.make
+ (ref.null func)
+ (ref.null extern)
+ (ref.null any)
+ (v128.const i32x4 0x6c15621e 0x74813332 0x00f8da01 0x001644ae)
+ (ref.null exn)
+ )
+ )
+ )
+ )
+ )
)
- (br_if $label$3
- (local.get $6)
+ (br_if $label$13
+ (i32.eqz
+ (global.get $global$_0)
+ )
)
- (i32.const -33)
+ (i64.const -4611686018427387904)
)
)
)
- (i32.const 2147483647)
- (if (result i32)
- (i32.eqz
- (i32.const 32768)
- )
- (i32.const 16404)
- (i32.const -1024)
- )
)
- (f32.const -nan:0x7fffa8)
- (block $label$4 (result f32)
- (call $log-i32
- (call $hashMemory)
- )
- (f32.const 18446744073709551615)
+ )
+ (i32.const 15)
+ )
+ (i32.const 1111245614)
+ )
+ (i32.const 15)
+ )
+ )
+ )
+ )
+ )
+ (return)
+ )
+ (block $label$20
+ (nop)
+ (i64.shr_u
+ (loop $label$40
+ (block
+ (if
+ (i32.eqz
+ (global.get $hangLimit)
+ )
+ (return)
+ )
+ (global.set $hangLimit
+ (i32.sub
+ (global.get $hangLimit)
+ (i32.const 1)
+ )
+ )
+ )
+ (block
+ (block $label$41
+ (call $log-i32
+ (i32.const -66)
+ )
+ )
+ (br_if $label$40
+ (i32.eqz
+ (loop $label$42 (result i32)
+ (block
+ (if
+ (i32.eqz
+ (global.get $hangLimit)
+ )
+ (return)
+ )
+ (global.set $hangLimit
+ (i32.sub
+ (global.get $hangLimit)
+ (i32.const 1)
)
)
)
- (if (result f32)
- (i32.eqz
- (ref.is_null
- (if (result externref)
+ (block (result i32)
+ (block $label$43
+ (nop)
+ (drop
+ (if (result f64)
(i32.eqz
- (i32.const -65535)
+ (block $label$44
+ (call $log-i32
+ (call $hashMemory)
+ )
+ (br $label$40)
+ )
+ )
+ (tuple.extract 0
+ (tuple.make
+ (f64.const 18446744073709551615)
+ (i64.const 4294967222)
+ (i32.const 847216455)
+ )
+ )
+ (block $label$45
+ (br $label$40)
)
- (ref.null extern)
- (local.get $12)
)
)
)
- (block $label$5 (result f32)
- (call $log-f32
- (select
- (f32.const 4096)
- (f32.const -nan:0x7fffa1)
- (i32.const 2097640319)
+ (br_if $label$42
+ (i32.eqz
+ (if (result i32)
+ (loop $label$46
+ (block
+ (if
+ (i32.eqz
+ (global.get $hangLimit)
+ )
+ (return)
+ )
+ (global.set $hangLimit
+ (i32.sub
+ (global.get $hangLimit)
+ (i32.const 1)
+ )
+ )
+ )
+ (block $label$47
+ (nop)
+ (br $label$42)
+ )
+ )
+ (i32.const 0)
+ (i32.const 2376257)
)
)
- (f32.const 8192)
)
- (f32.const -1125899906842624)
+ (i8x16.all_true
+ (v128.const i32x4 0xff7fff7f 0x00ffffff 0xfdff0c03 0xffe5fff7)
+ )
)
)
- (i32.const 15)
)
- (i32.const 1)
- (i32.const 2)
)
- (return
- (local.get $6)
+ (block $label$48
+ (br_if $label$40
+ (i32.const -32768)
+ )
+ (return)
)
)
- (block $label$6 (result i32)
- (loop $label$7
- (block
- (if
- (i32.eqz
- (global.get $hangLimit)
- )
- (return
- (i32.const -4096)
- )
- )
- (global.set $hangLimit
- (i32.sub
- (global.get $hangLimit)
- (i32.const 1)
- )
- )
+ )
+ (loop $label$49
+ (block
+ (if
+ (i32.eqz
+ (global.get $hangLimit)
)
- (block $label$8
- (nop)
- (memory.fill
- (i32.and
- (i8x16.extract_lane_s 11
- (i8x16.max_s
- (local.get $4)
- (i16x8.add_saturate_u
- (v128.const i32x4 0xffea0f4a 0xfdffffec 0xdfff0512 0x1910ffff)
- (local.get $4)
- )
- )
- )
- (i32.const 15)
+ (return)
+ )
+ (global.set $hangLimit
+ (i32.sub
+ (global.get $hangLimit)
+ (i32.const 1)
+ )
+ )
+ )
+ (block
+ (block $label$50
+ (nop)
+ (block $label$51
+ (drop
+ (ref.func $func_2)
+ )
+ (br_if $label$51
+ (f64.eq
+ (f64.const 4398046511104)
+ (global.get $global$_1)
)
- (i32.const 1326258715)
- (local.get $6)
)
)
)
- (local.get $6)
- )
- )
- (block $label$9 (result i32)
- (i32.const 10353)
- )
- (local.get $6)
- )
- (select
- (i32.const 33554433)
- (i32.trunc_f64_s
- (f64.const 3402823466385288598117041e14)
- )
- (local.tee $6
- (select
- (local.get $6)
- (ref.eq
- (loop $label$0 (result i31ref)
- (block
- (if
- (i32.eqz
- (global.get $hangLimit)
- )
- (return
- (i32.const -32768)
+ (br_if $label$49
+ (i32.eqz
+ (loop $label$52 (result i32)
+ (block
+ (if
+ (i32.eqz
+ (global.get $hangLimit)
+ )
+ (return)
)
- )
- (global.set $hangLimit
- (i32.sub
- (global.get $hangLimit)
- (i32.const 1)
+ (global.set $hangLimit
+ (i32.sub
+ (global.get $hangLimit)
+ (i32.const 1)
+ )
)
)
- )
- (block (result i31ref)
- (local.set $5
- (f64.const 371920655)
- )
- (br_if $label$0
- (i32.eqz
- (local.tee $6
- (local.tee $6
- (local.tee $6
- (local.tee $6
- (local.get $6)
+ (block (result i32)
+ (block $label$53
+ (if
+ (i32.eqz
+ (i31.get_s
+ (i31.new
+ (i32.const -93)
+ )
+ )
+ )
+ (br_if $label$49
+ (i32.eqz
+ (i32.const 353711929)
+ )
+ )
+ (block $label$54
+ (call $log-i32
+ (call $hashMemory)
+ )
+ (if
+ (i32.eqz
+ (i32.const 511508602)
+ )
+ (block $label$55
+ (br_if $label$53
+ (i32.eqz
+ (i31.get_u
+ (i31.new
+ (i32.const -512)
+ )
+ )
+ )
+ )
+ (loop $label$56
+ (block
+ (if
+ (i32.eqz
+ (global.get $hangLimit)
+ )
+ (return)
+ )
+ (global.set $hangLimit
+ (i32.sub
+ (global.get $hangLimit)
+ (i32.const 1)
+ )
+ )
+ )
+ (block
+ (call $log-i32
+ (loop $label$57 (result i32)
+ (block
+ (if
+ (i32.eqz
+ (global.get $hangLimit)
+ )
+ (return)
+ )
+ (global.set $hangLimit
+ (i32.sub
+ (global.get $hangLimit)
+ (i32.const 1)
+ )
+ )
+ )
+ (block $label$58 (result i32)
+ (ref.eq
+ (i31.new
+ (i32.const -28)
+ )
+ (local.tee $1
+ (local.get $1)
+ )
+ )
+ )
+ )
+ )
+ (br_if $label$56
+ (i32.eqz
+ (loop $label$59 (result i32)
+ (block
+ (if
+ (i32.eqz
+ (global.get $hangLimit)
+ )
+ (return)
+ )
+ (global.set $hangLimit
+ (i32.sub
+ (global.get $hangLimit)
+ (i32.const 1)
+ )
+ )
+ )
+ (block (result i32)
+ (br_if $label$54
+ (i32.eqz
+ (i32.const 3)
+ )
+ )
+ (br_if $label$59
+ (i32.const -32769)
+ )
+ (i32.const 337444946)
+ )
+ )
+ )
+ )
+ (nop)
+ )
+ )
+ )
+ (block $label$60
+ (nop)
)
)
)
)
+ (nop)
+ )
+ (br_if $label$52
+ (i32.eqz
+ (i32.const 127)
+ )
)
+ (i32.const 524288)
)
- (local.get $15)
)
)
- (local.get $10)
)
- (local.get $6)
+ (return)
)
)
)
)
)
- (global.get $global$4)
)
)
(func $hangLimitInitializer
diff --git a/test/unit/input/hello_world.wat b/test/unit/input/hello_world.wat
new file mode 100644
index 000000000..680ee809a
--- /dev/null
+++ b/test/unit/input/hello_world.wat
@@ -0,0 +1,11 @@
+(module
+ (type $i32_i32_=>_i32 (func (param i32 i32) (result i32)))
+ (memory $0 256 256)
+ (export "add" (func $add))
+ (func $add (param $x i32) (param $y i32) (result i32)
+ (i32.add
+ (local.get $x)
+ (local.get $y)
+ )
+ )
+)
diff --git a/test/unit/input/random_data.txt b/test/unit/input/random_data.txt
new file mode 100644
index 000000000..d5000bfbd
--- /dev/null
+++ b/test/unit/input/random_data.txt
@@ -0,0 +1 @@
+6sgkjdfghk34589n-947-vn98f2yr-nb8f7t08b7gv*~&!%&^@}{PASD kjgsdf768
diff --git a/test/unit/test_initial_fuzz.py b/test/unit/test_initial_fuzz.py
new file mode 100644
index 000000000..2ddc29884
--- /dev/null
+++ b/test/unit/test_initial_fuzz.py
@@ -0,0 +1,35 @@
+import subprocess
+from scripts.test import shared
+from . import utils
+
+
+class InitialFuzzTest(utils.BinaryenTestCase):
+ def test_empty_initial(self):
+ # generate fuzz from random data
+ data = self.input_path('random_data.txt')
+ a = shared.run_process(shared.WASM_OPT + ['-ttf', '--print', data],
+ stdout=subprocess.PIPE).stdout
+
+ # generate fuzz from random data with initial empty wasm
+ empty_wasm = self.input_path('empty.wasm')
+ b = shared.run_process(
+ shared.WASM_OPT + ['-ttf', '--print', data,
+ '--initial-fuzz=' + empty_wasm],
+ stdout=subprocess.PIPE).stdout
+
+ # an empty initial wasm causes no changes
+ self.assertEqual(a, b)
+
+ def test_small_initial(self):
+ data = self.input_path('random_data.txt')
+ hello_wat = self.input_path('hello_world.wat')
+ out = shared.run_process(shared.WASM_OPT + ['-ttf', '--print', data,
+ '--initial-fuzz=' + hello_wat],
+ stdout=subprocess.PIPE).stdout
+
+ # the function should be there (perhaps with modified contents - don't
+ # check that)
+ self.assertIn('(export "add" (func $add))', out)
+
+ # there should be other fuzz contents added as well
+ self.assertGreater(out.count('(export '), 1)