diff --git a/scripts/clusterfuzz/embed_wasms.py b/scripts/clusterfuzz/embed_wasms.py deleted file mode 100644 index 17b4bcaf06d..00000000000 --- a/scripts/clusterfuzz/embed_wasms.py +++ /dev/null @@ -1,76 +0,0 @@ -# -# Copyright 2025 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. - -"""Reverse of extract_wasms.py. - -extract_wasms.py extracts wasm files from a JavaScript testcase (which has wasm -files embedded as arrays of numbers), and this script re-embeds them back. To -do so, we use the magic comments that the extractor uses: it replaces each -wasm array with - - 'undefined /* extracted wasm */' - -We simply replace those with the given wasm files, in JS format. - -For example, assume INFILE.js contains two wasm files. Then - - extract_wasms.py INFILE.js OUTFILE - -will emit - - OUTFILE.js, OUTFILE.0.wasm, OUTFILE.1.wasm - -We now have a JS file without the wasm (which includes the magic comments -mentioned before) and one binary wasm file for each wasm. We can now re-embed -them, creating a merged JS file containing JS + wasm, using - - embed_wasms.py OUTFILE.js OUTFILE.0.wasm OUTFILE.1.wasm MERGED.js - -The first argument is the input JS, then the wasm files, then the last argument -is the output JS. -""" - -import re -import sys - -in_js = sys.argv[1] -in_wasms = sys.argv[2:-1] -out_js = sys.argv[-1] - -with open(in_js) as f: - js = f.read() - -wasm_index = 0 - - -def replace_wasm(_text): - global wasm_index - wasm_file = in_wasms[wasm_index] - wasm_index += 1 - - with open(wasm_file, 'rb') as f: - wasm = f.read() - - bytes = [str(int(x)) for x in wasm] - bytes = ', '.join(bytes) - - return f'new Uint8Array([{bytes}])' - - -js = re.sub(r'undefined [/][*] extracted wasm [*][/]', replace_wasm, js) - -# Write out the new JS. -with open(out_js, 'w') as f: - f.write(js) diff --git a/scripts/clusterfuzz/extract_wasms.py b/scripts/clusterfuzz/extract_wasms.py deleted file mode 100644 index 2833305b92d..00000000000 --- a/scripts/clusterfuzz/extract_wasms.py +++ /dev/null @@ -1,94 +0,0 @@ -# -# Copyright 2024 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. - -"""Wasm extractor for testcases generated by the ClusterFuzz run.py script. - -This is general enough to also handle Fuzzilli output. - -Usage: - -extract_wasms.py INFILE.js OUTFILE - -That will find embedded wasm files in INFILE.js, of the form - - new Uint8Array([..wasm_contents..]); - -and extract them into OUTFILE.0.wasm, OUTFILE.1.wasm, etc. It also emits -OUTFILE.js which will no longer contain the embedded contents, after which the -script can be run as - - d8 OUTFILE.js -- OUTFILE.0.wasm - -That is, the embedded file can now be provided as a filename argument. -""" - -import re -import sys - -file_counter = 0 - - -def get_wasm_filename(): - global file_counter - file_counter += 1 - return f'{out}.{file_counter - 1}.wasm' - - -in_js = sys.argv[1] -out = sys.argv[2] - -with open(in_js) as f: - js = f.read() - - -def repl(match): - text = match.group(0) - - # We found something of the form - # - # new Uint8Array([..binary data as numbers..]); - # - # See if the numbers are the beginnings of a wasm file, "\0asm". If so, we - # assume it is wasm. (We are careful here because Fuzzilli output can - # contain normal JavaScript Typed Arrays, which we do not want to touch.) - numbers = match.groups()[0] - numbers = numbers.split(',') - - try: - # Handle both base 10 and 16 by passing in base 0. - parsed = [int(n, 0) for n in numbers] - binary = bytes(parsed) - except ValueError: - # Not wasm; return the existing text. - return text - - if binary[:4] != b'\0asm': - return text - - # It is wasm. Parse out the numbers into a binary wasm file. - with open(get_wasm_filename(), 'wb') as f: - f.write(binary) - - # Replace the Uint8Array with undefined + a comment. - return 'undefined /* extracted wasm */' - - -# Replace the wasm files and write them out. We investigate any new Uint8Array -# on an array of values like [100, 200] or [0x61, 0x6D, 0x6a] etc. -js = re.sub(r'new Uint8Array\(\[([\d,x a-fA-F]+)\]\)', repl, js) - -# Write out the new JS. -with open(f'{out}.js', 'w') as f: - f.write(js) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 67b00e7b94d..5b5b967897b 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -1863,10 +1863,7 @@ def handle_pair(self, input, before_wasm, after_wasm, opts): if output != IGNORE and INSTANTIATE_ERROR not in output: # Do the work to find if there were function exports: extract the # wasm from the JS, and process it. - run([sys.executable, - in_binaryen('scripts', 'clusterfuzz', 'extract_wasms.py'), - fuzz_file, - 'extracted']) + run([in_bin('wasm-embed'), '--extract', fuzz_file, 'extracted']) if get_exports('extracted.0.wasm', ['func']): assert FUZZ_EXEC_EXPORT_PREFIX in output diff --git a/scripts/test/shared.py b/scripts/test/shared.py index 0e65e159056..2041d6c59f7 100644 --- a/scripts/test/shared.py +++ b/scripts/test/shared.py @@ -270,6 +270,7 @@ def is_exe(fpath): WASM_CTOR_EVAL = [os.path.join(options.binaryen_bin, 'wasm-ctor-eval')] WASM_SHELL = [os.path.join(options.binaryen_bin, 'wasm-shell')] WASM_REDUCE = [os.path.join(options.binaryen_bin, 'wasm-reduce')] +WASM_EMBED = [os.path.join(options.binaryen_bin, 'wasm-embed')] WASM_METADCE = [os.path.join(options.binaryen_bin, 'wasm-metadce')] WASM_EMSCRIPTEN_FINALIZE = [os.path.join(options.binaryen_bin, 'wasm-emscripten-finalize')] diff --git a/scripts/update_help_checks.py b/scripts/update_help_checks.py index e7e3e0deb21..13680b88554 100755 --- a/scripts/update_help_checks.py +++ b/scripts/update_help_checks.py @@ -26,7 +26,8 @@ TOOLS = ['wasm-opt', 'wasm-as', 'wasm-dis', 'wasm2js', 'wasm-ctor-eval', 'wasm-shell', 'wasm-reduce', 'wasm-metadce', 'wasm-split', - 'wasm-fuzz-types', 'wasm-emscripten-finalize', 'wasm-merge'] + 'wasm-fuzz-types', 'wasm-emscripten-finalize', 'wasm-merge', + 'wasm-embed'] def main(): diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt index 4a24d506ec0..061a8e4747a 100644 --- a/src/tools/CMakeLists.txt +++ b/src/tools/CMakeLists.txt @@ -21,6 +21,7 @@ if(NOT BUILD_EMSCRIPTEN_TOOLS_ONLY) binaryen_add_executable(wasm-merge wasm-merge.cpp) binaryen_add_executable(wasm-fuzz-types "${fuzzing_SOURCES};wasm-fuzz-types.cpp") binaryen_add_executable(wasm-fuzz-lattices "${fuzzing_SOURCES};wasm-fuzz-lattices.cpp") + binaryen_add_executable(wasm-embed wasm-embed.cpp) add_subdirectory(wasm-reduce) endif() diff --git a/src/tools/wasm-embed.cpp b/src/tools/wasm-embed.cpp new file mode 100644 index 00000000000..d01a6e68363 --- /dev/null +++ b/src/tools/wasm-embed.cpp @@ -0,0 +1,151 @@ +/* + * Copyright 2026 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. + */ + +// +// Embed Wasm binaries into a JS file, or extract embedded Wasm binaries from a +// JS file. +// + +#include +#include +#include + +#include "parsing.h" +#include "pass.h" +#include "support/command-line.h" +#include "support/file.h" +#include "support/js-embedded-module.h" +#include "support/utilities.h" +#include "wasm-io.h" +#include "wasm-validator.h" + +using namespace wasm; + +int main(int argc, const char* argv[]) { + const std::string WasmEmbedOption = "wasm-embed options"; + + bool extract = false; + std::string output; + std::vector positionals; + + Options options( + "wasm-embed", + "Embed Wasm binaries into a JS file, or extract embedded Wasm binaries " + "from a JS file"); + options + .add("--extract", + "-e", + "Extract embedded Wasm modules from the input JS file into " + ".0.wasm, .1.wasm, ...", + WasmEmbedOption, + Options::Arguments::Zero, + [&](Options* o, const std::string& argument) { extract = true; }) + .add("--output", + "-o", + "Output JS file (or output prefix when --extract is used)", + WasmEmbedOption, + Options::Arguments::One, + [&](Options* o, const std::string& argument) { output = argument; }) + .add_positional("INFILE.js [WASM_FILES...] [OUTFILE]", + Options::Arguments::N, + [&](Options* o, const std::string& argument) { + positionals.push_back(argument); + }); + options.parse(argc, argv); + + if (extract) { + std::string inJsFile; + if (output.empty()) { + if (positionals.size() != 2) { + Fatal() << "Expected input JS file and output prefix"; + } + inJsFile = positionals[0]; + output = positionals[1]; + } else { + if (positionals.size() != 1) { + Fatal() << "Expected a single input JS file when --output is specified"; + } + inJsFile = positionals[0]; + } + + auto js = read_file(inJsFile, Flags::Text); + auto modules = findEmbeddedModules(js); + for (size_t i = 0; i < modules.size(); ++i) { + write_file(output + "." + std::to_string(i) + ".wasm", modules[i].bytes); + } + flush_and_quick_exit(0); + } + + std::string inJsFile; + std::vector wasmFiles; + if (output.empty()) { + if (positionals.size() < 2) { + Fatal() << "Expected input JS file, wasm files, and output JS file"; + } + inJsFile = positionals.front(); + output = positionals.back(); + wasmFiles.assign(positionals.begin() + 1, positionals.end() - 1); + } else { + if (positionals.empty()) { + Fatal() << "Expected input JS file"; + } + inJsFile = positionals.front(); + wasmFiles.assign(positionals.begin() + 1, positionals.end()); + } + + auto js = read_file(inJsFile, Flags::Text); + auto existing = findEmbeddedModules(js); + if (existing.size() != wasmFiles.size()) { + Fatal() << "Number of embedded wasm modules in " << inJsFile << " (" + << existing.size() + << ") does not match number of provided wasm files (" + << wasmFiles.size() << ")"; + } + + for (size_t i = existing.size(); i > 0; --i) { + const auto& mod = existing[i - 1]; + const auto& wasmFile = wasmFiles[i - 1]; + auto bytes = read_file>(wasmFile, Flags::Binary); + bool isBinary = bytes.size() >= 4 && bytes[0] == '\0' && bytes[1] == 'a' && + bytes[2] == 's' && bytes[3] == 'm'; + + Module wasm; + wasm.features = FeatureSet::All; + try { + ModuleReader().readData(bytes, wasm); + } catch (ParseException& p) { + p.dump(std::cerr); + std::cerr << '\n'; + Fatal() << "error parsing wasm (" << wasmFile << ")"; + } + + if (!WasmValidator().validate(wasm)) { + Fatal() << "error validating module (" << wasmFile << ")"; + } + + if (!isBinary) { + PassOptions passOptions; + ModuleWriter writer(passOptions); + writer.setBinary(true); + writer.write(wasm, bytes); + } + + js.replace(mod.start, mod.end - mod.start, formatByteArray(bytes)); + } + + write_file(output, js); + flush_and_quick_exit(0); +} diff --git a/test/lit/help/wasm-embed.test b/test/lit/help/wasm-embed.test new file mode 100644 index 00000000000..7b0ce9aaae0 --- /dev/null +++ b/test/lit/help/wasm-embed.test @@ -0,0 +1,27 @@ +;; RUN: wasm-embed --help | filecheck %s +;; CHECK: ================================================================================ +;; CHECK-NEXT: wasm-embed INFILE.js [WASM_FILES...] [OUTFILE] +;; CHECK-EMPTY: +;; CHECK-NEXT: Embed Wasm binaries into a JS file, or extract embedded Wasm binaries from a JS +;; CHECK-NEXT: file +;; CHECK-NEXT: ================================================================================ +;; CHECK-EMPTY: +;; CHECK-EMPTY: +;; CHECK-NEXT: wasm-embed options: +;; CHECK-NEXT: ------------------- +;; CHECK-EMPTY: +;; CHECK-NEXT: --extract,-e Extract embedded Wasm modules from the input JS file into +;; CHECK-NEXT: .0.wasm, .1.wasm, ... +;; CHECK-EMPTY: +;; CHECK-NEXT: --output,-o Output JS file (or output prefix when --extract is used) +;; CHECK-EMPTY: +;; CHECK-EMPTY: +;; CHECK-NEXT: General options: +;; CHECK-NEXT: ---------------- +;; CHECK-EMPTY: +;; CHECK-NEXT: --version Output version information and exit +;; CHECK-EMPTY: +;; CHECK-NEXT: --help,-h Show this help message and exit +;; CHECK-EMPTY: +;; CHECK-NEXT: --debug,-d Print debug information to stderr +;; CHECK-EMPTY: diff --git a/test/lit/scripts/embed_wasms.lit b/test/lit/scripts/embed_wasms.lit deleted file mode 100644 index d5b579bac66..00000000000 --- a/test/lit/scripts/embed_wasms.lit +++ /dev/null @@ -1,25 +0,0 @@ -;; Test embedding wasm files into JS. - -;; Wasm files replace undefined + magical comments, like these: -;; RUN: echo "good1(undefined /* extracted wasm */);" > %t.js - -;; Slight changes mean we ignore the pattern. -;; RUN: echo "bad(undefinedey /* random stuff */);" >> %t.js - -;; Add a second valid one. -;; RUN: echo "good2(undefined /* extracted wasm */);" >> %t.js - -;; Generate two valid wasm files to embed. -;; RUN: echo "(module)" > %t.1.wat -;; RUN: echo "(module (func $foo))" > %t.2.wat - -;; RUN: wasm-as %t.1.wat -o %t.1.wasm -;; RUN: wasm-as %t.2.wat -o %t.2.wasm - -;; RUN: python %S/../../../scripts/clusterfuzz/embed_wasms.py %t.js %t.1.wasm %t.2.wasm %t.out.js -;; RUN: cat %t.out.js | filecheck %s -;; -;; CHECK: good1(new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0])); -;; CHECK: bad(undefinedey -;; CHECK: good2(new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0, 1, 4, 1, 96, 0, 0, 3, 2, 1, 0, 10, 4, 1, 2, 0, 11])); - diff --git a/test/lit/scripts/extract_wasms.lit b/test/lit/scripts/extract_wasms.lit deleted file mode 100644 index 70cc59b4a59..00000000000 --- a/test/lit/scripts/extract_wasms.lit +++ /dev/null @@ -1,28 +0,0 @@ -;; Test extracting wasm files from JS. - -;; A proper wasm start sequence (\0asm), so we will extract it. -;; RUN: echo "good1(new Uint8Array([0x00, 0x61, 0x73, 0x6D, 0x01]));" > %t.js - -;; A difference in the second byte, so we won't. -;; RUN: echo "bad1(new Uint8Array([0x00, 0xff, 0x73, 0x6D, 0x01]));" >> %t.js - -;; The last byte is unparsable as an integer, so we won't. -;; RUN: echo "bad2(new Uint8Array([0x00, 0x61, 0x73, 0x6D, 6Dx0]));" >> %t.js - -;; This is not a Uint8Array, so we do nothing. -;; RUN: echo "bad3(new Uint16Array([0x00, 0x61, 0x73, 0x6D, 0x01]));" >> %t.js - -;; Another proper one. Note the second number is in base 10, which works too, -;; & there is various odd whitespace which we also ignore. -;; RUN: echo "good2(new Uint8Array([0x00,97, 0x73, 0x6D,0x01]));" >> %t.js - -;; RUN: python %S/../../../scripts/clusterfuzz/extract_wasms.py %t.js %t.out -;; RUN: cat %t.out.js | filecheck %s -;; -;; We extracted the good but not the bad. -;; CHECK: good1(undefined /* extracted wasm */) -;; CHECK: bad1(new Uint8Array -;; CHECK: bad2(new Uint8Array -;; CHECK: bad3(new Uint16Array -;; CHECK: good2(undefined /* extracted wasm */) - diff --git a/test/lit/wasm-embed/embed.test b/test/lit/wasm-embed/embed.test new file mode 100644 index 00000000000..f7b2eaefc73 --- /dev/null +++ b/test/lit/wasm-embed/embed.test @@ -0,0 +1,48 @@ +;; Test embedding wasm and wat files into JS by replacing existing embedded modules. + +;; Existing embedded modules will be replaced: +;; RUN: echo "good1(new Uint8Array([0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x2a]));" > %t.js + +;; Non-wasm arrays are ignored: +;; RUN: echo "bad(new Uint8Array([0x00, 0xff, 0x73, 0x6d, 0x01]));" >> %t.js + +;; Add a second valid embedded module without new Uint8Array: +;; RUN: echo "good2([0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00]);" >> %t.js + +;; Assemble %t.wat with -g so %t.wasm contains a name custom section that would +;; be stripped if the module were reserialized. Pass %t.wasm as the first input +;; (verifying original binary bytes are preserved) and %t.wat as the second +;; input (verifying WAT is converted to binary before embedding). +;; RUN: echo "(module (func $foo))" > %t.wat +;; RUN: wasm-as %t.wat -g -o %t.wasm + +;; RUN: wasm-embed %t.js %t.wasm %t.wat %t.out.js +;; RUN: cat %t.out.js | filecheck %s +;; +;; CHECK: good1(new Uint8Array([ +;; CHECK-NEXT: 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x04, 0x01, 0x60, 0x00, 0x00, 0x03, 0x02, +;; CHECK-NEXT: 0x01, 0x00, 0x0a, 0x04, 0x01, 0x02, 0x00, 0x0b, 0x00, 0x0d, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x01, +;; CHECK-NEXT: 0x06, 0x01, 0x00, 0x03, 0x66, 0x6f, 0x6f +;; CHECK-NEXT: ])); +;; CHECK-NEXT: bad(new Uint8Array([0x00, 0xff, 0x73, 0x6d, 0x01])); +;; CHECK-NEXT: good2([ +;; CHECK-NEXT: 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x04, 0x01, 0x60, 0x00, 0x00, 0x03, 0x02, +;; CHECK-NEXT: 0x01, 0x00, 0x0a, 0x04, 0x01, 0x02, 0x00, 0x0b +;; CHECK-NEXT: ]); + +;; Verify that invalid WAT text errors out: +;; RUN: echo "not a module" > %t.bad.wat +;; RUN: not wasm-embed %t.js %t.bad.wat %t.wat %t.out.js 2>&1 | filecheck %s --check-prefix=BAD-WAT +;; BAD-WAT: Fatal: 1:0: error: unrecognized module field + +;; Verify that malformed binary Wasm errors out: +;; RUN: printf '\x00asm\x01' > %t.bad.wasm +;; RUN: not wasm-embed %t.js %t.bad.wasm %t.wat %t.out.js 2>&1 | filecheck %s --check-prefix=BAD-WASM +;; BAD-WASM: Fatal: error parsing wasm + +;; Verify that WAT and binary Wasm modules failing WebAssembly validation error out: +;; RUN: echo "(module (func (result i32) (f32.const 0)))" > %t.invalid.wat +;; RUN: wasm-as %t.invalid.wat -v none -o %t.invalid.wasm +;; RUN: not wasm-embed %t.js %t.invalid.wat %t.wat %t.out.js 2>&1 | filecheck %s --check-prefix=INVALID-MOD +;; RUN: not wasm-embed %t.js %t.invalid.wasm %t.wat %t.out.js 2>&1 | filecheck %s --check-prefix=INVALID-MOD +;; INVALID-MOD: Fatal: error validating module diff --git a/test/lit/wasm-embed/extract.test b/test/lit/wasm-embed/extract.test new file mode 100644 index 00000000000..e7f7b29267b --- /dev/null +++ b/test/lit/wasm-embed/extract.test @@ -0,0 +1,28 @@ +;; Test extracting wasm files from JS. + +;; A proper wasm module (\0asm), so we will extract it as %t.out.0.wasm. +;; RUN: echo "good1(new Uint8Array([0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00]));" > %t.js + +;; A difference in the second byte, so we won't extract it. +;; RUN: echo "bad1(new Uint8Array([0x00, 0xff, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00]));" >> %t.js + +;; The last byte is unparsable as an integer, so we won't extract it. +;; RUN: echo "bad2(new Uint8Array([0x00, 0x61, 0x73, 0x6D, 6Dx0]));" >> %t.js + +;; Another proper one ((module (func))) with base 10 and odd whitespace, +;; extracted as %t.out.1.wasm. +;; RUN: echo "good2([0x00,97, 0x73, 0x6D,0x01, 0x00, 0x00, 0x00, 0x01, 0x04, 0x01, 0x60, 0x00, 0x00, 0x03, 0x02, 0x01, 0x00, 0x0a, 0x04, 0x01, 0x02, 0x00, 0x0b]);" >> %t.js + +;; RUN: wasm-embed --extract %t.js %t.out +;; RUN: wasm-dis %t.out.0.wasm | filecheck %s --check-prefix=WASM0 +;; RUN: wasm-dis %t.out.1.wasm | filecheck %s --check-prefix=WASM1 +;; RUN: not wasm-dis %t.out.2.wasm + +;; WASM0: (module +;; WASM0-NEXT: ) + +;; WASM1: (module +;; WASM1-NEXT: (type $0 (func)) +;; WASM1-NEXT: (func $0 +;; WASM1-NEXT: ) +;; WASM1-NEXT: ) diff --git a/test/unit/test_cluster_fuzz.py b/test/unit/test_cluster_fuzz.py index 578ec2690a5..31d73f7bb1e 100644 --- a/test/unit/test_cluster_fuzz.py +++ b/test/unit/test_cluster_fuzz.py @@ -194,8 +194,8 @@ def test_file_contents(self): # stale files. for f in glob.glob('extracted*'): os.unlink(f) - extractor = shared.in_binaryen('scripts', 'clusterfuzz', 'extract_wasms.py') - subprocess.check_call([sys.executable, extractor, fuzz_file, 'extracted']) + subprocess.check_call( + shared.WASM_EMBED + ['--extract', fuzz_file, 'extracted']) # One wasm file must always exist, and must be valid. binary_file = 'extracted.0.wasm'