preprocess-wasm-bytes 0.1.5 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,738 +1,711 @@
|
|
|
1
|
-
//
|
|
2
|
-
// src/preprocess_wasm_bytes.ts
|
|
1
|
+
// preprocessWasmBytes.ts
|
|
3
2
|
//
|
|
4
|
-
//
|
|
5
|
-
// -
|
|
6
|
-
//
|
|
7
|
-
// -
|
|
8
|
-
// -
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
3
|
+
// Goal: replicate the Rust/walrus “preprocess_wasm_bytes” in TypeScript, with a
|
|
4
|
+
// performance-first approach:
|
|
5
|
+
// - Parse only the sections we need.
|
|
6
|
+
// - Scan function bodies for the `ref.func` opcode (0xd2) without building a full AST.
|
|
7
|
+
// - Patch/insert the Export section (id=7) by copying existing bytes and appending new exports.
|
|
8
|
+
//
|
|
9
|
+
// Notes / assumptions (matching typical Rust-produced wasm):
|
|
10
|
+
// - Element/global const-exprs are expected to be “simple” (i32.const/global.get/ref.func/ref.null/...)
|
|
11
|
+
// If an unsupported opcode appears inside a const-expr, this implementation bails out and returns empty bytes.
|
|
12
|
+
// - Handles funcref tables encoded as `funcref` (0x70) and also typed refs `(ref ... func)` / `(ref null ... func)`
|
|
13
|
+
// encoded as 0x64/0x63 + heaptype = -16 (func) or -13 (nofunc).
|
|
14
|
+
const td = new TextDecoder("utf-8");
|
|
15
|
+
const te = new TextEncoder();
|
|
16
|
+
// Section ids
|
|
17
|
+
const SECTION_CUSTOM = 0;
|
|
18
|
+
const SECTION_IMPORT = 2;
|
|
19
|
+
const SECTION_FUNCTION = 3;
|
|
20
|
+
const SECTION_TABLE = 4;
|
|
21
|
+
const SECTION_MEMORY = 5;
|
|
22
|
+
const SECTION_GLOBAL = 6;
|
|
23
|
+
const SECTION_EXPORT = 7;
|
|
24
|
+
const SECTION_ELEMENT = 9;
|
|
25
|
+
const SECTION_CODE = 10;
|
|
26
|
+
// Export kinds (ExternalKind)
|
|
27
|
+
const EXTERNAL_KIND_FUNCTION = 0;
|
|
28
|
+
const EXTERNAL_KIND_TABLE = 1;
|
|
29
|
+
const EXTERNAL_KIND_MEMORY = 2;
|
|
30
|
+
const EXTERNAL_KIND_GLOBAL = 3;
|
|
31
|
+
// Opcode we care about inside function bodies / const-exprs.
|
|
32
|
+
const OPCODE_REF_FUNC = 0xd2;
|
|
33
|
+
// Type encodings (commonly used)
|
|
34
|
+
const VALTYPE_FUNCREF = 0x70; // funcref
|
|
35
|
+
const VALTYPE_REF_NULL = 0x63; // (ref null ht) - in GC encoding, followed by heaptype
|
|
36
|
+
const VALTYPE_REF = 0x64; // (ref ht) - in GC encoding, followed by heaptype
|
|
37
|
+
// Heaptype signed LEB values for abstract heap types (GC encoding).
|
|
38
|
+
// `func` is -16, `nofunc` is -13. These are widely used encodings (e.g. V8 constants).
|
|
39
|
+
const HEAPTYPE_FUNC = -16;
|
|
40
|
+
const HEAPTYPE_NOFUNC = -13;
|
|
41
|
+
class Reader {
|
|
42
|
+
bytes;
|
|
43
|
+
pos;
|
|
44
|
+
constructor(bytes, pos = 0) {
|
|
45
|
+
this.bytes = bytes;
|
|
46
|
+
this.pos = pos;
|
|
47
|
+
}
|
|
48
|
+
ensure(n) {
|
|
49
|
+
if (this.pos + n > this.bytes.length) {
|
|
50
|
+
throw new Error("Unexpected EOF");
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
u8() {
|
|
54
|
+
this.ensure(1);
|
|
55
|
+
return this.bytes[this.pos++];
|
|
56
|
+
}
|
|
57
|
+
skip(n) {
|
|
58
|
+
this.ensure(n);
|
|
59
|
+
this.pos += n;
|
|
60
|
+
}
|
|
61
|
+
// Unsigned LEB128 (u32)
|
|
62
|
+
varU32() {
|
|
63
|
+
let result = 0;
|
|
64
|
+
let mul = 1;
|
|
65
|
+
while (true) {
|
|
66
|
+
const b = this.u8();
|
|
67
|
+
result += (b & 0x7f) * mul;
|
|
68
|
+
if ((b & 0x80) === 0)
|
|
69
|
+
return result >>> 0;
|
|
70
|
+
mul *= 128;
|
|
71
|
+
// varuint32 max 5 bytes; this is just a safety net
|
|
72
|
+
if (mul > 128 ** 6)
|
|
73
|
+
throw new Error("varU32 too large");
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// Signed LEB128, up to ~33 bits (s33), used by heaptype in GC encoding.
|
|
77
|
+
varS33() {
|
|
78
|
+
let result = 0;
|
|
79
|
+
let mul = 1;
|
|
80
|
+
let b = 0;
|
|
81
|
+
while (true) {
|
|
82
|
+
b = this.u8();
|
|
83
|
+
result += (b & 0x7f) * mul;
|
|
84
|
+
mul *= 128;
|
|
85
|
+
if ((b & 0x80) === 0)
|
|
86
|
+
break;
|
|
87
|
+
if (mul > 128 ** 7)
|
|
88
|
+
throw new Error("varS33 too large");
|
|
89
|
+
}
|
|
90
|
+
// sign-extend based on 0x40 of final byte
|
|
91
|
+
if (b & 0x40)
|
|
92
|
+
result -= mul;
|
|
93
|
+
return result;
|
|
94
|
+
}
|
|
95
|
+
// Signed LEB128 (s32), used by i32.const
|
|
96
|
+
varS32() {
|
|
97
|
+
let result = 0;
|
|
98
|
+
let mul = 1;
|
|
99
|
+
let b = 0;
|
|
100
|
+
while (true) {
|
|
101
|
+
b = this.u8();
|
|
102
|
+
result += (b & 0x7f) * mul;
|
|
103
|
+
mul *= 128;
|
|
104
|
+
if ((b & 0x80) === 0)
|
|
105
|
+
break;
|
|
106
|
+
if (mul > 128 ** 6)
|
|
107
|
+
throw new Error("varS32 too large");
|
|
108
|
+
}
|
|
109
|
+
if (b & 0x40)
|
|
110
|
+
result -= mul;
|
|
111
|
+
return result | 0;
|
|
112
|
+
}
|
|
113
|
+
// Skip a signed LEB128 of arbitrary length (used for i64.const, etc.)
|
|
114
|
+
skipVarInt() {
|
|
115
|
+
while (true) {
|
|
116
|
+
const b = this.u8();
|
|
117
|
+
if ((b & 0x80) === 0)
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
readBytes(n) {
|
|
122
|
+
this.ensure(n);
|
|
123
|
+
const sub = this.bytes.subarray(this.pos, this.pos + n);
|
|
124
|
+
this.pos += n;
|
|
125
|
+
return sub;
|
|
126
|
+
}
|
|
127
|
+
skipString() {
|
|
128
|
+
const len = this.varU32();
|
|
129
|
+
this.skip(len);
|
|
130
|
+
}
|
|
131
|
+
readString() {
|
|
132
|
+
const len = this.varU32();
|
|
133
|
+
const start = this.pos;
|
|
134
|
+
this.skip(len);
|
|
135
|
+
return td.decode(this.bytes.subarray(start, start + len));
|
|
25
136
|
}
|
|
26
|
-
let i = 1;
|
|
27
|
-
while (used.has(`${cleanBase}__${i}`))
|
|
28
|
-
i++;
|
|
29
|
-
const out = `${cleanBase}__${i}`;
|
|
30
|
-
used.add(out);
|
|
31
|
-
return out;
|
|
32
137
|
}
|
|
33
|
-
|
|
34
|
-
|
|
138
|
+
class Writer {
|
|
139
|
+
buf;
|
|
140
|
+
len = 0;
|
|
141
|
+
constructor(initialCapacity = 1024) {
|
|
142
|
+
this.buf = new Uint8Array(initialCapacity);
|
|
143
|
+
}
|
|
144
|
+
ensure(n) {
|
|
145
|
+
const needed = this.len + n;
|
|
146
|
+
if (needed <= this.buf.length)
|
|
147
|
+
return;
|
|
148
|
+
let cap = this.buf.length;
|
|
149
|
+
while (cap < needed)
|
|
150
|
+
cap = cap * 2;
|
|
151
|
+
const next = new Uint8Array(cap);
|
|
152
|
+
next.set(this.buf, 0);
|
|
153
|
+
this.buf = next;
|
|
154
|
+
}
|
|
155
|
+
u8(v) {
|
|
156
|
+
this.ensure(1);
|
|
157
|
+
this.buf[this.len++] = v & 0xff;
|
|
158
|
+
}
|
|
159
|
+
bytes(arr) {
|
|
160
|
+
this.ensure(arr.length);
|
|
161
|
+
this.buf.set(arr, this.len);
|
|
162
|
+
this.len += arr.length;
|
|
163
|
+
}
|
|
35
164
|
// u32 LEB128
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
body.push(e.kind);
|
|
58
|
-
writeU32LebTo(body, e.index >>> 0);
|
|
165
|
+
varU32(v) {
|
|
166
|
+
let x = v >>> 0;
|
|
167
|
+
while (true) {
|
|
168
|
+
const byte = x & 0x7f;
|
|
169
|
+
x >>>= 7;
|
|
170
|
+
if (x === 0) {
|
|
171
|
+
this.u8(byte);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
this.u8(byte | 0x80);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
string(s) {
|
|
180
|
+
const data = te.encode(s);
|
|
181
|
+
this.varU32(data.length);
|
|
182
|
+
this.bytes(data);
|
|
183
|
+
}
|
|
184
|
+
finish() {
|
|
185
|
+
return this.buf.subarray(0, this.len);
|
|
59
186
|
}
|
|
60
|
-
// section header: id=7 + size
|
|
61
|
-
const out = [];
|
|
62
|
-
out.push(0x07);
|
|
63
|
-
writeU32LebTo(out, body.length);
|
|
64
|
-
for (let i = 0; i < body.length; i++)
|
|
65
|
-
out.push(body[i]);
|
|
66
|
-
return new Uint8Array(out);
|
|
67
187
|
}
|
|
68
|
-
function
|
|
188
|
+
function concat(chunks) {
|
|
69
189
|
let total = 0;
|
|
70
190
|
for (const c of chunks)
|
|
71
|
-
total += c.
|
|
191
|
+
total += c.length;
|
|
72
192
|
const out = new Uint8Array(total);
|
|
73
193
|
let off = 0;
|
|
74
194
|
for (const c of chunks) {
|
|
75
195
|
out.set(c, off);
|
|
76
|
-
off += c.
|
|
196
|
+
off += c.length;
|
|
77
197
|
}
|
|
78
198
|
return out;
|
|
79
199
|
}
|
|
80
|
-
function
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
// Insert before first *known* section with id > 7 (custom sections (0) can appear anywhere)
|
|
92
|
-
while (pos < endAll) {
|
|
93
|
-
const sectionStart = pos;
|
|
94
|
-
const id = bytes[pos++];
|
|
95
|
-
const rSize = readU32Leb(bytes, pos, endAll);
|
|
96
|
-
const size = rSize.value;
|
|
97
|
-
pos = rSize.pos;
|
|
98
|
-
const payloadStart = pos;
|
|
99
|
-
const payloadEnd = payloadStart + size;
|
|
100
|
-
if (payloadEnd > endAll)
|
|
101
|
-
throw new Error("[wasm-preprocess] section size out of bounds");
|
|
102
|
-
// If export section is missing, insert it before the first known section > 7
|
|
103
|
-
if (!inserted && !replaced && id !== 0 && id > 7) {
|
|
104
|
-
chunks.push(newExportSection);
|
|
105
|
-
inserted = true;
|
|
106
|
-
}
|
|
107
|
-
if (id === 7) {
|
|
108
|
-
// Replace existing export section
|
|
109
|
-
if (!replaced) {
|
|
110
|
-
chunks.push(newExportSection);
|
|
111
|
-
replaced = true;
|
|
112
|
-
inserted = true;
|
|
113
|
-
}
|
|
114
|
-
// Skip the old export section bytes
|
|
115
|
-
}
|
|
116
|
-
else {
|
|
117
|
-
// Copy original section unchanged
|
|
118
|
-
chunks.push(bytes.subarray(sectionStart, payloadEnd));
|
|
119
|
-
}
|
|
120
|
-
pos = payloadEnd;
|
|
121
|
-
}
|
|
122
|
-
// If we never inserted/replaced (no export section and no later section to trigger insertion), append it.
|
|
123
|
-
if (!inserted && !replaced) {
|
|
124
|
-
chunks.push(newExportSection);
|
|
125
|
-
}
|
|
126
|
-
return concatChunks(chunks);
|
|
200
|
+
function isWasm(bytes) {
|
|
201
|
+
return (bytes.length >= 8 &&
|
|
202
|
+
bytes[0] === 0x00 &&
|
|
203
|
+
bytes[1] === 0x61 &&
|
|
204
|
+
bytes[2] === 0x73 &&
|
|
205
|
+
bytes[3] === 0x6d &&
|
|
206
|
+
// version (usually 1)
|
|
207
|
+
bytes[4] === 0x01 &&
|
|
208
|
+
bytes[5] === 0x00 &&
|
|
209
|
+
bytes[6] === 0x00 &&
|
|
210
|
+
bytes[7] === 0x00);
|
|
127
211
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
212
|
+
function encodeSection(id, payload) {
|
|
213
|
+
const w = new Writer(1 + 5 + payload.length);
|
|
214
|
+
w.u8(id);
|
|
215
|
+
w.varU32(payload.length);
|
|
216
|
+
w.bytes(payload);
|
|
217
|
+
return w.finish();
|
|
134
218
|
}
|
|
135
|
-
function
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const b = bytes[pos++];
|
|
142
|
-
result |= (b & 0x7f) << shift;
|
|
143
|
-
if ((b & 0x80) === 0)
|
|
144
|
-
return { value: result >>> 0, pos };
|
|
145
|
-
shift += 7;
|
|
146
|
-
}
|
|
147
|
-
fail("u32 leb too long");
|
|
219
|
+
function skipLimits(r) {
|
|
220
|
+
const flags = r.u8();
|
|
221
|
+
r.varU32(); // min
|
|
222
|
+
if (flags & 0x01)
|
|
223
|
+
r.varU32(); // max
|
|
224
|
+
// Other flags (shared/memory64) exist; we don't need them here.
|
|
148
225
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
const b = bytes[pos++];
|
|
156
|
-
result |= (b & 0x7f) << shift;
|
|
157
|
-
if ((b & 0x80) === 0)
|
|
158
|
-
return { value: result >>> 0, pos };
|
|
159
|
-
shift += 7;
|
|
226
|
+
// Reads a `reftype`/`valtype` byte and any trailing heaptype if it is ref/refnull (0x64/0x63).
|
|
227
|
+
function readRefType(r) {
|
|
228
|
+
const code = r.u8();
|
|
229
|
+
if (code === VALTYPE_REF || code === VALTYPE_REF_NULL) {
|
|
230
|
+
const heapType = r.varS33();
|
|
231
|
+
return { code, heapType };
|
|
160
232
|
}
|
|
161
|
-
return
|
|
233
|
+
return { code };
|
|
162
234
|
}
|
|
163
|
-
function
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
fail("unexpected EOF while skipping leb");
|
|
167
|
-
const b = bytes[pos++];
|
|
168
|
-
if ((b & 0x80) === 0)
|
|
169
|
-
return pos;
|
|
170
|
-
}
|
|
171
|
-
fail("leb too long");
|
|
235
|
+
function skipValType(r) {
|
|
236
|
+
// valtype is the same grammar as reftype for our purposes here.
|
|
237
|
+
readRefType(r);
|
|
172
238
|
}
|
|
173
|
-
function
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
const s = utf8.decode(bytes.subarray(pos, next));
|
|
181
|
-
return { value: s, pos: next };
|
|
239
|
+
function isFuncRefTableType(rt) {
|
|
240
|
+
if (rt.code === VALTYPE_FUNCREF)
|
|
241
|
+
return true;
|
|
242
|
+
if ((rt.code === VALTYPE_REF || rt.code === VALTYPE_REF_NULL) && rt.heapType != null) {
|
|
243
|
+
return rt.heapType === HEAPTYPE_FUNC || rt.heapType === HEAPTYPE_NOFUNC;
|
|
244
|
+
}
|
|
245
|
+
return false;
|
|
182
246
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
*/
|
|
188
|
-
function parseConstExprCollectRefFuncs(bytes, pos, end, outFuncs, ctx) {
|
|
247
|
+
// Skip a const expr, optionally collecting ref.func indices.
|
|
248
|
+
// This only supports a conservative subset of opcodes typical in wasm init/offset exprs.
|
|
249
|
+
// If you hit an unsupported opcode, we throw (caller returns empty output, like the Rust code).
|
|
250
|
+
function skipConstExpr(r, onRefFunc) {
|
|
189
251
|
while (true) {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
252
|
+
const op = r.u8();
|
|
253
|
+
if (op === 0x0b)
|
|
254
|
+
return; // end
|
|
193
255
|
switch (op) {
|
|
194
|
-
case
|
|
195
|
-
|
|
196
|
-
case 0xd2: {
|
|
197
|
-
// ref.func <funcidx:u32>
|
|
198
|
-
const r = readU32Leb(bytes, pos, end);
|
|
199
|
-
outFuncs.add(r.value);
|
|
200
|
-
pos = r.pos;
|
|
201
|
-
break;
|
|
202
|
-
}
|
|
203
|
-
case 0xd0:
|
|
204
|
-
// ref.null <reftype>
|
|
205
|
-
if (pos >= end)
|
|
206
|
-
fail(`unexpected EOF after ref.null (${ctx})`);
|
|
207
|
-
pos += 1;
|
|
208
|
-
break;
|
|
209
|
-
case 0x23:
|
|
210
|
-
// global.get <globalidx:u32>
|
|
211
|
-
pos = skipLeb(bytes, pos, end, 5);
|
|
212
|
-
break;
|
|
213
|
-
case 0x41:
|
|
214
|
-
// i32.const <varint32>
|
|
215
|
-
pos = skipLeb(bytes, pos, end, 5);
|
|
216
|
-
break;
|
|
217
|
-
case 0x42:
|
|
218
|
-
// i64.const <varint64>
|
|
219
|
-
pos = skipLeb(bytes, pos, end, 10);
|
|
220
|
-
break;
|
|
221
|
-
case 0x43:
|
|
222
|
-
// f32.const <4 bytes>
|
|
223
|
-
if (pos + 4 > end)
|
|
224
|
-
fail(`unexpected EOF after f32.const (${ctx})`);
|
|
225
|
-
pos += 4;
|
|
256
|
+
case 0x41: // i32.const
|
|
257
|
+
r.varS32();
|
|
226
258
|
break;
|
|
227
|
-
case
|
|
228
|
-
|
|
229
|
-
if (pos + 8 > end)
|
|
230
|
-
fail(`unexpected EOF after f64.const (${ctx})`);
|
|
231
|
-
pos += 8;
|
|
232
|
-
break;
|
|
233
|
-
// Accept a few ref ops that have no immediates (rare in const exprs, but safe to skip)
|
|
234
|
-
case 0xd1: // ref.is_null
|
|
235
|
-
case 0xd3: // ref.eq
|
|
236
|
-
case 0xd4: // ref.as_non_null
|
|
237
|
-
break;
|
|
238
|
-
default:
|
|
239
|
-
fail(`unsupported opcode 0x${op.toString(16)} in const expr (${ctx}). ` +
|
|
240
|
-
`If your modules use extended-const-exprs, we can add more opcode support.`);
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
function parseLimitsU32(bytes, pos, end, ctx) {
|
|
245
|
-
if (pos >= end)
|
|
246
|
-
fail(`unexpected EOF in limits (${ctx})`);
|
|
247
|
-
const flags = bytes[pos++];
|
|
248
|
-
// Core variants:
|
|
249
|
-
// 0x00: min
|
|
250
|
-
// 0x01: min, max
|
|
251
|
-
// Additional variants exist in newer proposals (shared/memory64), but we just skip in a tolerant way.
|
|
252
|
-
if (flags === 0x00) {
|
|
253
|
-
pos = skipLeb(bytes, pos, end, 5);
|
|
254
|
-
return pos;
|
|
255
|
-
}
|
|
256
|
-
if (flags === 0x01 || flags === 0x03) {
|
|
257
|
-
// treat 0x03 (shared) same shape for skipping: min + max
|
|
258
|
-
pos = skipLeb(bytes, pos, end, 5);
|
|
259
|
-
pos = skipLeb(bytes, pos, end, 5);
|
|
260
|
-
return pos;
|
|
261
|
-
}
|
|
262
|
-
// Some proposals introduce more flags; best-effort: read min + max.
|
|
263
|
-
dbg(`limits(${ctx}) uses uncommon flags 0x${flags.toString(16)}; best-effort skipping min+max`);
|
|
264
|
-
pos = skipLeb(bytes, pos, end, 5);
|
|
265
|
-
pos = skipLeb(bytes, pos, end, 5);
|
|
266
|
-
return pos;
|
|
267
|
-
}
|
|
268
|
-
function parseTableType(bytes, pos, end) {
|
|
269
|
-
if (pos >= end)
|
|
270
|
-
fail("unexpected EOF in tabletype");
|
|
271
|
-
// reftype byte
|
|
272
|
-
pos += 1;
|
|
273
|
-
pos = parseLimitsU32(bytes, pos, end, "tabletype.limits");
|
|
274
|
-
return pos;
|
|
275
|
-
}
|
|
276
|
-
function parseMemType(bytes, pos, end) {
|
|
277
|
-
// memtype is limits
|
|
278
|
-
pos = parseLimitsU32(bytes, pos, end, "memtype.limits");
|
|
279
|
-
return pos;
|
|
280
|
-
}
|
|
281
|
-
function parseGlobalType(bytes, pos, end) {
|
|
282
|
-
if (pos + 2 > end)
|
|
283
|
-
fail("unexpected EOF in globaltype");
|
|
284
|
-
// valtype byte
|
|
285
|
-
pos += 1;
|
|
286
|
-
// mutability byte: 0 const, 1 var
|
|
287
|
-
const mut = bytes[pos++];
|
|
288
|
-
return { pos, mutable: mut === 1 };
|
|
289
|
-
}
|
|
290
|
-
/**
|
|
291
|
-
* Element section parsing based on the spec forms 0..7:
|
|
292
|
-
* https://webassembly.github.io/spec/core/binary/modules.html#element-section
|
|
293
|
-
*/
|
|
294
|
-
function parseElementSectionCollectFuncIdxs(bytes, start, end, outFuncs) {
|
|
295
|
-
let pos = start;
|
|
296
|
-
const rCount = readU32Leb(bytes, pos, end);
|
|
297
|
-
const count = rCount.value;
|
|
298
|
-
pos = rCount.pos;
|
|
299
|
-
dbg(`element section: ${count} segment(s)`);
|
|
300
|
-
for (let i = 0; i < count; i++) {
|
|
301
|
-
const rForm = readU32Leb(bytes, pos, end);
|
|
302
|
-
const form = rForm.value;
|
|
303
|
-
pos = rForm.pos;
|
|
304
|
-
const segCollected = [];
|
|
305
|
-
const addFunc = (idx) => {
|
|
306
|
-
outFuncs.add(idx);
|
|
307
|
-
segCollected.push(idx);
|
|
308
|
-
};
|
|
309
|
-
const readFuncIdxVec = () => {
|
|
310
|
-
const rN = readU32Leb(bytes, pos, end);
|
|
311
|
-
const n = rN.value;
|
|
312
|
-
pos = rN.pos;
|
|
313
|
-
for (let k = 0; k < n; k++) {
|
|
314
|
-
const rF = readU32Leb(bytes, pos, end);
|
|
315
|
-
pos = rF.pos;
|
|
316
|
-
addFunc(rF.value);
|
|
317
|
-
}
|
|
318
|
-
};
|
|
319
|
-
const readExprVec = (ctx) => {
|
|
320
|
-
const rN = readU32Leb(bytes, pos, end);
|
|
321
|
-
const n = rN.value;
|
|
322
|
-
pos = rN.pos;
|
|
323
|
-
for (let k = 0; k < n; k++) {
|
|
324
|
-
pos = parseConstExprCollectRefFuncs(bytes, pos, end, outFuncs, ctx);
|
|
325
|
-
}
|
|
326
|
-
};
|
|
327
|
-
switch (form) {
|
|
328
|
-
case 0: {
|
|
329
|
-
// 0: u32, offset expr, vec(funcidx) (active, table 0, funcref implicit)
|
|
330
|
-
pos = parseConstExprCollectRefFuncs(bytes, pos, end, outFuncs, `elem[${i}].offset(form0)`);
|
|
331
|
-
readFuncIdxVec();
|
|
332
|
-
break;
|
|
333
|
-
}
|
|
334
|
-
case 1: {
|
|
335
|
-
// 1: u32, elemkind, vec(funcidx) (passive)
|
|
336
|
-
if (pos >= end)
|
|
337
|
-
fail("unexpected EOF reading elemkind (form1)");
|
|
338
|
-
pos += 1; // elemkind
|
|
339
|
-
readFuncIdxVec();
|
|
340
|
-
break;
|
|
341
|
-
}
|
|
342
|
-
case 2: {
|
|
343
|
-
// 2: u32, tableidx, offset expr, elemkind, vec(funcidx) (active, explicit table)
|
|
344
|
-
pos = readU32Leb(bytes, pos, end).pos; // tableidx
|
|
345
|
-
pos = parseConstExprCollectRefFuncs(bytes, pos, end, outFuncs, `elem[${i}].offset(form2)`);
|
|
346
|
-
if (pos >= end)
|
|
347
|
-
fail("unexpected EOF reading elemkind (form2)");
|
|
348
|
-
pos += 1; // elemkind
|
|
349
|
-
readFuncIdxVec();
|
|
259
|
+
case 0x42: // i64.const
|
|
260
|
+
r.skipVarInt();
|
|
350
261
|
break;
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
// 3: u32, elemkind, vec(funcidx) (declarative)
|
|
354
|
-
if (pos >= end)
|
|
355
|
-
fail("unexpected EOF reading elemkind (form3)");
|
|
356
|
-
pos += 1; // elemkind
|
|
357
|
-
readFuncIdxVec();
|
|
262
|
+
case 0x43: // f32.const
|
|
263
|
+
r.skip(4);
|
|
358
264
|
break;
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
// 4: u32, offset expr, vec(expr) (active, table 0, funcref implicit)
|
|
362
|
-
pos = parseConstExprCollectRefFuncs(bytes, pos, end, outFuncs, `elem[${i}].offset(form4)`);
|
|
363
|
-
readExprVec(`elem[${i}].init(form4)`);
|
|
265
|
+
case 0x44: // f64.const
|
|
266
|
+
r.skip(8);
|
|
364
267
|
break;
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
// 5: u32, reftype, vec(expr) (passive)
|
|
368
|
-
if (pos >= end)
|
|
369
|
-
fail("unexpected EOF reading reftype (form5)");
|
|
370
|
-
pos += 1; // reftype
|
|
371
|
-
readExprVec(`elem[${i}].init(form5)`);
|
|
268
|
+
case 0x23: // global.get
|
|
269
|
+
r.varU32();
|
|
372
270
|
break;
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
// 6:u32 x:tableidx e:expr et:reftype el*:vec(expr)
|
|
376
|
-
pos = readU32Leb(bytes, pos, end).pos; // tableidx
|
|
377
|
-
pos = parseConstExprCollectRefFuncs(bytes, pos, end, outFuncs, `elem[${i}].offset(form6)`);
|
|
378
|
-
if (pos >= end)
|
|
379
|
-
fail("unexpected EOF reading reftype (form6)");
|
|
380
|
-
pos += 1; // reftype
|
|
381
|
-
readExprVec(`elem[${i}].init(form6)`);
|
|
271
|
+
case 0xd0: // ref.null (ref types / GC): immediate is heaptype (signed LEB)
|
|
272
|
+
r.varS33();
|
|
382
273
|
break;
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
fail("unexpected EOF reading reftype (form7)");
|
|
388
|
-
pos += 1; // reftype
|
|
389
|
-
readExprVec(`elem[${i}].init(form7)`);
|
|
274
|
+
case 0xd2: {
|
|
275
|
+
// ref.func
|
|
276
|
+
const idx = r.varU32();
|
|
277
|
+
onRefFunc?.(idx);
|
|
390
278
|
break;
|
|
391
279
|
}
|
|
280
|
+
// Many const-safe ops have no immediates (e.g. i32.add). If you want to be more permissive,
|
|
281
|
+
// you can add them here as "no-op" (no immediate to skip).
|
|
392
282
|
default:
|
|
393
|
-
|
|
394
|
-
}
|
|
395
|
-
if (debugEnabled()) {
|
|
396
|
-
const uniq = Array.from(new Set(segCollected)).sort((a, b) => a - b);
|
|
397
|
-
dbg(` elem[${i}] form=${form} collected funcidx:`, uniq);
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
if (pos !== end) {
|
|
401
|
-
// Not necessarily fatal, but useful signal.
|
|
402
|
-
dbg(`element section: parsed ${pos - start} bytes, expected ${end - start} bytes (diff=${end - pos})`);
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
function parseExportSection(bytes, start, end, exportNames, alreadyExported, exportEntries) {
|
|
406
|
-
let pos = start;
|
|
407
|
-
const rCount = readU32Leb(bytes, pos, end);
|
|
408
|
-
const count = rCount.value;
|
|
409
|
-
pos = rCount.pos;
|
|
410
|
-
for (let i = 0; i < count; i++) {
|
|
411
|
-
const nm = readName(bytes, pos, end);
|
|
412
|
-
const name = nm.value;
|
|
413
|
-
pos = nm.pos;
|
|
414
|
-
if (pos >= end)
|
|
415
|
-
fail("unexpected EOF in export entry kind");
|
|
416
|
-
const kind = bytes[pos++];
|
|
417
|
-
const rIdx = readU32Leb(bytes, pos, end);
|
|
418
|
-
const idx = rIdx.value;
|
|
419
|
-
pos = rIdx.pos;
|
|
420
|
-
exportNames.add(name);
|
|
421
|
-
// keep exact original entries so we can rebuild export section without webassemblyjs
|
|
422
|
-
if (kind === 0 || kind === 1 || kind === 2 || kind === 3) {
|
|
423
|
-
exportEntries.push({ name, kind: kind, index: idx >>> 0 });
|
|
424
|
-
}
|
|
425
|
-
switch (kind) {
|
|
426
|
-
case 0: // func
|
|
427
|
-
alreadyExported.Func.add(idx);
|
|
428
|
-
break;
|
|
429
|
-
case 1: // table
|
|
430
|
-
alreadyExported.Table.add(idx);
|
|
431
|
-
break;
|
|
432
|
-
case 2: // mem
|
|
433
|
-
alreadyExported.Memory.add(idx);
|
|
434
|
-
break;
|
|
435
|
-
case 3: // global
|
|
436
|
-
alreadyExported.Global.add(idx);
|
|
437
|
-
break;
|
|
438
|
-
default:
|
|
439
|
-
// ignore unknown kinds (future-proof)
|
|
440
|
-
break;
|
|
283
|
+
throw new Error(`Unsupported opcode in const-expr: 0x${op.toString(16)}`);
|
|
441
284
|
}
|
|
442
285
|
}
|
|
443
|
-
dbg("existing export names:", Array.from(exportNames).sort());
|
|
444
|
-
dbg("existing exported indices:", {
|
|
445
|
-
Func: Array.from(alreadyExported.Func).sort((a, b) => a - b),
|
|
446
|
-
Table: Array.from(alreadyExported.Table).sort((a, b) => a - b),
|
|
447
|
-
Memory: Array.from(alreadyExported.Memory).sort((a, b) => a - b),
|
|
448
|
-
Global: Array.from(alreadyExported.Global).sort((a, b) => a - b),
|
|
449
|
-
});
|
|
450
286
|
}
|
|
451
|
-
function
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
fail("bad WASM magic");
|
|
458
|
-
}
|
|
459
|
-
// version bytes[4..8) (ignored)
|
|
460
|
-
let pos = 8;
|
|
461
|
-
const exportNames = new Set();
|
|
462
|
-
const alreadyExported = {
|
|
463
|
-
Func: new Set(),
|
|
464
|
-
Table: new Set(),
|
|
465
|
-
Memory: new Set(),
|
|
466
|
-
Global: new Set(),
|
|
467
|
-
};
|
|
468
|
-
const exportEntries = [];
|
|
469
|
-
const funcsToExport = new Set();
|
|
470
|
-
let importFunctions = 0;
|
|
471
|
-
let localFunctions = 0;
|
|
472
|
-
let importTables = 0, localTables = 0;
|
|
473
|
-
let importMemories = 0, localMemories = 0;
|
|
474
|
-
let importGlobals = 0, localGlobals = 0;
|
|
475
|
-
const mutableGlobalsToExport = new Set();
|
|
476
|
-
const noteSection = (id) => {
|
|
477
|
-
switch (id) {
|
|
478
|
-
case 2:
|
|
479
|
-
return "import";
|
|
480
|
-
case 4:
|
|
481
|
-
return "table";
|
|
482
|
-
case 5:
|
|
483
|
-
return "memory";
|
|
484
|
-
case 6:
|
|
485
|
-
return "global";
|
|
486
|
-
case 7:
|
|
487
|
-
return "export";
|
|
488
|
-
case 9:
|
|
489
|
-
return "element";
|
|
490
|
-
default:
|
|
491
|
-
return `section(${id})`;
|
|
287
|
+
export function preprocess_wasm_bytes(wasmBytes, opts = {}) {
|
|
288
|
+
try {
|
|
289
|
+
if (!isWasm(wasmBytes)) {
|
|
290
|
+
if (opts.debug)
|
|
291
|
+
console.warn("Not a wasm binary (bad header).");
|
|
292
|
+
return new Uint8Array(0);
|
|
492
293
|
}
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
const
|
|
500
|
-
const
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
294
|
+
const sections = [];
|
|
295
|
+
// Imports + definitions summary
|
|
296
|
+
let importedFuncCount = 0;
|
|
297
|
+
let importedTableCount = 0;
|
|
298
|
+
let importedMemCount = 0;
|
|
299
|
+
let importedGlobalCount = 0;
|
|
300
|
+
const importedTableTypes = [];
|
|
301
|
+
const importedGlobalMutable = [];
|
|
302
|
+
let localFuncCount = 0;
|
|
303
|
+
const localTableTypes = [];
|
|
304
|
+
let localMemCount = 0;
|
|
305
|
+
const localGlobalMutable = [];
|
|
306
|
+
// Name section maps (optional)
|
|
307
|
+
const funcNameByIndex = new Map();
|
|
308
|
+
const globalNameByIndex = new Map();
|
|
309
|
+
// Existing exports
|
|
310
|
+
let exportSectionStart = -1;
|
|
311
|
+
let exportSectionEnd = -1;
|
|
312
|
+
let exportCount = 0;
|
|
313
|
+
let exportEntriesBytes = null;
|
|
314
|
+
const usedExportNames = new Set();
|
|
315
|
+
const exportedFuncs = new Set();
|
|
316
|
+
const exportedGlobals = new Set();
|
|
317
|
+
const exportedMems = new Set();
|
|
318
|
+
const exportedTables = new Set();
|
|
319
|
+
// Things we will export (collected)
|
|
320
|
+
const funcsToExport = new Set();
|
|
321
|
+
const markFunc = (idx) => {
|
|
322
|
+
const total = importedFuncCount + localFuncCount;
|
|
323
|
+
if (idx >>> 0 >= total >>> 0)
|
|
324
|
+
return; // bounds check (helps avoid false positives)
|
|
325
|
+
funcsToExport.add(idx >>> 0);
|
|
326
|
+
};
|
|
327
|
+
// Parse the module sections
|
|
328
|
+
const r = new Reader(wasmBytes, 8);
|
|
329
|
+
while (r.pos < wasmBytes.length) {
|
|
330
|
+
const start = r.pos;
|
|
331
|
+
const id = r.u8();
|
|
332
|
+
const size = r.varU32();
|
|
333
|
+
const payloadStart = r.pos;
|
|
334
|
+
const payloadEnd = payloadStart + size;
|
|
335
|
+
if (payloadEnd > wasmBytes.length) {
|
|
336
|
+
throw new Error("Section extends past end of file");
|
|
337
|
+
}
|
|
338
|
+
sections.push({ id, start, end: payloadEnd, payloadStart, payloadEnd });
|
|
339
|
+
// Slice reader to payload while parsing.
|
|
340
|
+
const savedPos = r.pos;
|
|
341
|
+
r.pos = payloadStart;
|
|
342
|
+
switch (id) {
|
|
343
|
+
case SECTION_CUSTOM: {
|
|
344
|
+
// custom section: name + bytes
|
|
345
|
+
const name = r.readString();
|
|
346
|
+
if (name === "name") {
|
|
347
|
+
// name subsection parsing
|
|
348
|
+
while (r.pos < payloadEnd) {
|
|
349
|
+
const subId = r.u8();
|
|
350
|
+
const subSize = r.varU32();
|
|
351
|
+
const subEnd = r.pos + subSize;
|
|
352
|
+
if (subEnd > payloadEnd)
|
|
353
|
+
throw new Error("Name subsection out of bounds");
|
|
354
|
+
const subReader = new Reader(wasmBytes.subarray(r.pos, subEnd), 0);
|
|
355
|
+
if (subId === 1) {
|
|
356
|
+
// function names
|
|
357
|
+
const count = subReader.varU32();
|
|
358
|
+
for (let i = 0; i < count; i++) {
|
|
359
|
+
const idx = subReader.varU32();
|
|
360
|
+
const nm = subReader.readString();
|
|
361
|
+
funcNameByIndex.set(idx, nm);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
else if (subId === 7) {
|
|
365
|
+
// global names
|
|
366
|
+
const count = subReader.varU32();
|
|
367
|
+
for (let i = 0; i < count; i++) {
|
|
368
|
+
const idx = subReader.varU32();
|
|
369
|
+
const nm = subReader.readString();
|
|
370
|
+
globalNameByIndex.set(idx, nm);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
r.pos = subEnd;
|
|
538
374
|
}
|
|
539
|
-
case 4: // tag (future-proof)
|
|
540
|
-
p = readU32Leb(bytes, p, end).pos;
|
|
541
|
-
break;
|
|
542
|
-
default:
|
|
543
|
-
dbg(`unknown import kind ${kind}; cannot safely skip (bailing import parse)`);
|
|
544
|
-
// safest: bail parsing this section
|
|
545
|
-
i = n;
|
|
546
|
-
break;
|
|
547
375
|
}
|
|
376
|
+
else {
|
|
377
|
+
// skip rest
|
|
378
|
+
r.pos = payloadEnd;
|
|
379
|
+
}
|
|
380
|
+
break;
|
|
548
381
|
}
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
382
|
+
case SECTION_IMPORT: {
|
|
383
|
+
const count = r.varU32();
|
|
384
|
+
for (let i = 0; i < count; i++) {
|
|
385
|
+
r.skipString(); // module
|
|
386
|
+
r.skipString(); // name
|
|
387
|
+
const kind = r.u8();
|
|
388
|
+
switch (kind) {
|
|
389
|
+
case EXTERNAL_KIND_FUNCTION:
|
|
390
|
+
importedFuncCount++;
|
|
391
|
+
r.varU32(); // type idx
|
|
392
|
+
break;
|
|
393
|
+
case EXTERNAL_KIND_TABLE: {
|
|
394
|
+
importedTableCount++;
|
|
395
|
+
const rt = readRefType(r);
|
|
396
|
+
importedTableTypes.push(rt);
|
|
397
|
+
skipLimits(r);
|
|
398
|
+
break;
|
|
399
|
+
}
|
|
400
|
+
case EXTERNAL_KIND_MEMORY:
|
|
401
|
+
importedMemCount++;
|
|
402
|
+
skipLimits(r);
|
|
403
|
+
break;
|
|
404
|
+
case EXTERNAL_KIND_GLOBAL: {
|
|
405
|
+
importedGlobalCount++;
|
|
406
|
+
skipValType(r);
|
|
407
|
+
const mut = r.u8();
|
|
408
|
+
importedGlobalMutable.push(mut !== 0);
|
|
409
|
+
break;
|
|
410
|
+
}
|
|
411
|
+
default:
|
|
412
|
+
throw new Error(`Unknown import kind: ${kind}`);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
break;
|
|
561
416
|
}
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
p = rCount.pos;
|
|
570
|
-
for (let i = 0; i < n; i++)
|
|
571
|
-
p = parseTableType(bytes, p, end);
|
|
572
|
-
localTables += n;
|
|
573
|
-
dbg("tables:", { localTables, totalTables: importTables + localTables });
|
|
574
|
-
}
|
|
575
|
-
else if (id === 5) {
|
|
576
|
-
// memory section
|
|
577
|
-
let p = start;
|
|
578
|
-
const rCount = readU32Leb(bytes, p, end);
|
|
579
|
-
const n = rCount.value;
|
|
580
|
-
p = rCount.pos;
|
|
581
|
-
for (let i = 0; i < n; i++)
|
|
582
|
-
p = parseMemType(bytes, p, end);
|
|
583
|
-
localMemories += n;
|
|
584
|
-
dbg("memories:", { localMemories, totalMemories: importMemories + localMemories });
|
|
585
|
-
}
|
|
586
|
-
else if (id === 6) {
|
|
587
|
-
// global section
|
|
588
|
-
let p = start;
|
|
589
|
-
const rCount = readU32Leb(bytes, p, end);
|
|
590
|
-
const n = rCount.value;
|
|
591
|
-
p = rCount.pos;
|
|
592
|
-
let globalIndex = importGlobals;
|
|
593
|
-
for (let i = 0; i < n; i++) {
|
|
594
|
-
const gt = parseGlobalType(bytes, p, end);
|
|
595
|
-
p = gt.pos;
|
|
596
|
-
if (gt.mutable)
|
|
597
|
-
mutableGlobalsToExport.add(globalIndex);
|
|
598
|
-
// init expr (also collect ref.func if present)
|
|
599
|
-
p = parseConstExprCollectRefFuncs(bytes, p, end, funcsToExport, `global[${globalIndex}].init`);
|
|
600
|
-
globalIndex++;
|
|
417
|
+
case SECTION_FUNCTION: {
|
|
418
|
+
const count = r.varU32();
|
|
419
|
+
localFuncCount = count;
|
|
420
|
+
// skip type indices
|
|
421
|
+
for (let i = 0; i < count; i++)
|
|
422
|
+
r.varU32();
|
|
423
|
+
break;
|
|
601
424
|
}
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
}
|
|
609
|
-
else if (id === 7) {
|
|
610
|
-
// export section
|
|
611
|
-
parseExportSection(bytes, start, end, exportNames, alreadyExported, exportEntries);
|
|
612
|
-
}
|
|
613
|
-
else if (id === 9) {
|
|
614
|
-
// element section (the big one we care about for function refs)
|
|
615
|
-
parseElementSectionCollectFuncIdxs(bytes, start, end, funcsToExport);
|
|
616
|
-
}
|
|
617
|
-
else if (id === 10) {
|
|
618
|
-
// code section: vec(code)
|
|
619
|
-
// We scan for opcode 0xD2 (ref.func) and record its immediate funcidx.
|
|
620
|
-
const totalFunctions = importFunctions + localFunctions;
|
|
621
|
-
let p = start;
|
|
622
|
-
const rCount = readU32Leb(bytes, p, end);
|
|
623
|
-
const nBodies = rCount.value;
|
|
624
|
-
p = rCount.pos;
|
|
625
|
-
dbg("code section bodies:", nBodies);
|
|
626
|
-
for (let bi = 0; bi < nBodies; bi++) {
|
|
627
|
-
const rBodySize = readU32Leb(bytes, p, end);
|
|
628
|
-
const bodySize = rBodySize.value;
|
|
629
|
-
p = rBodySize.pos;
|
|
630
|
-
const bodyStart = p;
|
|
631
|
-
const bodyEnd = bodyStart + bodySize;
|
|
632
|
-
if (bodyEnd > end)
|
|
633
|
-
fail("code body size out of bounds");
|
|
634
|
-
// Parse locals vec to find instruction start
|
|
635
|
-
let q = bodyStart;
|
|
636
|
-
const rLocalCount = readU32Leb(bytes, q, bodyEnd);
|
|
637
|
-
const localCount = rLocalCount.value;
|
|
638
|
-
q = rLocalCount.pos;
|
|
639
|
-
for (let li = 0; li < localCount; li++) {
|
|
640
|
-
// locals entry: count:u32, valtype:byte
|
|
641
|
-
const rN = readU32Leb(bytes, q, bodyEnd);
|
|
642
|
-
q = rN.pos;
|
|
643
|
-
if (q >= bodyEnd)
|
|
644
|
-
fail("unexpected EOF in locals");
|
|
645
|
-
q += 1; // valtype
|
|
425
|
+
case SECTION_TABLE: {
|
|
426
|
+
const count = r.varU32();
|
|
427
|
+
for (let i = 0; i < count; i++) {
|
|
428
|
+
const rt = readRefType(r);
|
|
429
|
+
localTableTypes.push(rt);
|
|
430
|
+
skipLimits(r);
|
|
646
431
|
}
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
432
|
+
break;
|
|
433
|
+
}
|
|
434
|
+
case SECTION_MEMORY: {
|
|
435
|
+
const count = r.varU32();
|
|
436
|
+
localMemCount = count;
|
|
437
|
+
for (let i = 0; i < count; i++)
|
|
438
|
+
skipLimits(r);
|
|
439
|
+
break;
|
|
440
|
+
}
|
|
441
|
+
case SECTION_GLOBAL: {
|
|
442
|
+
const count = r.varU32();
|
|
443
|
+
for (let i = 0; i < count; i++) {
|
|
444
|
+
skipValType(r);
|
|
445
|
+
const mut = r.u8();
|
|
446
|
+
localGlobalMutable.push(mut !== 0);
|
|
447
|
+
// init expr: collect ref.func
|
|
448
|
+
skipConstExpr(r, (f) => markFunc(f));
|
|
449
|
+
}
|
|
450
|
+
break;
|
|
451
|
+
}
|
|
452
|
+
case SECTION_EXPORT: {
|
|
453
|
+
exportSectionStart = start;
|
|
454
|
+
exportSectionEnd = payloadEnd;
|
|
455
|
+
exportCount = r.varU32();
|
|
456
|
+
const entriesStart = r.pos;
|
|
457
|
+
for (let i = 0; i < exportCount; i++) {
|
|
458
|
+
const nm = r.readString();
|
|
459
|
+
usedExportNames.add(nm);
|
|
460
|
+
const kind = r.u8();
|
|
461
|
+
const idx = r.varU32();
|
|
462
|
+
switch (kind) {
|
|
463
|
+
case EXTERNAL_KIND_FUNCTION:
|
|
464
|
+
exportedFuncs.add(idx);
|
|
465
|
+
break;
|
|
466
|
+
case EXTERNAL_KIND_TABLE:
|
|
467
|
+
exportedTables.add(idx);
|
|
468
|
+
break;
|
|
469
|
+
case EXTERNAL_KIND_MEMORY:
|
|
470
|
+
exportedMems.add(idx);
|
|
471
|
+
break;
|
|
472
|
+
case EXTERNAL_KIND_GLOBAL:
|
|
473
|
+
exportedGlobals.add(idx);
|
|
474
|
+
break;
|
|
475
|
+
default:
|
|
476
|
+
// ignore unknown kinds
|
|
477
|
+
break;
|
|
658
478
|
}
|
|
659
479
|
}
|
|
660
|
-
|
|
480
|
+
exportEntriesBytes = wasmBytes.subarray(entriesStart, payloadEnd);
|
|
481
|
+
break;
|
|
661
482
|
}
|
|
662
|
-
|
|
483
|
+
case SECTION_ELEMENT: {
|
|
484
|
+
const segCount = r.varU32();
|
|
485
|
+
for (let s = 0; s < segCount; s++) {
|
|
486
|
+
const flags = r.varU32();
|
|
487
|
+
const isExpr = (flags & 0x04) !== 0;
|
|
488
|
+
const kind = flags & 0x03;
|
|
489
|
+
// kind: 0 active (table 0), 1 passive, 2 active (explicit table), 3 declarative
|
|
490
|
+
if (kind === 2) {
|
|
491
|
+
// table index
|
|
492
|
+
r.varU32();
|
|
493
|
+
}
|
|
494
|
+
if (kind === 0 || kind === 2) {
|
|
495
|
+
// active segment has offset expr
|
|
496
|
+
skipConstExpr(r);
|
|
497
|
+
}
|
|
498
|
+
if (!isExpr) {
|
|
499
|
+
// function indices encoding
|
|
500
|
+
if (kind === 1 || kind === 3) {
|
|
501
|
+
// elemkind byte
|
|
502
|
+
r.u8();
|
|
503
|
+
}
|
|
504
|
+
const n = r.varU32();
|
|
505
|
+
for (let i = 0; i < n; i++) {
|
|
506
|
+
const f = r.varU32();
|
|
507
|
+
markFunc(f);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
else {
|
|
511
|
+
// expression encoding: has elemtype/reftype
|
|
512
|
+
readRefType(r);
|
|
513
|
+
const n = r.varU32();
|
|
514
|
+
for (let i = 0; i < n; i++) {
|
|
515
|
+
// each element is a const expr (e.g. ref.func ...)
|
|
516
|
+
skipConstExpr(r, (f) => markFunc(f));
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
break;
|
|
521
|
+
}
|
|
522
|
+
case SECTION_CODE: {
|
|
523
|
+
const bodyCount = r.varU32();
|
|
524
|
+
for (let i = 0; i < bodyCount; i++) {
|
|
525
|
+
const bodySize = r.varU32();
|
|
526
|
+
const bodyStart = r.pos;
|
|
527
|
+
const bodyEnd = bodyStart + bodySize;
|
|
528
|
+
if (bodyEnd > payloadEnd)
|
|
529
|
+
throw new Error("Function body out of bounds");
|
|
530
|
+
// locals
|
|
531
|
+
const localDecls = r.varU32();
|
|
532
|
+
for (let d = 0; d < localDecls; d++) {
|
|
533
|
+
r.varU32(); // count
|
|
534
|
+
skipValType(r); // type
|
|
535
|
+
}
|
|
536
|
+
// Scan the remaining bytes of the body (instructions) for opcode 0xd2.
|
|
537
|
+
// This is a fast, conservative scan (may over-approximate, but bounded by total func count).
|
|
538
|
+
const instrBytes = wasmBytes.subarray(r.pos, bodyEnd);
|
|
539
|
+
let searchFrom = 0;
|
|
540
|
+
while (true) {
|
|
541
|
+
const hit = instrBytes.indexOf(OPCODE_REF_FUNC, searchFrom);
|
|
542
|
+
if (hit === -1)
|
|
543
|
+
break;
|
|
544
|
+
// Decode following u32 LEB safely, within bounds.
|
|
545
|
+
const abs = r.pos + hit + 1;
|
|
546
|
+
if (abs < bodyEnd) {
|
|
547
|
+
const rr = new Reader(wasmBytes, abs);
|
|
548
|
+
try {
|
|
549
|
+
const idx = rr.varU32();
|
|
550
|
+
// only accept if rr.pos didn't run past end of body
|
|
551
|
+
if (rr.pos <= bodyEnd)
|
|
552
|
+
markFunc(idx);
|
|
553
|
+
}
|
|
554
|
+
catch {
|
|
555
|
+
// ignore malformed occurrences (likely false positive)
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
searchFrom = hit + 1;
|
|
559
|
+
}
|
|
560
|
+
// Jump to end of body
|
|
561
|
+
r.pos = bodyEnd;
|
|
562
|
+
}
|
|
563
|
+
break;
|
|
564
|
+
}
|
|
565
|
+
default:
|
|
566
|
+
// ignore
|
|
567
|
+
break;
|
|
663
568
|
}
|
|
569
|
+
// restore pos to end of section payload
|
|
570
|
+
r.pos = payloadEnd;
|
|
571
|
+
// safeguard: should match payloadEnd
|
|
572
|
+
if (r.pos !== payloadEnd)
|
|
573
|
+
r.pos = payloadEnd;
|
|
574
|
+
// restore savedPos is not needed; we control r.pos explicitly.
|
|
575
|
+
void savedPos;
|
|
664
576
|
}
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
577
|
+
// Build lists to export based on parsed info
|
|
578
|
+
// 3) mutable globals (imports + locals) => export them
|
|
579
|
+
const globalsToExport = [];
|
|
580
|
+
const allGlobalsMutable = importedGlobalMutable.concat(localGlobalMutable);
|
|
581
|
+
for (let i = 0; i < allGlobalsMutable.length; i++) {
|
|
582
|
+
if (allGlobalsMutable[i])
|
|
583
|
+
globalsToExport.push(i);
|
|
584
|
+
}
|
|
585
|
+
// 4) all memories (imports + locals)
|
|
586
|
+
const memoryCount = importedMemCount + localMemCount;
|
|
587
|
+
const memoriesToExport = [];
|
|
588
|
+
for (let i = 0; i < memoryCount; i++)
|
|
589
|
+
memoriesToExport.push(i);
|
|
590
|
+
// 5) any table of funcrefs (imports + locals)
|
|
591
|
+
const tablesToExport = [];
|
|
592
|
+
const allTableTypes = importedTableTypes.concat(localTableTypes);
|
|
593
|
+
for (let i = 0; i < allTableTypes.length; i++) {
|
|
594
|
+
if (isFuncRefTableType(allTableTypes[i]))
|
|
595
|
+
tablesToExport.push(i);
|
|
596
|
+
}
|
|
597
|
+
// Prepare new exports
|
|
598
|
+
const newExports = [];
|
|
599
|
+
const makeUniqueName = (base, fallbackSuffix) => {
|
|
600
|
+
if (!usedExportNames.has(base)) {
|
|
601
|
+
usedExportNames.add(base);
|
|
602
|
+
return base;
|
|
603
|
+
}
|
|
604
|
+
const alt = `${base}${fallbackSuffix}`;
|
|
605
|
+
if (!usedExportNames.has(alt)) {
|
|
606
|
+
usedExportNames.add(alt);
|
|
607
|
+
return alt;
|
|
608
|
+
}
|
|
609
|
+
// deterministic collision resolver
|
|
610
|
+
let n = 2;
|
|
611
|
+
while (true) {
|
|
612
|
+
const cand = `${alt}_${n++}`;
|
|
613
|
+
if (!usedExportNames.has(cand)) {
|
|
614
|
+
usedExportNames.add(cand);
|
|
615
|
+
return cand;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
};
|
|
619
|
+
// Functions
|
|
620
|
+
const funcIdxs = Array.from(funcsToExport);
|
|
621
|
+
funcIdxs.sort((a, b) => a - b); // deterministic
|
|
622
|
+
for (const funcIdx of funcIdxs) {
|
|
623
|
+
if (exportedFuncs.has(funcIdx))
|
|
624
|
+
continue;
|
|
625
|
+
const debugName = funcNameByIndex.get(funcIdx);
|
|
626
|
+
const base = debugName ?? `__f${funcIdx}`;
|
|
627
|
+
const name = makeUniqueName(base, `__f${funcIdx}`);
|
|
628
|
+
newExports.push({ name, kind: EXTERNAL_KIND_FUNCTION, index: funcIdx });
|
|
629
|
+
}
|
|
630
|
+
// Globals (mutable)
|
|
631
|
+
for (const g of globalsToExport) {
|
|
632
|
+
if (exportedGlobals.has(g))
|
|
633
|
+
continue;
|
|
634
|
+
const debugName = globalNameByIndex.get(g);
|
|
635
|
+
const base = debugName ?? `__global_${g}`;
|
|
636
|
+
const name = makeUniqueName(base, `__global_${g}`);
|
|
637
|
+
newExports.push({ name, kind: EXTERNAL_KIND_GLOBAL, index: g });
|
|
638
|
+
}
|
|
639
|
+
// Memories (all)
|
|
640
|
+
for (const m of memoriesToExport) {
|
|
641
|
+
if (exportedMems.has(m))
|
|
642
|
+
continue;
|
|
643
|
+
const base = `__memory_${m}`;
|
|
644
|
+
const name = makeUniqueName(base, `__memory_${m}`);
|
|
645
|
+
newExports.push({ name, kind: EXTERNAL_KIND_MEMORY, index: m });
|
|
646
|
+
}
|
|
647
|
+
// Tables (funcref-ish)
|
|
648
|
+
for (const t of tablesToExport) {
|
|
649
|
+
if (exportedTables.has(t))
|
|
650
|
+
continue;
|
|
651
|
+
const base = `__table_${t}`;
|
|
652
|
+
const name = makeUniqueName(base, `__table_${t}`);
|
|
653
|
+
newExports.push({ name, kind: EXTERNAL_KIND_TABLE, index: t });
|
|
654
|
+
}
|
|
655
|
+
if (newExports.length === 0) {
|
|
656
|
+
// Nothing to change
|
|
657
|
+
return wasmBytes;
|
|
658
|
+
}
|
|
659
|
+
// Encode appended export entries
|
|
660
|
+
const newEntriesW = new Writer(Math.max(256, newExports.length * 20));
|
|
661
|
+
for (const e of newExports) {
|
|
662
|
+
newEntriesW.string(e.name);
|
|
663
|
+
newEntriesW.u8(e.kind);
|
|
664
|
+
newEntriesW.varU32(e.index);
|
|
665
|
+
}
|
|
666
|
+
const newEntriesBytes = newEntriesW.finish();
|
|
667
|
+
// Build new export section payload
|
|
668
|
+
let newExportPayload;
|
|
669
|
+
if (exportEntriesBytes) {
|
|
670
|
+
const w = new Writer(10 + exportEntriesBytes.length + newEntriesBytes.length);
|
|
671
|
+
w.varU32(exportCount + newExports.length);
|
|
672
|
+
w.bytes(exportEntriesBytes);
|
|
673
|
+
w.bytes(newEntriesBytes);
|
|
674
|
+
newExportPayload = w.finish();
|
|
675
|
+
}
|
|
676
|
+
else {
|
|
677
|
+
const w = new Writer(10 + newEntriesBytes.length);
|
|
678
|
+
w.varU32(newExports.length);
|
|
679
|
+
w.bytes(newEntriesBytes);
|
|
680
|
+
newExportPayload = w.finish();
|
|
681
|
+
}
|
|
682
|
+
const newExportSectionBytes = encodeSection(SECTION_EXPORT, newExportPayload);
|
|
683
|
+
// Splice into original module bytes
|
|
684
|
+
if (exportSectionStart >= 0 && exportSectionEnd >= 0) {
|
|
685
|
+
// Replace existing export section
|
|
686
|
+
const before = wasmBytes.subarray(0, exportSectionStart);
|
|
687
|
+
const after = wasmBytes.subarray(exportSectionEnd);
|
|
688
|
+
return concat([before, newExportSectionBytes, after]);
|
|
689
|
+
}
|
|
690
|
+
else {
|
|
691
|
+
// Insert export section before first non-custom section with id > 7 (Start/Element/Code/etc),
|
|
692
|
+
// otherwise append at end.
|
|
693
|
+
let insertAt = wasmBytes.length;
|
|
694
|
+
for (const s of sections) {
|
|
695
|
+
if (s.id !== SECTION_CUSTOM && s.id > SECTION_EXPORT) {
|
|
696
|
+
insertAt = s.start;
|
|
697
|
+
break;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
const before = wasmBytes.subarray(0, insertAt);
|
|
701
|
+
const after = wasmBytes.subarray(insertAt);
|
|
702
|
+
return concat([before, newExportSectionBytes, after]);
|
|
668
703
|
}
|
|
669
|
-
pos = end;
|
|
670
|
-
}
|
|
671
|
-
const totalTables = importTables + localTables;
|
|
672
|
-
const totalMemories = importMemories + localMemories;
|
|
673
|
-
dbg("binary scan summary:", {
|
|
674
|
-
funcsToExport: Array.from(funcsToExport).sort((a, b) => a - b),
|
|
675
|
-
mutableGlobalsToExport: Array.from(mutableGlobalsToExport).sort((a, b) => a - b),
|
|
676
|
-
totalTables,
|
|
677
|
-
totalMemories,
|
|
678
|
-
});
|
|
679
|
-
return {
|
|
680
|
-
exportNames,
|
|
681
|
-
alreadyExported,
|
|
682
|
-
exportEntries,
|
|
683
|
-
funcsToExport,
|
|
684
|
-
mutableGlobalsToExport,
|
|
685
|
-
totalMemories,
|
|
686
|
-
totalTables,
|
|
687
|
-
};
|
|
688
|
-
}
|
|
689
|
-
/* =======================================================================================
|
|
690
|
-
* Main entry
|
|
691
|
-
* ======================================================================================= */
|
|
692
|
-
export async function preprocess_wasm_bytes(wasmBytes) {
|
|
693
|
-
// Scan is authoritative for what we need to export
|
|
694
|
-
const { exportNames, alreadyExported, exportEntries, funcsToExport, mutableGlobalsToExport, totalMemories, totalTables, } = scanWasmBinary(wasmBytes);
|
|
695
|
-
const added = [];
|
|
696
|
-
// 1) Functions referenced by element segments / ref.func
|
|
697
|
-
const funcList = Array.from(funcsToExport).sort((a, b) => a - b);
|
|
698
|
-
for (const idx of funcList) {
|
|
699
|
-
if (alreadyExported.Func.has(idx))
|
|
700
|
-
continue;
|
|
701
|
-
const name = uniqueName(`__f${idx}`, exportNames, `__f${idx}`);
|
|
702
|
-
added.push({ name, kind: 0, index: idx >>> 0 });
|
|
703
|
-
alreadyExported.Func.add(idx);
|
|
704
|
-
}
|
|
705
|
-
// 2) Mutable globals
|
|
706
|
-
const globList = Array.from(mutableGlobalsToExport).sort((a, b) => a - b);
|
|
707
|
-
for (const idx of globList) {
|
|
708
|
-
if (alreadyExported.Global.has(idx))
|
|
709
|
-
continue;
|
|
710
|
-
const name = uniqueName(`__global_${idx}`, exportNames, `__global_${idx}`);
|
|
711
|
-
added.push({ name, kind: 3, index: idx >>> 0 });
|
|
712
|
-
alreadyExported.Global.add(idx);
|
|
713
|
-
}
|
|
714
|
-
// 3) All memories
|
|
715
|
-
for (let idx = 0; idx < totalMemories; idx++) {
|
|
716
|
-
if (alreadyExported.Memory.has(idx))
|
|
717
|
-
continue;
|
|
718
|
-
const name = uniqueName(`__memory_${idx}`, exportNames, `__memory_${idx}`);
|
|
719
|
-
added.push({ name, kind: 2, index: idx >>> 0 });
|
|
720
|
-
alreadyExported.Memory.add(idx);
|
|
721
704
|
}
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
const name = uniqueName(`__table_${idx}`, exportNames, `__table_${idx}`);
|
|
727
|
-
added.push({ name, kind: 1, index: idx >>> 0 });
|
|
728
|
-
alreadyExported.Table.add(idx);
|
|
705
|
+
catch (e) {
|
|
706
|
+
if (opts.debug)
|
|
707
|
+
console.warn("preprocessWasmBytes failed:", e);
|
|
708
|
+
return new Uint8Array(0);
|
|
729
709
|
}
|
|
730
|
-
if (added.length === 0)
|
|
731
|
-
return wasmBytes;
|
|
732
|
-
// Rebuild export section = existing entries + added entries
|
|
733
|
-
const allExports = exportEntries.concat(added);
|
|
734
|
-
const newExportSection = buildExportSectionBytes(allExports);
|
|
735
|
-
// Replace (or insert) export section in the original binary
|
|
736
|
-
return rewriteWasmWithExportSection(wasmBytes, newExportSection);
|
|
737
710
|
}
|
|
738
711
|
//# sourceMappingURL=preprocess_wasm_bytes.js.map
|