Skip to content

Commit 8c8b213

Browse files
authored
Fuzzer: Add support to --fuzz-exec two files (#7947)
fuzz_shell.js already allows linking in a second file from JS. This does the same for wasm-opt's --fuzz-exec-before functionality. To do that, track linked instances in execution results, and link and run a second module there, if provided.
1 parent 8d395c0 commit 8c8b213

File tree

6 files changed

+112
-33
lines changed

6 files changed

+112
-33
lines changed

src/shell-interface.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,8 @@ struct ShellExternalInterface : ModuleRunner::ExternalInterface {
105105
ModuleRunner* getImportInstance(Importable* import) {
106106
auto it = linkedInstances.find(import->module);
107107
if (it == linkedInstances.end()) {
108-
Fatal() << "importGlobals: unknown import: " << import->module.str << "."
109-
<< import->base.str;
108+
Fatal() << "getImportInstance: unknown import: " << import->module.str
109+
<< "." << import->base.str;
110110
}
111111
return it->second.get();
112112
}

src/tools/execution-results.h

Lines changed: 56 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,11 @@ struct LoggingExternalInterface : public ShellExternalInterface {
5555
ModuleRunner* instance = nullptr;
5656

5757
public:
58-
LoggingExternalInterface(Loggings& loggings, Module& wasm)
59-
: loggings(loggings), wasm(wasm) {
58+
LoggingExternalInterface(
59+
Loggings& loggings,
60+
Module& wasm,
61+
std::map<Name, std::shared_ptr<ModuleRunner>> linkedInstances_ = {})
62+
: ShellExternalInterface(linkedInstances_), loggings(loggings), wasm(wasm) {
6063
for (auto& exp : wasm.exports) {
6164
if (exp->kind == ExternalKind::Table && exp->name == "table") {
6265
exportedTable = *exp->getInternalName();
@@ -185,7 +188,11 @@ struct LoggingExternalInterface : public ShellExternalInterface {
185188
} else if (import->base == "getTempRet0") {
186189
return {Literal(state.tempRet0)};
187190
}
191+
} else if (linkedInstances.count(import->module)) {
192+
// This is from a recognized module.
193+
return getImportInstance(import)->callExport(import->base, arguments);
188194
}
195+
// Anything else, we ignore.
189196
std::cerr << "[LoggingExternalInterface ignoring an unknown import "
190197
<< import->module << " . " << import->base << '\n';
191198
return {};
@@ -279,35 +286,24 @@ struct ExecutionResults {
279286
// If set, we should ignore this and not compare it to anything.
280287
bool ignore = false;
281288

282-
// get results of execution
283-
void get(Module& wasm) {
284-
LoggingExternalInterface interface(loggings, wasm);
289+
// Get results of executing a module. Optionally, provide a second module to
290+
// link with it (like fuzz_shell's second module).
291+
void get(Module& wasm, Module* second = nullptr) {
285292
try {
286-
ModuleRunner instance(wasm, &interface);
287-
// This is not an optimization: we want to execute anything, even relaxed
288-
// SIMD instructions.
289-
instance.setRelaxedBehavior(ModuleRunner::RelaxedBehavior::Execute);
290-
instance.instantiate();
291-
interface.setModuleRunner(&instance);
292-
// execute all exported methods (that are therefore preserved through
293-
// opts)
294-
for (auto& exp : wasm.exports) {
295-
if (exp->kind != ExternalKind::Function) {
296-
continue;
297-
}
298-
std::cout << "[fuzz-exec] calling " << exp->name << "\n";
299-
auto* func = wasm.getFunction(*exp->getInternalName());
300-
FunctionResult ret = run(func, wasm, instance);
301-
results[exp->name] = ret;
302-
if (auto* values = std::get_if<Literals>(&ret)) {
303-
// ignore the result if we hit an unreachable and returned no value
304-
if (values->size() > 0) {
305-
std::cout << "[fuzz-exec] note result: " << exp->name << " => ";
306-
for (auto value : *values) {
307-
printValue(value);
308-
}
309-
}
310-
}
293+
// Run the first module.
294+
LoggingExternalInterface interface(loggings, wasm);
295+
auto instance = std::make_shared<ModuleRunner>(wasm, &interface);
296+
runModule(wasm, *instance, interface);
297+
298+
if (second) {
299+
// Link and run the second module.
300+
std::map<Name, std::shared_ptr<ModuleRunner>> linkedInstances;
301+
linkedInstances["primary"] = instance;
302+
LoggingExternalInterface secondInterface(
303+
loggings, *second, linkedInstances);
304+
auto secondInstance = std::make_shared<ModuleRunner>(
305+
*second, &secondInterface, linkedInstances);
306+
runModule(*second, *secondInstance, secondInterface);
311307
}
312308
} catch (const TrapException&) {
313309
// May throw in instance creation (init of offsets).
@@ -319,6 +315,36 @@ struct ExecutionResults {
319315
}
320316
}
321317

318+
void runModule(Module& wasm,
319+
ModuleRunner& instance,
320+
LoggingExternalInterface& interface) {
321+
// This is not an optimization: we want to execute anything, even relaxed
322+
// SIMD instructions.
323+
instance.setRelaxedBehavior(ModuleRunner::RelaxedBehavior::Execute);
324+
instance.instantiate();
325+
interface.setModuleRunner(&instance);
326+
// execute all exported methods (that are therefore preserved through
327+
// opts)
328+
for (auto& exp : wasm.exports) {
329+
if (exp->kind != ExternalKind::Function) {
330+
continue;
331+
}
332+
std::cout << "[fuzz-exec] calling " << exp->name << "\n";
333+
auto* func = wasm.getFunction(*exp->getInternalName());
334+
FunctionResult ret = run(func, wasm, instance);
335+
results[exp->name] = ret;
336+
if (auto* values = std::get_if<Literals>(&ret)) {
337+
// ignore the result if we hit an unreachable and returned no value
338+
if (values->size() > 0) {
339+
std::cout << "[fuzz-exec] note result: " << exp->name << " => ";
340+
for (auto value : *values) {
341+
printValue(value);
342+
}
343+
}
344+
}
345+
}
346+
}
347+
322348
void printValue(Literal value) {
323349
// Unwrap an externalized GC value to get the actual value, but not strings,
324350
// which are normally a subtype of ext.

src/tools/wasm-opt.cpp

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ int main(int argc, const char* argv[]) {
7979
bool converge = false;
8080
bool fuzzExecBefore = false;
8181
bool fuzzExecAfter = false;
82+
std::string fuzzExecSecond;
8283
std::string extraFuzzCommand;
8384
bool translateToFuzz = false;
8485
std::string initialFuzz;
@@ -148,6 +149,15 @@ For more on how to optimize effectively, see
148149
[&](Options* o, const std::string& arguments) {
149150
fuzzExecBefore = fuzzExecAfter = true;
150151
})
152+
.add("--fuzz-exec-second",
153+
"",
154+
"A second module to link with the first, for fuzz-exec-before (only "
155+
"before, as optimizations are not applied to it)",
156+
WasmOptOption,
157+
Options::Arguments::One,
158+
[&](Options* o, const std::string& arguments) {
159+
fuzzExecSecond = arguments;
160+
})
151161
.add("--extra-fuzz-command",
152162
"-efc",
153163
"An extra command to run on the output before and after optimizing. "
@@ -345,7 +355,16 @@ For more on how to optimize effectively, see
345355

346356
ExecutionResults results;
347357
if (fuzzExecBefore) {
348-
results.get(wasm);
358+
if (fuzzExecSecond.empty()) {
359+
results.get(wasm);
360+
} else {
361+
// Add the second module.
362+
Module second;
363+
second.features = wasm.features;
364+
ModuleReader().read(fuzzExecSecond, second);
365+
366+
results.get(wasm, &second);
367+
}
349368
}
350369

351370
if (emitSpecWrapper.size() > 0) {

test/lit/exec/second.wast

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
2+
;; RUN: wasm-opt %s -all --fuzz-exec-before --fuzz-exec-second=%s.second -q -o /dev/null 2>&1 | filecheck %s
3+
4+
(module
5+
(func $first (export "first") (result i32)
6+
(i32.const 42)
7+
)
8+
)
9+
10+
;; Test that --fuzz-exec-second will run a second wasm file that is
11+
;; provided, and call its exports as well as the first module's.
12+
13+
;; CHECK: [fuzz-exec] calling first
14+
;; CHECK-NEXT: [fuzz-exec] note result: first => 42
15+
;; CHECK: [fuzz-exec] calling second
16+
;; CHECK-NEXT: [fuzz-exec] note result: second => 1337
17+

test/lit/exec/second.wast.second

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
(module
2+
(import "primary" "first" (func $first-func (result i32)))
3+
4+
(func $second (export "second") (result i32)
5+
;; Test we can call the first module, linked as "primary."
6+
(i32.add
7+
(call $first-func)
8+
(i32.const 1295)
9+
)
10+
)
11+
)
12+

test/lit/help/wasm-opt.test

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@
3838
;; CHECK-NEXT: after optimization, helping
3939
;; CHECK-NEXT: fuzzing find bugs
4040
;; CHECK-NEXT:
41+
;; CHECK-NEXT: --fuzz-exec-second A second module to link with the
42+
;; CHECK-NEXT: first, for fuzz-exec-before
43+
;; CHECK-NEXT: (only before, as optimizations
44+
;; CHECK-NEXT: are not applied to it)
45+
;; CHECK-NEXT:
4146
;; CHECK-NEXT: --extra-fuzz-command,-efc An extra command to run on the
4247
;; CHECK-NEXT: output before and after
4348
;; CHECK-NEXT: optimizing. The output is

0 commit comments

Comments
 (0)