Skip to content

Commit 7a94261

Browse files
authored
[Exceptions] Fix cross-module Tag handling in interpreter (#7955)
Make ExnData store a `Tag*`, allowing proper identification. Like FuncData etc., move that logic into the interpreter.
1 parent 4563696 commit 7a94261

11 files changed

Lines changed: 154 additions & 45 deletions

scripts/test/shared.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -442,7 +442,6 @@ def get_tests(test_dir, extensions=[], recursive=False):
442442
'instance.wast', # Requires wast `module definition` support
443443
'table64.wast', # Requires wast `module definition` support
444444
'table_grow.wast', # Incorrect table linking semantics in interpreter
445-
'try_catch.wast', # Incorrect imported tag semantics in interpreter
446445
'tag.wast', # Non-empty tag results allowed by stack switching
447446
'try_table.wast', # Requires try_table interpretation
448447
'local_init.wast', # Requires local validation to respect unnamed blocks

src/literal.h

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -785,18 +785,6 @@ struct GCData {
785785
: type(type), values(std::move(values)), desc(desc) {}
786786
};
787787

788-
// The data of a (ref exn) literal.
789-
struct ExnData {
790-
// The tag of this exn data.
791-
// TODO: handle cross-module calls using something other than a Name here.
792-
Name tag;
793-
794-
// The payload of this exn data.
795-
Literals payload;
796-
797-
ExnData(Name tag, Literals payload) : tag(tag), payload(payload) {}
798-
};
799-
800788
} // namespace wasm
801789

