summaryrefslogtreecommitdiff
path: root/src/tools
diff options
context:
space:
mode:
authorAlon Zakai <azakai@google.com>2024-10-23 10:17:13 -0700
committerGitHub <noreply@github.com>2024-10-23 10:17:13 -0700
commitdcc70bbfb16c2f8fce29dad94d80d1b78123655f (patch)
tree05db3b38ea848cef1010eac458549f012342b2fa /src/tools
parent0d9b7508e5de1ca7befef493ed3e357b8a5613a1 (diff)
downloadbinaryen-dcc70bbfb16c2f8fce29dad94d80d1b78123655f.tar.gz
binaryen-dcc70bbfb16c2f8fce29dad94d80d1b78123655f.tar.bz2
binaryen-dcc70bbfb16c2f8fce29dad94d80d1b78123655f.zip
[EH] Fuzz throws from JS (#7027)
We already generated (throw ..) instructions in wasm, but it makes sense to model throws from outside as well, as they cross the module boundary. This adds a new fuzzer import to the generated modules, "throw", that just does a throw from JS etc. Also be more precise about handling fuzzing-support imports in fuzz-exec: we now check that logging functions start with "log*" and error otherwise (this check is now needed given we have "throw", which is not logging). Also fix a minor issue with name conflicts for logging functions by using getValidFunctionName for them, both for logging and for throw.
Diffstat (limited to 'src/tools')
-rw-r--r--src/tools/execution-results.h47
-rw-r--r--src/tools/fuzzing.h10
-rw-r--r--src/tools/fuzzing/fuzzing.cpp66
3 files changed, 85 insertions, 38 deletions
diff --git a/src/tools/execution-results.h b/src/tools/execution-results.h
index 920bb200a..d9fefc44e 100644
--- a/src/tools/execution-results.h
+++ b/src/tools/execution-results.h
@@ -27,6 +27,7 @@ using Loggings = std::vector<Literal>;
// Logs every relevant import call parameter.
struct LoggingExternalInterface : public ShellExternalInterface {
+private:
Loggings& loggings;
struct State {
@@ -37,30 +38,40 @@ struct LoggingExternalInterface : public ShellExternalInterface {
uint32_t tempRet0 = 0;
} state;
+public:
LoggingExternalInterface(Loggings& loggings) : loggings(loggings) {}
Literals callImport(Function* import, const Literals& arguments) override {
if (import->module == "fuzzing-support") {
- std::cout << "[LoggingExternalInterface logging";
- loggings.push_back(Literal()); // buffer with a None between calls
- for (auto argument : arguments) {
- if (argument.type == Type::i64) {
- // To avoid JS legalization changing logging results, treat a logging
- // of an i64 as two i32s (which is what legalization would turn us
- // into).
- auto low = Literal(int32_t(argument.getInteger()));
- auto high = Literal(int32_t(argument.getInteger() >> int32_t(32)));
- std::cout << ' ' << low;
- loggings.push_back(low);
- std::cout << ' ' << high;
- loggings.push_back(high);
- } else {
- std::cout << ' ' << argument;
- loggings.push_back(argument);
+ if (import->base.startsWith("log")) {
+ // This is a logging function like log-i32 or log-f64
+ std::cout << "[LoggingExternalInterface logging";
+ loggings.push_back(Literal()); // buffer with a None between calls
+ for (auto argument : arguments) {
+ if (argument.type == Type::i64) {
+ // To avoid JS legalization changing logging results, treat a
+ // logging of an i64 as two i32s (which is what legalization would
+ // turn us into).
+ auto low = Literal(int32_t(argument.getInteger()));
+ auto high = Literal(int32_t(argument.getInteger() >> int32_t(32)));
+ std::cout << ' ' << low;
+ loggings.push_back(low);
+ std::cout << ' ' << high;
+ loggings.push_back(high);
+ } else {
+ std::cout << ' ' << argument;
+ loggings.push_back(argument);
+ }
}
+ 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)});
+ } else {
+ WASM_UNREACHABLE("unknown fuzzer import");
}
- std::cout << "]\n";
- return {};
} else if (import->module == ENV) {
if (import->base == "log_execution") {
std::cout << "[LoggingExternalInterface log-execution";
diff --git a/src/tools/fuzzing.h b/src/tools/fuzzing.h
index 92e997913..69fee656c 100644
--- a/src/tools/fuzzing.h
+++ b/src/tools/fuzzing.h
@@ -104,6 +104,10 @@ private:
Name funcrefTableName;
+ std::unordered_map<Type, Name> logImportNames;
+
+ Name throwImportName;
+
std::unordered_map<Type, std::vector<Name>> globalsByType;
std::unordered_map<Type, std::vector<Name>> mutableGlobalsByType;
std::unordered_map<Type, std::vector<Name>> immutableGlobalsByType;
@@ -220,12 +224,16 @@ 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 addImportThrowingSupport();
void addHashMemorySupport();
// Special expression makers
Expression* makeHangLimitCheck();
- Expression* makeLogging();
+ Expression* makeImportLogging();
+ Expression* makeImportThrowing(Type type);
Expression* makeMemoryHashLogging();
// Function creation
diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp
index d3f52d3b0..b7d075dcc 100644
--- a/src/tools/fuzzing/fuzzing.cpp
+++ b/src/tools/fuzzing/fuzzing.cpp
@@ -176,9 +176,10 @@ void TranslateToFuzzReader::build() {
setupGlobals();
if (wasm.features.hasExceptionHandling()) {
setupTags();
+ addImportThrowingSupport();
}
- modifyInitialFunctions();
addImportLoggingSupport();
+ modifyInitialFunctions();
// keep adding functions until we run out of input
while (!random.finished()) {
auto* func = addFunction();
@@ -583,16 +584,31 @@ void TranslateToFuzzReader::addHangLimitSupport() {
void TranslateToFuzzReader::addImportLoggingSupport() {
for (auto type : loggableTypes) {
- auto* func = new Function;
- Name name = std::string("log-") + type.toString();
- func->name = name;
+ auto func = std::make_unique<Function>();
+ Name baseName = std::string("log-") + type.toString();
+ func->name = Names::getValidFunctionName(wasm, baseName);
+ logImportNames[type] = func->name;
func->module = "fuzzing-support";
- func->base = name;
+ func->base = baseName;
func->type = Signature(type, Type::none);
- wasm.addFunction(func);
+ 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
+ // something not exported if out of bounds. First we must also export
+ // tags sometimes.
+ throwImportName = Names::getValidFunctionName(wasm, "throw");
+ auto func = std::make_unique<Function>();
+ func->name = throwImportName;
+ func->module = "fuzzing-support";
+ func->base = "throw";
+ func->type = Signature(Type::none, Type::none);
+ wasm.addFunction(std::move(func));
+}
+
void TranslateToFuzzReader::addHashMemorySupport() {
// Add memory hasher helper (for the hash, see hash.h). The function looks
// like:
@@ -692,21 +708,30 @@ Expression* TranslateToFuzzReader::makeHangLimitCheck() {
builder.makeConst(int32_t(1)))));
}
-Expression* TranslateToFuzzReader::makeLogging() {
+Expression* TranslateToFuzzReader::makeImportLogging() {
auto type = getLoggableType();
- return builder.makeCall(
- std::string("log-") + type.toString(), {make(type)}, Type::none);
+ return builder.makeCall(logImportNames[type], {make(type)}, Type::none);
+}
+
+Expression* TranslateToFuzzReader::makeImportThrowing(Type type) {
+ // We throw from the import, so this call appears to be none and not
+ // unreachable.
+ assert(type == Type::none);
+
+ // TODO: This and makeThrow should probably be rare, as they halt the program.
+ return builder.makeCall(throwImportName, {}, Type::none);
}
Expression* TranslateToFuzzReader::makeMemoryHashLogging() {
auto* hash = builder.makeCall(std::string("hashMemory"), {}, Type::i32);
- return builder.makeCall(std::string("log-i32"), {hash}, Type::none);
+ return builder.makeCall(logImportNames[Type::i32], {hash}, Type::none);
}
// TODO: return std::unique_ptr<Function>
Function* TranslateToFuzzReader::addFunction() {
LOGGING_PERCENT = upToSquared(100);
- auto* func = new Function;
+ auto allocation = std::make_unique<Function>();
+ auto* func = allocation.get();
func->name = Names::getValidFunctionName(wasm, "func");
FunctionCreationContext context(*this, func);
assert(funcContext->typeLocals.empty());
@@ -765,7 +790,7 @@ Function* TranslateToFuzzReader::addFunction() {
}
// Add hang limit checks after all other operations on the function body.
- wasm.addFunction(func);
+ wasm.addFunction(std::move(allocation));
// Export some functions, but not all (to allow inlining etc.). Try to export
// at least one, though, to keep each testcase interesting. Only functions
// with valid params and returns can be exported because the trap fuzzer
@@ -1215,10 +1240,13 @@ void TranslateToFuzzReader::modifyInitialFunctions() {
// the end (currently that is not needed atm, but it might in the future).
for (Index i = 0; i < wasm.functions.size(); i++) {
auto* func = wasm.functions[i].get();
+ // We can't allow extra imports, as the fuzzing infrastructure wouldn't
+ // know what to provide. Keep only our own fuzzer imports.
+ if (func->imported() && func->module == "fuzzing-support") {
+ continue;
+ }
FunctionCreationContext context(*this, func);
if (func->imported()) {
- // We can't allow extra imports, as the fuzzing infrastructure wouldn't
- // know what to provide.
func->module = func->base = Name();
func->body = make(func->getResults());
}
@@ -1261,10 +1289,9 @@ void TranslateToFuzzReader::dropToLog(Function* func) {
void visitDrop(Drop* curr) {
if (parent.isLoggableType(curr->value->type) && parent.oneIn(2)) {
- replaceCurrent(parent.builder.makeCall(std::string("log-") +
- curr->value->type.toString(),
- {curr->value},
- Type::none));
+ auto target = parent.logImportNames[curr->value->type];
+ replaceCurrent(
+ parent.builder.makeCall(target, {curr->value}, Type::none));
}
}
};
@@ -1430,7 +1457,7 @@ Expression* TranslateToFuzzReader::_makenone() {
auto choice = upTo(100);
if (choice < LOGGING_PERCENT) {
if (choice < LOGGING_PERCENT / 2) {
- return makeLogging();
+ return makeImportLogging();
} else {
return makeMemoryHashLogging();
}
@@ -1455,6 +1482,7 @@ Expression* TranslateToFuzzReader::_makenone() {
.add(FeatureSet::Atomics, &Self::makeAtomic)
.add(FeatureSet::ExceptionHandling, &Self::makeTry)
.add(FeatureSet::ExceptionHandling, &Self::makeTryTable)
+ .add(FeatureSet::ExceptionHandling, &Self::makeImportThrowing)
.add(FeatureSet::ReferenceTypes | FeatureSet::GC, &Self::makeCallRef)
.add(FeatureSet::ReferenceTypes | FeatureSet::GC, &Self::makeStructSet)
.add(FeatureSet::ReferenceTypes | FeatureSet::GC, &Self::makeArraySet)