Skip to content

Commit 192dcc3

Browse files
authored
Fuzzer: Log exported globals (#8466)
We already exported globals in the fuzzer, but did not do anything with them. This logs out their values, at the same time that we log out the values returned from calling exported functions (and using the same reordering etc.).
1 parent 13d9fb3 commit 192dcc3

5 files changed

Lines changed: 113 additions & 26 deletions

File tree

scripts/fuzz_opt.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1370,14 +1370,24 @@ class CtorEval(TestCaseHandler):
13701370
frequency = 0.1
13711371

13721372
def handle(self, wasm):
1373-
# get the expected execution results.
1374-
wasm_exec = run_bynterp(wasm, ['--fuzz-exec-before'])
1375-
13761373
# get the list of func exports, so we can tell ctor-eval what to eval.
1377-
ctors = ','.join(get_exports(wasm, ['func']))
1374+
func_exports = get_exports(wasm, ['func'])
1375+
ctors = ','.join(func_exports)
13781376
if not ctors:
13791377
return
13801378

1379+
# The fuzzer evaluates exports in the order they are given, so if there
1380+
# are global exports it may read them before the ctors are run - but
1381+
# the ctors are meant to run before anything else, and can modify
1382+
# those global values. Keep only function exports to avoid this
1383+
# confusion.
1384+
filtered = wasm + '.filtered.wasm'
1385+
filter_exports(wasm, filtered, func_exports)
1386+
wasm = filtered
1387+
1388+
# get the expected execution results.
1389+
wasm_exec = run_bynterp(wasm, ['--fuzz-exec-before'])
1390+
13811391
# Fix escaping of the names, as we will be passing them as commandline
13821392
# parameters below (e.g. we want --ctors=foo\28bar and not
13831393
# --ctors=foo\\28bar; that extra escaping \ would cause an error).
@@ -1871,7 +1881,8 @@ def handle(self, wasm):
18711881

18721882
# Make sure that we actually executed all exports from both
18731883
# wasm files.
1874-
exports = get_exports(wasm, ['func']) + get_exports(second_wasm, ['func'])
1884+
exports = get_exports(wasm, ['func', 'global'])
1885+
exports += get_exports(second_wasm, ['func', 'global'])
18751886
calls_in_output = output.count(FUZZ_EXEC_CALL_PREFIX)
18761887
if calls_in_output == 0:
18771888
print(f'warning: no calls in output. output:\n{output}')

scripts/fuzz_shell.js

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,11 @@ function callFunc(func) {
198198
return func.apply(null, args);
199199
}
200200

201+
// wasm2js does not define RuntimeError, so use that to check for it. wasm2js
202+
// overrides the entire WebAssembly object with a polyfill, so we know exactly
203+
// what it contains, and we need to handle some things differently below.
204+
var wasm2js = !WebAssembly.RuntimeError;
205+
201206
// Calls a given function in a try-catch. Return 1 if an exception was thrown.
202207
// If |rethrow| is set, and an exception is thrown, it is caught and rethrown.
203208
// Wasm traps are not swallowed (see details below).
@@ -213,11 +218,7 @@ function callFunc(func) {
213218

214219
// We only want to catch exceptions, not wasm traps: traps should still
215220
// halt execution. Handling this requires different code in wasm2js, so
216-
// check for that first (wasm2js does not define RuntimeError, so use
217-
// that for the check - when wasm2js is run, we override the entire
218-
// WebAssembly object with a polyfill, so we know exactly what it
219-
// contains).
220-
var wasm2js = !WebAssembly.RuntimeError;
221+
// check for that first.
221222
if (!wasm2js) {
222223
// When running native wasm, we can detect wasm traps.
223224
if (e instanceof WebAssembly.RuntimeError) {
@@ -555,13 +556,55 @@ function build(binary, isSecond) {
555556
name = e;
556557
value = exports[e];
557558
} else {
558-
// We are given an object form exportList, which has both a name and a
559+
// We are given an object from exportList, which has both a name and a
559560
// value.
560561
name = e.name;
561562
value = e.value;
562563
}
563564

565+
// Check for a global. Note we must be careful in wasm2js mode, where we
566+
// can't do instanceof here (the wasm polyfill there doesn't have such
567+
// things). In wasm2js we strip global exports to avoid needing to handle
568+
// them here (using stub-unsupported-js).
569+
if (!wasm2js && (value instanceof WebAssembly.Global)) {
570+
// We can log a global value and do other operations to check for bugs.
571+
// First, do some operations on the Global wrapper itself.
572+
JSON.stringify(value);
573+
value.foobar;
574+
575+
// Log it at the right time later using a lambda. Note that we can't just
576+
// capture |value| for the lambda, as the loop modifies it.
577+
(() => {
578+
var global = value;
579+
value = () => {
580+
// Time to log. Look at the exported value itself, not the global
581+
// wrapper.
582+
let actualValue;
583+
try {
584+
actualValue = global.value;
585+
} catch (e) {
586+
if (e.message.startsWith('get WebAssembly.Global.value')) {
587+
// Just log a string instead of a value we cannot access from JS,
588+
// like an exnref. Note we don't need matching code on the C++
589+
// side in execution-results.h because illegal exports are pruned
590+
// anyhow if we are going to compare execution in JS to C++.
591+
actualValue = '<illegal value>';
592+
} else {
593+
throw e;
594+
}
595+
}
596+
if (typeof actualValue === 'object') {
597+
// logRef can do a little more than logValue, so use it when possible.
598+
logRef(actualValue);
599+
} else {
600+
logValue(actualValue);
601+
}
602+
};
603+
})();
604+
}
605+
564606
if (typeof value !== 'function') {
607+
// Nothing we can call.
565608
continue;
566609
}
567610

src/passes/RemoveNonJSOps.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,6 @@ struct StubUnsupportedJSOpsPass
385385
void visitModule(Module* module) {
386386
// We remove global exports, as wasm2js doesn't emit them in a fully
387387
// compatible form yet (they aren't instances of WebAssembly.Global).
388-
// Globals.
389388
std::vector<Name> badExports;
390389
for (auto& exp : module->exports) {
391390
if (exp->kind == ExternalKind::Global) {

src/tools/execution-results.h

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -487,23 +487,33 @@ struct ExecutionResults {
487487
// execute all exported methods (that are therefore preserved through
488488
// opts)
489489
for (auto& exp : wasm.exports) {
490-
if (exp->kind != ExternalKind::Function) {
491-
continue;
492-
}
493-
std::cout << "[fuzz-exec] calling " << exp->name << "\n";
494-
auto* func = wasm.getFunction(*exp->getInternalName());
495-
FunctionResult ret = run(func, wasm, instance);
496-
results[exp->name] = ret;
497-
if (auto* values = std::get_if<Literals>(&ret)) {
498-
// ignore the result if we hit an unreachable and returned no value
499-
if (values->size() > 0) {
500-
std::cout << "[fuzz-exec] note result: " << exp->name << " => ";
501-
for (auto value : *values) {
502-
printValue(value);
503-
std::cout << '\n';
490+
if (exp->kind == ExternalKind::Function) {
491+
std::cout << "[fuzz-exec] calling " << exp->name << "\n";
492+
auto* func = wasm.getFunction(*exp->getInternalName());
493+
FunctionResult ret = run(func, wasm, instance);
494+
results[exp->name] = ret;
495+
if (auto* values = std::get_if<Literals>(&ret)) {
496+
// ignore the result if we hit an unreachable and returned no value
497+
if (values->size() > 0) {
498+
std::cout << "[fuzz-exec] note result: " << exp->name << " => ";
499+
for (auto value : *values) {
500+
printValue(value);
501+
std::cout << '\n';
502+
}
504503
}
505504
}
505+
} else if (exp->kind == ExternalKind::Global) {
506+
// Log the global's value. (We use "calling" here to match the output
507+
// for calls, which simplifies the fuzzer.)
508+
std::cout << "[fuzz-exec] calling " << exp->name << "\n";
509+
Literals* value = instance.getExportedGlobalOrNull(exp->name);
510+
assert(value);
511+
assert(value->size() == 1);
512+
std::cout << "[LoggingExternalInterface logging ";
513+
printValue((*value)[0]);
514+
std::cout << "]\n";
506515
}
516+
// Ignore other exports for now. TODO
507517
}
508518
}
509519

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
;; Test logging of global exports. We don't do this using update_lit_checks as
2+
;; global exports confuse the auto-updater.
3+
4+
(module
5+
(type $struct (struct))
6+
7+
(global $global (mut i32) (i32.const 42))
8+
(global $global-immref anyref (struct.new $struct))
9+
(global $global-v128 v128 (v128.const i64x2 12 34))
10+
11+
(export "global" (global $global))
12+
(export "global-immref" (global $global-immref))
13+
(export "global-v128" (global $global-v128))
14+
)
15+
16+
;; RUN: wasm-opt %s -all --fuzz-exec -o /dev/null 2>&1 | filecheck %s
17+
18+
;; CHECK: [fuzz-exec] calling global
19+
;; CHECK-NEXT: [LoggingExternalInterface logging 42]
20+
;; CHECK-NEXT: [fuzz-exec] calling global-immref
21+
;; CHECK-NEXT: [LoggingExternalInterface logging object]
22+
;; CHECK-NEXT: [fuzz-exec] calling global-v128
23+
;; CHECK-NEXT: [LoggingExternalInterface logging i32x4 0x0000000c 0x00000000 0x00000022 0x00000000]
24+

0 commit comments

Comments
 (0)