802790
namespace std {

src/shell-interface.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,10 @@ struct ShellExternalInterface : ModuleRunner::ExternalInterface {
165165
<< "." << import->name.str;
166166
}
167167

168+
Tag* getImportedTag(Tag* tag) override {
169+
WASM_UNREACHABLE("missing imported tag");
170+
}
171+
168172
int8_t load8s(Address addr, Name memoryName) override {
169173
auto it = memories.find(memoryName);
170174
assert(it != memories.end());

src/tools/execution-results.h

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,11 @@ struct LoggingExternalInterface : public ShellExternalInterface {
4242
Name exportedTable;
4343
Module& wasm;
4444

45-
// The name of the imported fuzzing tag for wasm.
46-
Name wasmTag;
45+
// The imported fuzzing tag for wasm.
46+
Tag wasmTag;
4747

48-
// The name of the imported tag for js exceptions. If it is not imported, we
49-
// use a default name here (which should differentiate it from any wasm
50-
// exceptions).
51-
Name jsTag = "__private";
48+
// The imported tag for js exceptions.
49+
Tag jsTag;
5250

5351
// The ModuleRunner and this ExternalInterface end up needing links both ways,
5452
// so we cannot init this in the constructor.
@@ -67,15 +65,26 @@ struct LoggingExternalInterface : public ShellExternalInterface {
6765
}
6866
}
6967

70-
for (auto& tag : wasm.tags) {
71-
if (tag->module == "fuzzing-support") {
72-
if (tag->base == "wasmtag") {
73-
wasmTag = tag->name;
74-
} else if (tag->base == "jstag") {
75-
jsTag = tag->name;
76-
}
68+
// Set up tags. (Setting these values is useful for debugging - making the
69+
// Tag objects valid - and also appears in fuzz-exec logging.)
70+
wasmTag.module = "fuzzing-support";
71+
wasmTag.base = "wasmtag";
72+
wasmTag.name = "imported-wasm-tag";
73+
wasmTag.type = Signature(Type::i32, Type::none);
74+
75+
jsTag.module = "fuzzing-support";
76+
jsTag.base = "jstag";
77+
jsTag.name = "imported-js-tag";
78+
jsTag.type = Signature(Type(HeapType::ext, Nullable), Type::none);
79+
}
80+
81+
Tag* getImportedTag(Tag* tag) override {
82+
for (auto* imported : {&wasmTag, &jsTag}) {
83+
if (imported->module == tag->module && imported->base == tag->base) {
84+
return imported;
7785
}
7886
}
87+
Fatal() << "missing host tag " << tag->module << '.' << tag->base;
7988
}
8089

8190
Literal getImportedFunction(Function* import) override {
@@ -122,7 +131,7 @@ struct LoggingExternalInterface : public ShellExternalInterface {
122131
if (arguments[0].geti32() == 0) {
123132
throwJSException();
124133
} else {
125-
auto payload = std::make_shared<ExnData>(wasmTag, arguments);
134+
auto payload = std::make_shared<ExnData>(&wasmTag, arguments);
126135
throwException(WasmException{Literal(payload)});
127136
}
128137
} else if (import->base == "table-get") {
@@ -213,7 +222,7 @@ struct LoggingExternalInterface : public ShellExternalInterface {
213222
auto empty = HeapType(Struct{});
214223
auto inner = Literal(std::make_shared<GCData>(empty, Literals{}), empty);
215224
Literals arguments = {inner.externalize()};
216-
auto payload = std::make_shared<ExnData>(jsTag, arguments);
225+
auto payload = std::make_shared<ExnData>(&jsTag, arguments);
217226
throwException(WasmException{Literal(payload)});
218227
}
219228

src/tools/wasm-ctor-eval.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,10 @@ struct CtorEvalExternalInterface : EvallingModuleRunner::ExternalInterface {
323323
import->type);
324324
}
325325

326+
Tag* getImportedTag(Tag* tag) override {
327+
WASM_UNREACHABLE("missing imported tag");
328+
}
329+
326330
// We assume the table is not modified FIXME
327331
Literal tableLoad(Name tableName, Address index) override {
328332
auto* table = wasm->getTableOrNull(tableName);

src/wasm-interpreter.h

Lines changed: 60 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,19 @@ struct FuncData {
160160
}
161161
};
162162

163+
// The data of a (ref exn) literal.
164+
struct ExnData {
165+
// The tag of this exn data.
166+
// TODO: Add self, like in FuncData, to handle the case of a module that is
167+
// instantiated multiple times.
168+
Tag* tag;
169+
170+
// The payload of this exn data.
171+
Literals payload;
172+
173+
ExnData(Tag* tag, Literals payload) : tag(tag), payload(payload) {}
174+
};
175+
163176
// Suspend/resume support.
164177
//
165178
// As we operate directly on our structured IR, we do not have a program counter
@@ -324,7 +337,7 @@ class ExpressionRunner : public OverriddenVisitor<SubType, Flow> {
324337
}
325338

326339
// Same as makeGCData but for ExnData.
327-
Literal makeExnData(Name tag, const Literals& payload) {
340+
Literal makeExnData(Tag* tag, const Literals& payload) {
328341
auto allocation = std::make_shared<ExnData>(tag, payload);
329342
#if __has_feature(leak_sanitizer) || __has_feature(address_sanitizer)
330343
__lsan_ignore_object(allocation.get());
@@ -1890,9 +1903,18 @@ class ExpressionRunner : public OverriddenVisitor<SubType, Flow> {
18901903
Flow visitTry(Try* curr) { WASM_UNREACHABLE("unimp"); }
18911904
Flow visitTryTable(TryTable* curr) { WASM_UNREACHABLE("unimp"); }
18921905
Flow visitThrow(Throw* curr) {
1906+
// Single-module implementation. This is used from Precompute, for example.
1907+
// It is overriden in ModuleRunner to add logic for finding the proper
1908+
// imported tag (which single-module cases don't care about).
18931909
Literals arguments;
18941910
VISIT_ARGUMENTS(flow, curr->operands, arguments);
1895-
throwException(WasmException{makeExnData(curr->tag, arguments)});
1911+
auto* tag = self()->getModule()->getTag(curr->tag);
1912+
if (tag->imported()) {
1913+
// The same tag can be imported twice, so by looking at only the current
1914+
// module we can't tell if two tags are the same or not.
1915+
return NONCONSTANT_FLOW;
1916+
}
1917+
throwException(WasmException{self()->makeExnData(tag, arguments)});
18961918
WASM_UNREACHABLE("throw");
18971919
}
18981920
Flow visitRethrow(Rethrow* curr) { WASM_UNREACHABLE("unimp"); }
@@ -2908,6 +2930,9 @@ class ModuleRunnerBase : public ExpressionRunner<SubType> {
29082930
virtual void trap(const char* why) = 0;
29092931
virtual void hostLimit(const char* why) = 0;
29102932
virtual void throwException(const WasmException& exn) = 0;
2933+
// Get the Tag instance for a tag implemented in the host, that is, not
2934+
// among the linked ModuleRunner instances, but imported from the host.
2935+
virtual Tag* getImportedTag(Tag* tag) = 0;
29112936

29122937
// the default impls for load and store switch on the sizes. you can either
29132938
// customize load/store, or the sub-functions which they call
@@ -3194,6 +3219,18 @@ class ModuleRunnerBase : public ExpressionRunner<SubType> {
31943219
return iter->second;
31953220
}
31963221

3222+
Tag* getExportedTag(Name name) {
3223+
Export* export_ = wasm.getExportOrNull(name);
3224+
if (!export_ || export_->kind != ExternalKind::Tag) {
3225+
externalInterface->trap("exported tag not found");
3226+
}
3227+
auto* tag = wasm.getTag(*export_->getInternalName());
3228+
if (tag->imported()) {
3229+
tag = externalInterface->getImportedTag(tag);
3230+
}
3231+
return tag;
3232+
}
3233+
31973234
std::string printFunctionStack() {
31983235
std::string ret = "/== (binaryen interpreter stack trace)\n";
31993236
for (int i = int(functionStack.size()) - 1; i >= 0; i--) {
@@ -3445,12 +3482,15 @@ class ModuleRunnerBase : public ExpressionRunner<SubType> {
34453482
Tag* getCanonicalTag(Name name) {
34463483
auto* inst = self();
34473484
auto* tag = inst->wasm.getTag(name);
3448-
while (tag->imported()) {
3449-
inst = inst->linkedInstances.at(tag->module).get();
3450-
auto* tagExport = inst->wasm.getExport(tag->base);
3451-
tag = inst->wasm.getTag(*tagExport->getInternalName());
3485+
if (!tag->imported()) {
3486+
return tag;
34523487
}
3453-
return tag;
3488+
auto iter = inst->linkedInstances.find(tag->module);
3489+
if (iter == inst->linkedInstances.end()) {
3490+
return externalInterface->getImportedTag(tag);
3491+
}
3492+
inst = iter->second.get();
3493+
return inst->getExportedTag(tag->base);
34543494
}
34553495

34563496
public:
@@ -4354,7 +4394,8 @@ class ModuleRunnerBase : public ExpressionRunner<SubType> {
43544394

43554395
auto exnData = e.exn.getExnData();
43564396
for (size_t i = 0; i < curr->catchTags.size(); i++) {
4357-
if (curr->catchTags[i] == exnData->tag) {
4397+
auto* tag = self()->getCanonicalTag(curr->catchTags[i]);
4398+
if (tag == exnData->tag) {
43584399
multiValues.push_back(exnData->payload);
43594400
return processCatchBody(curr->catchBodies[i]);
43604401
}
@@ -4377,7 +4418,8 @@ class ModuleRunnerBase : public ExpressionRunner<SubType> {
43774418
auto exnData = e.exn.getExnData();
43784419
for (size_t i = 0; i < curr->catchTags.size(); i++) {
43794420
auto catchTag = curr->catchTags[i];
4380-
if (!catchTag.is() || catchTag == exnData->tag) {
4421+
if (!catchTag.is() ||
4422+
self()->getCanonicalTag(catchTag) == exnData->tag) {
43814423
Flow ret;
43824424
ret.breakTo = curr->catchDests[i];
43834425
if (catchTag.is()) {
@@ -4395,6 +4437,13 @@ class ModuleRunnerBase : public ExpressionRunner<SubType> {
43954437
throw;
43964438
}
43974439
}
4440+
Flow visitThrow(Throw* curr) {
4441+
Literals arguments;
4442+
VISIT_ARGUMENTS(flow, curr->operands, arguments);
4443+
throwException(WasmException{
4444+
self()->makeExnData(self()->getCanonicalTag(curr->tag), arguments)});
4445+
WASM_UNREACHABLE("throw");
4446+
}
43984447
Flow visitRethrow(Rethrow* curr) {
43994448
for (int i = exceptionStack.size() - 1; i >= 0; i--) {
44004449
if (exceptionStack[i].second == curr->target) {
@@ -4463,9 +4512,8 @@ class ModuleRunnerBase : public ExpressionRunner<SubType> {
44634512
assert(self()->restoredValuesMap.empty());
44644513
// Throw, if we were resumed by resume_throw;
44654514
if (auto* tag = currContinuation->exceptionTag) {
4466-
// XXX tag->name lacks cross-module support
44674515
throwException(WasmException{
4468-
self()->makeExnData(tag->name, currContinuation->resumeArguments)});
4516+
self()->makeExnData(tag, currContinuation->resumeArguments)});
44694517
}
44704518
return currContinuation->resumeArguments;
44714519
}
@@ -4668,9 +4716,8 @@ class ModuleRunnerBase : public ExpressionRunner<SubType> {
46684716
// set), so resuming is done. (And throw, if resume_throw.)
46694717
self()->continuationStore->resuming = false;
46704718
if (auto* tag = currContinuation->exceptionTag) {
4671-
// XXX tag->name lacks cross-module support
46724719
throwException(WasmException{
4673-
self()->makeExnData(tag->name, currContinuation->resumeArguments)});
4720+
self()->makeExnData(tag, currContinuation->resumeArguments)});
46744721
}
46754722
}
46764723
}

src/wasm/wasm-interpreter.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ namespace wasm {
44

55
std::ostream& operator<<(std::ostream& o, const WasmException& exn) {
66
auto exnData = exn.exn.getExnData();
7-
return o << exnData->tag << " " << exnData->payload;
7+
return o << exnData->tag->name << " " << exnData->payload;
88
}
99

1010
} // namespace wasm

test/lit/exec/cont_export.wast

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424

2525
;; CHECK: [fuzz-exec] calling call-call-export
2626
;; CHECK-NEXT: [LoggingExternalInterface logging 10]
27-
;; CHECK-NEXT: [exception thrown: __private externref]
27+
;; CHECK-NEXT: [exception thrown: imported-js-tag externref]
2828
(func $call-call-export (export "call-call-export")
2929
;; Call suspend as an export. We cannot suspend through JS, so we throw.
3030
(call $call-export
@@ -35,7 +35,7 @@
3535

3636
;; CHECK: [fuzz-exec] calling handled
3737
;; CHECK-NEXT: [LoggingExternalInterface logging 10]
38-
;; CHECK-NEXT: [exception thrown: __private externref]
38+
;; CHECK-NEXT: [exception thrown: imported-js-tag externref]
3939
(func $handled (export "handled")
4040
;; As above, but inside a continuation, so it would be handled - if we could
4141
;; suspend though JS. But we can't, so we throw.

test/lit/exec/cont_export_throw.wast

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
)
2323

2424
;; CHECK: [fuzz-exec] calling handled
25-
;; CHECK-NEXT: [exception thrown: __private externref]
25+
;; CHECK-NEXT: [exception thrown: imported-js-tag externref]
2626
(func $handled (export "handled")
2727
(drop
2828
(block $block (result (ref $cont))
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
;; RUN: wasm-opt %s -all --fuzz-exec-before --fuzz-exec-second=%s.second -q -o /dev/null 2>&1 | filecheck %s
2+
3+
;; Define a tag in this module, and another tag in the secondary module, with
4+
;; the same name but different (incompatible) contents. The second module will
5+
;; call our export, and when we throw our tag, it should not catch it.
6+
7+
(module
8+
(tag $tag (param structref))
9+
10+
(export "primary-tag" (tag $tag))
11+
12+
(func $func (export "func") (result i32)
13+
(throw $tag
14+
(ref.null struct)
15+
)
16+
)
17+
)
18+
19+
;; CHECK: [fuzz-exec] calling func
20+
;; CHECK-NEXT: [exception thrown: tag nullref]
21+
;; CHECK-NEXT: [fuzz-exec] calling func2-internal
22+
;; CHECK-NEXT: [exception thrown: tag nullref]
23+
;; CHECK-NEXT: [fuzz-exec] calling func2-imported
24+
;; CHECK-NEXT: func2-imported => null
25+
26+

0 commit comments

Comments
 (0)