summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/tools/execution-results.h50
-rw-r--r--src/tools/fuzzing.h7
-rw-r--r--src/tools/fuzzing/fuzzing.cpp84
3 files changed, 136 insertions, 5 deletions
diff --git a/src/tools/execution-results.h b/src/tools/execution-results.h
index 78cc5af1f..25d0c0772 100644
--- a/src/tools/execution-results.h
+++ b/src/tools/execution-results.h
@@ -40,10 +40,15 @@ private:
// The name of the table exported by the name 'table.' Imports access it.
Name exportedTable;
+ Module& wasm;
+
+ // The ModuleRunner and this ExternalInterface end up needing links both ways,
+ // so we cannot init this in the constructor.
+ ModuleRunner* instance = nullptr;
public:
LoggingExternalInterface(Loggings& loggings, Module& wasm)
- : loggings(loggings) {
+ : loggings(loggings), wasm(wasm) {
for (auto& exp : wasm.exports) {
if (exp->kind == ExternalKind::Table && exp->name == "table") {
exportedTable = exp->value;
@@ -99,6 +104,18 @@ public:
}
tableStore(exportedTable, index, arguments[1]);
return {};
+ } else if (import->base == "call-export") {
+ callExport(arguments[0].geti32());
+ // Return nothing. If we wanted to return a value we'd need to have
+ // multiple such functions, one for each signature.
+ return {};
+ } else if (import->base == "call-export-catch") {
+ try {
+ callExport(arguments[0].geti32());
+ return {Literal(int32_t(0))};
+ } catch (const WasmException& e) {
+ return {Literal(int32_t(1))};
+ }
} else {
WASM_UNREACHABLE("unknown fuzzer import");
}
@@ -127,6 +144,35 @@ public:
auto payload = std::make_shared<ExnData>("__private", Literals{});
throwException(WasmException{Literal(payload)});
}
+
+ Literals callExport(Index index) {
+ if (index >= wasm.exports.size()) {
+ // No export.
+ throwEmptyException();
+ }
+ auto& exp = wasm.exports[index];
+ if (exp->kind != ExternalKind::Function) {
+ // No callable export.
+ throwEmptyException();
+ }
+ auto* func = wasm.getFunction(exp->value);
+
+ // TODO JS traps on some types on the boundary, which we should behave the
+ // same on. For now, this is not needed because the fuzzer will prune all
+ // non-JS-compatible exports anyhow.
+
+ // Send default values as arguments, or trap if we need anything else.
+ Literals arguments;
+ for (const auto& param : func->getParams()) {
+ if (!param.isDefaultable()) {
+ throwEmptyException();
+ }
+ arguments.push_back(Literal::makeZero(param));
+ }
+ return instance->callFunction(func->name, arguments);
+ }
+
+ void setModuleRunner(ModuleRunner* instance_) { instance = instance_; }
};
// gets execution results from a wasm module. this is useful for fuzzing
@@ -148,6 +194,7 @@ struct ExecutionResults {
LoggingExternalInterface interface(loggings, wasm);
try {
ModuleRunner instance(wasm, &interface);
+ interface.setModuleRunner(&instance);
// execute all exported methods (that are therefore preserved through
// opts)
for (auto& exp : wasm.exports) {
@@ -298,6 +345,7 @@ struct ExecutionResults {
LoggingExternalInterface interface(loggings, wasm);
try {
ModuleRunner instance(wasm, &interface);
+ interface.setModuleRunner(&instance);
return run(func, wasm, instance);
} catch (const TrapException&) {
// May throw in instance creation (init of offsets).
diff --git a/src/tools/fuzzing.h b/src/tools/fuzzing.h
index 6f73feca9..3e8ec5b97 100644
--- a/src/tools/fuzzing.h
+++ b/src/tools/fuzzing.h
@@ -104,10 +104,11 @@ private:
Name funcrefTableName;
std::unordered_map<Type, Name> logImportNames;
-
Name throwImportName;
Name tableGetImportName;
Name tableSetImportName;
+ Name callExportImportName;
+ Name callExportCatchImportName;
std::unordered_map<Type, std::vector<Name>> globalsByType;
std::unordered_map<Type, std::vector<Name>> mutableGlobalsByType;
@@ -225,9 +226,8 @@ private:
void finalizeTable();
void prepareHangLimitSupport();
void addHangLimitSupport();
- // Imports that we call to log out values.
void addImportLoggingSupport();
- // An import that we call to throw an exception from outside.
+ void addImportCallingSupport();
void addImportThrowingSupport();
void addImportTableSupport();
void addHashMemorySupport();
@@ -238,6 +238,7 @@ private:
Expression* makeImportThrowing(Type type);
Expression* makeImportTableGet();
Expression* makeImportTableSet(Type type);
+ Expression* makeImportCallExport(Type type);
Expression* makeMemoryHashLogging();
// Function creation
diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp
index 1329e886e..f4e21d870 100644
--- a/src/tools/fuzzing/fuzzing.cpp
+++ b/src/tools/fuzzing/fuzzing.cpp
@@ -182,6 +182,7 @@ void TranslateToFuzzReader::build() {
addImportTableSupport();
}
addImportLoggingSupport();
+ addImportCallingSupport();
modifyInitialFunctions();
// keep adding functions until we run out of input
while (!random.finished()) {
@@ -635,6 +636,49 @@ void TranslateToFuzzReader::addImportLoggingSupport() {
}
}
+void TranslateToFuzzReader::addImportCallingSupport() {
+ // Only add these some of the time, as they inhibit some fuzzing (things like
+ // wasm-ctor-eval and wasm-merge are sensitive to the wasm being able to call
+ // its own exports, and to care about the indexes of the exports):
+ //
+ // 0 - none
+ // 1 - call-export
+ // 2 - call-export-catch
+ // 3 - call-export & call-export-catch
+ // 4 - none
+ // 5 - none
+ //
+ auto choice = upTo(6);
+ if (choice >= 4) {
+ return;
+ }
+
+ if (choice & 1) {
+ // Given an export index, call it from JS.
+ callExportImportName = Names::getValidFunctionName(wasm, "call-export");
+ auto func = std::make_unique<Function>();
+ func->name = callExportImportName;
+ func->module = "fuzzing-support";
+ func->base = "call-export";
+ func->type = Signature({Type::i32}, Type::none);
+ wasm.addFunction(std::move(func));
+ }
+
+ if (choice & 2) {
+ // Given an export index, call it from JS and catch all exceptions. Return
+ // whether we caught. Exceptions are common (if the index is invalid, in
+ // particular), so a variant that catches is useful to avoid halting.
+ callExportCatchImportName =
+ Names::getValidFunctionName(wasm, "call-export-catch");
+ auto func = std::make_unique<Function>();
+ func->name = callExportCatchImportName;
+ func->module = "fuzzing-support";
+ func->base = "call-export-catch";
+ func->type = Signature(Type::i32, Type::i32);
+ wasm.addFunction(std::move(func));
+ }
+}
+
void TranslateToFuzzReader::addImportThrowingSupport() {
// Throw some kind of exception from JS.
// TODO: Send an index, which is which exported wasm Tag we should throw, or
@@ -820,6 +864,38 @@ Expression* TranslateToFuzzReader::makeImportTableSet(Type type) {
Type::none);
}
+Expression* TranslateToFuzzReader::makeImportCallExport(Type type) {
+ // The none-returning variant just does the call. The i32-returning one
+ // catches any errors and returns 1 when it saw an error. Based on the
+ // variant, pick which to call, and the maximum index to call.
+ Name target;
+ Index maxIndex = wasm.exports.size();
+ if (type == Type::none) {
+ target = callExportImportName;
+ } else if (type == Type::i32) {
+ target = callExportCatchImportName;
+ // This never traps, so we can be less careful, but we do still want to
+ // avoid trapping a lot as executing code is more interesting. (Note that
+ // even though we double here, the risk is not that great: we are still
+ // adding functions as we go, so the first half of functions/exports can
+ // double here and still end up in bounds by the time we've added them all.)
+ maxIndex = (maxIndex + 1) * 2;
+ } else {
+ WASM_UNREACHABLE("bad import.call");
+ }
+ // We must have set up the target function.
+ assert(target);
+
+ // Most of the time, call a valid export index in the range we picked, but
+ // sometimes allow anything at all.
+ auto* index = make(Type::i32);
+ if (!allowOOB || !oneIn(10)) {
+ index = builder.makeBinary(
+ RemUInt32, index, builder.makeConst(int32_t(maxIndex)));
+ }
+ return builder.makeCall(target, {index}, type);
+}
+
Expression* TranslateToFuzzReader::makeMemoryHashLogging() {
auto* hash = builder.makeCall(std::string("hashMemory"), {}, Type::i32);
return builder.makeCall(logImportNames[Type::i32], {hash}, Type::none);
@@ -1511,6 +1587,9 @@ Expression* TranslateToFuzzReader::_makeConcrete(Type type) {
options.add(FeatureSet::Atomics, &Self::makeAtomic);
}
if (type == Type::i32) {
+ if (callExportCatchImportName) {
+ options.add(FeatureSet::MVP, &Self::makeImportCallExport);
+ }
options.add(FeatureSet::ReferenceTypes, &Self::makeRefIsNull);
options.add(FeatureSet::ReferenceTypes | FeatureSet::GC,
&Self::makeRefEq,
@@ -1590,6 +1669,9 @@ Expression* TranslateToFuzzReader::_makenone() {
if (tableSetImportName) {
options.add(FeatureSet::ReferenceTypes, &Self::makeImportTableSet);
}
+ if (callExportImportName) {
+ options.add(FeatureSet::MVP, &Self::makeImportCallExport);
+ }
return (this->*pick(options))(Type::none);
}
@@ -2023,7 +2105,7 @@ Expression* TranslateToFuzzReader::makeCallIndirect(Type type) {
return makeTrivial(type);
}
}
- // with high probability, make sure the type is valid otherwise, most are
+ // with high probability, make sure the type is valid - otherwise, most are
// going to trap
auto addressType = wasm.getTable(funcrefTableName)->addressType;
Expression* target;