diff --git a/scripts/test/shared.py b/scripts/test/shared.py index 2fd1934f4c9..5b49475345b 100644 --- a/scripts/test/shared.py +++ b/scripts/test/shared.py @@ -442,7 +442,6 @@ def get_tests(test_dir, extensions=[], recursive=False): 'instance.wast', # Requires wast `module definition` support 'table64.wast', # Requires wast `module definition` support 'table_grow.wast', # Incorrect table linking semantics in interpreter - 'try_catch.wast', # Incorrect imported tag semantics in interpreter 'tag.wast', # Non-empty tag results allowed by stack switching 'try_table.wast', # Requires try_table interpretation 'local_init.wast', # Requires local validation to respect unnamed blocks diff --git a/src/literal.h b/src/literal.h index f20213e0529..10636f8b89a 100644 --- a/src/literal.h +++ b/src/literal.h @@ -785,18 +785,6 @@ struct GCData { : type(type), values(std::move(values)), desc(desc) {} }; -// The data of a (ref exn) literal. -struct ExnData { - // The tag of this exn data. - // TODO: handle cross-module calls using something other than a Name here. - Name tag; - - // The payload of this exn data. - Literals payload; - - ExnData(Name tag, Literals payload) : tag(tag), payload(payload) {} -}; - } // namespace wasm namespace std { diff --git a/src/tools/execution-results.h b/src/tools/execution-results.h index aad6ed35102..f61cc2a8ae9 100644 --- a/src/tools/execution-results.h +++ b/src/tools/execution-results.h @@ -42,13 +42,11 @@ struct LoggingExternalInterface : public ShellExternalInterface { Name exportedTable; Module& wasm; - // The name of the imported fuzzing tag for wasm. - Name wasmTag; + // The imported fuzzing tag for wasm. + Tag wasmTag; - // The name of the imported tag for js exceptions. If it is not imported, we - // use a default name here (which should differentiate it from any wasm - // exceptions). - Name jsTag = "__private"; + // The imported tag for js exceptions. + Tag jsTag; // The ModuleRunner and this ExternalInterface end up needing links both ways, // so we cannot init this in the constructor. @@ -67,17 +65,34 @@ struct LoggingExternalInterface : public ShellExternalInterface { } } + // Default names for tags. + wasmTag.module = "fuzzing-support"; + wasmTag.base = wasmTag.name = "wasmtag"; + + jsTag.module = "fuzzing-support"; + jsTag.base = "jstag"; + jsTag.name = "__private"; + for (auto& tag : wasm.tags) { if (tag->module == "fuzzing-support") { if (tag->base == "wasmtag") { - wasmTag = tag->name; + wasmTag.name = tag->name; } else if (tag->base == "jstag") { - jsTag = tag->name; + jsTag.name = tag->name; } } } } + Tag* getImportedTag(Name name) override { + if (name == jsTag.name) { + return &jsTag; + } else if (name == wasmTag.name) { + return &wasmTag; + } + Fatal() << "missing host tag " << name; + } + Literal getImportedFunction(Function* import) override { if (linkedInstances.count(import->module)) { return getImportInstance(import)->getExportedFunction(import->base); @@ -122,7 +137,7 @@ struct LoggingExternalInterface : public ShellExternalInterface { if (arguments[0].geti32() == 0) { throwJSException(); } else { - auto payload = std::make_shared(wasmTag, arguments); + auto payload = std::make_shared(&wasmTag, arguments); throwException(WasmException{Literal(payload)}); } } else if (import->base == "table-get") { @@ -213,7 +228,7 @@ struct LoggingExternalInterface : public ShellExternalInterface { auto empty = HeapType(Struct{}); auto inner = Literal(std::make_shared(empty, Literals{}), empty); Literals arguments = {inner.externalize()}; - auto payload = std::make_shared(jsTag, arguments); + auto payload = std::make_shared(&jsTag, arguments); throwException(WasmException{Literal(payload)}); } diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 4fe7cea6bbf..34339f625cf 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -160,6 +160,19 @@ struct FuncData { } }; +// The data of a (ref exn) literal. +struct ExnData { + // The tag of this exn data. + // TODO: Add self, like in FuncData, to handle the case of a module that is + // instantiated multiple times. + Tag* tag; + + // The payload of this exn data. + Literals payload; + + ExnData(Tag* tag, Literals payload) : tag(tag), payload(payload) {} +}; + // Suspend/resume support. // // As we operate directly on our structured IR, we do not have a program counter @@ -324,7 +337,7 @@ class ExpressionRunner : public OverriddenVisitor { } // Same as makeGCData but for ExnData. - Literal makeExnData(Name tag, const Literals& payload) { + Literal makeExnData(Tag* tag, const Literals& payload) { auto allocation = std::make_shared(tag, payload); #if __has_feature(leak_sanitizer) || __has_feature(address_sanitizer) __lsan_ignore_object(allocation.get()); @@ -1890,9 +1903,13 @@ class ExpressionRunner : public OverriddenVisitor { Flow visitTry(Try* curr) { WASM_UNREACHABLE("unimp"); } Flow visitTryTable(TryTable* curr) { WASM_UNREACHABLE("unimp"); } Flow visitThrow(Throw* curr) { + // Single-module implementation. This is used from Precompute, for example. + // It is overriden in ModuleRunner to add logic for finding the proper + // imported tag (which single-module cases don't care about). Literals arguments; VISIT_ARGUMENTS(flow, curr->operands, arguments); - throwException(WasmException{makeExnData(curr->tag, arguments)}); + throwException(WasmException{ + self()->makeExnData(self()->getModule()->getTag(curr->tag), arguments)}); WASM_UNREACHABLE("throw"); } Flow visitRethrow(Rethrow* curr) { WASM_UNREACHABLE("unimp"); } @@ -2908,6 +2925,9 @@ class ModuleRunnerBase : public ExpressionRunner { virtual void trap(const char* why) = 0; virtual void hostLimit(const char* why) = 0; virtual void throwException(const WasmException& exn) = 0; + // Get the Tag instance for a tag implemented in the host, that is, not + // among the linked ModuleRunner instances. + virtual Tag* getImportedTag(Name name) { WASM_UNREACHABLE("missing imported tag"); } // the default impls for load and store switch on the sizes. you can either // customize load/store, or the sub-functions which they call @@ -3194,6 +3214,18 @@ class ModuleRunnerBase : public ExpressionRunner { return iter->second; } + Tag* getExportedTag(Name name) { + Export* export_ = wasm.getExportOrNull(name); + if (!export_ || export_->kind != ExternalKind::Tag) { + externalInterface->trap("exported tag not found"); + } + auto* tag = wasm.getTag(*export_->getInternalName()); + if (tag->imported()) { + tag = externalInterface->getImportedTag(name); + } + return tag; + } + std::string printFunctionStack() { std::string ret = "/== (binaryen interpreter stack trace)\n"; for (int i = int(functionStack.size()) - 1; i >= 0; i--) { @@ -3446,9 +3478,12 @@ class ModuleRunnerBase : public ExpressionRunner { auto* inst = self(); auto* tag = inst->wasm.getTag(name); while (tag->imported()) { - inst = inst->linkedInstances.at(tag->module).get(); - auto* tagExport = inst->wasm.getExport(tag->base); - tag = inst->wasm.getTag(*tagExport->getInternalName()); + auto iter = inst->linkedInstances.find(tag->module); + if (iter == inst->linkedInstances.end()) { + return externalInterface->getImportedTag(name); + } + inst = iter->second.get(); + tag = inst->getExportedTag(tag->base); } return tag; } @@ -4354,7 +4389,8 @@ class ModuleRunnerBase : public ExpressionRunner { auto exnData = e.exn.getExnData(); for (size_t i = 0; i < curr->catchTags.size(); i++) { - if (curr->catchTags[i] == exnData->tag) { + auto* tag = self()->getCanonicalTag(curr->catchTags[i]); + if (tag == exnData->tag) { multiValues.push_back(exnData->payload); return processCatchBody(curr->catchBodies[i]); } @@ -4377,7 +4413,8 @@ class ModuleRunnerBase : public ExpressionRunner { auto exnData = e.exn.getExnData(); for (size_t i = 0; i < curr->catchTags.size(); i++) { auto catchTag = curr->catchTags[i]; - if (!catchTag.is() || catchTag == exnData->tag) { + if (!catchTag.is() || + self()->getCanonicalTag(catchTag) == exnData->tag) { Flow ret; ret.breakTo = curr->catchDests[i]; if (catchTag.is()) { @@ -4395,6 +4432,13 @@ class ModuleRunnerBase : public ExpressionRunner { throw; } } + Flow visitThrow(Throw* curr) { + Literals arguments; + VISIT_ARGUMENTS(flow, curr->operands, arguments); + throwException(WasmException{ + self()->makeExnData(self()->getCanonicalTag(curr->tag), arguments)}); + WASM_UNREACHABLE("throw"); + } Flow visitRethrow(Rethrow* curr) { for (int i = exceptionStack.size() - 1; i >= 0; i--) { if (exceptionStack[i].second == curr->target) { @@ -4463,9 +4507,8 @@ class ModuleRunnerBase : public ExpressionRunner { assert(self()->restoredValuesMap.empty()); // Throw, if we were resumed by resume_throw; if (auto* tag = currContinuation->exceptionTag) { - // XXX tag->name lacks cross-module support throwException(WasmException{ - self()->makeExnData(tag->name, currContinuation->resumeArguments)}); + self()->makeExnData(tag, currContinuation->resumeArguments)}); } return currContinuation->resumeArguments; } @@ -4668,9 +4711,8 @@ class ModuleRunnerBase : public ExpressionRunner { // set), so resuming is done. (And throw, if resume_throw.) self()->continuationStore->resuming = false; if (auto* tag = currContinuation->exceptionTag) { - // XXX tag->name lacks cross-module support throwException(WasmException{ - self()->makeExnData(tag->name, currContinuation->resumeArguments)}); + self()->makeExnData(tag, currContinuation->resumeArguments)}); } } } diff --git a/src/wasm/wasm-interpreter.cpp b/src/wasm/wasm-interpreter.cpp index 37ac05556ac..3af29d2c773 100644 --- a/src/wasm/wasm-interpreter.cpp +++ b/src/wasm/wasm-interpreter.cpp @@ -4,7 +4,7 @@ namespace wasm { std::ostream& operator<<(std::ostream& o, const WasmException& exn) { auto exnData = exn.exn.getExnData(); - return o << exnData->tag << " " << exnData->payload; + return o << exnData->tag->name << " " << exnData->payload; } } // namespace wasm diff --git a/test/lit/exec/tag-cross-module.wast b/test/lit/exec/tag-cross-module.wast new file mode 100644 index 00000000000..9ae493c87b5 --- /dev/null +++ b/test/lit/exec/tag-cross-module.wast @@ -0,0 +1,26 @@ +;; RUN: wasm-opt %s -all --fuzz-exec-before --fuzz-exec-second=%s.second -q -o /dev/null 2>&1 | filecheck %s + +;; Define a tag in this module, and another tag in the secondary module, with +;; the same name but different (incompatible) contents. The second module will +;; call our export, and when we throw our tag, it should not catch it. + +(module + (tag $tag (param structref)) + + (export "primary-tag" (tag $tag)) + + (func $func (export "func") (result i32) + (throw $tag + (ref.null struct) + ) + ) +) + +;; CHECK: [fuzz-exec] calling func +;; CHECK-NEXT: [exception thrown: tag nullref] +;; CHECK-NEXT: [fuzz-exec] calling func2-internal +;; CHECK-NEXT: [exception thrown: tag nullref] +;; CHECK-NEXT: [fuzz-exec] calling func2-imported +;; CHECK-NEXT: func2-imported => null + + diff --git a/test/lit/exec/tag-cross-module.wast.second b/test/lit/exec/tag-cross-module.wast.second new file mode 100644 index 00000000000..3384f19d9b3 --- /dev/null +++ b/test/lit/exec/tag-cross-module.wast.second @@ -0,0 +1,32 @@ +(module + (import "primary" "func" (func $import (result i32))) + + (import "primary" "primary-tag" (tag $ptag (param structref))) + + (tag $tag (param (ref array))) + + (func $func2-internal (export "func2-internal") (result (ref array)) + ;; Try to catch the internal tag. This fails to catch. + (block $block (result (ref array)) + (try_table (catch $tag $block) + (drop + (call $import) + ) + ) + (unreachable) + ) + ) + + (func $func2-imported (export "func2-imported") (result structref) + ;; Try to catch the imported tag. This successfully catches. + (block $block (result structref) + (try_table (catch $ptag $block) + (drop + (call $import) + ) + ) + (unreachable) + ) + ) +) +