wasm_validate (wasm v0.1.0)
View SourceModule-level validation. This is what decides whether you get {error, #{class := invalid}} back, and everything it proves is what the interpreter is then
allowed to assume.
It runs as a separate pass over the decoded AST, before any IR is built and never during execution. That separation is the whole point: once a module validates, the interpreter can assume every index is in range, every operand has the type it expects, and every branch target exists. Anything the interpreter would otherwise have to re-check on the hot path is checked exactly once here.
Function bodies are checked by wasm_validate_code. This module owns the
surrounding structure: index spaces, limits, imports, exports, segments,
start function, and constant expressions.
Summary
Types
-type annotated() :: {Height :: non_neg_integer(), instr()} | {Height :: non_neg_integer(), Base :: non_neg_integer(), instr()}.
-type externtype() :: {func, typeidx()} | {table, #tabletype{limits :: #limits{min :: non_neg_integer(), max :: undefined | non_neg_integer(), shared :: boolean(), index_type :: i32 | i64}, elemtype :: reftype(), init :: undefined | [instr()]}} | {mem, #memtype{limits :: #limits{min :: non_neg_integer(), max :: undefined | non_neg_integer(), shared :: boolean(), index_type :: i32 | i64}}} | {global, #globaltype{valtype :: valtype(), mut :: mut()}} | {tag, #tagtype{type :: typeidx()}}.
-type funcidx() :: non_neg_integer().
-type heaptype() :: func | extern | exn | any | eq | i31 | struct | array | nofunc | noextern | noexn | none | {type, typeidx()}.
-type memidx() :: non_neg_integer().
-type mut() :: const | var.
-type numtype() :: i32 | i64 | f32 | f64.
-type reftype() :: {ref, null | nonull, heaptype()}.
-type tableidx() :: non_neg_integer().
-type typeidx() :: non_neg_integer().
-type vectype() :: v128.
Functions
-spec cached_context(#module{identity :: undefined | {sha256, binary()} | reference(), types :: [#subtype{final :: boolean(), supers :: [typeidx()], body :: #functype{params :: [valtype()], results :: [valtype()]} | #structtype{fields :: [#fieldtype{type :: valtype() | i8 | i16, mut :: mut()}]} | #arraytype{field :: #fieldtype{type :: valtype() | i8 | i16, mut :: mut()}}}], rec_groups :: [{non_neg_integer(), non_neg_integer()}], imports :: [#import{module :: binary(), name :: binary(), desc :: externtype()}], funcs :: [#func{type :: typeidx(), locals :: [valtype()], body :: [instr()] | {validated, [annotated()]}}], tables :: [#tabletype{limits :: #limits{min :: non_neg_integer(), max :: undefined | non_neg_integer(), shared :: boolean(), index_type :: i32 | i64}, elemtype :: reftype(), init :: undefined | [instr()]}], mems :: [#memtype{limits :: #limits{min :: non_neg_integer(), max :: undefined | non_neg_integer(), shared :: boolean(), index_type :: i32 | i64}}], tags :: [#tagtype{type :: typeidx()}], globals :: [#global{type :: #globaltype{valtype :: valtype(), mut :: mut()}, init :: [instr()]}], exports :: [#export{name :: binary(), desc :: {func | table | mem | global | tag, non_neg_integer()}}], start :: undefined | funcidx(), elems :: [#elem{type :: reftype(), init :: [[instr()]], mode :: passive | declarative | {active, tableidx(), [instr()]}}], datas :: [#data{init :: binary(), mode :: passive | {active, memidx(), [instr()]}}], data_count :: undefined | non_neg_integer(), customs :: [{binary(), binary()}]}) -> #ctx{types :: tuple(), canon :: tuple(), fields :: tuple(), kinds :: tuple(), supers :: tuple(), funcs :: tuple(), tables :: tuple(), mems :: tuple(), globals :: tuple(), tags :: tuple(), elems :: tuple(), n_datas :: non_neg_integer(), n_imported_globals :: non_neg_integer(), refs :: #{non_neg_integer() => true}, shared_globals :: #{non_neg_integer() => true}, shared_mems :: #{non_neg_integer() => true}, locals :: tuple(), results :: [valtype()]}.
As context/1, memoised per module in the calling process.
Instantiation rebuilds the context each time, and canonicalisation interns every recursive type group. A module is immutable, so the answer cannot change; recomputing it per instance cost a third of instantiation time.
-spec context(#module{identity :: undefined | {sha256, binary()} | reference(), types :: [#subtype{final :: boolean(), supers :: [typeidx()], body :: #functype{params :: [valtype()], results :: [valtype()]} | #structtype{fields :: [#fieldtype{type :: valtype() | i8 | i16, mut :: mut()}]} | #arraytype{field :: #fieldtype{type :: valtype() | i8 | i16, mut :: mut()}}}], rec_groups :: [{non_neg_integer(), non_neg_integer()}], imports :: [#import{module :: binary(), name :: binary(), desc :: externtype()}], funcs :: [#func{type :: typeidx(), locals :: [valtype()], body :: [instr()] | {validated, [annotated()]}}], tables :: [#tabletype{limits :: #limits{min :: non_neg_integer(), max :: undefined | non_neg_integer(), shared :: boolean(), index_type :: i32 | i64}, elemtype :: reftype(), init :: undefined | [instr()]}], mems :: [#memtype{limits :: #limits{min :: non_neg_integer(), max :: undefined | non_neg_integer(), shared :: boolean(), index_type :: i32 | i64}}], tags :: [#tagtype{type :: typeidx()}], globals :: [#global{type :: #globaltype{valtype :: valtype(), mut :: mut()}, init :: [instr()]}], exports :: [#export{name :: binary(), desc :: {func | table | mem | global | tag, non_neg_integer()}}], start :: undefined | funcidx(), elems :: [#elem{type :: reftype(), init :: [[instr()]], mode :: passive | declarative | {active, tableidx(), [instr()]}}], datas :: [#data{init :: binary(), mode :: passive | {active, memidx(), [instr()]}}], data_count :: undefined | non_neg_integer(), customs :: [{binary(), binary()}]}) -> #ctx{types :: tuple(), canon :: tuple(), fields :: tuple(), kinds :: tuple(), supers :: tuple(), funcs :: tuple(), tables :: tuple(), mems :: tuple(), globals :: tuple(), tags :: tuple(), elems :: tuple(), n_datas :: non_neg_integer(), n_imported_globals :: non_neg_integer(), refs :: #{non_neg_integer() => true}, shared_globals :: #{non_neg_integer() => true}, shared_mems :: #{non_neg_integer() => true}, locals :: tuple(), results :: [valtype()]}.
-spec module(#module{identity :: undefined | {sha256, binary()} | reference(), types :: [#subtype{final :: boolean(), supers :: [typeidx()], body :: #functype{params :: [valtype()], results :: [valtype()]} | #structtype{fields :: [#fieldtype{type :: valtype() | i8 | i16, mut :: mut()}]} | #arraytype{field :: #fieldtype{type :: valtype() | i8 | i16, mut :: mut()}}}], rec_groups :: [{non_neg_integer(), non_neg_integer()}], imports :: [#import{module :: binary(), name :: binary(), desc :: externtype()}], funcs :: [#func{type :: typeidx(), locals :: [valtype()], body :: [instr()] | {validated, [annotated()]}}], tables :: [#tabletype{limits :: #limits{min :: non_neg_integer(), max :: undefined | non_neg_integer(), shared :: boolean(), index_type :: i32 | i64}, elemtype :: reftype(), init :: undefined | [instr()]}], mems :: [#memtype{limits :: #limits{min :: non_neg_integer(), max :: undefined | non_neg_integer(), shared :: boolean(), index_type :: i32 | i64}}], tags :: [#tagtype{type :: typeidx()}], globals :: [#global{type :: #globaltype{valtype :: valtype(), mut :: mut()}, init :: [instr()]}], exports :: [#export{name :: binary(), desc :: {func | table | mem | global | tag, non_neg_integer()}}], start :: undefined | funcidx(), elems :: [#elem{type :: reftype(), init :: [[instr()]], mode :: passive | declarative | {active, tableidx(), [instr()]}}], datas :: [#data{init :: binary(), mode :: passive | {active, memidx(), [instr()]}}], data_count :: undefined | non_neg_integer(), customs :: [{binary(), binary()}]}) -> {ok, #module{identity :: undefined | {sha256, binary()} | reference(), types :: [#subtype{final :: boolean(), supers :: [typeidx()], body :: #functype{params :: [valtype()], results :: [valtype()]} | #structtype{fields :: [#fieldtype{type :: valtype() | i8 | i16, mut :: mut()}]} | #arraytype{field :: #fieldtype{type :: valtype() | i8 | i16, mut :: mut()}}}], rec_groups :: [{non_neg_integer(), non_neg_integer()}], imports :: [#import{module :: binary(), name :: binary(), desc :: externtype()}], funcs :: [#func{type :: typeidx(), locals :: [valtype()], body :: [instr()] | {validated, [annotated()]}}], tables :: [#tabletype{limits :: #limits{min :: non_neg_integer(), max :: undefined | non_neg_integer(), shared :: boolean(), index_type :: i32 | i64}, elemtype :: reftype(), init :: undefined | [instr()]}], mems :: [#memtype{limits :: #limits{min :: non_neg_integer(), max :: undefined | non_neg_integer(), shared :: boolean(), index_type :: i32 | i64}}], tags :: [#tagtype{type :: typeidx()}], globals :: [#global{type :: #globaltype{valtype :: valtype(), mut :: mut()}, init :: [instr()]}], exports :: [#export{name :: binary(), desc :: {func | table | mem | global | tag, non_neg_integer()}}], start :: undefined | funcidx(), elems :: [#elem{type :: reftype(), init :: [[instr()]], mode :: passive | declarative | {active, tableidx(), [instr()]}}], datas :: [#data{init :: binary(), mode :: passive | {active, memidx(), [instr()]}}], data_count :: undefined | non_neg_integer(), customs :: [{binary(), binary()}]}} | {error, wasm_error:error()}.
-spec module_unchecked(#module{identity :: undefined | {sha256, binary()} | reference(), types :: [#subtype{final :: boolean(), supers :: [typeidx()], body :: #functype{params :: [valtype()], results :: [valtype()]} | #structtype{fields :: [#fieldtype{type :: valtype() | i8 | i16, mut :: mut()}]} | #arraytype{field :: #fieldtype{type :: valtype() | i8 | i16, mut :: mut()}}}], rec_groups :: [{non_neg_integer(), non_neg_integer()}], imports :: [#import{module :: binary(), name :: binary(), desc :: externtype()}], funcs :: [#func{type :: typeidx(), locals :: [valtype()], body :: [instr()] | {validated, [annotated()]}}], tables :: [#tabletype{limits :: #limits{min :: non_neg_integer(), max :: undefined | non_neg_integer(), shared :: boolean(), index_type :: i32 | i64}, elemtype :: reftype(), init :: undefined | [instr()]}], mems :: [#memtype{limits :: #limits{min :: non_neg_integer(), max :: undefined | non_neg_integer(), shared :: boolean(), index_type :: i32 | i64}}], tags :: [#tagtype{type :: typeidx()}], globals :: [#global{type :: #globaltype{valtype :: valtype(), mut :: mut()}, init :: [instr()]}], exports :: [#export{name :: binary(), desc :: {func | table | mem | global | tag, non_neg_integer()}}], start :: undefined | funcidx(), elems :: [#elem{type :: reftype(), init :: [[instr()]], mode :: passive | declarative | {active, tableidx(), [instr()]}}], datas :: [#data{init :: binary(), mode :: passive | {active, memidx(), [instr()]}}], data_count :: undefined | non_neg_integer(), customs :: [{binary(), binary()}]}) -> #module{identity :: undefined | {sha256, binary()} | reference(), types :: [#subtype{final :: boolean(), supers :: [typeidx()], body :: #functype{params :: [valtype()], results :: [valtype()]} | #structtype{fields :: [#fieldtype{type :: valtype() | i8 | i16, mut :: mut()}]} | #arraytype{field :: #fieldtype{type :: valtype() | i8 | i16, mut :: mut()}}}], rec_groups :: [{non_neg_integer(), non_neg_integer()}], imports :: [#import{module :: binary(), name :: binary(), desc :: externtype()}], funcs :: [#func{type :: typeidx(), locals :: [valtype()], body :: [instr()] | {validated, [annotated()]}}], tables :: [#tabletype{limits :: #limits{min :: non_neg_integer(), max :: undefined | non_neg_integer(), shared :: boolean(), index_type :: i32 | i64}, elemtype :: reftype(), init :: undefined | [instr()]}], mems :: [#memtype{limits :: #limits{min :: non_neg_integer(), max :: undefined | non_neg_integer(), shared :: boolean(), index_type :: i32 | i64}}], tags :: [#tagtype{type :: typeidx()}], globals :: [#global{type :: #globaltype{valtype :: valtype(), mut :: mut()}, init :: [instr()]}], exports :: [#export{name :: binary(), desc :: {func | table | mem | global | tag, non_neg_integer()}}], start :: undefined | funcidx(), elems :: [#elem{type :: reftype(), init :: [[instr()]], mode :: passive | declarative | {active, tableidx(), [instr()]}}], datas :: [#data{init :: binary(), mode :: passive | {active, memidx(), [instr()]}}], data_count :: undefined | non_neg_integer(), customs :: [{binary(), binary()}]}.