skip to content
==> src/validate.c <==
1/* Validation: the module's rules, then each function body's type check. */
2#include "wasm/validate.h"
3
4#include "wasm/instr.h"
5#include "wasm/reader.h"
6
7#include <stdlib.h>
8#include <string.h>
9
10const char *wasm_feature_name(wasm_features f) {
11    switch (f) {
12    case WASM_F_MUTABLE_GLOBAL: return "mutable global";
13    case WASM_F_SIGN_EXT: return "sign extension operations";
14    case WASM_F_SAT_FLOAT_TO_INT: return "saturating float to int conversions";
15    case WASM_F_MULTI_VALUE: return "multi-value";
16    case WASM_F_REFERENCE_TYPES: return "reference types";
17    case WASM_F_BULK_MEMORY: return "bulk memory";
18    case WASM_F_SIMD: return "SIMD";
19    case WASM_F_RELAXED_SIMD: return "relaxed SIMD";
20    case WASM_F_THREADS: return "threads";
21    case WASM_F_EXTENDED_CONST: return "extended constant expressions";
22    case WASM_F_MULTI_MEMORY: return "multi-memory";
23    case WASM_F_MEMORY64: return "memory64";
24    default: return "this proposal's";
25    }
26}
27
28/* ---- the module's rules ------------------------------------------------------------------- */
29
30typedef struct {
31    const wasm_module *m;
32    wasm_features f;
33    wasm_validate_report *rep;
34    wasm_reader r;     /* for messages: offsets are into m->bytes */
35    bool *declared;    /* functions a ref.func may name */
36} M;
37
38static bool need(M *v, wasm_features feat, size_t at) {
39    if (v->f & feat) return true;
40    return wasm_rd_fail_at(&v->r, at, "%s support is not enabled", wasm_feature_name(feat));
41}
42
43static bool valtype_ok(M *v, wasm_valtype t, size_t at) {
44    if (t == WASM_V128) return need(v, WASM_F_SIMD, at);
45    if (wasm_is_ref(t)) return need(v, WASM_F_REFERENCE_TYPES, at);
46    return true;
47}
48
49static size_t off(M *v, const void *p) { return (size_t)((const uint8_t *)p - v->m->bytes); }
50
51static bool types_ok(M *v) {
52    for (uint32_t i = 0; i < v->m->ntypes; i++) {
53        const wasm_functype *t = &v->m->types[i];
54        for (uint32_t k = 0; k < t->nparams; k++)
55            if (!valtype_ok(v, t->params[k], off(v, &t->params[k]))) return false;
56        for (uint32_t k = 0; k < t->nresults; k++)
57            if (!valtype_ok(v, t->results[k], off(v, &t->results[k]))) return false;
58        if (t->nresults > 1 && !need(v, WASM_F_MULTI_VALUE, off(v, t->results))) return false;
59    }
60    return true;
61}
62
63static bool mem_ok(M *v, const wasm_limits *l, size_t at) {
64    bool is64 = l->flags & WASM_LIMITS_64;
65    if (is64 && !need(v, WASM_F_MEMORY64, at)) return false;
66    if (l->flags & WASM_LIMITS_SHARED) {
67        if (!need(v, WASM_F_THREADS, at)) return false;
68        if (v->rep) v->rep->uses_threads = true;
69        if (!(l->flags & WASM_LIMITS_HAS_MAX)) return wasm_rd_fail_at(&v->r, at, "shared memory must have maximum size");
70    }
71    uint64_t pages = is64 ? (1ull << 48) : WASM_MAX_PAGES;
72    if (l->min > pages)
73        return wasm_rd_fail_at(&v->r, at, "memory size must be at most %llu pages (4GiB)", (unsigned long long)pages);
74    if (l->flags & WASM_LIMITS_HAS_MAX) {
75        if (l->max > pages)
76            return wasm_rd_fail_at(&v->r, at, "memory size must be at most %llu pages (4GiB)", (unsigned long long)pages);
77        if (l->max < l->min) return wasm_rd_fail_at(&v->r, at, "size minimum must not be greater than maximum");
78    }
79    return true;
80}
81
82static bool table_ok(M *v, const wasm_tabletype *t, size_t at) {
83    if (t->elem == WASM_EXTERNREF && !need(v, WASM_F_REFERENCE_TYPES, at)) return false;
84    if (t->limits.min > WASM_MAX_TABLE) return wasm_rd_fail_at(&v->r, at, "minimum table size is out of bounds");
85    if ((t->limits.flags & WASM_LIMITS_HAS_MAX) && t->limits.max < t->limits.min)
86        return wasm_rd_fail_at(&v->r, at, "size minimum must not be greater than maximum");
87    return true;
88}
89
90/* A constant expression of type want. Constant: the t.const ops, ref.null, ref.func, global.get
91 * of an imported immutable global (or, as wasmparser, any earlier immutable one), and with
92 * extended constant expressions i32/i64 add, sub and mul. */
93static bool const_ok(M *v, const wasm_expr *e, wasm_valtype want, uint32_t globals_visible) {
94    wasm_reader r = v->r;
95    r.p = e->start;
96    r.end = e->start + e->len;
97    wasm_valtype stack[64];
98    uint32_t sp = 0;
99    wasm_instr in;
100    for (;;) {
101        if (!wasm_rd_instr(&r, &in)) {
102            v->r = r;
103            return false;
104        }
105        if (in.op == WASM_OP_END) break;
106        wasm_valtype push = 0;
107        uint8_t pops = 0;
108        switch (in.op) {
109        case WASM_OP_I32_CONST: push = WASM_I32; break;
110        case WASM_OP_I64_CONST: push = WASM_I64; break;
111        case WASM_OP_F32_CONST: push = WASM_F32; break;
112        case WASM_OP_F64_CONST: push = WASM_F64; break;
113        case WASM_OP_REF_NULL:
114            if (!need(v, WASM_F_REFERENCE_TYPES, in.offset)) return false;
115            push = in.reftype;
116            break;
117        case WASM_OP_REF_FUNC:
118            if (!need(v, WASM_F_REFERENCE_TYPES, in.offset)) return false;
119            if (in.a >= v->m->nfuncs)
120                return wasm_rd_fail_at(&v->r, in.offset, "unknown function %u: function index out of bounds", in.a);
121            v->declared[in.a] = true;
122            push = WASM_FUNCREF;
123            break;
124        case WASM_OP_GLOBAL_GET:
125            if (in.a >= globals_visible || in.a >= v->m->nglobals)
126                return wasm_rd_fail_at(&v->r, in.offset, "unknown global %u: global index out of bounds", in.a);
127            if (v->m->globals[in.a].mut)
128                return wasm_rd_fail_at(&v->r, in.offset, "constant expression required: global.get of mutable global");
129            push = v->m->globals[in.a].type;
130            break;
131        case 0x6a: case 0x6b: case 0x6c: /* i32 add sub mul */
132            if (!need(v, WASM_F_EXTENDED_CONST, in.offset)) return false;
133            pops = 2;
134            push = WASM_I32;
135            break;
136        case 0x7c: case 0x7d: case 0x7e: /* i64 add sub mul */
137            if (!need(v, WASM_F_EXTENDED_CONST, in.offset)) return false;
138            pops = 2;
139            push = WASM_I64;
140            break;
141        default:
142            return wasm_rd_fail_at(&v->r, in.offset, "constant expression required: non-constant operator: %s", in.info.name);
143        }
144        for (uint8_t k = 0; k < pops; k++) {
145            if (sp == 0 || stack[sp - 1] != push)
146                return wasm_rd_fail_at(&v->r, in.offset, "type mismatch: expected %s", wasm_valtype_name(push));
147            sp--;
148        }
149        if (sp == sizeof stack / sizeof stack[0])
150            return wasm_rd_fail_at(&v->r, in.offset, "constant expression is too deep");
151        stack[sp++] = push;
152    }
153    if (sp != 1 || stack[0] != want)
154        return wasm_rd_fail_at(&v->r, e->offset, "type mismatch: expected %s", wasm_valtype_name(want));
155    return true;
156}
157
158static bool module_ok(M *v) {
159    const wasm_module *m = v->m;
160    if (!types_ok(v)) return false;
161    for (uint32_t i = 0; i < m->nimports; i++) {
162        const wasm_import *im = &m->imports[i];
163        size_t at = off(v, im->name);
164        switch (im->kind) {
165        case WASM_EXTERN_FUNC:
166            if (im->func_type >= m->ntypes) return wasm_rd_fail_at(&v->r, at, "unknown type: type index out of bounds");
167            break;
168        case WASM_EXTERN_TABLE:
169            if (!table_ok(v, &im->table, at)) return false;
170            break;
171        case WASM_EXTERN_MEMORY:
172            if (!mem_ok(v, &im->mem, at)) return false;
173            break;
174        case WASM_EXTERN_GLOBAL:
175            if (!valtype_ok(v, im->global.type, at)) return false;
176            if (im->global.mut && !need(v, WASM_F_MUTABLE_GLOBAL, at)) return false;
177            break;
178        }
179    }
180    for (uint32_t i = m->nfunc_imports; i < m->nfuncs; i++)
181        if (m->funcs[i] >= m->ntypes)
182            return wasm_rd_fail_at(&v->r, m->codes ? m->codes[i - m->nfunc_imports].offset : 0,
183                                   "unknown type: type index out of bounds");
184    if (m->ntables > 1 && !need(v, WASM_F_REFERENCE_TYPES, 0)) return false;
185    for (uint32_t i = m->ntable_imports; i < m->ntables; i++)
186        if (!table_ok(v, &m->tables[i], 0)) return false;
187    if (m->nmems > 1 && !(v->f & WASM_F_MULTI_MEMORY)) return wasm_rd_fail_at(&v->r, 0, "multiple memories");
188    for (uint32_t i = m->nmem_imports; i < m->nmems; i++)
189        if (!mem_ok(v, &m->mems[i], 0)) return false;
190    for (uint32_t i = m->nglobal_imports; i < m->nglobals; i++) {
191        const wasm_expr *e = &m->global_inits[i - m->nglobal_imports];
192        if (!valtype_ok(v, m->globals[i].type, e->offset)) return false;
193        if (!const_ok(v, e, m->globals[i].type, i)) return false;
194    }
195    for (uint32_t i = 0; i < m->nexports; i++) {
196        const wasm_export *e = &m->exports[i];
197        size_t at = off(v, e->name);
198        for (uint32_t k = 0; k < i; k++)
199            if (m->exports[k].name_len == e->name_len && memcmp(m->exports[k].name, e->name, e->name_len) == 0)
200                return wasm_rd_fail_at(&v->r, at, "duplicate export name");
201        uint32_t n = e->kind == WASM_EXTERN_FUNC ? m->nfuncs
202                     : e->kind == WASM_EXTERN_TABLE ? m->ntables
203                     : e->kind == WASM_EXTERN_MEMORY ? m->nmems
204                                                     : m->nglobals;
205        if (e->index >= n) return wasm_rd_fail_at(&v->r, at, "exported %s index out of bounds",
206                                                  e->kind == WASM_EXTERN_FUNC ? "function" : e->kind == WASM_EXTERN_TABLE ? "table" : e->kind == WASM_EXTERN_MEMORY ? "memory" : "global");
207        if (e->kind == WASM_EXTERN_FUNC) v->declared[e->index] = true;
208        if (e->kind == WASM_EXTERN_GLOBAL && m->globals[e->index].mut && !need(v, WASM_F_MUTABLE_GLOBAL, at)) return false;
209    }
210    if (m->has_start) {
211        if (m->start >= m->nfuncs) return wasm_rd_fail_at(&v->r, 0, "unknown function %u: start function index out of bounds", m->start);
212        const wasm_functype *t = wasm_func_type(m, m->start);
213        if (t->nparams || t->nresults) return wasm_rd_fail_at(&v->r, 0, "invalid start function type");
214    }
215    for (uint32_t i = 0; i < m->nelems; i++) {
216        const wasm_elem *e = &m->elems[i];
217        size_t at = e->offset_in_binary;
218        bool mvp = e->mode == WASM_SEG_ACTIVE && e->table == 0 && e->funcs && e->type == WASM_FUNCREF;
219        if (!mvp && !need(v, WASM_F_BULK_MEMORY | WASM_F_REFERENCE_TYPES, at)) return false;
220        if (e->type == WASM_EXTERNREF && !need(v, WASM_F_REFERENCE_TYPES, at)) return false;
221        if (e->mode == WASM_SEG_ACTIVE) {
222            if (e->table >= m->ntables) return wasm_rd_fail_at(&v->r, at, "unknown table %u: table index out of bounds", e->table);
223            if (m->tables[e->table].elem != e->type)
224                return wasm_rd_fail_at(&v->r, at, "type mismatch: invalid element type");
225            if (!const_ok(v, &e->offset, WASM_I32, m->nglobals)) return false;
226        }
227        for (uint32_t k = 0; k < e->count; k++) {
228            if (e->funcs) {
229                if (e->funcs[k] >= m->nfuncs)
230                    return wasm_rd_fail_at(&v->r, at, "unknown function %u: function index out of bounds", e->funcs[k]);
231                v->declared[e->funcs[k]] = true;
232            } else if (!const_ok(v, &e->exprs[k], e->type, m->nglobals)) {
233                return false;
234            }
235        }
236    }
237    for (uint32_t i = 0; i < m->ndatas; i++) {
238        const wasm_data *d = &m->datas[i];
239        if (d->mode == WASM_SEG_PASSIVE && !need(v, WASM_F_BULK_MEMORY, d->offset_in_binary)) return false;
240        if (d->mode == WASM_SEG_ACTIVE) {
241            if (d->mem >= m->nmems) return wasm_rd_fail_at(&v->r, d->offset_in_binary, "unknown memory %u", d->mem);
242            wasm_valtype it = (m->mems[d->mem].flags & WASM_LIMITS_64) ? WASM_I64 : WASM_I32;
243            if (!const_ok(v, &d->offset, it, m->nglobals)) return false;
244        }
245    }
246    return true;
247}
248
249/* ---- function bodies ---------------------------------------------------------------------- */
250
251#define UNKNOWN 0 /* a polymorphic stack's value: matches anything */
252
253typedef struct {
254    uint32_t op;       /* block, loop, if, else, or 0 for the function */
255    int64_t bt;        /* block type (or for the function: -1 and its type in F.ft) */
256    wasm_valtype one;  /* a value-type block type's type */
257    uint32_t height;   /* operand stack height at entry, params excluded */
258    bool unreachable;
259} frame;
260
261typedef struct {
262    M *v;
263    wasm_reader *r; /* the module's: one failure for everything */
264    const wasm_functype *ft;
265    wasm_valtype *locals;
266    uint32_t nlocals;
267    wasm_valtype *stack;
268    uint32_t sp, scap;
269    frame *ctl;
270    uint32_t nctl, ccap;
271} F;
272
273static bool fail(F *x, size_t at, const char *fmt, const char *a, const char *b) {
274    return wasm_rd_fail_at(x->r, at, fmt, a, b);
275}
276
277static void block_types(F *x, const frame *fr, bool params, const wasm_valtype **ts, uint32_t *n) {
278    if (fr->op == 0) {
279        *ts = params ? NULL : x->ft->results;
280        *n = params ? 0 : x->ft->nresults;
281    } else if (fr->bt >= 0) {
282        const wasm_functype *t = &x->v->m->types[fr->bt];
283        *ts = params ? t->params : t->results;
284        *n = params ? t->nparams : t->nresults;
285    } else if (fr->bt == WASM_BT_EMPTY || params) {
286        *ts = NULL;
287        *n = 0;
288    } else {
289        *ts = &fr->one;
290        *n = 1;
291    }
292}
293
294/* The types a branch to fr carries: a loop's params, else its results. */
295static void label_types(F *x, const frame *fr, const wasm_valtype **ts, uint32_t *n) {
296    block_types(x, fr, fr->op == WASM_OP_LOOP, ts, n);
297}
298
299static bool push(F *x, wasm_valtype t) {
300    if (x->sp == x->scap) {
301        uint32_t cap = x->scap ? x->scap * 2 : 64;
302        wasm_valtype *s = realloc(x->stack, cap);
303        if (!s) return wasm_rd_fail(x->r, "out of memory");
304        x->stack = s;
305        x->scap = cap;
306    }
307    x->stack[x->sp++] = t;
308    return true;
309}
310
311/* Pops one operand; *t is UNKNOWN below an unreachable frame's height. */
312static bool pop(F *x, size_t at, wasm_valtype *t) {
313    frame *fr = &x->ctl[x->nctl - 1];
314    if (x->sp == fr->height) {
315        if (fr->unreachable) {
316            *t = UNKNOWN;
317            return true;
318        }
319        return wasm_rd_fail_at(x->r, at, "type mismatch: operand stack empty");
320    }
321    *t = x->stack[--x->sp];
322    return true;
323}
324
325static bool pop_expect(F *x, size_t at, wasm_valtype want) {
326    wasm_valtype t;
327    if (!pop(x, at, &t)) return false;
328    if (t != UNKNOWN && want != UNKNOWN && t != want)
329        return fail(x, at, "type mismatch: expected %s, found %s", wasm_valtype_name(want), wasm_valtype_name(t));
330    return true;
331}
332
333static bool pop_types(F *x, size_t at, const wasm_valtype *ts, uint32_t n) {
334    for (uint32_t i = n; i-- > 0;)
335        if (!pop_expect(x, at, ts[i])) return false;
336    return true;
337}
338
339static bool push_types(F *x, const wasm_valtype *ts, uint32_t n) {
340    for (uint32_t i = 0; i < n; i++)
341        if (!push(x, ts[i])) return false;
342    return true;
343}
344
345static bool push_ctrl(F *x, uint32_t op, int64_t bt, wasm_valtype one) {
346    if (x->nctl == x->ccap) {
347        uint32_t cap = x->ccap ? x->ccap * 2 : 16;
348        frame *c = realloc(x->ctl, cap * sizeof *c);
349        if (!c) return wasm_rd_fail(x->r, "out of memory");
350        x->ctl = c;
351        x->ccap = cap;
352    }
353    frame *fr = &x->ctl[x->nctl++];
354    fr->op = op;
355    fr->bt = bt;
356    fr->one = one;
357    fr->height = x->sp;
358    fr->unreachable = false;
359    const wasm_valtype *ts;
360    uint32_t n;
361    block_types(x, fr, true, &ts, &n);
362    return push_types(x, ts, n);
363}
364
365/* Pops the innermost frame, checking its results are exactly what's on the stack. */
366static bool pop_ctrl(F *x, size_t at, frame *out) {
367    frame *fr = &x->ctl[x->nctl - 1];
368    const wasm_valtype *ts;
369    uint32_t n;
370    block_types(x, fr, false, &ts, &n);
371    if (!pop_types(x, at, ts, n)) return false;
372    if (x->sp != fr->height) return wasm_rd_fail_at(x->r, at, "type mismatch: values remaining on stack at end of block");
373    *out = *fr;
374    x->nctl--;
375    return true;
376}
377
378static void unreachable(F *x) {
379    frame *fr = &x->ctl[x->nctl - 1];
380    x->sp = fr->height;
381    fr->unreachable = true;
382}
383
384static bool blocktype_ok(F *x, const wasm_instr *in) {
385    if (in->blocktype >= 0) {
386        if ((uint64_t)in->blocktype >= x->v->m->ntypes)
387            return wasm_rd_fail_at(x->r, in->offset, "unknown type: type index out of bounds");
388        const wasm_functype *t = &x->v->m->types[in->blocktype];
389        if ((t->nparams || t->nresults > 1) && !need(x->v, WASM_F_MULTI_VALUE, in->offset)) return false;
390        return true;
391    }
392    if (in->blocktype != WASM_BT_EMPTY) return valtype_ok(x->v, in->bt_val, in->offset);
393    return true;
394}
395
396static const frame *label(F *x, const wasm_instr *in, uint32_t depth) {
397    if (depth >= x->nctl) {
398        wasm_rd_fail_at(x->r, in->offset, "unknown label: branch depth too large");
399        return NULL;
400    }
401    return &x->ctl[x->nctl - 1 - depth];
402}
403
404static bool memarg_ok(F *x, const wasm_instr *in, uint32_t natural) {
405    const wasm_module *m = x->v->m;
406    if (in->mem != 0 && !need(x->v, WASM_F_MULTI_MEMORY, in->offset)) return false;
407    if (in->mem >= m->nmems) return wasm_rd_fail_at(x->r, in->offset, "unknown memory %u", in->mem);
408    if (in->align > 63 || (1ull << in->align) > natural)
409        return wasm_rd_fail_at(x->r, in->offset, "alignment must not be larger than natural");
410    if (!(m->mems[in->mem].flags & WASM_LIMITS_64) && in->moffset > UINT32_MAX)
411        return wasm_rd_fail_at(x->r, in->offset, "offset out of range: must be <= 2**32");
412    return true;
413}
414
415static wasm_valtype addr_type(F *x, uint32_t mem) {
416    return mem < x->v->m->nmems && (x->v->m->mems[mem].flags & WASM_LIMITS_64) ? WASM_I64 : WASM_I32;
417}
418
419static bool table_index(F *x, const wasm_instr *in, uint32_t t) {
420    if (t >= x->v->m->ntables) return wasm_rd_fail_at(x->r, in->offset, "unknown table %u: table index out of bounds", t);
421    return true;
422}
423
424static bool plain(F *x, const wasm_instr *in) {
425    const char *sig = in->info.sig;
426    const char *gt = strchr(sig, '>');
427    for (const char *c = gt; c-- > sig;)
428        if (!pop_expect(x, in->offset, wasm_sig_type(*c))) return false;
429    for (const char *c = gt + 1; *c; c++)
430        if (!push(x, wasm_sig_type(*c))) return false;
431    return true;
432}
433
434/* One instruction of a type-checked body. *done: the function's own frame ended. */
435static bool instr(F *x, const wasm_instr *in, bool *done) {
436    M *v = x->v;
437    const wasm_module *m = v->m;
438    size_t at = in->offset;
439    wasm_valtype t, u;
440    if (in->info.sig && in->info.imm != WASM_IMM_MEMARG && in->op != WASM_OP_MEMORY_SIZE && in->op != WASM_OP_MEMORY_GROW)
441        return plain(x, in);
442    switch (in->op) {
443    case WASM_OP_UNREACHABLE: unreachable(x); return true;
444    case WASM_OP_NOP: return true;
445    case WASM_OP_BLOCK:
446    case WASM_OP_LOOP:
447    case WASM_OP_IF: {
448        if (!blocktype_ok(x, in)) return false;
449        if (in->op == WASM_OP_IF && !pop_expect(x, at, WASM_I32)) return false;
450        frame tmp = {in->op, in->blocktype, in->bt_val, 0, false};
451        const wasm_valtype *ts;
452        uint32_t n;
453        block_types(x, &tmp, true, &ts, &n);
454        if (!pop_types(x, at, ts, n)) return false;
455        return push_ctrl(x, in->op, in->blocktype, in->bt_val);
456    }
457    case WASM_OP_ELSE: {
458        frame fr;
459        if (x->ctl[x->nctl - 1].op != WASM_OP_IF) return wasm_rd_fail_at(x->r, at, "else found outside of an `if` block");
460        if (!pop_ctrl(x, at, &fr)) return false;
461        return push_ctrl(x, WASM_OP_ELSE, fr.bt, fr.one);
462    }
463    case WASM_OP_END: {
464        frame fr;
465        if (!pop_ctrl(x, at, &fr)) return false;
466        /* An if without else must have matching params and results (it falls through empty). */
467        if (fr.op == WASM_OP_IF) {
468            const wasm_valtype *pin, *pout;
469            uint32_t nin, nout;
470            block_types(x, &fr, true, &pin, &nin);
471            block_types(x, &fr, false, &pout, &nout);
472            if (nin != nout || (nin && memcmp(pin, pout, nin) != 0))
473                return wasm_rd_fail_at(x->r, at, "type mismatch: else branch missing");
474        }
475        if (fr.op == 0) {
476            *done = true;
477            return true;
478        }
479        const wasm_valtype *ts;
480        uint32_t n;
481        block_types(x, &fr, false, &ts, &n);
482        return push_types(x, ts, n);
483    }
484    case WASM_OP_BR:
485    case WASM_OP_BR_IF: {
486        const frame *fr = label(x, in, in->a);
487        if (!fr) return false;
488        const wasm_valtype *ts;
489        uint32_t n;
490        label_types(x, fr, &ts, &n);
491        if (in->op == WASM_OP_BR_IF && !pop_expect(x, at, WASM_I32)) return false;
492        if (!pop_types(x, at, ts, n)) return false;
493        if (in->op == WASM_OP_BR) unreachable(x);
494        else if (!push_types(x, ts, n)) return false;
495        return true;
496    }
497    case WASM_OP_BR_TABLE: {
498        if (!pop_expect(x, at, WASM_I32)) return false;
499        uint32_t *targets = malloc(((size_t)in->ntargets + 1) * sizeof *targets);
500        if (!targets) return wasm_rd_fail(x->r, "out of memory");
501        wasm_instr_targets(in, targets);
502        const frame *def = label(x, in, targets[in->ntargets]);
503        bool ok = def != NULL;
504        const wasm_valtype *dts = NULL;
505        uint32_t dn = 0;
506        if (ok) label_types(x, def, &dts, &dn);
507        for (uint32_t i = 0; ok && i < in->ntargets; i++) {
508            const frame *fr = label(x, in, targets[i]);
509            if (!fr) {
510                ok = false;
511                break;
512            }
513            const wasm_valtype *ts;
514            uint32_t n;
515            label_types(x, fr, &ts, &n);
516            if (n != dn) {
517                ok = wasm_rd_fail_at(x->r, at, "type mismatch: br_table target labels have different number of types");
518                break;
519            }
520            /* Each target's types must match the operands (checked without consuming them). */
521            uint32_t sp = x->sp;
522            ok = pop_types(x, at, ts, n);
523            x->sp = sp;
524        }
525        free(targets);
526        if (!ok) return false;
527        if (!pop_types(x, at, dts, dn)) return false;
528        unreachable(x);
529        return true;
530    }
531    case WASM_OP_RETURN:
532        if (!pop_types(x, at, x->ft->results, x->ft->nresults)) return false;
533        unreachable(x);
534        return true;
535    case WASM_OP_CALL: {
536        if (in->a >= m->nfuncs) return wasm_rd_fail_at(x->r, at, "unknown function %u: function index out of bounds", in->a);
537        const wasm_functype *ft = wasm_func_type(m, in->a);
538        return pop_types(x, at, ft->params, ft->nparams) && push_types(x, ft->results, ft->nresults);
539    }
540    case WASM_OP_CALL_INDIRECT: {
541        if (in->b != 0 && !need(v, WASM_F_REFERENCE_TYPES, at)) return false;
542        if (!table_index(x, in, in->b)) return false;
543        if (m->tables[in->b].elem != WASM_FUNCREF)
544            return wasm_rd_fail_at(x->r, at, "indirect calls must go through a table with type <= funcref");
545        if (in->a >= m->ntypes) return wasm_rd_fail_at(x->r, at, "unknown type: type index out of bounds");
546        const wasm_functype *ft = &m->types[in->a];
547        return pop_expect(x, at, WASM_I32) && pop_types(x, at, ft->params, ft->nparams) &&
548               push_types(x, ft->results, ft->nresults);
549    }
550    case WASM_OP_DROP: return pop(x, at, &t);
551    case WASM_OP_SELECT: {
552        if (!pop_expect(x, at, WASM_I32) || !pop(x, at, &t) || !pop(x, at, &u)) return false;
553        if (t != UNKNOWN && u != UNKNOWN && t != u)
554            return fail(x, at, "type mismatch: select operands have different types (%s, %s)", wasm_valtype_name(u), wasm_valtype_name(t));
555        wasm_valtype r = t != UNKNOWN ? t : u;
556        if (r != UNKNOWN && !wasm_is_num(r) && r != WASM_V128)
557            return wasm_rd_fail_at(x->r, at, "type mismatch: select only takes integral types");
558        return push(x, r);
559    }
560    case WASM_OP_SELECT_T:
561        if (!valtype_ok(v, in->select_type, at)) return false;
562        return pop_expect(x, at, WASM_I32) && pop_expect(x, at, in->select_type) &&
563               pop_expect(x, at, in->select_type) && push(x, in->select_type);
564    case WASM_OP_LOCAL_GET:
565    case WASM_OP_LOCAL_SET:
566    case WASM_OP_LOCAL_TEE: {
567        if (in->a >= x->nlocals) return wasm_rd_fail_at(x->r, at, "unknown local %u: local index out of bounds", in->a);
568        wasm_valtype lt = x->locals[in->a];
569        if (in->op == WASM_OP_LOCAL_GET) return push(x, lt);
570        if (!pop_expect(x, at, lt)) return false;
571        return in->op == WASM_OP_LOCAL_TEE ? push(x, lt) : true;
572    }
573    case WASM_OP_GLOBAL_GET:
574    case WASM_OP_GLOBAL_SET:
575        if (in->a >= m->nglobals) return wasm_rd_fail_at(x->r, at, "unknown global %u: global index out of bounds", in->a);
576        if (in->op == WASM_OP_GLOBAL_GET) return push(x, m->globals[in->a].type);
577        if (!m->globals[in->a].mut) return wasm_rd_fail_at(x->r, at, "global is immutable: cannot modify it with `global.set`");
578        return pop_expect(x, at, m->globals[in->a].type);
579    case WASM_OP_TABLE_GET:
580        if (!table_index(x, in, in->a)) return false;
581        return pop_expect(x, at, WASM_I32) && push(x, m->tables[in->a].elem);
582    case WASM_OP_TABLE_SET:
583        if (!table_index(x, in, in->a)) return false;
584        return pop_expect(x, at, m->tables[in->a].elem) && pop_expect(x, at, WASM_I32);
585    case WASM_OP_MEMORY_SIZE:
586    case WASM_OP_MEMORY_GROW: {
587        if (in->mem != 0 && !need(v, WASM_F_MULTI_MEMORY, at)) return false;
588        if (in->mem >= m->nmems) return wasm_rd_fail_at(x->r, at, "unknown memory %u", in->mem);
589        wasm_valtype it = addr_type(x, in->mem);
590        if (in->op == WASM_OP_MEMORY_GROW && !pop_expect(x, at, it)) return false;
591        return push(x, it);
592    }
593    case WASM_OP_REF_NULL: return push(x, in->reftype);
594    case WASM_OP_REF_IS_NULL:
595        if (!pop(x, at, &t)) return false;
596        if (t != UNKNOWN && !wasm_is_ref(t))
597            return fail(x, at, "type mismatch: expected %s, found %s", "reference", wasm_valtype_name(t));
598        return push(x, WASM_I32);
599    case WASM_OP_REF_FUNC:
600        if (in->a >= m->nfuncs) return wasm_rd_fail_at(x->r, at, "unknown function %u: function index out of bounds", in->a);
601        if (!v->declared[in->a]) return wasm_rd_fail_at(x->r, at, "undeclared function reference");
602        return push(x, WASM_FUNCREF);
603    case WASM_OP_MEMORY_INIT:
604        if (!m->has_datacount) return wasm_rd_fail_at(x->r, at, "data count section required");
605        if (in->mem >= m->nmems) return wasm_rd_fail_at(x->r, at, "unknown memory %u", in->mem);
606        if (in->a >= m->datacount) return wasm_rd_fail_at(x->r, at, "unknown data segment %u", in->a);
607        return pop_expect(x, at, WASM_I32) && pop_expect(x, at, WASM_I32) && pop_expect(x, at, addr_type(x, in->mem));
608    case WASM_OP_DATA_DROP:
609        if (!m->has_datacount) return wasm_rd_fail_at(x->r, at, "data count section required");
610        if (in->a >= m->datacount) return wasm_rd_fail_at(x->r, at, "unknown data segment %u", in->a);
611        return true;
612    case WASM_OP_MEMORY_COPY:
613        if ((in->mem || in->b) && !need(v, WASM_F_MULTI_MEMORY, at)) return false;
614        if (in->mem >= m->nmems || in->b >= m->nmems) return wasm_rd_fail_at(x->r, at, "unknown memory %u", in->mem >= m->nmems ? in->mem : in->b);
615        return pop_expect(x, at, addr_type(x, in->mem)) && pop_expect(x, at, addr_type(x, in->b)) &&
616               pop_expect(x, at, addr_type(x, in->mem));
617    case WASM_OP_MEMORY_FILL:
618        if (in->mem != 0 && !need(v, WASM_F_MULTI_MEMORY, at)) return false;
619        if (in->mem >= m->nmems) return wasm_rd_fail_at(x->r, at, "unknown memory %u", in->mem);
620        return pop_expect(x, at, addr_type(x, in->mem)) && pop_expect(x, at, WASM_I32) && pop_expect(x, at, addr_type(x, in->mem));
621    case WASM_OP_TABLE_INIT:
622        if (!table_index(x, in, in->b)) return false;
623        if (in->a >= m->nelems) return wasm_rd_fail_at(x->r, at, "unknown elem segment %u: segment index out of bounds", in->a);
624        if (m->elems[in->a].type != m->tables[in->b].elem) return wasm_rd_fail_at(x->r, at, "type mismatch");
625        return pop_expect(x, at, WASM_I32) && pop_expect(x, at, WASM_I32) && pop_expect(x, at, WASM_I32);
626    case WASM_OP_ELEM_DROP:
627        if (in->a >= m->nelems) return wasm_rd_fail_at(x->r, at, "unknown elem segment %u: segment index out of bounds", in->a);
628        return true;
629    case WASM_OP_TABLE_COPY:
630        if (!table_index(x, in, in->a) || !table_index(x, in, in->b)) return false;
631        if (m->tables[in->a].elem != m->tables[in->b].elem) return wasm_rd_fail_at(x->r, at, "type mismatch");
632        return pop_expect(x, at, WASM_I32) && pop_expect(x, at, WASM_I32) && pop_expect(x, at, WASM_I32);
633    case WASM_OP_TABLE_GROW:
634        if (!table_index(x, in, in->a)) return false;
635        return pop_expect(x, at, WASM_I32) && pop_expect(x, at, m->tables[in->a].elem) && push(x, WASM_I32);
636    case WASM_OP_TABLE_SIZE:
637        if (!table_index(x, in, in->a)) return false;
638        return push(x, WASM_I32);
639    case WASM_OP_TABLE_FILL:
640        if (!table_index(x, in, in->a)) return false;
641        return pop_expect(x, at, WASM_I32) && pop_expect(x, at, m->tables[in->a].elem) && pop_expect(x, at, WASM_I32);
642    default: break;
643    }
644    if (in->info.imm == WASM_IMM_MEMARG) {
645        if (!memarg_ok(x, in, in->info.mem_bytes)) return false;
646        /* A load or store: the address is the memory's index type, then the signature. */
647        const char *sig = in->info.sig;
648        const char *gt = strchr(sig, '>');
649        for (const char *c = gt; c-- > sig + 1;)
650            if (!pop_expect(x, at, wasm_sig_type(*c))) return false;
651        if (!pop_expect(x, at, addr_type(x, in->mem))) return false;
652        for (const char *c = gt + 1; *c; c++)
653            if (!push(x, wasm_sig_type(*c))) return false;
654        return true;
655    }
656    return wasm_rd_fail_at(x->r, at, "internal: unhandled instruction %s", in->info.name);
657}
658
659/* The rest of a body past its first SIMD instruction: structure and features only. */
660static bool walk_rest(F *x, uint32_t depth) {
661    wasm_instr in;
662    while (depth > 0) {
663        if (wasm_rd_eof(x->r)) return wasm_rd_fail(x->r, "control frames remain at end of function: END opcode expected");
664        if (!wasm_rd_instr(x->r, &in)) return false;
665        if (in.info.feature && !need(x->v, in.info.feature, in.offset)) return false;
666        if (in.info.feature == WASM_F_THREADS && x->v->rep) x->v->rep->uses_threads = true;
667        if (in.op == WASM_OP_BLOCK || in.op == WASM_OP_LOOP || in.op == WASM_OP_IF) depth++;
668        else if (in.op == WASM_OP_END) depth--;
669    }
670    return true;
671}
672
673static bool body_ok(M *v, uint32_t index) {
674    const wasm_module *m = v->m;
675    const wasm_code *c = &m->codes[index - m->nfunc_imports];
676    F x = {0};
677    x.v = v;
678    x.r = &v->r;
679    v->r.p = c->body.start;
680    v->r.end = c->body.start + c->body.len;
681    x.ft = wasm_func_type(m, index);
682    x.nlocals = (uint32_t)(x.ft->nparams + c->nlocals);
683    bool ok = false;
684    if (!(x.locals = malloc(x.nlocals ? x.nlocals : 1))) {
685        wasm_rd_fail(x.r, "out of memory");
686        goto out;
687    }
688    if (x.ft->nparams) memcpy(x.locals, x.ft->params, x.ft->nparams);
689    uint32_t k = x.ft->nparams;
690    for (uint32_t g = 0; g < c->nlocal_groups; g++) {
691        if (!valtype_ok(v, c->locals[g].type, c->offset)) goto out;
692        memset(x.locals + k, c->locals[g].type, c->locals[g].count);
693        k += c->locals[g].count;
694    }
695    if (!push_ctrl(&x, 0, -1, 0)) goto out;
696    wasm_instr in;
697    bool done = false;
698    while (!done) {
699        if (wasm_rd_eof(x.r)) {
700            wasm_rd_fail(x.r, "control frames remain at end of function: END opcode expected");
701            goto out;
702        }
703        if (!wasm_rd_instr(x.r, &in)) goto out;
704        if (in.info.feature && !need(v, in.info.feature, in.offset)) goto out;
705        if (in.info.feature & (WASM_F_SIMD | WASM_F_RELAXED_SIMD | WASM_F_THREADS)) {
706            /* Enabled, but not type-checked here (see validate.h): walk the rest. */
707            if (v->rep) {
708                if (in.info.feature == WASM_F_THREADS) v->rep->uses_threads = true;
709                else v->rep->uses_simd = true;
710            }
711            if (!walk_rest(&x, x.nctl)) goto out;
712            break;
713        }
714        if (!instr(&x, &in, &done)) goto out;
715    }
716    if (!wasm_rd_eof(x.r)) {
717        wasm_rd_fail(x.r, "operators remaining after end of function");
718        goto out;
719    }
720    ok = true;
721out:
722    free(x.locals);
723    free(x.stack);
724    free(x.ctl);
725    return ok;
726}
727
728bool wasm_validate(const wasm_module *m, wasm_features features, wasm_validate_report *report, char *err,
729                   size_t errlen) {
730    M v = {0};
731    v.m = m;
732    v.f = features;
733    v.rep = report;
734    if (report) memset(report, 0, sizeof *report);
735    v.r = wasm_reader_at(m->bytes, m->bytes, m->bytes + m->len, err, errlen);
736    v.declared = calloc(m->nfuncs ? m->nfuncs : 1, sizeof *v.declared);
737    if (!v.declared) return wasm_rd_fail(&v.r, "out of memory");
738    bool ok = module_ok(&v);
739    for (uint32_t i = m->nfunc_imports; ok && i < m->nfuncs; i++) ok = body_ok(&v, i);
740    free(v.declared);
741    return ok && !v.r.failed;
742}