==> include/wasm/reader.h <==1/* @ajax/wasm-reader: a cursor over a WebAssembly binary. Every read is bounds-checked; the first
2 * failure is recorded as "<what> (at offset 0x<n>)", wasmparser's style, and every later read
3 * returns zeroes, so a parser can read straight through and check `failed` at the end.
4 * Header-only. */
5#ifndef WASM_READER_H
6#define WASM_READER_H
7
8#include "wasm/leb128.h"
9#include "wasm/types.h"
10
11#include <stdarg.h>
12#include <stdbool.h>
13#include <stddef.h>
14#include <stdint.h>
15#include <stdio.h>
16#include <string.h>
17
18typedef struct {
19 const uint8_t *base; /* the binary's first byte: offsets are from here */
20 const uint8_t *p;
21 const uint8_t *end;
22 bool failed;
23 char *err; /* the first failure's message, or NULL to keep none */
24 size_t errlen;
25} wasm_reader;
26
27static inline wasm_reader wasm_reader_at(const uint8_t *base, const uint8_t *p, const uint8_t *end, char *err,
28 size_t errlen) {
29 wasm_reader r = {base, p, end, false, err, errlen};
30 if (err && errlen) err[0] = 0;
31 return r;
32}
33
34static inline size_t wasm_rd_offset(const wasm_reader *r) { return (size_t)(r->p - r->base); }
35static inline bool wasm_rd_eof(const wasm_reader *r) { return r->p >= r->end; }
36static inline size_t wasm_rd_left(const wasm_reader *r) { return r->p < r->end ? (size_t)(r->end - r->p) : 0; }
37
38/* Records a failure at offset `at` (only the first one counts). Returns false. */
39static inline bool wasm_rd_fail_at(wasm_reader *r, size_t at, const char *fmt, ...) {
40 if (!r->failed) {
41 r->failed = true;
42 if (r->err && r->errlen) {
43 va_list ap;
44 va_start(ap, fmt);
45 int n = vsnprintf(r->err, r->errlen, fmt, ap);
46 va_end(ap);
47 if (n >= 0 && (size_t)n < r->errlen) snprintf(r->err + n, r->errlen - (size_t)n, " (at offset 0x%zx)", at);
48 }
49 }
50 r->p = r->end;
51 return false;
52}
53#define wasm_rd_fail(r, ...) wasm_rd_fail_at((r), wasm_rd_offset(r), __VA_ARGS__)
54
55static inline uint8_t wasm_rd_u8(wasm_reader *r) {
56 if (r->p >= r->end) {
57 wasm_rd_fail(r, "unexpected end-of-file");
58 return 0;
59 }
60 return *r->p++;
61}
62
63static inline uint32_t wasm_rd_u32(wasm_reader *r) {
64 uint32_t v = 0;
65 size_t n = r->p < r->end ? wasm_leb_u32(r->p, r->end, &v) : 0;
66 if (!n) {
67 wasm_rd_fail(r, r->p >= r->end ? "unexpected end-of-file" : "invalid var_u32");
68 return 0;
69 }
70 r->p += n;
71 return v;
72}
73
74static inline uint64_t wasm_rd_u64(wasm_reader *r) {
75 uint64_t v = 0;
76 size_t n = r->p < r->end ? wasm_leb_u64(r->p, r->end, &v) : 0;
77 if (!n) {
78 wasm_rd_fail(r, r->p >= r->end ? "unexpected end-of-file" : "invalid var_u64");
79 return 0;
80 }
81 r->p += n;
82 return v;
83}
84
85static inline int32_t wasm_rd_s32(wasm_reader *r) {
86 int32_t v = 0;
87 size_t n = r->p < r->end ? wasm_leb_s32(r->p, r->end, &v) : 0;
88 if (!n) {
89 wasm_rd_fail(r, r->p >= r->end ? "unexpected end-of-file" : "invalid var_i32");
90 return 0;
91 }
92 r->p += n;
93 return v;
94}
95
96static inline int64_t wasm_rd_s33(wasm_reader *r) {
97 int64_t v = 0;
98 size_t n = r->p < r->end ? wasm_leb_s33(r->p, r->end, &v) : 0;
99 if (!n) {
100 wasm_rd_fail(r, r->p >= r->end ? "unexpected end-of-file" : "invalid var_s33");
101 return 0;
102 }
103 r->p += n;
104 return v;
105}
106
107static inline int64_t wasm_rd_s64(wasm_reader *r) {
108 int64_t v = 0;
109 size_t n = r->p < r->end ? wasm_leb_s64(r->p, r->end, &v) : 0;
110 if (!n) {
111 wasm_rd_fail(r, r->p >= r->end ? "unexpected end-of-file" : "invalid var_i64");
112 return 0;
113 }
114 r->p += n;
115 return v;
116}
117
118/* n raw bytes: a pointer to them, or NULL (and a failure) past the end. */
119static inline const uint8_t *wasm_rd_bytes(wasm_reader *r, size_t n) {
120 if (wasm_rd_left(r) < n) {
121 wasm_rd_fail(r, "unexpected end-of-file");
122 return NULL;
123 }
124 const uint8_t *s = r->p;
125 r->p += n;
126 return s;
127}
128
129/* Little-endian fixed-width values (float constants). */
130static inline uint32_t wasm_rd_le32(wasm_reader *r) {
131 const uint8_t *b = wasm_rd_bytes(r, 4);
132 return b ? (uint32_t)b[0] | (uint32_t)b[1] << 8 | (uint32_t)b[2] << 16 | (uint32_t)b[3] << 24 : 0;
133}
134static inline uint64_t wasm_rd_le64(wasm_reader *r) {
135 uint64_t lo = wasm_rd_le32(r);
136 uint64_t hi = wasm_rd_le32(r);
137 return lo | hi << 32;
138}
139
140/* A string is valid UTF-8 (no overlongs, surrogates, or code points past U+10FFFF). */
141static inline bool wasm_utf8_valid(const uint8_t *s, size_t n) {
142 size_t i = 0;
143 while (i < n) {
144 uint8_t c = s[i];
145 size_t len;
146 uint32_t cp, min;
147 if (c < 0x80) {
148 i++;
149 continue;
150 } else if ((c & 0xe0) == 0xc0) {
151 len = 2, cp = c & 0x1f, min = 0x80;
152 } else if ((c & 0xf0) == 0xe0) {
153 len = 3, cp = c & 0x0f, min = 0x800;
154 } else if ((c & 0xf8) == 0xf0) {
155 len = 4, cp = c & 0x07, min = 0x10000;
156 } else {
157 return false;
158 }
159 if (i + len > n) return false;
160 for (size_t k = 1; k < len; k++) {
161 if ((s[i + k] & 0xc0) != 0x80) return false;
162 cp = cp << 6 | (s[i + k] & 0x3f);
163 }
164 if (cp < min || cp > 0x10ffff || (cp >= 0xd800 && cp <= 0xdfff)) return false;
165 i += len;
166 }
167 return true;
168}
169
170/* A name: u32 length, then that many bytes of UTF-8. */
171static inline const uint8_t *wasm_rd_name(wasm_reader *r, uint32_t *len) {
172 size_t at = wasm_rd_offset(r);
173 uint32_t n = wasm_rd_u32(r);
174 const uint8_t *s = wasm_rd_bytes(r, n);
175 if (s && !wasm_utf8_valid(s, n)) {
176 wasm_rd_fail_at(r, at, "malformed UTF-8 encoding");
177 s = NULL;
178 }
179 *len = s ? n : 0;
180 return s;
181}
182
183/* A vector's count, refused when it can't possibly fit in what is left (each element takes at
184 * least min_bytes), so a hostile count never makes us allocate. */
185static inline uint32_t wasm_rd_count(wasm_reader *r, size_t min_bytes, const char *what) {
186 uint32_t n = wasm_rd_u32(r);
187 if (!r->failed && min_bytes && (uint64_t)n * min_bytes > wasm_rd_left(r)) {
188 wasm_rd_fail(r, "%s count is out of bounds", what);
189 return 0;
190 }
191 return n;
192}
193
194#endif