/* * nanowasm, a tiny WebAssembly/Wasm interpreter * Copyright (C) 2023-2024 Xavier Del Campo Romero * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ #include #include #include #include #include #include #include #include #include #include #include #include static int check_body(FILE *const f, const unsigned long n) { unsigned long rem = n; while (rem) { const long before = ftell(f); uint8_t byte; if (before < 0) { LOG("%s: ftell(3) before: %s\n", __func__, strerror(errno)); return -1; } else if (!fread(&byte, sizeof byte, 1, f)) { LOG("%s: fread(3) failed, feof=%d, ferror=%d\n", __func__, feof(f), ferror(f)); return -1; } else if (rem == 1 && byte != OP_END) { LOG("%s: unexpected opcode %#" PRIx8 " at body end\n", __func__, byte); return -1; } else if (interp_check_opcode(byte, f)) { LOG("%s: interp_check_opcode failed\n", __func__); return -1; } const long after = ftell(f); if (after < 0) { LOG("%s: ftell(3) after: %s\n", __func__, strerror(errno)); return -1; } rem -= after - before; } return 0; } #if 0 static int push_locals(struct nw_interp *const i, const struct nw_frame *const f) { const enum value_type t = f->local_type; const size_t typesz = get_type_size(t); const unsigned long n = f->n_locals; if (mul_overflow(typesz, f->n_locals)) { LOG("%s: local variables size mul overflow", __func__); i->exception = "mul overfllow"; return -1; } const size_t totalsz = typesz * n, max = i->cfg.stack.n; if (totalsz > max || i->stack_i > max - totalsz) { LOG("%s: cannot allocate locals\n", __func__); i->exception = "stack overflow"; return 1; } init_locals(t, n, interp_stackptr(i)); i->stack_i += totalsz; return 0; } static int push_block(struct nw_interp *const i) { const long pc = ftell(i->f); if (pc < 0) { LOG("%s: ftell(3): %s\n", __func__, strerror(errno)); return -1; } const struct nw_block b = { .pc = pc }; if (interp_stack_push(i, &b, sizeof b)) { LOG("%s: interp_stack_push failed\n", __func__); return -1; } struct nw_block *const p = (struct nw_block *)interp_stackptr(i) - 1; if (i->fp->block) { for (struct nw_block *b = i->fp->block; b; b = b->next) if (!b->next) { b->next = p; break; } } else i->fp->block = p; return 0; } static int loop_push_blocks(struct nw_interp *const i) { for (;;) { uint8_t op; if (!fread(&op, sizeof op, 1, i->f)) { LOG("%s: fread(3) failed, feof=%d, ferror=%d\n", __func__, feof(i->f), ferror(i->f)); i->exception = "I/O error"; return -1; } else if (op >= sizeof ops / sizeof *ops) { LOG("%s: invalid opcode %#" PRIx8 "\n", __func__, op); i->exception = "invalid opcode"; return -1; } else if (interp_check_opcode(op, i->f)) { LOG("%s: interp_check_opcode failed\n", __func__); return -1; } switch (op) { case OP_END: return 0; case OP_BLOCK: if (push_block(i)) { LOG("%s: push_block failed\n", __func__); return -1; } break; default: break; } } } static int push_blocks(struct nw_interp *const i, const struct nw_frame *const fr) { FILE *const f = i->f; const long orig = ftell(f); if (orig < 0) { LOG("%s: ftell(3): %s\n", __func__, strerror(errno)); return -1; } else if (loop_push_blocks(i)) { LOG("%s: loop_push_blocks failed\n", __func__); return -1; } else if (fseek(f, orig, SEEK_SET)) { LOG("%s: fseek(3): %s\n", __func__, strerror(errno)); return -1; } return 0; } #endif static int push_local_count(struct nw_frame *const f, const varuint32 count, const varint7 type) { enum value_type vtype; if (get_value_type(type, &vtype)) { LOG("%s: get_value_type failed\n", __func__); return -1; } f->n_locals = count; f->local_type = vtype; return 0; } static int check_local(FILE *const f, struct nw_frame *const fr) { varuint32 count; varint7 type; if (varuint32_read(f, &count)) { LOG("%s: varuint32_read failed\n", __func__); return -1; } else if (varint7_read(f, &type)) { LOG("%s: varint7_read failed\n", __func__); return -1; } else if (fr && push_local_count(fr, count, type)) { LOG("%s: push_local_count failed\n", __func__); return -1; } return 0; } static int check_locals(FILE *const f, struct nw_frame *const fr) { varuint32 local_count; if (varuint32_read(f, &local_count)) { LOG("%s: varuint32_read local_count failed\n", __func__); return -1; } for (varuint32 i = 0; i < local_count; i++) if (check_local(f, fr)) { LOG("%s: check_local failed\n", __func__); return -1; } return 0; } static int check_function_body(FILE *const f, long *const out) { varuint32 body_size; if (varuint32_read(f, &body_size)) { LOG("%s: varuint32_read body_size failed\n", __func__); return -1; } const long start = ftell(f); if (start < 0) { LOG("%s: ftell(3) start: %s\n", __func__, strerror(errno)); return -1; } else if (check_locals(f, NULL)) { LOG("%s: check_locals failed\n", __func__); return -1; } const long end = ftell(f); if (end < 0) { LOG("%s: ftell(3) end: %s\n", __func__, strerror(errno)); return -1; } const unsigned long consumed = end - start; if (consumed > body_size) { LOG("%s: exceeded function body size\n", __func__); return -1; } else if (check_body(f, body_size - consumed)) { LOG("%s: check_body failed\n", __func__); return -1; } *out = start; return 0; } static int run(FILE *const f, const varuint32 idx, struct section_code *const out) { varuint32 count; if (varuint32_read(f, &count)) { LOG("%s: varuint32_read failed\n", __func__); return -1; } else if (count > ULONG_MAX - 1) { fprintf(stderr, "%s: count overflow\n", __func__); return -1; } for (varuint32 i = 0; i < count; i++) { long start; if (check_function_body(f, &start)) { LOG("%s: check_function_body failed\n", __func__); return -1; } else if (out && i == idx) { out->start = start; return 0; } } return 0; } int section_code_check(const struct section *const s, struct nw_mod *const m, const unsigned long len) { FILE *const f = s->f; if (m->sections.code) { LOG("%s: ignoring duplicate section\n", __func__); return fseek(f, len, SEEK_CUR); } const long start = ftell(f); if (start < 0) { LOG("%s: ftell(3): %s\n", __func__, strerror(errno)); return -1; } else if (run(f, 0, NULL)) { LOG("%s: run failed\n", __func__); return -1; } const long end = ftell(f); if (end < 0) { LOG("%s: ftell(3): %s\n", __func__, strerror(errno)); return -1; } const unsigned long size = end - start; if (size != len) { LOG("%s: size exceeded (%lu expected, got %lu)\n", __func__, len, size); return -1; } m->sections.code = start; return 0; } int section_code(const struct section *const s, const struct nw_mod *const m, const varuint32 idx, struct section_code *const out) { const long offset = m->sections.code; if (offset <= 0) { LOG("%s: code section not found", __func__); return -1; } else if (fseek(s->f, offset, SEEK_SET)) { LOG("%s: fseek(3): %s\n", __func__, strerror(errno)); return -1; } return run(s->f, idx, out); } int section_code_push(FILE *const f, const long pc, struct nw_frame *const fr) { if (fseek(f, pc, SEEK_SET)) { LOG("%s: fseek(3): %s\n", __func__, strerror(errno)); return -1; } else if (check_locals(f, fr)) { LOG("%s: check_local failed\n", __func__); return -1; } return 0; }