Skip to content

Commit 1f212af

Browse files
Check that module imports match their corresponding exports
1 parent 9d94003 commit 1f212af

File tree

8 files changed

+170
-42
lines changed

8 files changed

+170
-42
lines changed

scripts/test/shared.py

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -440,15 +440,14 @@ def get_tests(test_dir, extensions=[], recursive=False):
440440
'func.wast', # Duplicate parameter names not properly rejected
441441
'global.wast', # Fail to parse table
442442
'if.wast', # Requires more precise unreachable validation
443-
'imports.wast', # Missing validation of missing function on instantiation
443+
'imports.wast', # Requires fixing handling of mutation to imported globals
444444
'proposals/threads/imports.wast', # Missing memory type validation on instantiation
445445
'linking.wast', # Missing function type validation on instantiation
446446
'proposals/threads/memory.wast', # Missing memory type validation on instantiation
447-
'memory64-imports.wast', # Missing validation on instantiation
448447
'annotations.wast', # String annotations IDs should be allowed
449448
'id.wast', # Empty IDs should be disallowed
450-
# Requires correct handling of tag imports from different instances of the same module,
451-
# ref.null wast constants, and splitting for module instances
449+
# Requires correct handling of tag imports from different instances of the same module
450+
# and splitting for module instances
452451
'instance.wast',
453452
'table64.wast', # Requires validations for table size
454453
'table_grow.wast', # Incorrect table linking semantics in interpreter
@@ -473,12 +472,8 @@ def get_tests(test_dir, extensions=[], recursive=False):
473472
'type-rec.wast', # Missing function type validation on instantiation
474473
'type-subtyping.wast', # ShellExternalInterface::callTable does not handle subtyping
475474
'call_indirect.wast', # Bug with 64-bit inline element segment parsing
476-
'memory64.wast', # Requires validations for memory size
477-
'imports0.wast', # Missing memory type validation on instantiation
478-
'imports2.wast', # Missing memory type validation on instantiation
479-
'imports3.wast', # Missing memory type validation on instantiation
480-
'linking0.wast', # Missing memory type validation on instantiation
481-
'linking3.wast', # Fatal error on missing table.
475+
'imports3.wast', # Requires better checking of exports from the special "spectest" module
476+
'memory64.wast', # Requires validations on the max memory size
482477
'i16x8_relaxed_q15mulr_s.wast', # Requires wast `either` support
483478
'i32x4_relaxed_trunc.wast', # Requires wast `either` support
484479
'i8x16_relaxed_swizzle.wast', # Requires wast `either` support

src/shell-interface.h

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -134,8 +134,10 @@ struct ShellExternalInterface : ModuleRunner::ExternalInterface {
134134
auto inst = getImportInstance(import);
135135
auto* exportedGlobal = inst->wasm.getExportOrNull(import->base);
136136
if (!exportedGlobal || exportedGlobal->kind != ExternalKind::Global) {
137-
Fatal() << "importGlobals: unknown import: " << import->module.str
138-
<< "." << import->name.str;
137+
trap((std::stringstream()
138+
<< "importGlobals: unknown import: " << import->module.str << "."
139+
<< import->name.str)
140+
.str());
139141
}
140142
globals[import->name] = inst->globals[*exportedGlobal->getInternalName()];
141143
});
@@ -325,7 +327,9 @@ struct ShellExternalInterface : ModuleRunner::ExternalInterface {
325327
return true;
326328
}
327329

