Skip to content

Commit 09175fa

Browse files
authored
Prepare to update spec test suite (#1909)
* Prepare to update spec test suite Some minor updates in preparation for WebAssembly/testsuite#110: * Don't limit the size of tables in the validator and instead defer such maximal validation to runtimes themselves. * Parse the `(pagesize N)` syntax on "inline memories" * Always parse table/memory limits as 64-bit integers * Stop matching spec test suite error messages This last point is something I've tried to hold off on doing for a long time but as the giant `error_matches` function continues to grow and become more unwieldy it has become less and less tenable to do this. Overall it doesn't seem too beneficial to keep maintaining this especially in the face of numerous proposals, so instead basically remove it entirely. * Rename test
1 parent 9cebc6a commit 09175fa

File tree

12 files changed

+53
-203
lines changed

12 files changed

+53
-203
lines changed

crates/wasmparser/src/validator/core.rs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -821,12 +821,6 @@ impl Module {
821821
}
822822

823823
self.check_limits(ty.initial, ty.maximum, offset)?;
824-
if ty.initial > MAX_WASM_TABLE_ENTRIES as u64 {
825-
return Err(BinaryReaderError::new(
826-
"minimum table size is out of bounds",
827-
offset,
828-
));
829-
}
830824

831825
if ty.shared {
832826
if !features.shared_everything_threads() {

crates/wast/src/core/memory.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ pub enum MemoryKind<'a> {
3838
is64: bool,
3939
/// The inline data specified for this memory
4040
data: Vec<DataVal<'a>>,
41+
/// Optional page size for this inline memory.
42+
page_size_log2: Option<u32>,
4143
},
4244
}
4345

@@ -68,6 +70,7 @@ impl<'a> Parse<'a> for Memory<'a> {
6870
} else {
6971
parser.parse::<Option<kw::i64>>()?.is_some()
7072
};
73+
let page_size_log2 = page_size(parser)?;
7174
let data = parser.parens(|parser| {
7275
parser.parse::<kw::data>()?;
7376
let mut data = Vec::new();
@@ -76,7 +79,11 @@ impl<'a> Parse<'a> for Memory<'a> {
7679
}
7780
Ok(data)
7881
})?;
79-
MemoryKind::Inline { data, is64 }
82+
MemoryKind::Inline {
83+
data,
84+
is64,
85+
page_size_log2,
86+
}
8087
} else if l.peek::<u32>()? || l.peek::<kw::i32>()? || l.peek::<kw::i64>()? {
8188
MemoryKind::Normal(parser.parse()?)
8289
} else {

crates/wast/src/core/resolve/deinline_import_export.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,11 @@ pub fn run(fields: &mut Vec<ModuleField>) {
4848
}
4949
// If data is defined inline insert an explicit `data` module
5050
// field here instead, switching this to a `Normal` memory.
51-
MemoryKind::Inline { is64, ref data } => {
51+
MemoryKind::Inline {
52+
is64,
53+
ref data,
54+
page_size_log2,
55+
} => {
5256
let len = data.iter().map(|l| l.len()).sum::<usize>() as u64;
5357
let pages = (len + default_page_size() - 1) / default_page_size();
5458
let kind = MemoryKind::Normal(MemoryType {
@@ -58,7 +62,7 @@ pub fn run(fields: &mut Vec<ModuleField>) {
5862
max: Some(pages),
5963
},
6064
shared: false,
61-
page_size_log2: None,
65+
page_size_log2,
6266
});
6367
let data = match mem::replace(&mut m.kind, kind) {
6468
MemoryKind::Inline { data, .. } => data,

crates/wast/src/core/types.rs

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -613,17 +613,9 @@ impl<'a> Parse<'a> for Limits {
613613
false
614614
};
615615

616-
let parse = || {
617-
if is64 {
618-
parser.parse::<u64>()
619-
} else {
620-
parser.parse::<u32>().map(|x| x.into())
621-
}
622-
};
623-
624-
let min = parse()?;
616+
let min = parser.parse()?;
625617
let max = if parser.peek::<u64>()? {
626-
Some(parse()?)
618+
Some(parser.parse()?)
627619
} else {
628620
None
629621
};
@@ -663,8 +655,9 @@ pub struct MemoryType {
663655
pub page_size_log2: Option<u32>,
664656
}
665657

666-
fn page_size(parser: Parser<'_>) -> Result<Option<u32>> {
667-
if parser.peek::<LParen>()? {
658+
/// Parse `(pagesize N)` or nothing.
659+
pub fn page_size(parser: Parser<'_>) -> Result<Option<u32>> {
660+
if parser.peek::<LParen>()? && parser.peek2::<kw::pagesize>()? {
668661
Ok(Some(parser.parens(|parser| {
669662
parser.parse::<kw::pagesize>()?;
670663
let span = parser.cur_span();

tests/local/table-big.wast

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
(module definition
2+
(table 1_000_000_000 funcref)
3+
)

tests/local/table-too-big.wast

Lines changed: 0 additions & 5 deletions
This file was deleted.

tests/roundtrip.rs

Lines changed: 11 additions & 165 deletions
Original file line numberDiff line numberDiff line change
@@ -697,173 +697,19 @@ fn error_matches(test: &Path, error: &str, message: &str) -> bool {
697697
if error.contains(message) {
698698
return true;
699699
}
700-
// we are in control over all tsets in `tests/local/*` so all the error
701-
// messages there should exactly match the `assert_invalid` or such. No need
702-
// for fuzzy matching on error messages.
700+
701+
// we are in control over all tests in `tests/local/*` so all the error
702+
// messages there should exactly match the `assert_invalid` or such
703703
if test.starts_with("tests/local") {
704704
return false;
705705
}
706706

707-
if message == "unknown operator"
708-
|| message == "unexpected token"
709-
|| message == "wrong number of lane literals"
710-
|| message == "type mismatch"
711-
|| message == "malformed lane index"
712-
|| message == "expected i8 literal"
713-
|| message == "invalid lane length"
714-
|| message == "unclosed annotation"
715-
|| message == "malformed annotation id"
716-
|| message == "alignment must be a power of two"
717-
|| message == "i32 constant out of range"
718-
|| message == "constant expression required"
719-
|| message == "legacy exceptions support is not enabled"
720-
{
721-
return error.contains("expected ")
722-
|| error.contains("constant out of range")
723-
|| error.contains("extra tokens remaining")
724-
|| error.contains("unimplemented validation of deprecated opcode")
725-
|| error.contains("legacy exceptions support is not enabled");
726-
}
727-
728-
if message == "illegal character" {
729-
return error.contains("unexpected character");
730-
}
731-
732-
if message == "unclosed string" {
733-
return error.contains("unexpected end-of-file");
734-
}
735-
736-
if message == "duplicate identifier" {
737-
return error.contains("duplicate") && error.contains("identifier");
738-
}
739-
740-
// wasmparser differentiates these cases, the spec interpreter apparently
741-
// doesn't
742-
if message == "function and code section have inconsistent lengths" {
743-
return error.contains("code section without function section");
744-
}
745-
746-
// This test in binary.wast uses a section id implemented by other
747-
// proposals, so it's valid from wasmparser's point of view
748-
if message == "malformed section id" {
749-
return error.contains("unexpected end-of-file");
750-
}
751-
752-
// The spec interpreter will apparently read beyond the limits of a section
753-
// as defined by its size to parse a function, wasmparser doesn't do that.
754-
// That means that the error message here is legitimately different.
755-
if message == "section size mismatch" {
756-
return error.contains("control frames remain at end of function");
757-
}
758-
759-
if message == "malformed import kind" {
760-
return error.contains("invalid leading byte")
761-
// wasmparser understands more import kinds than the default spec
762-
// interpreter
763-
|| error.contains("unexpected end-of-file");
764-
}
765-
766-
if message == "integer representation too long" {
767-
// wasmparser implements more features than the default spec
768-
// interpreter, so these error looks different.
769-
return error.contains("invalid memory limits flags")
770-
|| error.contains("invalid table resizable limits flags")
771-
// different error message for types
772-
|| error.contains("invalid leading byte")
773-
// the spec interpreter will read past section boundaries when
774-
// decoding, wasmparser won't, producing different errors.
775-
|| error.contains("unexpected end-of-file")
776-
|| error.contains("malformed section id");
777-
}
778-
779-
if message == "integer too large" {
780-
// wasmparser implements more features than the default spec
781-
// interpreter, so these error looks different.
782-
return error.contains("threads must be enabled for shared memories")
783-
|| error.contains("shared tables require the shared-everything-threads proposal")
784-
|| error.contains("invalid table resizable limits flags")
785-
// honestly this feels like the spec interpreter is just weird
786-
|| error.contains("unexpected end-of-file")
787-
// This mostly comes from the memory64/binary-leb128.wast test file
788-
// which I think is largely busted as it looks like a bunch of lebs
789-
// were inflated to a larger size while not updating the binary
790-
// encoding of the size of the section.
791-
|| error.contains("invalid var_u32: integer representation too long")
792-
|| error.contains("malformed section id");
793-
}
794-
795-
// wasmparser blames a truncated file here, the spec interpreter blames the
796-
// section counts/lengths.
797-
if message == "length out of bounds" || message == "unexpected end of section or function" {
798-
return error.contains("unexpected end-of-file")
799-
|| error.contains("control frames remain at end of function");
800-
}
801-
802-
// binary.wast includes a test in which a 0b (End) is eaten by a botched
803-
// br_table. The test assumes that the parser (not the validator) errors on
804-
// a missing End before failing to validate the botched instruction. However
805-
// wasmparser fails to validate the botched instruction first
806-
if message == "unexpected end" {
807-
return error.contains("type index out of bounds") || error.contains("END opcode expected");
808-
}
809-
810-
if message == "unexpected content after last section" {
811-
return error.contains("section out of order")
812-
|| error.contains("function and code section have inconsistent lengths")
813-
|| error.contains("type index out of bounds");
814-
}
815-
816-
if message == "malformed limits flags" {
817-
return error.contains("invalid memory limits flags")
818-
|| error.contains("invalid table resizable limits flags")
819-
// These tests need to be updated for the new limits flags in the
820-
// custom-page-sizes-proposal.
821-
|| error.contains("unexpected end-of-file");
822-
}
823-
824-
if message == "malformed memop flags" {
825-
return error.contains("malformed memop alignment");
826-
}
827-
828-
// Our error for these tests is happening as a parser error of
829-
// the text file, not a validation error of the binary.
830-
if message == "memory size must be at most 65536 pages (4GiB)" {
831-
return error.contains("invalid u32 number: constant out of range");
832-
}
833-
834-
if message == "illegal opcode" {
835-
// The test suite contains a test with a global section where the
836-
// init expression is truncated and doesn't have an "end"
837-
// instruction. That's reported with wasmparser as end-of-file
838-
// because the end of the section was reached while the spec
839-
// interpreter says "illegal opcode".
840-
return error.contains("unexpected end-of-file");
841-
}
842-
if message == "unknown global" {
843-
return error.contains("global.get of locally defined global");
844-
}
845-
846-
if message == "immutable global" {
847-
return error.contains("global is immutable");
848-
}
849-
850-
if message.starts_with("unknown operator") {
851-
return error.starts_with("unknown operator") || error.starts_with("unexpected token");
852-
}
853-
854-
if message.starts_with("type mismatch") {
855-
return error.starts_with("type mismatch");
856-
}
857-
858-
if message == "table size must be at most 2^32-1" {
859-
return error.contains("invalid u32 number: constant out of range");
860-
}
861-
862-
// WebAssembly/annotations#25 - the spec interpreter's lexer is different
863-
// than ours which produces a different error message.
864-
if message == "empty identifier" || message == "empty annotation id" {
865-
return error.contains("invalid character in string");
866-
}
867-
868-
return false;
707+
// Historically wasm-tools tried to match the upstream error message. This
708+
// generally led to a large sequence of matches here which is not easy to
709+
// maintain and is particularly difficult when test suites and proposals
710+
// conflict with each other (e.g. one asserts one error message and another
711+
// asserts a different error message). Overall we didn't benefit a whole lot
712+
// from trying to match errors so just assume the error is roughly the same
713+
// and otherwise don't try to match it.
714+
true
869715
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"source_filename": "tests/local/table-big.wast",
3+
"commands": [
4+
{
5+
"type": "module_definition",
6+
"line": 1,
7+
"filename": "table-big.0.wasm",
8+
"module_type": "binary"
9+
}
10+
]
11+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
(module
2+
(table (;0;) 1000000000 funcref)
3+
)

tests/snapshots/local/table-too-big.wast.json

Lines changed: 0 additions & 12 deletions
This file was deleted.

0 commit comments

Comments
 (0)