From 60a2fc4d532cc03a1dfb360035175c49dae93b8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Mon, 25 Mar 2024 17:37:22 +0100 Subject: [PATCH] wasm-merge: check that the types of imports and exports match. --- src/tools/wasm-merge.cpp | 107 +++++++++++++++++++++++++++++++++++++++ test/lit/merge/types.wat | 83 ++++++++++++++++++++++++++++++ 2 files changed, 190 insertions(+) create mode 100644 test/lit/merge/types.wat diff --git a/src/tools/wasm-merge.cpp b/src/tools/wasm-merge.cpp index ec67d56fc5a..9d6b42d2c9f 100644 --- a/src/tools/wasm-merge.cpp +++ b/src/tools/wasm-merge.cpp @@ -381,6 +381,37 @@ void copyModuleContents(Module& input, Name inputName) { // TODO: type names, features, debug info, custom sections, dylink info, etc. } +void reportTypeMismatch(bool& valid, const char* kind, Importable* import) { + valid = false; + std::cerr << "Type mismatch when importing " << kind << " " << import->base + << " from module " << import->module << ": "; +} + +// Check that the export and import limits match. +template +void checkLimit(bool& valid, const char* kind, T* export_, T* import) { + if (export_->initial < import->initial) { + reportTypeMismatch(valid, kind, import); + std::cerr << "minimal size " << export_->initial + << " is smaller than expected minimal size " << import->initial + << ".\n"; + } + if (import->hasMax()) { + if (!export_->hasMax()) { + reportTypeMismatch(valid, kind, import); + std::cerr << "expecting a bounded " << kind + << " but the " + "imported " + << kind << " is unbounded.\n"; + } else if (export_->max > import->max) { + reportTypeMismatch(valid, kind, import); + std::cerr << "maximal size " << export_->max + << " is larger than expected maximal size " << import->max + << ".\n"; + } + } +} + // Find pairs of matching imports and exports, and make uses of the import refer // to the exported item (which has been merged into the module). void fuseImportsAndExports() { @@ -428,6 +459,82 @@ void fuseImportsAndExports() { } }); + // Make sure that the export types match the import types. + bool valid = true; + ModuleUtils::iterImportedFunctions(merged, [&](Function* import) { + auto internalName = kindModuleExportMaps[ExternalKind::Function] + [import->module][import->base]; + if (internalName.is()) { + auto* export_ = merged.getFunction(internalName); + if (!HeapType::isSubType(export_->type, import->type)) { + reportTypeMismatch(valid, "function", import); + std::cerr << "type " << export_->type << " is not a subtype of " + << import->type << ".\n"; + } + } + }); + ModuleUtils::iterImportedTables(merged, [&](Table* import) { + auto internalName = + kindModuleExportMaps[ExternalKind::Table][import->module][import->base]; + if (internalName.is()) { + auto* export_ = merged.getTable(internalName); + checkLimit(valid, "table", export_, import); + if (export_->type != import->type) { + reportTypeMismatch(valid, "table", import); + std::cerr << "export type " << export_->type + << " is different from import type " << import->type << ".\n"; + } + } + }); + ModuleUtils::iterImportedMemories(merged, [&](Memory* import) { + auto internalName = + kindModuleExportMaps[ExternalKind::Memory][import->module][import->base]; + if (internalName.is()) { + auto* export_ = merged.getMemory(internalName); + if (export_->is64() != import->is64()) { + reportTypeMismatch(valid, "memory", import); + std::cerr << "index type should match.\n"; + } + checkLimit(valid, "memory", export_, import); + } + }); + ModuleUtils::iterImportedGlobals(merged, [&](Global* import) { + auto internalName = + kindModuleExportMaps[ExternalKind::Global][import->module][import->base]; + if (internalName.is()) { + auto* export_ = merged.getGlobal(internalName); + if (export_->mutable_ != import->mutable_) { + reportTypeMismatch(valid, "global", import); + std::cerr << "mutability should match.\n"; + } + if (export_->mutable_ && export_->type != import->type) { + reportTypeMismatch(valid, "global", import); + std::cerr << "export type " << export_->type + << " is different from import type " << import->type << ".\n"; + } + if (!export_->mutable_ && !Type::isSubType(export_->type, import->type)) { + reportTypeMismatch(valid, "global", import); + std::cerr << "type " << export_->type << " is not a subtype of " + << import->type << ".\n"; + } + } + }); + ModuleUtils::iterImportedTags(merged, [&](Tag* import) { + auto internalName = + kindModuleExportMaps[ExternalKind::Tag][import->module][import->base]; + if (internalName.is()) { + auto* export_ = merged.getTag(internalName); + if (HeapType(export_->sig) != HeapType(import->sig)) { + reportTypeMismatch(valid, "tag", import); + std::cerr << "export type " << export_->sig + << " is different from import type " << import->sig << ".\n"; + } + } + }); + if (!valid) { + Fatal() << "import/export mismatches"; + } + // Update the things we found. updateNames(merged, kindNameUpdates); } diff --git a/test/lit/merge/types.wat b/test/lit/merge/types.wat new file mode 100644 index 00000000000..77a43020824 --- /dev/null +++ b/test/lit/merge/types.wat @@ -0,0 +1,83 @@ +;; RUN: not wasm-merge %s env -all 2>&1 | filecheck %s + +;; Test of exports / imports type matching + +;; CHECK: Type mismatch when importing function f1 from module env: type (type $func.0 (func)) is not a subtype of (type $func.0 (func (param (ref eq)))). +;; CHECK-NEXT: Type mismatch when importing function f3 from module env: type (type $func.0 (sub (func (result anyref)))) is not a subtype of (type $func.0 (sub $func.1 (func (result eqref)))). +;; CHECK-NEXT: Type mismatch when importing table t1 from module env: minimal size 10 is smaller than expected minimal size 11. +;; CHECK-NEXT: Type mismatch when importing table t1 from module env: maximal size 100 is larger than expected maximal size 99. +;; CHECK-NEXT: Type mismatch when importing table t2 from module env: expecting a bounded table but the imported table is unbounded. +;; CHECK-NEXT: Type mismatch when importing table t3 from module env: export type anyref is different from import type funcref. +;; CHECK-NEXT: Type mismatch when importing memory m1 from module env: minimal size 10 is smaller than expected minimal size 11. +;; CHECK-NEXT: Type mismatch when importing memory m1 from module env: maximal size 100 is larger than expected maximal size 99. +;; CHECK-NEXT: Type mismatch when importing memory m2 from module env: expecting a bounded memory but the imported memory is unbounded. +;; CHECK-NEXT: Type mismatch when importing memory m3 from module env: index type should match. +;; CHECK-NEXT: Type mismatch when importing global g1 from module env: mutability should match. +;; CHECK-NEXT: Type mismatch when importing global g2 from module env: mutability should match. +;; CHECK-NEXT: Type mismatch when importing global g2 from module env: export type eqref is different from import type i31ref. +;; CHECK-NEXT: Type mismatch when importing global g2 from module env: export type eqref is different from import type anyref. +;; CHECK-NEXT: Type mismatch when importing global g1 from module env: type eqref is not a subtype of i31ref. +;; CHECK-NEXT: Type mismatch when importing tag t from module env: export type (func (param eqref)) is different from import type (func (param anyref)). +;; CHECK-NEXT: Type mismatch when importing tag t from module env: export type (func (param eqref)) is different from import type (func (param i31ref)). +;; CHECK-NEXT: Fatal: import/export mismatches + +(module + (type $f (sub (func (result anyref)))) + (type $g (sub $f (func (result eqref)))) + + (func (export "f1")) + (func (export "f2") (type $g) (ref.null eq)) + (func (export "f3") (type $f) (ref.null eq)) + + (import "env" "f1" (func)) + (import "env" "f2" (func (type $f))) + + (import "env" "f1" (func (param (ref eq)))) + (import "env" "f3" (func (type $g))) + + (table (export "t1") 10 100 funcref) + (table (export "t2") 10 funcref) + (table (export "t3") 10 anyref) + + (import "env" "t1" (table 10 funcref)) + (import "env" "t1" (table 10 100 funcref)) + (import "env" "t2" (table 10 funcref)) + (import "env" "t3" (table 10 anyref)) + + (import "env" "t1" (table 11 funcref)) + (import "env" "t1" (table 10 99 funcref)) + (import "env" "t2" (table 10 100 funcref)) + (import "env" "t3" (table 10 funcref)) + + (memory (export "m1") 10 100) + (memory (export "m2") 10) + (memory (export "m3") i64 10) + + (import "env" "m1" (memory 10)) + (import "env" "m1" (memory 10 100)) + (import "env" "m2" (memory 10)) + (import "env" "m3" (memory i64 10)) + + (import "env" "m1" (memory 11)) + (import "env" "m1" (memory 10 99)) + (import "env" "m2" (memory 10 100)) + (import "env" "m3" (memory 10)) + + (global (export "g1") eqref (ref.null eq)) + (global (export "g2") (mut eqref) (ref.null eq)) + + (import "env" "g1" (global anyref)) + (import "env" "g2" (global (mut eqref))) + + (import "env" "g1" (global (mut eqref))) + (import "env" "g2" (global eqref)) + (import "env" "g2" (global (mut i31ref))) + (import "env" "g2" (global (mut anyref))) + (import "env" "g1" (global i31ref)) + + (tag (export "t") (param eqref)) + (import "env" "t" (tag (param eqref))) + + (import "env" "t" (tag (param anyref))) + (import "env" "t" (tag (param i31ref))) +)