328-
void trap(const char* why) override {
330+
void trap(const char* why) override { trap(std::string_view(why)); }
331+
332+
void trap(std::string_view why) override {
329333
std::cout << "[trap " << why << "]\n";
330334
throw TrapException();
331335
}

src/tools/wasm-shell.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,8 @@ struct Shell {
176176
// SIMD instructions.
177177
instance->setRelaxedBehavior(ModuleRunner::RelaxedBehavior::Execute);
178178
instance->instantiate();
179+
} catch (const std::exception& e) {
180+
return Err{std::string("failed to instantiate module: ") + e.what()};
179181
} catch (...) {
180182
return Err{"failed to instantiate module"};
181183
}

src/wasm-interpreter.h

Lines changed: 96 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#define wasm_wasm_interpreter_h
3030

3131
#include <cmath>
32+
#include <iomanip>
3233
#include <limits.h>
3334
#include <sstream>
3435
#include <variant>
@@ -86,7 +87,7 @@ class Flow {
8687
}
8788

8889
Literals values;
89-
Name breakTo; // if non-null, a break is going on
90+
Name breakTo; // if non-null, a break is going on
9091
Tag* suspendTag = nullptr; // if non-null, breakTo must be SUSPEND_FLOW, and
9192
// this is the tag being suspended
9293

@@ -2658,6 +2659,8 @@ class ExpressionRunner : public OverriddenVisitor<SubType, Flow> {
26582659

26592660
virtual void trap(const char* why) { WASM_UNREACHABLE("unimp"); }
26602661

2662+
virtual void trap(std::string_view why) { WASM_UNREACHABLE("unimp"); }
2663+
26612664
virtual void hostLimit(const char* why) { WASM_UNREACHABLE("unimp"); }
26622665

26632666
virtual void throwException(const WasmException& exn) {
@@ -2937,6 +2940,7 @@ class ModuleRunnerBase : public ExpressionRunner<SubType> {
29372940
Index oldSize,
29382941
Index newSize) = 0;
29392942
virtual void trap(const char* why) = 0;
2943+
virtual void trap(std::string_view why) { trap(std::string(why).c_str()); }
29402944
virtual void hostLimit(const char* why) = 0;
29412945
virtual void throwException(const WasmException& exn) = 0;
29422946
// Get the Tag instance for a tag implemented in the host, that is, not
@@ -3178,6 +3182,8 @@ class ModuleRunnerBase : public ExpressionRunner<SubType> {
31783182
// initialize the rest of the external interface
31793183
externalInterface->init(wasm, *self());
31803184

3185+
validateImports();
3186+
31813187
initializeTableContents();
31823188
initializeMemoryContents();
31833189

@@ -3269,6 +3275,76 @@ class ModuleRunnerBase : public ExpressionRunner<SubType> {
32693275
Name name;
32703276
};
32713277

3278+
void validateImportKindMatches(ExternalKind kind,
3279+
const Importable& importable) {
3280+
auto it = linkedInstances.find(importable.module);
3281+
if (it == linkedInstances.end()) {
3282+
trap((std::stringstream()
3283+
<< "Import module " << std::quoted(importable.module.toString())
3284+
<< " doesn't exist.")
3285+
.str());
3286+
}
3287+
SubType* importedInstance = it->second.get();
3288+
3289+
Export* export_ = importedInstance->wasm.getExportOrNull(importable.base);
3290+
3291+
if (!export_) {
3292+
trap((std::stringstream()
3293+
<< "Export " << importable.base << " doesn't exist.")
3294+
.str());
3295+
}
3296+
if (export_->kind != kind) {
3297+
trap("Exported kind doesn't match");
3298+
}
3299+
}
3300+
3301+
// Trap if types don't match between all imports and their corresponding
3302+
// exports. Imported memories and tables must also be a subtype of their
3303+
// export.
3304+
void validateImports() {
3305+
ModuleUtils::iterImportable(
3306+
wasm,
3307+
[this](ExternalKind kind,
3308+
std::variant<Function*, Memory*, Tag*, Global*, Table*> import) {
3309+
Importable* importable = std::visit(
3310+
[](const auto& import) -> Importable* { return import; }, import);
3311+
3312+
// These two modules are injected implicitly to tests. We won't find any
3313+
// import information for them.
3314+
if ((importable->module == "spectest" &&
3315+
importable->base.startsWith("print")) ||
3316+
importable->module == "fuzzing-support") {
3317+
return;
3318+
}
3319+
3320+
validateImportKindMatches(kind, *importable);
3321+
3322+
SubType* importedInstance =
3323+
linkedInstances.at(importable->module).get();
3324+
Export* export_ =
3325+
importedInstance->wasm.getExportOrNull(importable->base);
3326+
3327+
if (auto** memory = std::get_if<Memory*>(&import)) {
3328+
Memory exportedMemory =
3329+
*importedInstance->wasm.getMemory(*export_->getInternalName());
3330+
exportedMemory.initial =
3331+
importedInstance->getMemorySize(*export_->getInternalName());
3332+
3333+
if (!exportedMemory.isSubType(**memory)) {
3334+
trap("Imported memory isn't compatible.");
3335+
}
3336+
}
3337+
3338+
if (auto** table = std::get_if<Table*>(&import)) {
3339+
Table* exportedTable =
3340+
importedInstance->wasm.getTable(*export_->getInternalName());
3341+
if (!(*table)->isSubType(*exportedTable)) {
3342+
trap("Imported table isn't compatible");
3343+
}
3344+
}
3345+
});
3346+
}
3347+
32723348
TableInstanceInfo getTableInstanceInfo(Name name) {
32733349
auto* table = wasm.getTable(name);
32743350
if (table->imported()) {
@@ -3326,12 +3402,16 @@ class ModuleRunnerBase : public ExpressionRunner<SubType> {
33263402
};
33273403

33283404
MemoryInstanceInfo getMemoryInstanceInfo(Name name) {
3329-
auto* memory = wasm.getMemory(name);
3330-
if (memory->imported()) {
3331-
auto& importedInstance = linkedInstances.at(memory->module);
3332-
auto* memoryExport = importedInstance->wasm.getExport(memory->base);
3333-
return importedInstance->getMemoryInstanceInfo(
3334-
*memoryExport->getInternalName());
3405+
auto* instance = self();
3406+
Export* memoryExport = nullptr;
3407+
for (auto* memory = instance->wasm.getMemory(name); memory->imported();
3408+
memory = instance->wasm.getMemory(*memoryExport->getInternalName())) {
3409+
instance = instance->linkedInstances.at(memory->module).get();
3410+
memoryExport = instance->wasm.getExport(memory->base);
3411+
}
3412+
3413+
if (memoryExport) {
3414+
return instance->getMemoryInstanceInfo(*memoryExport->getInternalName());
33353415
}
33363416

33373417
return MemoryInstanceInfo{self(), name};
@@ -3385,6 +3465,11 @@ class ModuleRunnerBase : public ExpressionRunner<SubType> {
33853465
}
33863466

33873467
Address getMemorySize(Name memory) {
3468+
auto info = getMemoryInstanceInfo(memory);
3469+
if (info.instance != self()) {
3470+
return info.instance->getMemorySize(info.name);
3471+
}
3472+
33883473
auto iter = memorySizes.find(memory);
33893474
if (iter == memorySizes.end()) {
33903475
externalInterface->trap("getMemorySize called on non-existing memory");
@@ -3397,7 +3482,7 @@ class ModuleRunnerBase : public ExpressionRunner<SubType> {
33973482
if (iter == memorySizes.end()) {
33983483
externalInterface->trap("setMemorySize called on non-existing memory");
33993484
}
3400-
memorySizes[memory] = size;
3485+
iter->second = size;
34013486
}
34023487

34033488
public:
@@ -4682,12 +4767,14 @@ class ModuleRunnerBase : public ExpressionRunner<SubType> {
46824767
}
46834768
Flow visitStackSwitch(StackSwitch* curr) { return Flow(NONCONSTANT_FLOW); }
46844769

4685-
void trap(const char* why) override {
4770+
void trap(std::string_view why) override {
46864771
// Traps break all current continuations - they will never be resumable.
46874772
self()->clearContinuationStore();
46884773
externalInterface->trap(why);
46894774
}
46904775

4776+
void trap(const char* why) override { trap(std::string_view(why)); }
4777+
46914778
void hostLimit(const char* why) override {
46924779
self()->clearContinuationStore();
46934780
externalInterface->hostLimit(why);

src/wasm.h

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2435,6 +2435,28 @@ class Table : public Importable {
24352435
initial = 0;
24362436
max = kMaxSize;
24372437
}
2438+
2439+
static bool isSubType(const Table& a, const Table& b) {
2440+
if (a.addressType != b.addressType) {
2441+
return false;
2442+
}
2443+
2444+
if (!Type::isSubType(a.type, b.type)) {
2445+
return false;
2446+
}
2447+
2448+
if (a.initial > b.initial) {
2449+
return false;
2450+
}
2451+
2452+
if (a.max < b.max) {
2453+
return false;
2454+
}
2455+
2456+
return true;
2457+
}
2458+
2459+
bool isSubType(const Table& other) { return Table::isSubType(*this, other); }
24382460
};
24392461

24402462
class DataSegment : public Named {
@@ -2470,6 +2492,15 @@ class Memory : public Importable {
24702492
shared = false;
24712493
addressType = Type::i32;
24722494
}
2495+
2496+
static bool isSubType(const Memory& a, const Memory& b) {
2497+
return a.shared == b.shared && a.addressType == b.addressType &&
2498+
a.initial >= b.initial && a.max <= b.max;
2499+
}
2500+
2501+
bool isSubType(const Memory& other) {
2502+
return Memory::isSubType(*this, other);
2503+
}
24732504
};
24742505

24752506
class Global : public Importable {

test/spec/exact-func-import.wast

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,27 @@
11
;; TODO: Use the upstream version from the custom descriptors proposal.
22

3-
(module
3+
(module definition
44
(type $f (func))
55
(import "" "" (func $1 (exact (type 0))))
6-
(import "" "" (func $2 (exact (type $f) (param) (result))))
7-
(import "" "" (func $3 (exact (type $f))))
8-
(import "" "" (func $4 (exact (type 1)))) ;; Implicitly defined next
9-
(import "" "" (func $5 (exact (param i32) (result i64))))
6+
;; (import "" "" (func $2 (exact (type $f) (param) (result)))) ;; TODO: parser support
7+
(import "" "" (func $2 (exact (type $f))))
8+
(import "" "" (func $3 (exact (type 1)))) ;; Implicitly defined next
9+
(import "" "" (func $4 (exact (param i32) (result i64))))
1010

11-
(func $6 (import "" "") (exact (type 0)))
12-
(func $7 (import "" "") (exact (type $f) (param) (result)))
13-
(func $8 (import "" "") (exact (type $f)))
14-
(func $9 (import "" "") (exact (type 2))) ;; Implicitly defined next
15-
(func $10 (import "" "") (exact (param i64) (result i32)))
11+
(func $5 (import "" "") (exact (type 0)))
12+
;; (func $6 (import "" "") (exact (type $f) (param) (result))) ;; TODO: parser support
13+
(func $6 (import "" "") (exact (type $f)))
14+
;; (func $7 (import "" "") (exact (type 2))) ;; Implicitly defined next
15+
;; (func $8 (import "" "") (exact (param i64) (result i32))) ;; TODO: parser support
1616

1717
(global (ref (exact $f)) (ref.func $1))
1818
(global (ref (exact $f)) (ref.func $2))
19-
(global (ref (exact $f)) (ref.func $3))
19+
(global (ref (exact 1)) (ref.func $3))
2020
(global (ref (exact 1)) (ref.func $4))
21-
(global (ref (exact 1)) (ref.func $5))
21+
(global (ref (exact $f)) (ref.func $5))
2222
(global (ref (exact $f)) (ref.func $6))
23-
(global (ref (exact $f)) (ref.func $7))
24-
(global (ref (exact $f)) (ref.func $8))
25-
(global (ref (exact 2)) (ref.func $9))
26-
(global (ref (exact 2)) (ref.func $10))
23+
;; (global (ref (exact 2)) (ref.func $7))
24+
;; (global (ref (exact 2)) (ref.func $8))
2725
)
2826

2927
;; References to inexact imports are not exact.
@@ -51,7 +49,7 @@
5149

5250
;; Inexact imports can still be referenced inexactly, though.
5351

54-
(module
52+
(module definition
5553
(type $f (func))
5654
(import "" "" (func $1 (type $f)))
5755
(global (ref $f) (ref.func $1))
@@ -70,7 +68,9 @@
7068
;; Import and re-export inexactly.
7169
(module $B
7270
(type $f (func))
73-
(func (export "f") (import "A" "f") (type $f))
71+
;; (func (import "A" "f") (export "f") (type $f))
72+
(func (import "A" "f") (type $f))
73+
(export "f" (func 0))
7474
)
7575
(register "B")
7676

@@ -220,7 +220,7 @@
220220
;; Test the binary format
221221

222222
;; Exact function imports use 0x20.
223-
(module binary
223+
(module definition binary
224224
"\00asm" "\01\00\00\00"
225225
"\01" ;; Type section id
226226
"\04" ;; Type section length
@@ -265,4 +265,4 @@
265265
"\0b" ;; End
266266
)
267267
"malformed export kind"
268-
)
268+
)

test/spec/ref_func.wast

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
;; TODO: use test/spec/testsuite/ref_func.wast once it's fixed.
2+
13
(module
24
(func (export "f") (param $x i32) (result i32) (local.get $x))
35
)
6+
(register "M")
47
(module
58
(func $f (import "M" "f") (param i32) (result i32))
69
(func $g (param $x i32) (result i32) (i32.add (local.get $x) (i32.const 1)))

test/spec/tags.wast

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
;; Test tags
22

3+
(module
4+
(tag (export "im0") (param i32))
5+
(tag (export "im1") (param i32 f32))
6+
)
7+
(register "env")
8+
39
(module
410
(tag $e-import (import "env" "im0") (param i32))
511
(import "env" "im1" (tag (param i32 f32)))

0 commit comments

Comments
 (0)