diff --git a/design/mvp/Binary.md b/design/mvp/Binary.md index f8aa2772..6e5a602a 100644 --- a/design/mvp/Binary.md +++ b/design/mvp/Binary.md @@ -287,6 +287,10 @@ Notes: `none` case of an optional immediate.) * 🔧 for fixed-sized lists the length of the list must be larger than 0 to pass validation. +* Validation of each `defvaltype` requires every resolved AST node (after + despecialization) to satisfy the `elem_size` bound in + [Element Size](CanonicalABI.md#element-size). This is a static validation + error, not a runtime trap. ## Canonical Definitions diff --git a/design/mvp/CanonicalABI.md b/design/mvp/CanonicalABI.md index 621296b5..3201f5a4 100644 --- a/design/mvp/CanonicalABI.md +++ b/design/mvp/CanonicalABI.md @@ -2361,6 +2361,17 @@ def elem_size_flags(labels): return 4 ``` +Validation of each `defvaltype` requires that every node `u` in the resolved +structural AST — after `despecialize`, including implicit nodes such as `map`'s +key/value record — satisfies `elem_size(u, ptr_type) ≤ 2^28−1` for both +`ptr_type ∈ {i32, i64}`. This is a static validation error, not a runtime trap. +The bound is the same number as `MAX_LIST_BYTE_LENGTH` / `MAX_STRING_BYTE_LENGTH`. +`elem_size` is unbounded-integer math; fixed-width implementations must reject +overflow rather than wrap. Checking only the root type is not enough: +`map>` has a small list header but an oversized pair record. +Incremental or memoized checking is allowed only if every resolved node of that +definition is still covered. + ## Loading The `load` function defines how to read a value of a given value type `t` @@ -3594,6 +3605,9 @@ performed for a component. These are defined as: * `lift(T)` * requires `realloc` if `T` contains a `list` or `string` +Value types used by `lift`/`lower` are already rejected at `defvaltype` +definition if they exceed the [Element Size](#element-size) bound, so lift and +lower may assume static layouts fit. ### `canon lift` diff --git a/design/mvp/Explainer.md b/design/mvp/Explainer.md index 28d51969..49149368 100644 --- a/design/mvp/Explainer.md +++ b/design/mvp/Explainer.md @@ -1040,6 +1040,15 @@ is a central part of validation and, e.g., occurs when validating that the `with` arguments of an [`instantiate`](#instance-definitions) expression are type-compatible with the `import`s of the component being instantiated. +##### Maximum static value type size + +Each `defvaltype` is rejected unless every node of its resolved structural AST +(after despecialization) has `elem_size` at most `2^28 − 1` for both `i32` and +`i64` pointer types. Checking only the root is not enough: wrappers such as +`map`, `option`, `stream`, and `future` can have a small header while a nested +node exceeds the bound. This is a static validation error. See +[Element Size](CanonicalABI.md#element-size). + To incrementally describe how type-checking works, we'll start by asking how *type equality* works for non-resource, non-handle, local type definitions and build up from there. diff --git a/test/nyi.txt b/test/nyi.txt index ffb2d182..8f3e4250 100644 --- a/test/nyi.txt +++ b/test/nyi.txt @@ -1,4 +1,5 @@ # See README.md +./validation/max-value-size.wast ./async/during-sync-call-may-block-if-other-ready-threads.wast ./async/during-sync-call-no-exclusive-resume.wast ./async/during-sync-call-no-sibling-resume.wast diff --git a/test/validation/max-value-size.wast b/test/validation/max-value-size.wast new file mode 100644 index 00000000..4b307d6e --- /dev/null +++ b/test/validation/max-value-size.wast @@ -0,0 +1,92 @@ +;; Validation rejects defvaltypes whose resolved structural AST exceeds +;; 2^28 - 1 bytes. See CanonicalABI.md#element-size. + +;; valid boundaries + +(component + (type (list u8 268435455)) +) + +(component + (type (list u64 33554431)) +) + +(component + (type (list string 16777215)) +) + +;; valid map despecialization (pair record is well under the bound) + +(component + (type (map u8 (list u8 4))) +) + +;; single fixed list just over the limit + +(assert_invalid + (component (type (list u8 268435456))) + "exceeds maximum byte size") + +;; fixed list whose product exceeds MAX + +(assert_invalid + (component (type (list u64 33554432))) + "exceeds maximum byte size") + +;; u32 wrap class: real byte size is 2^32 but naive u32 multiply wraps to 0 + +(assert_invalid + (component (type (list u64 536870912))) + "exceeds maximum byte size") + +;; compound sum exceeds MAX + +(assert_invalid + (component + (type (tuple (list u8 268435455) (list u8 1)))) + "exceeds maximum byte size") + +(assert_invalid + (component + (type (record + (field "a" (list u8 134217728)) + (field "b" (list u8 134217728))))) + "exceeds maximum byte size") + +;; map despecialization: inner list valid alone, pair record is MAX + 1 + +(assert_invalid + (component (type (map u8 (list u8 268435455)))) + "exceeds maximum byte size") + +;; nested fixed list + +(assert_invalid + (component (type (list (list u8 268435455) 2))) + "exceeds maximum byte size") + +;; pointer-width-sensitive rejection (passes i32, fails i64) + +(assert_invalid + (component (type (list string 16777216))) + "exceeds maximum byte size") + +;; nested map despecialization inside option and record + +(assert_invalid + (component (type (option (map u8 (list u8 268435455))))) + "exceeds maximum byte size") + +(assert_invalid + (component (type (record (field "m" (map u8 (list u8 268435455)))))) + "exceeds maximum byte size") + +;; stream/future handle size is 4; payload must still be checked + +(assert_invalid + (component (type (stream (list u8 268435455)))) + "exceeds maximum byte size") + +(assert_invalid + (component (type (future (list u8 268435455)))) + "exceeds maximum byte size")