summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xscripts/fuzz_opt.py39
-rw-r--r--scripts/fuzz_shell.js10
-rw-r--r--src/tools/execution-results.h48
-rw-r--r--src/tools/fuzzing.h5
-rw-r--r--src/tools/fuzzing/fuzzing.cpp70
-rw-r--r--test/lit/exec/fuzzing-api.wast61
-rw-r--r--test/passes/translate-to-fuzz_all-features_metrics_noprint.txt87
7 files changed, 254 insertions, 66 deletions
diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py
index 838ed54cc..3d4663501 100755
--- a/scripts/fuzz_opt.py
+++ b/scripts/fuzz_opt.py
@@ -1178,11 +1178,36 @@ class Wasm2JS(TestCaseHandler):
return all_disallowed(['exception-handling', 'simd', 'threads', 'bulk-memory', 'nontrapping-float-to-int', 'tail-call', 'sign-ext', 'reference-types', 'multivalue', 'gc', 'multimemory'])
+# given a wasm, find all the exports of particular kinds (for example, kinds
+# can be ['func', 'table'] and then we would find exported functions and
+# tables).
+def get_exports(wasm, kinds):
+ wat = run([in_bin('wasm-dis'), wasm] + FEATURE_OPTS)
+ p = re.compile(r'^ [(]export "(.*[^\\]?)" [(](?:' + '|'.join(kinds) + ')')
+ exports = []
+ for line in wat.splitlines():
+ m = p.match(line)
+ if m:
+ export = m[1]
+ exports.append(export)
+ return exports
+
+
# given a wasm and a list of exports we want to keep, remove all other exports.
def filter_exports(wasm, output, keep):
# based on
# https://github.com/WebAssembly/binaryen/wiki/Pruning-unneeded-code-in-wasm-files-with-wasm-metadce#example-pruning-exports
+ # we append to keep; avoid modifying the object that was sent in.
+ keep = keep[:]
+
+ # some exports must always be preserved, if they exist, like the table
+ # (which can be called from JS imports for table operations).
+ existing_exports = set(get_exports(wasm, ['func', 'table']))
+ for export in ['table']:
+ if export in existing_exports:
+ keep.append(export)
+
# build json to represent the exports we want.
graph = [{
'name': 'outside',
@@ -1304,18 +1329,10 @@ class CtorEval(TestCaseHandler):
# get the expected execution results.
wasm_exec = run_bynterp(wasm, ['--fuzz-exec-before'])
- # get the list of exports, so we can tell ctor-eval what to eval.
- wat = run([in_bin('wasm-dis'), wasm] + FEATURE_OPTS)
- p = re.compile(r'^ [(]export "(.*[^\\]?)" [(]func')
- exports = []
- for line in wat.splitlines():
- m = p.match(line)
- if m:
- export = m[1]
- exports.append(export)
- if not exports:
+ # get the list of func exports, so we can tell ctor-eval what to eval.
+ ctors = ','.join(get_exports(wasm, ['func']))
+ if not ctors:
return
- ctors = ','.join(exports)
# eval the wasm.
# we can use --ignore-external-input because the fuzzer passes in 0 to
diff --git a/scripts/fuzz_shell.js b/scripts/fuzz_shell.js
index 4cf3ba358..c4c0056f0 100644
--- a/scripts/fuzz_shell.js
+++ b/scripts/fuzz_shell.js
@@ -152,7 +152,15 @@ var imports = {
// Throw an exception from JS.
'throw': () => {
throw 'some JS error';
- }
+ },
+
+ // Table operations.
+ 'table-get': (index) => {
+ return exports.table.get(index >>> 0);
+ },
+ 'table-set': (index, value) => {
+ exports.table.set(index >>> 0, value);
+ },
},
// Emscripten support.
'env': {
diff --git a/src/tools/execution-results.h b/src/tools/execution-results.h
index d9fefc44e..eb302b4b2 100644
--- a/src/tools/execution-results.h
+++ b/src/tools/execution-results.h
@@ -38,8 +38,19 @@ private:
uint32_t tempRet0 = 0;
} state;
+ // The name of the table exported by the name 'table.' Imports access it.
+ Name exportedTable;
+
public:
- LoggingExternalInterface(Loggings& loggings) : loggings(loggings) {}
+ LoggingExternalInterface(Loggings& loggings, Module& wasm)
+ : loggings(loggings) {
+ for (auto& exp : wasm.exports) {
+ if (exp->kind == ExternalKind::Table && exp->name == "table") {
+ exportedTable = exp->value;
+ break;
+ }
+ }
+ }
Literals callImport(Function* import, const Literals& arguments) override {
if (import->module == "fuzzing-support") {
@@ -66,9 +77,28 @@ public:
std::cout << "]\n";
return {};
} else if (import->base == "throw") {
- // Throw something. We use a (hopefully) private name here.
- auto payload = std::make_shared<ExnData>("__private", Literals{});
- throwException(WasmException{Literal(payload)});
+ throwEmptyException();
+ } else if (import->base == "table-get") {
+ // Check for errors here, duplicating tableLoad(), because that will
+ // trap, and we just want to throw an exception (the same as JS would).
+ if (!exportedTable) {
+ throwEmptyException();
+ }
+ Index index = arguments[0].geti32();
+ if (index >= tables[exportedTable].size()) {
+ throwEmptyException();
+ }
+ return {tableLoad(exportedTable, index)};
+ } else if (import->base == "table-set") {
+ if (!exportedTable) {
+ throwEmptyException();
+ }
+ Index index = arguments[0].geti32();
+ if (index >= tables[exportedTable].size()) {
+ throwEmptyException();
+ }
+ tableStore(exportedTable, index, arguments[1]);
+ return {};
} else {
WASM_UNREACHABLE("unknown fuzzer import");
}
@@ -91,6 +121,12 @@ public:
<< import->module << " . " << import->base << '\n';
return {};
}
+
+ void throwEmptyException() {
+ // Use a hopefully private tag.
+ auto payload = std::make_shared<ExnData>("__private", Literals{});
+ throwException(WasmException{Literal(payload)});
+ }
};
// gets execution results from a wasm module. this is useful for fuzzing
@@ -109,7 +145,7 @@ struct ExecutionResults {
// get results of execution
void get(Module& wasm) {
- LoggingExternalInterface interface(loggings);
+ LoggingExternalInterface interface(loggings, wasm);
try {
ModuleRunner instance(wasm, &interface);
// execute all exported methods (that are therefore preserved through
@@ -259,7 +295,7 @@ struct ExecutionResults {
bool operator!=(ExecutionResults& other) { return !((*this) == other); }
FunctionResult run(Function* func, Module& wasm) {
- LoggingExternalInterface interface(loggings);
+ LoggingExternalInterface interface(loggings, wasm);
try {
ModuleRunner instance(wasm, &interface);
return run(func, wasm, instance);
diff --git a/src/tools/fuzzing.h b/src/tools/fuzzing.h
index 69fee656c..0ecc751a4 100644
--- a/src/tools/fuzzing.h
+++ b/src/tools/fuzzing.h
@@ -107,6 +107,8 @@ private:
std::unordered_map<Type, Name> logImportNames;
Name throwImportName;
+ Name tableGetImportName;
+ Name tableSetImportName;
std::unordered_map<Type, std::vector<Name>> globalsByType;
std::unordered_map<Type, std::vector<Name>> mutableGlobalsByType;
@@ -228,12 +230,15 @@ private:
void addImportLoggingSupport();
// An import that we call to throw an exception from outside.
void addImportThrowingSupport();
+ void addImportTableSupport();
void addHashMemorySupport();
// Special expression makers
Expression* makeHangLimitCheck();
Expression* makeImportLogging();
Expression* makeImportThrowing(Type type);
+ Expression* makeImportTableGet();
+ Expression* makeImportTableSet(Type type);
Expression* makeMemoryHashLogging();
// Function creation
diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp
index b7d075dcc..5dcdc66d5 100644
--- a/src/tools/fuzzing/fuzzing.cpp
+++ b/src/tools/fuzzing/fuzzing.cpp
@@ -178,6 +178,9 @@ void TranslateToFuzzReader::build() {
setupTags();
addImportThrowingSupport();
}
+ if (wasm.features.hasReferenceTypes()) {
+ addImportTableSupport();
+ }
addImportLoggingSupport();
modifyInitialFunctions();
// keep adding functions until we run out of input
@@ -609,6 +612,49 @@ void TranslateToFuzzReader::addImportThrowingSupport() {
wasm.addFunction(std::move(func));
}
+void TranslateToFuzzReader::addImportTableSupport() {
+ // For the table imports to be able to do anything, we must export a table
+ // for them. For simplicity, use the funcref table we use internally, though
+ // we could pick one at random, support non-funcref ones, and even export
+ // multiple ones TODO
+ if (!funcrefTableName) {
+ return;
+ }
+
+ // If a "table" export already exists, skip fuzzing these imports, as the
+ // current export may not contain a valid table for it.
+ if (wasm.getExportOrNull("table")) {
+ return;
+ }
+
+ // Export the table.
+ wasm.addExport(
+ builder.makeExport("table", funcrefTableName, ExternalKind::Table));
+
+ // Get from the table.
+ {
+ tableGetImportName = Names::getValidFunctionName(wasm, "table-get");
+ auto func = std::make_unique<Function>();
+ func->name = tableGetImportName;
+ func->module = "fuzzing-support";
+ func->base = "table-get";
+ func->type = Signature({Type::i32}, Type(HeapType::func, Nullable));
+ wasm.addFunction(std::move(func));
+ }
+
+ // Set into the table.
+ {
+ tableSetImportName = Names::getValidFunctionName(wasm, "table-set");
+ auto func = std::make_unique<Function>();
+ func->name = tableSetImportName;
+ func->module = "fuzzing-support";
+ func->base = "table-set";
+ func->type =
+ Signature({Type::i32, Type(HeapType::func, Nullable)}, Type::none);
+ wasm.addFunction(std::move(func));
+ }
+}
+
void TranslateToFuzzReader::addHashMemorySupport() {
// Add memory hasher helper (for the hash, see hash.h). The function looks
// like:
@@ -722,6 +768,21 @@ Expression* TranslateToFuzzReader::makeImportThrowing(Type type) {
return builder.makeCall(throwImportName, {}, Type::none);
}
+Expression* TranslateToFuzzReader::makeImportTableGet() {
+ assert(tableGetImportName);
+ return builder.makeCall(
+ tableGetImportName, {make(Type::i32)}, Type(HeapType::func, Nullable));
+}
+
+Expression* TranslateToFuzzReader::makeImportTableSet(Type type) {
+ assert(type == Type::none);
+ assert(tableSetImportName);
+ return builder.makeCall(
+ tableSetImportName,
+ {make(Type::i32), makeBasicRef(Type(HeapType::func, Nullable))},
+ Type::none);
+}
+
Expression* TranslateToFuzzReader::makeMemoryHashLogging() {
auto* hash = builder.makeCall(std::string("hashMemory"), {}, Type::i32);
return builder.makeCall(logImportNames[Type::i32], {hash}, Type::none);
@@ -1489,6 +1550,9 @@ Expression* TranslateToFuzzReader::_makenone() {
.add(FeatureSet::ReferenceTypes | FeatureSet::GC, &Self::makeBrOn)
.add(FeatureSet::ReferenceTypes | FeatureSet::GC,
&Self::makeArrayBulkMemoryOp);
+ if (tableSetImportName) {
+ options.add(FeatureSet::ReferenceTypes, &Self::makeImportTableSet);
+ }
return (this->*pick(options))(Type::none);
}
@@ -2679,6 +2743,12 @@ Expression* TranslateToFuzzReader::makeBasicRef(Type type) {
return null;
}
case HeapType::func: {
+ // Rarely, emit a call to imported table.get (when nullable, unshared, and
+ // where we can emit a call).
+ if (type.isNullable() && share == Unshared && funcContext &&
+ tableGetImportName && !oneIn(3)) {
+ return makeImportTableGet();
+ }
return makeRefFuncConst(type);
}
case HeapType::cont: {
diff --git a/test/lit/exec/fuzzing-api.wast b/test/lit/exec/fuzzing-api.wast
index d37d7ef4a..0d0f25130 100644
--- a/test/lit/exec/fuzzing-api.wast
+++ b/test/lit/exec/fuzzing-api.wast
@@ -10,6 +10,13 @@
(import "fuzzing-support" "throw" (func $throw))
+ (import "fuzzing-support" "table-set" (func $table.set (param i32 funcref)))
+ (import "fuzzing-support" "table-get" (func $table.get (param i32) (result funcref)))
+
+ (table $table 10 20 funcref)
+
+ (export "table" (table $table))
+
;; CHECK: [fuzz-exec] calling logging
;; CHECK-NEXT: [LoggingExternalInterface logging 42]
;; CHECK-NEXT: [LoggingExternalInterface logging 3.14159]
@@ -24,10 +31,52 @@
;; CHECK: [fuzz-exec] calling throwing
;; CHECK-NEXT: [exception thrown: __private ()]
- ;; CHECK-NEXT: warning: no passes specified, not doing any work
(func $throwing (export "throwing")
(call $throw)
)
+
+ ;; CHECK: [fuzz-exec] calling table.setting
+ ;; CHECK-NEXT: [exception thrown: __private ()]
+ (func $table.setting (export "table.setting")
+ (call $table.set
+ (i32.const 5)
+ (ref.func $table.setting)
+ )
+ ;; Out of bounds sets will throw.
+ (call $table.set
+ (i32.const 9999)
+ (ref.func $table.setting)
+ )
+ )
+
+ ;; CHECK: [fuzz-exec] calling table.getting
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 1]
+ ;; CHECK-NEXT: [exception thrown: __private ()]
+ ;; CHECK-NEXT: warning: no passes specified, not doing any work
+ (func $table.getting (export "table.getting")
+ ;; There is a non-null value at 5, and a null at 6.
+ (call $log-i32
+ (ref.is_null
+ (call $table.get
+ (i32.const 5)
+ )
+ )
+ )
+ (call $log-i32
+ (ref.is_null
+ (call $table.get
+ (i32.const 6)
+ )
+ )
+ )
+ ;; Out of bounds gets will throw.
+ (drop
+ (call $table.get
+ (i32.const 9999)
+ )
+ )
+ )
)
;; CHECK: [fuzz-exec] calling logging
;; CHECK-NEXT: [LoggingExternalInterface logging 42]
@@ -35,5 +84,15 @@
;; CHECK: [fuzz-exec] calling throwing
;; CHECK-NEXT: [exception thrown: __private ()]
+
+;; CHECK: [fuzz-exec] calling table.setting
+;; CHECK-NEXT: [exception thrown: __private ()]
+
+;; CHECK: [fuzz-exec] calling table.getting
+;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+;; CHECK-NEXT: [LoggingExternalInterface logging 1]
+;; CHECK-NEXT: [exception thrown: __private ()]
;; CHECK-NEXT: [fuzz-exec] comparing logging
+;; CHECK-NEXT: [fuzz-exec] comparing table.getting
+;; CHECK-NEXT: [fuzz-exec] comparing table.setting
;; CHECK-NEXT: [fuzz-exec] comparing throwing
diff --git a/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt b/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt
index c17c9cd1e..3189277b0 100644
--- a/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt
+++ b/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt
@@ -1,58 +1,51 @@
Metrics
total
- [exports] : 3
- [funcs] : 4
+ [exports] : 4
+ [funcs] : 3
[globals] : 26
- [imports] : 6
+ [imports] : 8
[memories] : 1
[memory-data] : 20
[table-data] : 0
[tables] : 1
[tags] : 2
- [total] : 665
- [vars] : 20
- ArrayGet : 2
- ArrayLen : 2
- ArrayNew : 15
- ArrayNewFixed : 3
- AtomicCmpxchg : 1
- AtomicRMW : 1
- Binary : 71
- Block : 63
- BrOn : 4
- Break : 7
- Call : 18
- CallRef : 2
- Const : 142
- Drop : 10
- GlobalGet : 33
+ [total] : 534
+ [vars] : 21
+ ArrayGet : 1
+ ArrayLen : 1
+ ArrayNew : 13
+ ArrayNewFixed : 2
+ AtomicNotify : 2
+ Binary : 68
+ Block : 53
+ BrOn : 1
+ Break : 8
+ Call : 11
+ CallRef : 1
+ Const : 117
+ DataDrop : 1
+ Drop : 7
+ GlobalGet : 27
GlobalSet : 16
- I31Get : 1
- If : 21
- Load : 20
- LocalGet : 61
- LocalSet : 52
+ If : 13
+ Load : 19
+ LocalGet : 56
+ LocalSet : 39
Loop : 6
- MemoryFill : 1
- MemoryInit : 1
- Nop : 7
- Pop : 1
- RefAs : 11
- RefEq : 1
- RefFunc : 3
- RefI31 : 1
- RefNull : 14
- Return : 5
- SIMDExtract : 1
- Select : 2
- StringConst : 5
- StringEncode : 1
- StructGet : 4
- StructNew : 16
- StructSet : 1
- Try : 1
+ Nop : 2
+ Pop : 3
+ RefAs : 4
+ RefFunc : 2
+ RefNull : 7
+ RefTest : 1
+ Return : 3
+ SIMDExtract : 3
+ Store : 2
+ StringConst : 2
+ StringWTF16Get : 1
+ StructNew : 11
+ Try : 3
TryTable : 1
- TupleExtract : 2
- TupleMake : 6
- Unary : 20
- Unreachable : 9
+ TupleMake : 3
+ Unary : 14
+ Unreachable : 10