jsilnet 0.0.0-stage → 0.2.0

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.
Files changed (74) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +98 -3
  3. package/REFERENCE.md +191 -0
  4. package/dist/browser/index.js +345 -0
  5. package/dist/browser/worker.js +42480 -0
  6. package/example/README.md +22 -0
  7. package/example/Sample.cs +10 -0
  8. package/example/Sample.csproj +7 -0
  9. package/example/edit-constant.js +48 -0
  10. package/example/inspect.js +37 -0
  11. package/package.json +61 -6
  12. package/src/assembly/instruction-editing.js +165 -0
  13. package/src/assembly/method-removal.js +789 -0
  14. package/src/assembly/pointer-table-editing.js +506 -0
  15. package/src/assembly/portable-pdb-editing.js +441 -0
  16. package/src/binary/errors.js +17 -0
  17. package/src/binary/reader.js +206 -0
  18. package/src/browser/index.d.ts +123 -0
  19. package/src/browser/index.js +373 -0
  20. package/src/browser/worker-runtime.js +287 -0
  21. package/src/browser/worker.js +47 -0
  22. package/src/il/body.js +165 -0
  23. package/src/il/instructions.js +217 -0
  24. package/src/il/opcodes.js +102 -0
  25. package/src/il/rewrite.js +331 -0
  26. package/src/index.d.ts +267 -0
  27. package/src/index.js +13307 -0
  28. package/src/metadata/classic-pdb.js +2530 -0
  29. package/src/metadata/custom-attributes.js +387 -0
  30. package/src/metadata/portable-pdb.js +3347 -0
  31. package/src/metadata/resources.js +37 -0
  32. package/src/metadata/root-writer.js +54 -0
  33. package/src/metadata/signatures.js +339 -0
  34. package/src/metadata/streams.js +285 -0
  35. package/src/metadata/strong-name.js +230 -0
  36. package/src/metadata/tables.js +390 -0
  37. package/src/metadata/writer.js +191 -0
  38. package/src/pe/headers.js +491 -0
  39. package/src/platform/bytes.js +21 -0
  40. package/src/platform/compression.js +24 -0
  41. package/src/platform/crypto.js +17 -0
  42. package/src/resolution/module.js +64 -0
  43. package/src/types/assembly/instruction-editing.d.ts +33 -0
  44. package/src/types/assembly/method-removal.d.ts +3 -0
  45. package/src/types/assembly/pointer-table-editing.d.ts +3 -0
  46. package/src/types/assembly/portable-pdb-editing.d.ts +23 -0
  47. package/src/types/binary/errors.d.ts +7 -0
  48. package/src/types/binary/reader.d.ts +30 -0
  49. package/src/types/il/body.d.ts +51 -0
  50. package/src/types/il/instructions.d.ts +33 -0
  51. package/src/types/il/opcodes.d.ts +2 -0
  52. package/src/types/il/rewrite.d.ts +38 -0
  53. package/src/types/index.d.ts +864 -0
  54. package/src/types/metadata/classic-pdb.d.ts +688 -0
  55. package/src/types/metadata/custom-attributes.d.ts +28 -0
  56. package/src/types/metadata/portable-pdb.d.ts +296 -0
  57. package/src/types/metadata/resources.d.ts +1 -0
  58. package/src/types/metadata/root-writer.d.ts +1 -0
  59. package/src/types/metadata/signatures.d.ts +5 -0
  60. package/src/types/metadata/streams.d.ts +26 -0
  61. package/src/types/metadata/strong-name.d.ts +11 -0
  62. package/src/types/metadata/tables.d.ts +25 -0
  63. package/src/types/metadata/writer.d.ts +16 -0
  64. package/src/types/pe/headers.d.ts +160 -0
  65. package/src/types/platform/bytes.d.ts +3 -0
  66. package/src/types/platform/compression.d.ts +2 -0
  67. package/src/types/platform/crypto.d.ts +4 -0
  68. package/src/types/resolution/module.d.ts +12 -0
  69. package/src/types/writer/patch.d.ts +4 -0
  70. package/src/types/writer/pe.d.ts +9 -0
  71. package/src/types/writer/structural.d.ts +4 -0
  72. package/src/writer/patch.js +42 -0
  73. package/src/writer/pe.js +196 -0
  74. package/src/writer/structural.js +241 -0
@@ -0,0 +1,3347 @@
1
+ import { deflateRawSync, inflateRawSync } from "../platform/compression.js";
2
+ import { createHash } from "../platform/crypto.js";
3
+ import { BinaryReader, readCompressedInt, readCompressedUInt } from "../binary/reader.js";
4
+ import { FormatError, UnsupportedFormatError } from "../binary/errors.js";
5
+ import {
6
+ appendBlobHeap,
7
+ appendCompressedInt,
8
+ appendCompressedUInt,
9
+ appendStringHeap,
10
+ serializeTablesStream
11
+ } from "./writer.js";
12
+ import { serializeMetadataRoot } from "./root-writer.js";
13
+ import { encodeCodedIndex } from "./tables.js";
14
+
15
+ const maxEmbeddedSourceSize = 0x10000000;
16
+ const maxEmbeddedPortablePdbSize = 0x10000000;
17
+ const documentHashAlgorithms = new Map([
18
+ ["ff1816ec-aa5e-4d10-87f7-6f4963833460", "sha1"],
19
+ ["8829d00f-11b8-4213-878b-770e8597ac16", "sha256"],
20
+ ["d99cfeb1-8c43-444a-8a6c-b61269d2a0bf", "sha384"],
21
+ ["ef2d1afc-6550-46d6-b14b-d70afe9a5566", "sha512"]
22
+ ]);
23
+ const embeddedSourceKind = "0e8a571b-6926-466e-b4ad-8ab04611f5fe";
24
+ const sourceLinkKind = "cc110556-a091-4d38-9fec-25ab9a351a6a";
25
+ const defaultNamespaceKind = "58b2eab6-209f-4e4e-a22c-b2d0f910c782";
26
+ const dynamicLocalVariablesKind = "83c563c4-b4f3-47d5-b824-ba5441477ea8";
27
+ const compilationMetadataReferencesKind = "7e4d4708-096e-4c5c-aeda-cb10ba6a740d";
28
+ const compilationOptionsKind = "b5feec05-8cd0-4a83-96da-466284bb4bd8";
29
+ const typeDefinitionDocumentKind = "932e74bc-dba9-4478-8d46-0f32a7bab3d3";
30
+ const asyncMethodSteppingInformationKind = "54fd2ac5-e925-401a-9c2a-f94f171072f8";
31
+ const stateMachineHoistedLocalScopesKind = "6da9a61e-f8c7-4874-be62-68bc5630df71";
32
+ const editAndContinueStateMachineStateMapKind = "8b78cd68-2ede-420b-980b-e15884b8aaa3";
33
+ const editAndContinueLocalSlotMapKind = "755f52a8-91c5-45be-b4b8-209571e552bd";
34
+ const editAndContinueLambdaAndClosureMapKind = "a643004c-0240-496f-a783-30d64f4979de";
35
+ const embeddedSourceGuidBytes = Uint8Array.from([
36
+ 0x1b, 0x57, 0x8a, 0x0e, 0x26, 0x69, 0x6e, 0x46,
37
+ 0xb4, 0xad, 0x8a, 0xb0, 0x46, 0x11, 0xf5, 0xfe
38
+ ]);
39
+ const sourceLinkGuidBytes = Uint8Array.from([
40
+ 0x56, 0x05, 0x11, 0xcc, 0x91, 0xa0, 0x38, 0x4d,
41
+ 0x9f, 0xec, 0x25, 0xab, 0x9a, 0x35, 0x1a, 0x6a
42
+ ]);
43
+ const typeDefinitionDocumentGuidBytes = Uint8Array.from([
44
+ 0xbc, 0x74, 0x2e, 0x93, 0xa9, 0xdb, 0x78, 0x44,
45
+ 0x8d, 0x46, 0x0f, 0x32, 0xa7, 0xba, 0xb3, 0xd3
46
+ ]);
47
+
48
+ export function validatePortablePdbAssemblyReferences(pdb, assemblyRowCounts) {
49
+ if (!pdb || !(pdb.rowCounts instanceof Map) || !Array.isArray(assemblyRowCounts)) {
50
+ throw new TypeError("Portable PDB validation requires parsed PDB data and assembly row counts");
51
+ }
52
+ for (const [table, count] of pdb.rowCounts) {
53
+ if (assemblyRowCounts[table] !== count) {
54
+ throw new FormatError(
55
+ `Portable PDB row count for type-system table ${table} does not match assembly metadata`
56
+ );
57
+ }
58
+ }
59
+ if (pdb.entryPointToken !== 0 &&
60
+ (pdb.entryPointToken & 0x00ffffff) > assemblyRowCounts[6]) {
61
+ throw new FormatError("Portable PDB entry point references a missing MethodDef row");
62
+ }
63
+ }
64
+
65
+ export function updateEmbeddedPortablePdbRowCounts(pdb, assemblyRowCounts) {
66
+ if (!pdb?.metadataRoot?.streams || !(pdb.rowCounts instanceof Map) ||
67
+ !Array.isArray(pdb.tables?.counts) || !Array.isArray(assemblyRowCounts)) {
68
+ throw new TypeError("Portable PDB row-count update requires parsed data and assembly row counts");
69
+ }
70
+ const rowCounts = new Map();
71
+ let changed = false;
72
+ for (const [table, count] of pdb.rowCounts) {
73
+ const updatedCount = assemblyRowCounts[table];
74
+ if (!Number.isInteger(updatedCount) || updatedCount < 0 ||
75
+ (updatedCount < count && (table === 6 || table === 17))) {
76
+ throw new UnsupportedFormatError(
77
+ `Portable PDB row-count update cannot safely change type-system table ${table}`
78
+ );
79
+ }
80
+ rowCounts.set(table, updatedCount);
81
+ changed ||= updatedCount !== count;
82
+ }
83
+ if (!changed) return null;
84
+
85
+ const pdbStream = new Uint8Array(32 + rowCounts.size * 4);
86
+ pdbStream.set(pdb.id);
87
+ const pdbView = new DataView(pdbStream.buffer);
88
+ pdbView.setUint32(20, pdb.entryPointToken, true);
89
+ pdbView.setUint32(24, Number(pdb.referencedTypeSystemTables & 0xffffffffn), true);
90
+ pdbView.setUint32(28, Number(pdb.referencedTypeSystemTables >> 32n), true);
91
+ let countOffset = 32;
92
+ for (const [table, count] of [...rowCounts].sort(([left], [right]) => left - right)) {
93
+ pdbView.setUint32(countOffset, count, true);
94
+ countOffset += 4;
95
+ }
96
+
97
+ const tableCounts = [...pdb.tables.counts];
98
+ for (let table = 0; table < 45; table++) {
99
+ tableCounts[table] = assemblyRowCounts[table];
100
+ }
101
+ const streams = new Map([...pdb.metadataRoot.streams].map(([name, stream]) =>
102
+ [name, { ...stream, bytes: Uint8Array.from(stream.bytes) }]
103
+ ));
104
+ const metadataPdbStream = streams.get("#Pdb");
105
+ if (!metadataPdbStream) throw new FormatError("Portable PDB is missing its #Pdb stream");
106
+ metadataPdbStream.bytes = pdbStream;
107
+ return serializeEmbeddedPortablePdb({
108
+ ...pdb,
109
+ metadataRoot: { ...pdb.metadataRoot, streams },
110
+ rowCounts,
111
+ tables: { ...pdb.tables, counts: tableCounts }
112
+ });
113
+ }
114
+
115
+ function decodeUtf8(bytes, context) {
116
+ try {
117
+ return new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes);
118
+ } catch (error) {
119
+ throw new FormatError(`Invalid UTF-8 data: ${error.message}`, { context });
120
+ }
121
+ }
122
+
123
+ function readNullTerminatedUtf8(bytes, offset, context) {
124
+ const terminator = bytes.indexOf(0, offset);
125
+ if (terminator < 0) {
126
+ throw new FormatError(`${context} string is not null-terminated`);
127
+ }
128
+ return {
129
+ value: decodeUtf8(bytes.subarray(offset, terminator), context),
130
+ nextOffset: terminator + 1
131
+ };
132
+ }
133
+
134
+ function formatGuid(bytes) {
135
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
136
+ const first = view.getUint32(0, true).toString(16).padStart(8, "0");
137
+ const second = view.getUint16(4, true).toString(16).padStart(4, "0");
138
+ const third = view.getUint16(6, true).toString(16).padStart(4, "0");
139
+ const tail = [...bytes.subarray(8)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
140
+ return `${first}-${second}-${third}-${tail.slice(0, 4)}-${tail.slice(4)}`;
141
+ }
142
+
143
+ export function validatePortablePdbCodeViewIdentity(pdb, codeView, timeDateStamp) {
144
+ if (!pdb || !(pdb.id instanceof Uint8Array) || !codeView) {
145
+ throw new TypeError("Portable PDB identity validation requires parsed PDB and CodeView data");
146
+ }
147
+ if (codeView.format !== "RSDS") return;
148
+ const guid = formatGuid(pdb.id.subarray(0, 16));
149
+ if (codeView.guid !== guid) {
150
+ throw new FormatError("Embedded Portable PDB identity does not match its CodeView GUID");
151
+ }
152
+ if (codeView.age !== 1) {
153
+ throw new FormatError("Embedded Portable PDB CodeView age must be one");
154
+ }
155
+ if (timeDateStamp !== undefined) {
156
+ const idStamp = new DataView(pdb.id.buffer, pdb.id.byteOffset, pdb.id.byteLength)
157
+ .getUint32(16, true);
158
+ if (timeDateStamp !== idStamp) {
159
+ throw new FormatError("Embedded Portable PDB identity does not match its CodeView stamp");
160
+ }
161
+ }
162
+ }
163
+
164
+ export function decodePortablePdbCustomDebugInformation(
165
+ row,
166
+ heaps,
167
+ documentCount = 0xffffffff,
168
+ methodCount = 0xffffffff
169
+ ) {
170
+ const kindBytes = heaps.getGuid(row.Kind);
171
+ if (!kindBytes) throw new FormatError("Portable PDB custom debug information has a nil Kind");
172
+ const kind = formatGuid(kindBytes);
173
+ const value = heaps.getBlob(row.Value);
174
+ if (kind === embeddedSourceKind) {
175
+ if (row.Parent.target?.table !== 48) {
176
+ throw new FormatError("EmbeddedSource custom debug information must belong to a Document");
177
+ }
178
+ if (value.length < 4) {
179
+ throw new FormatError("EmbeddedSource custom debug information is truncated");
180
+ }
181
+ const format = new DataView(value.buffer, value.byteOffset, 4).getInt32(0, true);
182
+ if (format < 0) {
183
+ throw new UnsupportedFormatError("EmbeddedSource uses a reserved content format");
184
+ }
185
+ const content = value.subarray(4);
186
+ if (format === 0) return { kind, embeddedSource: content.slice() };
187
+ if (format > maxEmbeddedSourceSize) {
188
+ throw new UnsupportedFormatError(
189
+ `EmbeddedSource exceeds the ${maxEmbeddedSourceSize}-byte decompression limit`
190
+ );
191
+ }
192
+ let decompressed;
193
+ try {
194
+ decompressed = inflateRawSync(content, { maxOutputLength: format });
195
+ } catch (error) {
196
+ throw new FormatError(`EmbeddedSource deflate stream is invalid: ${error.message}`);
197
+ }
198
+ if (decompressed.length !== format) {
199
+ throw new FormatError("EmbeddedSource decompressed size does not match its header");
200
+ }
201
+ return { kind, embeddedSource: Uint8Array.from(decompressed) };
202
+ }
203
+ if (kind === sourceLinkKind) {
204
+ if (row.Parent.target?.table !== 0) {
205
+ throw new FormatError("SourceLink custom debug information must belong to the Module");
206
+ }
207
+ let sourceLink;
208
+ try {
209
+ sourceLink = JSON.parse(decodeUtf8(value, "Portable PDB SourceLink"));
210
+ } catch (error) {
211
+ if (error instanceof FormatError) throw error;
212
+ throw new FormatError(`Portable PDB SourceLink JSON is invalid: ${error.message}`);
213
+ }
214
+ if (sourceLink === null || Array.isArray(sourceLink) || typeof sourceLink !== "object") {
215
+ throw new FormatError("Portable PDB SourceLink value must be a JSON object");
216
+ }
217
+ if (sourceLink.documents === null || Array.isArray(sourceLink.documents) ||
218
+ typeof sourceLink.documents !== "object") {
219
+ throw new FormatError("Portable PDB SourceLink documents value must be a JSON object");
220
+ }
221
+ if (Object.values(sourceLink.documents).some((mapping) => typeof mapping !== "string")) {
222
+ throw new FormatError("Portable PDB SourceLink document mappings must be strings");
223
+ }
224
+ return { kind, sourceLink };
225
+ }
226
+ if (kind === defaultNamespaceKind) {
227
+ if (row.Parent.target?.table !== 0) {
228
+ throw new FormatError("DefaultNamespace custom debug information must belong to the Module");
229
+ }
230
+ return {
231
+ kind,
232
+ defaultNamespace: decodeUtf8(value, "Portable PDB DefaultNamespace")
233
+ };
234
+ }
235
+ if (kind === dynamicLocalVariablesKind) {
236
+ if (![51, 52].includes(row.Parent.target?.table)) {
237
+ throw new FormatError(
238
+ "DynamicLocalVariables custom debug information must belong to a LocalVariable or LocalConstant"
239
+ );
240
+ }
241
+ return { kind, dynamicLocalVariables: value.slice() };
242
+ }
243
+ if (kind === compilationOptionsKind) {
244
+ if (row.Parent.target?.table !== 0) {
245
+ throw new FormatError("CompilationOptions custom debug information must belong to the Module");
246
+ }
247
+ const options = [];
248
+ const names = new Set();
249
+ let offset = 0;
250
+ while (offset < value.length) {
251
+ const name = readNullTerminatedUtf8(value, offset, "Portable PDB compilation option name");
252
+ const optionValue = readNullTerminatedUtf8(
253
+ value,
254
+ name.nextOffset,
255
+ "Portable PDB compilation option value"
256
+ );
257
+ if (names.has(name.value)) {
258
+ throw new FormatError(`Duplicate Portable PDB compilation option ${name.value}`);
259
+ }
260
+ names.add(name.value);
261
+ options.push({ name: name.value, value: optionValue.value });
262
+ offset = optionValue.nextOffset;
263
+ }
264
+ return { kind, compilationOptions: options };
265
+ }
266
+ if (kind === compilationMetadataReferencesKind) {
267
+ if (row.Parent.target?.table !== 0) {
268
+ throw new FormatError(
269
+ "CompilationMetadataReferences custom debug information must belong to the Module"
270
+ );
271
+ }
272
+ const references = [];
273
+ let offset = 0;
274
+ while (offset < value.length) {
275
+ const fileName = readNullTerminatedUtf8(
276
+ value,
277
+ offset,
278
+ "Portable PDB metadata-reference filename"
279
+ );
280
+ const aliases = readNullTerminatedUtf8(
281
+ value,
282
+ fileName.nextOffset,
283
+ "Portable PDB metadata-reference aliases"
284
+ );
285
+ offset = aliases.nextOffset;
286
+ if (value.length - offset < 25) {
287
+ throw new FormatError("Portable PDB metadata-reference record is truncated");
288
+ }
289
+ const flags = value[offset];
290
+ if ((flags & ~0x03) !== 0) {
291
+ throw new FormatError("Portable PDB metadata-reference flags contain reserved bits");
292
+ }
293
+ const view = new DataView(value.buffer, value.byteOffset + offset, 9);
294
+ references.push({
295
+ fileName: fileName.value,
296
+ aliases: aliases.value,
297
+ flags,
298
+ timeStamp: view.getUint32(1, true),
299
+ fileSize: view.getUint32(5, true),
300
+ mvid: value.slice(offset + 9, offset + 25)
301
+ });
302
+ offset += 25;
303
+ }
304
+ return { kind, compilationMetadataReferences: references };
305
+ }
306
+ if (kind === typeDefinitionDocumentKind) {
307
+ if (row.Parent.target?.table !== 2) {
308
+ throw new FormatError("TypeDefinitionDocument custom debug information must belong to a TypeDef");
309
+ }
310
+ const reader = new BinaryReader(value, { context: "Portable PDB TypeDefinitionDocument" });
311
+ const documents = [];
312
+ while (reader.remaining > 0) {
313
+ const documentRid = readCompressedUInt(reader);
314
+ if (documentRid === 0 || documentRid > documentCount) {
315
+ throw new FormatError(
316
+ `Portable PDB TypeDefinitionDocument references invalid Document row ${documentRid}`
317
+ );
318
+ }
319
+ documents.push(documentRid);
320
+ }
321
+ return { kind, typeDefinitionDocuments: documents };
322
+ }
323
+ if (kind === asyncMethodSteppingInformationKind) {
324
+ if (row.Parent.target?.table !== 6) {
325
+ throw new FormatError(
326
+ "AsyncMethodSteppingInformation custom debug information must belong to a MethodDef"
327
+ );
328
+ }
329
+ if (value.length < 4) {
330
+ throw new FormatError("Portable PDB AsyncMethodSteppingInformation data is truncated");
331
+ }
332
+ const catchHandlerValue = new DataView(value.buffer, value.byteOffset, 4)
333
+ .getUint32(0, true);
334
+ if (catchHandlerValue > 0x80000000) {
335
+ throw new FormatError("Portable PDB AsyncMethodSteppingInformation has an invalid catch offset");
336
+ }
337
+ const reader = new BinaryReader(value, {
338
+ start: 4,
339
+ context: "Portable PDB AsyncMethodSteppingInformation"
340
+ });
341
+ const steps = [];
342
+ while (reader.remaining > 0) {
343
+ if (reader.remaining < 8) {
344
+ throw new FormatError("Portable PDB AsyncMethodSteppingInformation step is truncated");
345
+ }
346
+ const yieldOffset = reader.u4();
347
+ const resumeOffset = reader.u4();
348
+ const methodRid = readCompressedUInt(reader);
349
+ if (yieldOffset >= 0x80000000 || resumeOffset >= 0x80000000 ||
350
+ methodRid === 0 || methodRid > methodCount) {
351
+ throw new FormatError(
352
+ "Portable PDB AsyncMethodSteppingInformation contains an invalid offset or MethodDef"
353
+ );
354
+ }
355
+ steps.push({ yieldOffset, resumeOffset, methodRid });
356
+ }
357
+ return {
358
+ kind,
359
+ asyncMethodSteppingInformation: {
360
+ catchHandlerOffset: catchHandlerValue === 0 ? -1 : catchHandlerValue - 1,
361
+ steps
362
+ }
363
+ };
364
+ }
365
+ if (kind === stateMachineHoistedLocalScopesKind) {
366
+ if (row.Parent.target?.table !== 6) {
367
+ throw new FormatError(
368
+ "StateMachineHoistedLocalScopes custom debug information must belong to a MethodDef"
369
+ );
370
+ }
371
+ if (value.length % 8 !== 0) {
372
+ throw new FormatError("Portable PDB StateMachineHoistedLocalScopes data is truncated");
373
+ }
374
+ const scopes = [];
375
+ const view = new DataView(value.buffer, value.byteOffset, value.byteLength);
376
+ for (let offset = 0; offset < value.length; offset += 8) {
377
+ const startOffset = view.getUint32(offset, true);
378
+ const length = view.getUint32(offset + 4, true);
379
+ if (startOffset >= 0x80000000 || length === 0 || length >= 0x80000000 ||
380
+ startOffset + length > 0x80000000) {
381
+ throw new FormatError(
382
+ "Portable PDB StateMachineHoistedLocalScopes contains an invalid IL scope"
383
+ );
384
+ }
385
+ scopes.push({ startOffset, length });
386
+ }
387
+ return { kind, stateMachineHoistedLocalScopes: scopes };
388
+ }
389
+ if (kind === editAndContinueStateMachineStateMapKind) {
390
+ if (row.Parent.target?.table !== 6) {
391
+ throw new FormatError(
392
+ "EditAndContinueStateMachineStateMap custom debug information must belong to a MethodDef"
393
+ );
394
+ }
395
+ const reader = new BinaryReader(value, {
396
+ context: "Portable PDB EditAndContinueStateMachineStateMap"
397
+ });
398
+ const stateCount = reader.remaining === 0 ? 0 : readCompressedUInt(reader);
399
+ if (stateCount === 0) {
400
+ if (reader.remaining !== 0) {
401
+ throw new FormatError(
402
+ "Portable PDB EditAndContinueStateMachineStateMap has trailing data"
403
+ );
404
+ }
405
+ return { kind, editAndContinueStateMachineStateMap: [] };
406
+ }
407
+ if (stateCount > Math.floor(reader.remaining / 2)) {
408
+ throw new FormatError(
409
+ "Portable PDB EditAndContinueStateMachineStateMap state list is truncated"
410
+ );
411
+ }
412
+ const syntaxOffsetBaseline = -readCompressedUInt(reader);
413
+ const states = [];
414
+ let previousSyntaxOffset = -1;
415
+ for (let index = 0; index < stateCount; index++) {
416
+ const stateNumber = readCompressedInt(reader);
417
+ const syntaxOffset = readCompressedUInt(reader) + syntaxOffsetBaseline;
418
+ if (syntaxOffset < previousSyntaxOffset) {
419
+ throw new FormatError(
420
+ "Portable PDB EditAndContinueStateMachineStateMap offsets are unsorted"
421
+ );
422
+ }
423
+ states.push({ stateNumber, syntaxOffset });
424
+ previousSyntaxOffset = syntaxOffset;
425
+ }
426
+ if (reader.remaining !== 0) {
427
+ throw new FormatError(
428
+ "Portable PDB EditAndContinueStateMachineStateMap has trailing data"
429
+ );
430
+ }
431
+ return { kind, editAndContinueStateMachineStateMap: states };
432
+ }
433
+ if (kind === editAndContinueLocalSlotMapKind) {
434
+ if (row.Parent.target?.table !== 6) {
435
+ throw new FormatError(
436
+ "EditAndContinueLocalSlotMap custom debug information must belong to a MethodDef"
437
+ );
438
+ }
439
+ const reader = new BinaryReader(value, {
440
+ context: "Portable PDB EditAndContinueLocalSlotMap"
441
+ });
442
+ let syntaxOffsetBaseline = -1;
443
+ const slots = [];
444
+ while (reader.remaining > 0) {
445
+ const header = reader.u1();
446
+ if (header === 0xff) {
447
+ syntaxOffsetBaseline = -readCompressedUInt(reader);
448
+ continue;
449
+ }
450
+ if (header === 0) {
451
+ slots.push({ kind: null, syntaxOffset: null, ordinal: null });
452
+ continue;
453
+ }
454
+ if ((header & 0x40) !== 0 || (header & 0x3f) === 0) {
455
+ throw new FormatError("Portable PDB EditAndContinueLocalSlotMap has an invalid slot kind");
456
+ }
457
+ const kind = (header & 0x3f) - 1;
458
+ const syntaxOffset = readCompressedUInt(reader) + syntaxOffsetBaseline;
459
+ const ordinal = (header & 0x80) !== 0 ? readCompressedUInt(reader) : 0;
460
+ slots.push({ kind, syntaxOffset, ordinal });
461
+ }
462
+ return { kind, editAndContinueLocalSlotMap: { syntaxOffsetBaseline, slots } };
463
+ }
464
+ if (kind === editAndContinueLambdaAndClosureMapKind) {
465
+ if (row.Parent.target?.table !== 6) {
466
+ throw new FormatError(
467
+ "EditAndContinueLambdaAndClosureMap custom debug information must belong to a MethodDef"
468
+ );
469
+ }
470
+ const reader = new BinaryReader(value, {
471
+ context: "Portable PDB EditAndContinueLambdaAndClosureMap"
472
+ });
473
+ const methodOrdinal = readCompressedUInt(reader);
474
+ const syntaxOffsetBaseline = readCompressedUInt(reader);
475
+ const closureCount = readCompressedUInt(reader);
476
+ if (closureCount > reader.remaining) {
477
+ throw new FormatError(
478
+ "Portable PDB EditAndContinueLambdaAndClosureMap closure list is truncated"
479
+ );
480
+ }
481
+ const closures = Array.from({ length: closureCount }, () => readCompressedUInt(reader));
482
+ const lambdas = [];
483
+ while (reader.remaining > 0) {
484
+ const syntaxOffset = readCompressedUInt(reader);
485
+ const closureOrdinal = readCompressedUInt(reader);
486
+ if (closureOrdinal > closureCount) {
487
+ throw new FormatError(
488
+ "Portable PDB EditAndContinueLambdaAndClosureMap references a missing closure"
489
+ );
490
+ }
491
+ lambdas.push({ syntaxOffset, closureOrdinal });
492
+ }
493
+ return {
494
+ kind,
495
+ editAndContinueLambdaAndClosureMap: {
496
+ methodOrdinal,
497
+ syntaxOffsetBaseline,
498
+ closures,
499
+ lambdas
500
+ }
501
+ };
502
+ }
503
+ return { kind };
504
+ }
505
+
506
+ function canRemapMethodScopedCustomDebugInformation(pdb) {
507
+ const recognizedRecords = new Set(
508
+ pdb.customDebugInformation
509
+ .filter((record) =>
510
+ record.Parent.target?.table === 6 &&
511
+ (Array.isArray(record.stateMachineHoistedLocalScopes) ||
512
+ Array.isArray(record.editAndContinueStateMachineStateMap) ||
513
+ record.asyncMethodSteppingInformation ||
514
+ record.editAndContinueLocalSlotMap ||
515
+ record.editAndContinueLambdaAndClosureMap)
516
+ )
517
+ .map((record) => record.rid)
518
+ );
519
+ return (pdb.tables.rows.get(55) ?? []).every((record) =>
520
+ record.Parent.target?.table !== 6 || recognizedRecords.has(record.rid)
521
+ );
522
+ }
523
+
524
+ function encodeAsyncMethodSteppingInformation(information) {
525
+ const { catchHandlerOffset, steps } = information;
526
+ if (!Number.isInteger(catchHandlerOffset) || catchHandlerOffset < -1 ||
527
+ catchHandlerOffset >= 0x80000000 || !Array.isArray(steps) ||
528
+ steps.some((step) =>
529
+ !Number.isInteger(step.yieldOffset) || !Number.isInteger(step.resumeOffset) ||
530
+ step.yieldOffset < 0 || step.resumeOffset < 0 ||
531
+ step.yieldOffset >= 0x80000000 || step.resumeOffset >= 0x80000000 ||
532
+ !Number.isInteger(step.methodRid) || step.methodRid < 1 ||
533
+ step.methodRid >= 0x20000000
534
+ )) {
535
+ throw new FormatError("Portable PDB async stepping data is invalid");
536
+ }
537
+ const bytes = new Uint8Array(4 + steps.reduce((length, step) =>
538
+ length + 8 + appendCompressedUInt(step.methodRid).length, 0
539
+ ));
540
+ const view = new DataView(bytes.buffer);
541
+ view.setUint32(0, catchHandlerOffset + 1, true);
542
+ let offset = 4;
543
+ for (const step of steps) {
544
+ view.setUint32(offset, step.yieldOffset, true);
545
+ view.setUint32(offset + 4, step.resumeOffset, true);
546
+ offset += 8;
547
+ const methodRid = appendCompressedUInt(step.methodRid);
548
+ bytes.set(methodRid, offset);
549
+ offset += methodRid.length;
550
+ }
551
+ return bytes;
552
+ }
553
+
554
+ function remapAsyncMethodReferences(pdb, records, remapMethodRid) {
555
+ const recordsByRid = new Map(
556
+ pdb.customDebugInformation
557
+ .filter((record) => record.asyncMethodSteppingInformation)
558
+ .map((record) => [record.rid, record.asyncMethodSteppingInformation])
559
+ );
560
+ let streams = null;
561
+ const updatedRecords = records.map((record) => {
562
+ const information = recordsByRid.get(record.rid);
563
+ if (!information) return { ...record };
564
+ const steps = information.steps.map((step) => ({
565
+ ...step,
566
+ methodRid: remapMethodRid(step.methodRid)
567
+ }));
568
+ if (steps.every((step, index) =>
569
+ step.methodRid === information.steps[index].methodRid
570
+ )) {
571
+ return { ...record };
572
+ }
573
+ streams ??= new Map([...pdb.metadataRoot.streams].map(([name, stream]) =>
574
+ [name, { ...stream, bytes: Uint8Array.from(stream.bytes) }]
575
+ ));
576
+ const blobStream = streams.get("#Blob");
577
+ if (!blobStream) throw new FormatError("Portable PDB metadata is missing its #Blob heap");
578
+ const appended = appendBlobHeap(
579
+ blobStream.bytes,
580
+ encodeAsyncMethodSteppingInformation({ ...information, steps })
581
+ );
582
+ blobStream.bytes = appended.heap;
583
+ return { ...record, Value: appended.index };
584
+ });
585
+ return { records: updatedRecords, streams };
586
+ }
587
+
588
+ function sortPortablePdbCustomDebugInformation(records) {
589
+ return records
590
+ .slice()
591
+ .sort((left, right) =>
592
+ left.Parent.raw - right.Parent.raw || left.rid - right.rid
593
+ )
594
+ .map((record, index) => ({ ...record, rid: index + 1 }));
595
+ }
596
+
597
+ export function decodePortablePdbDocumentName(blob, heaps) {
598
+ if (blob.length < 2) {
599
+ throw new FormatError("Portable PDB document name is truncated", {
600
+ context: "Portable PDB Document.Name"
601
+ });
602
+ }
603
+ const first = blob[0];
604
+ const separatorLength = first < 0x80 ? 1 :
605
+ first >= 0xc2 && first <= 0xdf ? 2 :
606
+ first >= 0xe0 && first <= 0xef ? 3 :
607
+ first >= 0xf0 && first <= 0xf4 ? 4 : 0;
608
+ if (separatorLength === 0 || separatorLength >= blob.length) {
609
+ throw new FormatError("Portable PDB document name has an invalid separator", {
610
+ context: "Portable PDB Document.Name"
611
+ });
612
+ }
613
+ const separator = first === 0 ? "" : decodeUtf8(blob.subarray(0, separatorLength), "Portable PDB document separator");
614
+ const reader = new BinaryReader(blob, {
615
+ start: separatorLength,
616
+ context: "Portable PDB Document.Name"
617
+ });
618
+ const parts = [];
619
+ while (reader.remaining > 0) {
620
+ const index = readCompressedUInt(reader);
621
+ parts.push(index === 0 ? "" : decodeUtf8(heaps.getBlob(index), "Portable PDB document name part"));
622
+ }
623
+ if (parts.length === 0) {
624
+ throw new FormatError("Portable PDB document name has no parts", {
625
+ context: "Portable PDB Document.Name"
626
+ });
627
+ }
628
+ return parts.join(separator);
629
+ }
630
+
631
+ export function encodePortablePdbDocumentName(separator, partBlobIndices) {
632
+ if (typeof separator !== "string" ||
633
+ (separator !== "" && [...separator].length !== 1) ||
634
+ separator.includes("\0")) {
635
+ throw new TypeError("Portable PDB document-name separator must be empty or one Unicode character");
636
+ }
637
+ if (!Array.isArray(partBlobIndices) || partBlobIndices.length === 0) {
638
+ throw new TypeError("Portable PDB document names require at least one blob index");
639
+ }
640
+ const separatorBytes = new TextEncoder().encode(separator);
641
+ if (new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(separatorBytes) !== separator) {
642
+ throw new TypeError("Portable PDB document-name separator must contain valid Unicode");
643
+ }
644
+ const output = [...(separatorBytes.length === 0 ? [0] : separatorBytes)];
645
+ for (const index of partBlobIndices) {
646
+ if (!Number.isInteger(index) || index < 0 || index >= 0x20000000) {
647
+ throw new RangeError("Portable PDB document-name blob index is out of range");
648
+ }
649
+ output.push(...appendCompressedUInt(index));
650
+ }
651
+ return Uint8Array.from(output);
652
+ }
653
+
654
+ export function appendPortablePdbDocumentName(blobHeap, name, separator) {
655
+ if (!(blobHeap instanceof Uint8Array)) {
656
+ throw new TypeError("Portable PDB document-name encoding requires a Blob heap");
657
+ }
658
+ if (typeof name !== "string") {
659
+ throw new TypeError("Portable PDB document names must be strings");
660
+ }
661
+ if (typeof separator !== "string" ||
662
+ (separator !== "" && [...separator].length !== 1) ||
663
+ separator.includes("\0")) {
664
+ throw new TypeError("Portable PDB document-name separator must be empty or one Unicode character");
665
+ }
666
+ const parts = separator === "" ? [name] : name.split(separator);
667
+ let updatedHeap = Uint8Array.from(blobHeap);
668
+ const partBlobIndices = parts.map((part) => {
669
+ if (part === "") return 0;
670
+ const bytes = new TextEncoder().encode(part);
671
+ if (new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes) !== part) {
672
+ throw new TypeError("Portable PDB document-name parts must contain valid Unicode");
673
+ }
674
+ const appended = appendBlobHeap(updatedHeap, bytes);
675
+ updatedHeap = appended.heap;
676
+ return appended.index;
677
+ });
678
+ const blob = encodePortablePdbDocumentName(separator, partBlobIndices);
679
+ const appended = appendBlobHeap(updatedHeap, blob);
680
+ return {
681
+ heap: appended.heap,
682
+ nameBlobIndex: appended.index,
683
+ nameBlob: blob
684
+ };
685
+ }
686
+
687
+ export function decodePortablePdbSequencePoints(blob, { documentRid, documentCount, localSignatureCount }) {
688
+ const reader = new BinaryReader(blob, { context: "Portable PDB sequence points" });
689
+ const localSignatureRid = readCompressedUInt(reader);
690
+ if (localSignatureRid > localSignatureCount) {
691
+ throw new FormatError("Portable PDB sequence points reference a missing StandAloneSig row");
692
+ }
693
+ let currentDocumentRid = documentRid;
694
+ if (currentDocumentRid === 0) currentDocumentRid = readCompressedUInt(reader);
695
+ if (currentDocumentRid === 0 || currentDocumentRid > documentCount) {
696
+ throw new FormatError("Portable PDB sequence points reference a missing Document row");
697
+ }
698
+
699
+ const points = [];
700
+ let previousOffset = 0;
701
+ let previousStartLine = null;
702
+ let previousStartColumn = null;
703
+ while (reader.remaining > 0) {
704
+ const deltaOffset = readCompressedUInt(reader);
705
+ if (deltaOffset === 0 && points.length > 0) {
706
+ currentDocumentRid = readCompressedUInt(reader);
707
+ if (currentDocumentRid === 0 || currentDocumentRid > documentCount) {
708
+ throw new FormatError("Portable PDB document record references a missing Document row");
709
+ }
710
+ continue;
711
+ }
712
+
713
+ const offset = points.length === 0 ? deltaOffset : previousOffset + deltaOffset;
714
+ if ((points.length > 0 && deltaOffset === 0) || offset >= 0x20000000) {
715
+ throw new FormatError("Portable PDB sequence-point IL offsets are invalid");
716
+ }
717
+ const deltaLines = readCompressedUInt(reader);
718
+ const deltaColumns = deltaLines === 0 ? readCompressedUInt(reader) : readCompressedInt(reader);
719
+ const hidden = deltaLines === 0 && deltaColumns === 0;
720
+ let startLine = 0xfeefee;
721
+ let startColumn = 0;
722
+ let endLine = 0xfeefee;
723
+ let endColumn = 0;
724
+ if (!hidden) {
725
+ const lineDelta = previousStartLine === null ? readCompressedUInt(reader) : readCompressedInt(reader);
726
+ const columnDelta = previousStartColumn === null ? readCompressedUInt(reader) : readCompressedInt(reader);
727
+ startLine = (previousStartLine ?? 0) + lineDelta;
728
+ startColumn = (previousStartColumn ?? 0) + columnDelta;
729
+ endLine = startLine + deltaLines;
730
+ endColumn = startColumn + deltaColumns;
731
+ if (startLine < 0 || startLine >= 0x20000000 || startLine === 0xfeefee ||
732
+ endLine < startLine || endLine >= 0x20000000 || endLine === 0xfeefee ||
733
+ startColumn < 0 || startColumn >= 0x10000 || endColumn < 0 || endColumn >= 0x10000 ||
734
+ (startLine === endLine && endColumn <= startColumn)) {
735
+ throw new FormatError("Portable PDB sequence-point source spans are invalid");
736
+ }
737
+ previousStartLine = startLine;
738
+ previousStartColumn = startColumn;
739
+ }
740
+ points.push({
741
+ offset,
742
+ documentRid: currentDocumentRid,
743
+ hidden,
744
+ startLine,
745
+ startColumn,
746
+ endLine,
747
+ endColumn
748
+ });
749
+ previousOffset = offset;
750
+ }
751
+ return { localSignatureRid, points };
752
+ }
753
+
754
+ export function encodePortablePdbSequencePoints(
755
+ sequencePoints,
756
+ { documentRid, documentCount, localSignatureCount } = {}
757
+ ) {
758
+ if (!sequencePoints || !Array.isArray(sequencePoints.points)) {
759
+ throw new TypeError("Portable PDB sequence points require a points array");
760
+ }
761
+ if (![documentRid, documentCount, localSignatureCount].every((value) =>
762
+ Number.isInteger(value) && value >= 0
763
+ )) {
764
+ throw new TypeError("Portable PDB sequence-point context counts must be non-negative integers");
765
+ }
766
+ const { localSignatureRid, points } = sequencePoints;
767
+ if (!Number.isInteger(localSignatureRid) || localSignatureRid < 0 ||
768
+ localSignatureRid > localSignatureCount) {
769
+ throw new RangeError("Portable PDB sequence points reference a missing StandAloneSig row");
770
+ }
771
+ if (documentRid > documentCount) {
772
+ throw new RangeError("Portable PDB sequence points reference a missing Document row");
773
+ }
774
+ const initialDocumentRid = documentRid === 0
775
+ ? points[0]?.documentRid
776
+ : documentRid;
777
+ if (initialDocumentRid === undefined) {
778
+ throw new RangeError("Portable PDB sequence points with a nil Document require an initial document");
779
+ }
780
+ if (!Number.isInteger(initialDocumentRid) || initialDocumentRid < 1 ||
781
+ initialDocumentRid > documentCount) {
782
+ throw new RangeError("Portable PDB sequence points reference a missing Document row");
783
+ }
784
+
785
+ const output = [...appendCompressedUInt(localSignatureRid)];
786
+ if (documentRid === 0) output.push(...appendCompressedUInt(initialDocumentRid));
787
+ let currentDocumentRid = initialDocumentRid;
788
+ let previousOffset = 0;
789
+ let previousStartLine = null;
790
+ let previousStartColumn = null;
791
+ for (let index = 0; index < points.length; index++) {
792
+ const point = points[index];
793
+ if (!point || !Number.isInteger(point.documentRid) ||
794
+ point.documentRid < 1 || point.documentRid > documentCount) {
795
+ throw new RangeError("Portable PDB sequence point references a missing Document row");
796
+ }
797
+ if (index === 0 && point.documentRid !== initialDocumentRid) {
798
+ throw new FormatError("First Portable PDB sequence point does not match its initial Document");
799
+ }
800
+ if (index > 0 && point.documentRid !== currentDocumentRid) {
801
+ output.push(0, ...appendCompressedUInt(point.documentRid));
802
+ currentDocumentRid = point.documentRid;
803
+ }
804
+ if (!Number.isInteger(point.offset) || point.offset < 0 || point.offset >= 0x20000000 ||
805
+ (index > 0 && point.offset <= previousOffset)) {
806
+ throw new RangeError("Portable PDB sequence-point IL offsets must be increasing and in range");
807
+ }
808
+ output.push(...appendCompressedUInt(index === 0 ? point.offset : point.offset - previousOffset));
809
+ if (typeof point.hidden !== "boolean") {
810
+ throw new TypeError("Portable PDB sequence points require a hidden flag");
811
+ }
812
+ if (point.hidden) {
813
+ output.push(0, 0);
814
+ } else {
815
+ const { startLine, startColumn, endLine, endColumn } = point;
816
+ if (![startLine, startColumn, endLine, endColumn].every(Number.isInteger) ||
817
+ startLine < 0 || startLine >= 0x20000000 || startLine === 0xfeefee ||
818
+ endLine < startLine || endLine >= 0x20000000 || endLine === 0xfeefee ||
819
+ startColumn < 0 || startColumn >= 0x10000 ||
820
+ endColumn < 0 || endColumn >= 0x10000 ||
821
+ (startLine === endLine && endColumn <= startColumn)) {
822
+ throw new FormatError("Portable PDB sequence-point source spans are invalid");
823
+ }
824
+ const deltaLines = endLine - startLine;
825
+ const deltaColumns = endColumn - startColumn;
826
+ output.push(...appendCompressedUInt(deltaLines));
827
+ output.push(...(deltaLines === 0
828
+ ? appendCompressedUInt(deltaColumns)
829
+ : appendCompressedInt(deltaColumns)));
830
+ output.push(...(previousStartLine === null
831
+ ? appendCompressedUInt(startLine)
832
+ : appendCompressedInt(startLine - previousStartLine)));
833
+ output.push(...(previousStartColumn === null
834
+ ? appendCompressedUInt(startColumn)
835
+ : appendCompressedInt(startColumn - previousStartColumn)));
836
+ previousStartLine = startLine;
837
+ previousStartColumn = startColumn;
838
+ }
839
+ previousOffset = point.offset;
840
+ }
841
+ return Uint8Array.from(output);
842
+ }
843
+
844
+ function validateLocalScopes(scopes, variables, constants, heaps) {
845
+ const variableOwners = new Set();
846
+ const constantOwners = new Set();
847
+ const nestedScopes = new Map();
848
+ let previous = null;
849
+ for (const scope of scopes) {
850
+ const key = [scope.Method.rid, scope.StartOffset, -scope.Length];
851
+ if (previous && (key[0] < previous[0] ||
852
+ (key[0] === previous[0] && (key[1] < previous[1] ||
853
+ (key[1] === previous[1] && key[2] < previous[2]))))) {
854
+ throw new FormatError("Portable PDB LocalScope rows are not ordered");
855
+ }
856
+ previous = key;
857
+ if (scope.Method.rid === 0) {
858
+ throw new FormatError(`Portable PDB LocalScope row ${scope.rid} has a nil Method`);
859
+ }
860
+ const end = scope.StartOffset + scope.Length;
861
+ if (scope.Length === 0 || scope.StartOffset >= 0x80000000 || end >= 0x80000000) {
862
+ throw new FormatError(`Portable PDB LocalScope row ${scope.rid} has an invalid IL range`);
863
+ }
864
+ const stack = nestedScopes.get(scope.Method.rid) ?? [];
865
+ while (stack.length > 0 && stack.at(-1).end <= scope.StartOffset) stack.pop();
866
+ if (stack.length > 0 && end > stack.at(-1).end) {
867
+ throw new FormatError(`Portable PDB LocalScope row ${scope.rid} partially overlaps another scope`);
868
+ }
869
+ stack.push({ start: scope.StartOffset, end });
870
+ nestedScopes.set(scope.Method.rid, stack);
871
+ }
872
+
873
+ const validateList = (column, rows, owners, kind) => {
874
+ if (rows.length === 0) return;
875
+ if (scopes.length === 0 || scopes[0][column].rid !== 1) {
876
+ throw new FormatError(`Portable PDB ${kind} rows are not owned by a LocalScope`);
877
+ }
878
+ for (let index = 0; index < scopes.length; index++) {
879
+ const scope = scopes[index];
880
+ const start = scope[column].rid;
881
+ const end = scopes[index + 1]?.[column].rid ?? rows.length + 1;
882
+ for (let rid = start; rid < end; rid++) {
883
+ const row = rows[rid - 1];
884
+ if (!row || owners.has(rid)) {
885
+ throw new FormatError(`Portable PDB ${kind} ownership ranges are invalid`);
886
+ }
887
+ owners.add(rid);
888
+ if (kind === "LocalVariable") {
889
+ if ((row.Attributes & ~1) !== 0) {
890
+ throw new FormatError(`Portable PDB LocalVariable row ${rid} has invalid attributes`);
891
+ }
892
+ const name = heaps.getString(row.Name);
893
+ const key = `${scope.rid}:${row.Index}`;
894
+ const namedKey = `${scope.rid}:${name}`;
895
+ if (owners.has(key) || owners.has(namedKey)) {
896
+ throw new FormatError(`Portable PDB LocalVariable row ${rid} duplicates a scope variable`);
897
+ }
898
+ owners.add(key);
899
+ owners.add(namedKey);
900
+ } else {
901
+ const name = heaps.getString(row.Name);
902
+ const key = `${scope.rid}:${name}`;
903
+ if (owners.has(key)) {
904
+ throw new FormatError(`Portable PDB LocalConstant row ${rid} duplicates a scope constant`);
905
+ }
906
+ if (heaps.getBlob(row.Signature).length === 0) {
907
+ throw new FormatError(`Portable PDB LocalConstant row ${rid} has an empty signature`);
908
+ }
909
+ owners.add(key);
910
+ }
911
+ }
912
+ }
913
+ };
914
+ validateList("VariableList", variables, variableOwners, "LocalVariable");
915
+ validateList("ConstantList", constants, constantOwners, "LocalConstant");
916
+ }
917
+
918
+ function validateImportScopes(scopes) {
919
+ const states = new Uint8Array(scopes.length + 1);
920
+ for (let rid = 1; rid <= scopes.length; rid++) {
921
+ if (states[rid] === 2) continue;
922
+ const path = [];
923
+ let current = rid;
924
+ while (current !== 0 && states[current] !== 2) {
925
+ if (states[current] === 1) {
926
+ throw new FormatError("Portable PDB ImportScope parent relationships contain a cycle");
927
+ }
928
+ states[current] = 1;
929
+ path.push(current);
930
+ current = scopes[current - 1].Parent.rid;
931
+ }
932
+ for (const item of path) states[item] = 2;
933
+ }
934
+ }
935
+
936
+ export function parsePortablePdbTables(tables, heaps, typeSystemCounts) {
937
+ const documentRows = tables.get(48);
938
+ const documents = [];
939
+ const documentNames = new Set();
940
+ for (const row of documentRows) {
941
+ if (row.Name === 0) {
942
+ throw new FormatError(`Portable PDB Document row ${row.rid} has a nil name`);
943
+ }
944
+ const name = decodePortablePdbDocumentName(heaps.getBlob(row.Name), heaps);
945
+ if (documentNames.has(name)) {
946
+ throw new FormatError("Portable PDB Document table contains duplicate names");
947
+ }
948
+ documentNames.add(name);
949
+ heaps.getGuid(row.HashAlgorithm);
950
+ heaps.getBlob(row.Hash);
951
+ heaps.getGuid(row.Language);
952
+ documents.push({ ...row, name });
953
+ }
954
+
955
+ const debugRows = tables.get(49);
956
+ if (debugRows.length !== 0 && debugRows.length !== typeSystemCounts[6]) {
957
+ throw new FormatError("Portable PDB MethodDebugInformation row count must match MethodDef");
958
+ }
959
+ const methodDebugInformation = debugRows.map((row) => {
960
+ if (row.SequencePoints === 0) return { ...row, localSignatureRid: 0, sequencePoints: [] };
961
+ const parsed = decodePortablePdbSequencePoints(heaps.getBlob(row.SequencePoints), {
962
+ documentRid: row.Document.rid,
963
+ documentCount: documentRows.length,
964
+ localSignatureCount: typeSystemCounts[17]
965
+ });
966
+ return { ...row, ...parsed, sequencePoints: parsed.points };
967
+ });
968
+ validateLocalScopes(
969
+ tables.get(50),
970
+ tables.get(51),
971
+ tables.get(52),
972
+ heaps
973
+ );
974
+ const importScopes = tables.get(53);
975
+ validateImportScopes(importScopes);
976
+ for (const row of importScopes) heaps.getBlob(row.Imports);
977
+ const stateMachineMethods = tables.get(54);
978
+ const kickoffMethods = new Set();
979
+ let previousMoveNext = 0;
980
+ for (const row of stateMachineMethods) {
981
+ if (row.MoveNextMethod.rid <= previousMoveNext ||
982
+ kickoffMethods.has(row.KickoffMethod.rid)) {
983
+ throw new FormatError("Portable PDB StateMachineMethod rows are unsorted or duplicated");
984
+ }
985
+ previousMoveNext = row.MoveNextMethod.rid;
986
+ kickoffMethods.add(row.KickoffMethod.rid);
987
+ }
988
+ let previousCustomDebugParent = 0;
989
+ for (const row of tables.get(55)) {
990
+ if (row.Parent.raw < previousCustomDebugParent || row.Kind === 0) {
991
+ throw new FormatError("Portable PDB CustomDebugInformation rows are unsorted or have a nil Kind");
992
+ }
993
+ previousCustomDebugParent = row.Parent.raw;
994
+ heaps.getGuid(row.Kind);
995
+ heaps.getBlob(row.Value);
996
+ }
997
+ const customDebugInformation = tables.get(55).map((row) => ({
998
+ ...row,
999
+ ...decodePortablePdbCustomDebugInformation(
1000
+ row,
1001
+ heaps,
1002
+ documents.length,
1003
+ typeSystemCounts[6]
1004
+ )
1005
+ }));
1006
+ return { documents, methodDebugInformation, stateMachineMethods, customDebugInformation };
1007
+ }
1008
+
1009
+ export function serializeEmbeddedPortablePdb(pdb) {
1010
+ if (!pdb?.metadataRoot?.streams || !(pdb.tables?.rows instanceof Map) ||
1011
+ !Array.isArray(pdb.tables.counts)) {
1012
+ throw new TypeError("Embedded Portable PDB serialization requires parsed PDB data");
1013
+ }
1014
+ const streams = new Map([...pdb.metadataRoot.streams].map(([name, stream]) =>
1015
+ [name, Uint8Array.from(stream.bytes)]
1016
+ ));
1017
+ const tableStreamName = streams.has("#~") ? "#~" : streams.has("#-") ? "#-" : null;
1018
+ if (!tableStreamName || !streams.has("#Pdb")) {
1019
+ throw new FormatError("Embedded Portable PDB is missing a required metadata stream");
1020
+ }
1021
+ streams.set(tableStreamName, serializeTablesStream(pdb.tables, streams));
1022
+ const metadata = serializeMetadataRoot(pdb.metadataRoot, streams);
1023
+ if (metadata.length === 0 || metadata.length > maxEmbeddedPortablePdbSize ||
1024
+ metadata.length > 0xffffffff) {
1025
+ throw new UnsupportedFormatError("Serialized Portable PDB metadata exceeds the supported size");
1026
+ }
1027
+ const compressed = deflateRawSync(metadata);
1028
+ if (compressed.length > 0xffffffff - 8) {
1029
+ throw new RangeError("Compressed Portable PDB exceeds the container size limit");
1030
+ }
1031
+ const output = new Uint8Array(8 + compressed.length);
1032
+ output.set([0x4d, 0x50, 0x44, 0x42]);
1033
+ new DataView(output.buffer).setUint32(4, metadata.length, true);
1034
+ output.set(compressed, 8);
1035
+ return output;
1036
+ }
1037
+
1038
+ export function refreshEmbeddedPortablePdbId(pdb) {
1039
+ if (!pdb?.metadataRoot?.streams || !(pdb.tables?.rows instanceof Map) ||
1040
+ !Array.isArray(pdb.tables.counts)) {
1041
+ throw new TypeError("Portable PDB ID generation requires parsed PDB data");
1042
+ }
1043
+ const streams = new Map([...pdb.metadataRoot.streams].map(([name, stream]) =>
1044
+ [name, Uint8Array.from(stream.bytes)]
1045
+ ));
1046
+ const tableStreamName = streams.has("#~") ? "#~" : streams.has("#-") ? "#-" : null;
1047
+ const pdbStream = streams.get("#Pdb");
1048
+ if (!tableStreamName || !pdbStream || pdbStream.length < 20) {
1049
+ throw new FormatError("Portable PDB ID generation requires #Pdb and table streams");
1050
+ }
1051
+ streams.set(tableStreamName, serializeTablesStream(pdb.tables, streams));
1052
+ pdbStream.fill(0, 0, 20);
1053
+ const metadata = serializeMetadataRoot(pdb.metadataRoot, streams);
1054
+ const hashes = new Map();
1055
+ for (const [algorithm, name] of [
1056
+ ["SHA256", "sha256"],
1057
+ ["SHA384", "sha384"],
1058
+ ["SHA512", "sha512"]
1059
+ ]) {
1060
+ hashes.set(algorithm, Uint8Array.from(createHash(name).update(metadata).digest()));
1061
+ }
1062
+ const id = hashes.get("SHA256").slice(0, 20);
1063
+ pdbStream.set(id);
1064
+ const updatedStreams = new Map([...streams].map(([name, bytes]) => [
1065
+ name,
1066
+ { ...pdb.metadataRoot.streams.get(name), bytes }
1067
+ ]));
1068
+ const updatedPdb = {
1069
+ ...pdb,
1070
+ metadataRoot: { ...pdb.metadataRoot, streams: updatedStreams }
1071
+ };
1072
+ return {
1073
+ bytes: serializeEmbeddedPortablePdb(updatedPdb),
1074
+ id,
1075
+ hashes
1076
+ };
1077
+ }
1078
+
1079
+ export function replaceEmbeddedPortablePdbDocumentName(pdb, documentRid, name, separator) {
1080
+ if (!pdb?.metadataRoot?.streams || !(pdb.tables?.rows instanceof Map) ||
1081
+ !Array.isArray(pdb.tables.counts) || !pdb.heaps?.getBlob ||
1082
+ !Array.isArray(pdb.documents)) {
1083
+ throw new TypeError("Portable PDB document editing requires parsed PDB data");
1084
+ }
1085
+ if (!Number.isInteger(documentRid) || documentRid < 1 ||
1086
+ documentRid > pdb.documents.length) {
1087
+ throw new RangeError(`Invalid Portable PDB Document row ID ${documentRid}`);
1088
+ }
1089
+ if (typeof name !== "string" || name.length === 0 || name.includes("\0")) {
1090
+ throw new TypeError("Portable PDB document names must be nonempty null-free strings");
1091
+ }
1092
+ const documentRows = pdb.tables.rows.get(48) ?? [];
1093
+ const documentRow = documentRows[documentRid - 1];
1094
+ if (!documentRow || documentRow.rid !== documentRid) {
1095
+ throw new FormatError(`Portable PDB Document row ${documentRid} is missing`);
1096
+ }
1097
+ const streams = new Map([...pdb.metadataRoot.streams].map(([streamName, stream]) =>
1098
+ [streamName, { ...stream, bytes: Uint8Array.from(stream.bytes) }]
1099
+ ));
1100
+ const blobStream = streams.get("#Blob");
1101
+ if (!blobStream) throw new FormatError("Portable PDB metadata is missing its #Blob heap");
1102
+ const documentName = appendPortablePdbDocumentName(blobStream.bytes, name, separator);
1103
+ blobStream.bytes = documentName.heap;
1104
+ for (const document of pdb.documents) {
1105
+ if (document.rid !== documentRid && document.name === name) {
1106
+ throw new FormatError(`Portable PDB Document name ${JSON.stringify(name)} already exists`);
1107
+ }
1108
+ }
1109
+ const rows = new Map(pdb.tables.rows);
1110
+ rows.set(48, documentRows.map((row) =>
1111
+ row.rid === documentRid ? { ...row, Name: documentName.nameBlobIndex } : { ...row }
1112
+ ));
1113
+ return serializeEmbeddedPortablePdb({
1114
+ ...pdb,
1115
+ metadataRoot: { ...pdb.metadataRoot, streams },
1116
+ tables: { ...pdb.tables, rows }
1117
+ });
1118
+ }
1119
+
1120
+ export function replaceEmbeddedPortablePdbDocumentMetadata(pdb, documentRid, options) {
1121
+ if (!pdb?.metadataRoot?.streams || !(pdb.tables?.rows instanceof Map) ||
1122
+ !Array.isArray(pdb.tables.counts) || !pdb.heaps?.getBlob ||
1123
+ !pdb.heaps?.getGuid) {
1124
+ throw new TypeError("Portable PDB document editing requires parsed PDB data");
1125
+ }
1126
+ if (!Number.isInteger(documentRid) || documentRid < 1 ||
1127
+ documentRid > pdb.tables.counts[48]) {
1128
+ throw new RangeError(`Invalid Portable PDB Document row ID ${documentRid}`);
1129
+ }
1130
+ if (options === null || typeof options !== "object" || Array.isArray(options)) {
1131
+ throw new TypeError("Portable PDB document metadata options must be an object");
1132
+ }
1133
+ const documentRows = pdb.tables.rows.get(48) ?? [];
1134
+ if (documentRows.length !== pdb.tables.counts[48]) {
1135
+ throw new FormatError("Portable PDB Document row count is inconsistent");
1136
+ }
1137
+ const documentRow = documentRows[documentRid - 1];
1138
+ if (!documentRow || documentRow.rid !== documentRid) {
1139
+ throw new FormatError(`Portable PDB Document row ${documentRid} is missing`);
1140
+ }
1141
+ const streams = new Map([...pdb.metadataRoot.streams].map(([name, stream]) =>
1142
+ [name, { ...stream, bytes: Uint8Array.from(stream.bytes) }]
1143
+ ));
1144
+ const oldHashAlgorithmGuid = pdb.heaps.getGuid(documentRow.HashAlgorithm) ?? null;
1145
+ const oldHash = pdb.heaps.getBlob(documentRow.Hash) ?? new Uint8Array();
1146
+ const oldLanguageGuid = pdb.heaps.getGuid(documentRow.Language) ?? null;
1147
+ const updatesHashAlgorithm = Object.hasOwn(options, "hashAlgorithmGuid");
1148
+ const updatesHash = Object.hasOwn(options, "hash");
1149
+ const updatesLanguage = Object.hasOwn(options, "languageGuid");
1150
+ const hashAlgorithmGuid = updatesHashAlgorithm
1151
+ ? options.hashAlgorithmGuid
1152
+ : oldHashAlgorithmGuid;
1153
+ const hash = updatesHash
1154
+ ? options.hash === null ? null : options.hash
1155
+ : oldHash;
1156
+ const languageGuid = updatesLanguage
1157
+ ? options.languageGuid
1158
+ : oldLanguageGuid;
1159
+ if ((hashAlgorithmGuid !== null &&
1160
+ (!(hashAlgorithmGuid instanceof Uint8Array) || hashAlgorithmGuid.length !== 16)) ||
1161
+ (hash !== null && !(hash instanceof Uint8Array)) ||
1162
+ (languageGuid !== null &&
1163
+ (!(languageGuid instanceof Uint8Array) || languageGuid.length !== 16)) ||
1164
+ (hashAlgorithmGuid === null && hash !== null && hash.length > 0)) {
1165
+ throw new TypeError("Portable PDB document hashes and GUIDs have invalid values");
1166
+ }
1167
+ const appendGuid = (guid) => {
1168
+ if (guid === null) return 0;
1169
+ const guidStream = streams.get("#GUID");
1170
+ if (!guidStream) throw new FormatError("Portable PDB metadata is missing its #GUID heap");
1171
+ if (guidStream.bytes.length % 16 !== 0) {
1172
+ throw new FormatError("Portable PDB GUID heap size is not a multiple of 16 bytes");
1173
+ }
1174
+ const index = guidStream.bytes.length / 16 + 1;
1175
+ const updated = new Uint8Array(guidStream.bytes.length + 16);
1176
+ updated.set(guidStream.bytes);
1177
+ updated.set(guid, guidStream.bytes.length);
1178
+ guidStream.bytes = updated;
1179
+ return index;
1180
+ };
1181
+ const hashAlgorithm = updatesHashAlgorithm
1182
+ ? appendGuid(hashAlgorithmGuid)
1183
+ : documentRow.HashAlgorithm;
1184
+ const language = updatesLanguage ? appendGuid(languageGuid) : documentRow.Language;
1185
+ let hashIndex = documentRow.Hash;
1186
+ if (updatesHash) {
1187
+ if (hash === null) {
1188
+ hashIndex = 0;
1189
+ } else {
1190
+ const blobStream = streams.get("#Blob");
1191
+ if (!blobStream) throw new FormatError("Portable PDB metadata is missing its #Blob heap");
1192
+ const appendedHash = appendBlobHeap(blobStream.bytes, hash);
1193
+ blobStream.bytes = appendedHash.heap;
1194
+ hashIndex = appendedHash.index;
1195
+ }
1196
+ }
1197
+ const rows = new Map(pdb.tables.rows);
1198
+ rows.set(48, documentRows.map((row) =>
1199
+ row.rid === documentRid
1200
+ ? {
1201
+ ...row,
1202
+ HashAlgorithm: hashAlgorithm,
1203
+ Hash: hashIndex,
1204
+ Language: language
1205
+ }
1206
+ : { ...row }
1207
+ ));
1208
+ return serializeEmbeddedPortablePdb({
1209
+ ...pdb,
1210
+ metadataRoot: { ...pdb.metadataRoot, streams },
1211
+ tables: { ...pdb.tables, rows }
1212
+ });
1213
+ }
1214
+
1215
+ function addPortablePdbDocumentToParsedPdb(
1216
+ pdb,
1217
+ name,
1218
+ {
1219
+ separator = "/",
1220
+ hashAlgorithmGuid = null,
1221
+ hash = new Uint8Array(),
1222
+ languageGuid = null
1223
+ } = {}
1224
+ ) {
1225
+ if (!pdb?.metadataRoot?.streams || !(pdb.tables?.rows instanceof Map) ||
1226
+ !Array.isArray(pdb.tables.counts) || !Array.isArray(pdb.documents)) {
1227
+ throw new TypeError("Portable PDB document editing requires parsed PDB data");
1228
+ }
1229
+ if (typeof name !== "string" || name.length === 0 || name.includes("\0")) {
1230
+ throw new TypeError("Portable PDB document names must be nonempty null-free strings");
1231
+ }
1232
+ if (!(hash instanceof Uint8Array) ||
1233
+ (hashAlgorithmGuid === null && hash.length > 0) ||
1234
+ (hashAlgorithmGuid !== null &&
1235
+ (!(hashAlgorithmGuid instanceof Uint8Array) || hashAlgorithmGuid.length !== 16)) ||
1236
+ (languageGuid !== null &&
1237
+ (!(languageGuid instanceof Uint8Array) || languageGuid.length !== 16))) {
1238
+ throw new TypeError("Portable PDB document hashes and GUIDs have invalid values");
1239
+ }
1240
+ const documentRows = pdb.tables.rows.get(48) ?? [];
1241
+ if (documentRows.length !== pdb.tables.counts[48] ||
1242
+ pdb.documents.length !== documentRows.length) {
1243
+ throw new FormatError("Portable PDB Document row count is inconsistent");
1244
+ }
1245
+ if (pdb.documents.some((document) => document.name === name)) {
1246
+ throw new FormatError(`Portable PDB Document name ${JSON.stringify(name)} already exists`);
1247
+ }
1248
+ const streams = new Map([...pdb.metadataRoot.streams].map(([streamName, stream]) =>
1249
+ [streamName, { ...stream, bytes: Uint8Array.from(stream.bytes) }]
1250
+ ));
1251
+ const blobStream = streams.get("#Blob");
1252
+ if (!blobStream) throw new FormatError("Portable PDB metadata is missing its #Blob heap");
1253
+ const documentName = appendPortablePdbDocumentName(blobStream.bytes, name, separator);
1254
+ blobStream.bytes = documentName.heap;
1255
+ const hashBlob = appendBlobHeap(blobStream.bytes, hash);
1256
+ blobStream.bytes = hashBlob.heap;
1257
+
1258
+ const appendGuid = (guid) => {
1259
+ if (guid === null) return 0;
1260
+ const guidStream = streams.get("#GUID");
1261
+ if (!guidStream) throw new FormatError("Portable PDB metadata is missing its #GUID heap");
1262
+ if (guidStream.bytes.length % 16 !== 0) {
1263
+ throw new FormatError("Portable PDB GUID heap size is not a multiple of 16 bytes");
1264
+ }
1265
+ const index = guidStream.bytes.length / 16 + 1;
1266
+ const updated = new Uint8Array(guidStream.bytes.length + 16);
1267
+ updated.set(guidStream.bytes);
1268
+ updated.set(guid, guidStream.bytes.length);
1269
+ guidStream.bytes = updated;
1270
+ return index;
1271
+ };
1272
+ const hashAlgorithm = appendGuid(hashAlgorithmGuid);
1273
+ const language = appendGuid(languageGuid);
1274
+ const rows = new Map(pdb.tables.rows);
1275
+ const rid = documentRows.length + 1;
1276
+ rows.set(48, [
1277
+ ...documentRows.map((row) => ({ ...row })),
1278
+ {
1279
+ table: 48,
1280
+ tableName: "Document",
1281
+ rid,
1282
+ Name: documentName.nameBlobIndex,
1283
+ HashAlgorithm: hashAlgorithm,
1284
+ Hash: hashBlob.index,
1285
+ Language: language
1286
+ }
1287
+ ]);
1288
+ const counts = [...pdb.tables.counts];
1289
+ counts[48] = rid;
1290
+ return {
1291
+ ...pdb,
1292
+ metadataRoot: { ...pdb.metadataRoot, streams },
1293
+ tables: { ...pdb.tables, rows, counts, valid: pdb.tables.valid | (1n << 48n) }
1294
+ };
1295
+ }
1296
+
1297
+ export function addEmbeddedPortablePdbDocument(pdb, name, options) {
1298
+ return serializeEmbeddedPortablePdb(
1299
+ addPortablePdbDocumentToParsedPdb(pdb, name, options)
1300
+ );
1301
+ }
1302
+
1303
+ export function insertEmbeddedPortablePdbDocument(pdb, options = {}) {
1304
+ if (!pdb?.metadataRoot?.streams || !(pdb.tables?.rows instanceof Map) ||
1305
+ !Array.isArray(pdb.tables.counts) || !Array.isArray(pdb.documents) ||
1306
+ !Array.isArray(pdb.methodDebugInformation) ||
1307
+ !Array.isArray(pdb.customDebugInformation)) {
1308
+ throw new TypeError("Portable PDB document editing requires parsed PDB data");
1309
+ }
1310
+ if (options === null || typeof options !== "object" || Array.isArray(options)) {
1311
+ throw new TypeError("Portable PDB document insertion options must be an object");
1312
+ }
1313
+ const {
1314
+ beforeRid,
1315
+ name,
1316
+ separator = "/",
1317
+ hashAlgorithmGuid = null,
1318
+ hash = new Uint8Array(),
1319
+ languageGuid = null
1320
+ } = options;
1321
+ const documentRows = pdb.tables.rows.get(48) ?? [];
1322
+ if (documentRows.length !== pdb.tables.counts[48] ||
1323
+ pdb.documents.length !== documentRows.length) {
1324
+ throw new FormatError("Portable PDB Document row count is inconsistent");
1325
+ }
1326
+ if (!Number.isInteger(beforeRid) || beforeRid < 1 ||
1327
+ beforeRid > documentRows.length + 1) {
1328
+ throw new RangeError(`Invalid Portable PDB Document insertion row ID ${beforeRid}`);
1329
+ }
1330
+ const insertedRid = documentRows.length + 1;
1331
+ if (beforeRid !== insertedRid && pdb.customDebugInformation.some((record) =>
1332
+ !Object.keys(record).some((key) =>
1333
+ !["table", "tableName", "rid", "Parent", "Kind", "Value", "kind"].includes(key)
1334
+ )
1335
+ )) {
1336
+ throw new UnsupportedFormatError(
1337
+ "Portable PDB Document insertion cannot preserve opaque custom debug information"
1338
+ );
1339
+ }
1340
+
1341
+ const updated = addPortablePdbDocumentToParsedPdb(pdb, name, {
1342
+ separator,
1343
+ hashAlgorithmGuid,
1344
+ hash,
1345
+ languageGuid
1346
+ });
1347
+ if (beforeRid === insertedRid) return serializeEmbeddedPortablePdb(updated);
1348
+ const remapDocumentRid = (rid) => {
1349
+ if (rid === 0 || rid < beforeRid) return rid;
1350
+ if (rid === insertedRid) return beforeRid;
1351
+ return rid + 1;
1352
+ };
1353
+ const rows = new Map(updated.tables.rows);
1354
+ const updatedDocumentRows = (rows.get(48) ?? [])
1355
+ .map((row) => ({ ...row, rid: remapDocumentRid(row.rid) }))
1356
+ .sort((left, right) => left.rid - right.rid);
1357
+ rows.set(48, updatedDocumentRows);
1358
+
1359
+ let blobStream = null;
1360
+ const appendBlob = (value) => {
1361
+ if (!blobStream) {
1362
+ blobStream = updated.metadataRoot.streams.get("#Blob");
1363
+ if (!blobStream) {
1364
+ throw new FormatError("Portable PDB metadata is missing its #Blob heap");
1365
+ }
1366
+ }
1367
+ const appendedBlob = appendBlobHeap(blobStream.bytes, value);
1368
+ blobStream.bytes = appendedBlob.heap;
1369
+ return appendedBlob.index;
1370
+ };
1371
+ const methodRows = rows.get(49) ?? [];
1372
+ if (methodRows.length !== updated.methodDebugInformation.length) {
1373
+ throw new FormatError("Portable PDB MethodDebugInformation row count is inconsistent");
1374
+ }
1375
+ const updatedMethodRows = methodRows.map((row) => {
1376
+ const method = updated.methodDebugInformation[row.rid - 1];
1377
+ if (!method || method.rid !== row.rid) {
1378
+ throw new FormatError(
1379
+ `Portable PDB MethodDebugInformation row ${row.rid} is missing`
1380
+ );
1381
+ }
1382
+ const document = remapDocumentRid(row.Document.rid);
1383
+ const points = method.sequencePoints.map((point) => ({
1384
+ ...point,
1385
+ documentRid: remapDocumentRid(point.documentRid)
1386
+ }));
1387
+ const pointsChanged = points.some((point, index) =>
1388
+ point.documentRid !== method.sequencePoints[index].documentRid
1389
+ );
1390
+ return {
1391
+ ...row,
1392
+ Document: document === row.Document.rid
1393
+ ? { ...row.Document }
1394
+ : {
1395
+ ...row.Document,
1396
+ raw: document,
1397
+ rid: document
1398
+ },
1399
+ ...(row.SequencePoints !== 0 && pointsChanged
1400
+ ? {
1401
+ SequencePoints: appendBlob(encodePortablePdbSequencePoints({
1402
+ localSignatureRid: method.localSignatureRid,
1403
+ points
1404
+ }, {
1405
+ documentRid: document,
1406
+ documentCount: updatedDocumentRows.length,
1407
+ localSignatureCount: updated.rowCounts.get(17) ?? 0
1408
+ }))
1409
+ }
1410
+ : {})
1411
+ };
1412
+ });
1413
+ if (methodRows.length > 0) rows.set(49, updatedMethodRows);
1414
+
1415
+ const customDebugRows = rows.get(55) ?? [];
1416
+ if (customDebugRows.length !== updated.customDebugInformation.length) {
1417
+ throw new FormatError("Portable PDB CustomDebugInformation row count is inconsistent");
1418
+ }
1419
+ const records = new Map(
1420
+ updated.customDebugInformation.map((record) => [record.rid, record])
1421
+ );
1422
+ const remappedCustomDebugRows = sortPortablePdbCustomDebugInformation(
1423
+ customDebugRows.map((row) => {
1424
+ const record = records.get(row.rid);
1425
+ if (!record) {
1426
+ throw new FormatError(
1427
+ `Portable PDB CustomDebugInformation row ${row.rid} is missing its decoded record`
1428
+ );
1429
+ }
1430
+ let updatedRow = { ...row };
1431
+ if (record.Parent.target?.table === 48) {
1432
+ const rid = remapDocumentRid(record.Parent.target.rid);
1433
+ if (rid !== record.Parent.target.rid) {
1434
+ updatedRow = {
1435
+ ...updatedRow,
1436
+ Parent: {
1437
+ raw: encodeCodedIndex(48, rid, "HasCustomDebugInformation"),
1438
+ target: { ...record.Parent.target, rid }
1439
+ }
1440
+ };
1441
+ }
1442
+ }
1443
+ if (record.Parent.target?.table === 2 &&
1444
+ Array.isArray(record.typeDefinitionDocuments)) {
1445
+ const documentRids = record.typeDefinitionDocuments.map(remapDocumentRid);
1446
+ if (documentRids.some((rid, index) =>
1447
+ rid !== record.typeDefinitionDocuments[index]
1448
+ )) {
1449
+ updatedRow = {
1450
+ ...updatedRow,
1451
+ Value: appendBlob(encodeTypeDefinitionDocumentValue(
1452
+ documentRids,
1453
+ updatedDocumentRows.length
1454
+ ))
1455
+ };
1456
+ }
1457
+ }
1458
+ return updatedRow;
1459
+ })
1460
+ );
1461
+ if (customDebugRows.length > 0) rows.set(55, remappedCustomDebugRows);
1462
+ return serializeEmbeddedPortablePdb({
1463
+ ...updated,
1464
+ tables: { ...updated.tables, rows }
1465
+ });
1466
+ }
1467
+
1468
+ function encodeTypeDefinitionDocumentValue(documentRids, documentCount) {
1469
+ if (!Array.isArray(documentRids)) {
1470
+ throw new TypeError("TypeDefinitionDocument values must be an array of Document row IDs");
1471
+ }
1472
+ const encoded = [];
1473
+ for (const documentRid of documentRids) {
1474
+ if (!Number.isInteger(documentRid) || documentRid < 1 ||
1475
+ documentRid > documentCount) {
1476
+ throw new RangeError(`Invalid Portable PDB Document row ID ${documentRid}`);
1477
+ }
1478
+ encoded.push(...appendCompressedUInt(documentRid));
1479
+ }
1480
+ return Uint8Array.from(encoded);
1481
+ }
1482
+
1483
+ function editEmbeddedPortablePdbTypeDefinitionDocuments(pdb, typeRid, documentRids, add) {
1484
+ if (!pdb?.metadataRoot?.streams || !(pdb.tables?.rows instanceof Map) ||
1485
+ !Array.isArray(pdb.tables.counts) || !(pdb.rowCounts instanceof Map) ||
1486
+ !Array.isArray(pdb.customDebugInformation)) {
1487
+ throw new TypeError("Portable PDB TypeDefinitionDocument editing requires parsed PDB data");
1488
+ }
1489
+ const typeCount = pdb.rowCounts.get(2) ?? 0;
1490
+ const documentRows = pdb.tables.rows.get(48) ?? [];
1491
+ const debugRows = pdb.tables.rows.get(55) ?? [];
1492
+ if (!Number.isInteger(typeRid) || typeRid < 1 || typeRid > typeCount) {
1493
+ throw new RangeError(`Invalid Portable PDB TypeDef row ID ${typeRid}`);
1494
+ }
1495
+ if (documentRows.length !== pdb.tables.counts[48] ||
1496
+ debugRows.length !== pdb.tables.counts[55] ||
1497
+ pdb.customDebugInformation.length !== debugRows.length) {
1498
+ throw new FormatError("Portable PDB Document or custom-debug row count is inconsistent");
1499
+ }
1500
+ const value = encodeTypeDefinitionDocumentValue(documentRids, documentRows.length);
1501
+ const existing = pdb.customDebugInformation.filter((record) =>
1502
+ record.kind === typeDefinitionDocumentKind &&
1503
+ record.Parent.target?.table === 2 &&
1504
+ record.Parent.target.rid === typeRid
1505
+ );
1506
+ if (existing.length > 1) {
1507
+ throw new FormatError(
1508
+ `Portable PDB TypeDef row ${typeRid} has duplicate TypeDefinitionDocument records`
1509
+ );
1510
+ }
1511
+ const targetRecord = existing[0];
1512
+ const targetRow = targetRecord
1513
+ ? debugRows.find((row) => row.rid === targetRecord.rid)
1514
+ : undefined;
1515
+ if (targetRecord && (!targetRow || targetRow.Kind !== targetRecord.Kind)) {
1516
+ throw new FormatError(
1517
+ `Portable PDB TypeDef row ${typeRid} has inconsistent TypeDefinitionDocument data`
1518
+ );
1519
+ }
1520
+ if (add && existing.length !== 0) {
1521
+ throw new UnsupportedFormatError(
1522
+ `Portable PDB TypeDef row ${typeRid} already has TypeDefinitionDocument information`
1523
+ );
1524
+ }
1525
+ if (!add && existing.length === 0) {
1526
+ throw new UnsupportedFormatError(
1527
+ `Portable PDB TypeDef row ${typeRid} has no TypeDefinitionDocument information`
1528
+ );
1529
+ }
1530
+
1531
+ const streams = new Map([...pdb.metadataRoot.streams].map(([name, stream]) =>
1532
+ [name, { ...stream, bytes: Uint8Array.from(stream.bytes) }]
1533
+ ));
1534
+ const blobStream = streams.get("#Blob");
1535
+ if (!blobStream) throw new FormatError("Portable PDB metadata is missing its #Blob heap");
1536
+ const appendedValue = appendBlobHeap(blobStream.bytes, value);
1537
+ blobStream.bytes = appendedValue.heap;
1538
+
1539
+ let kindIndex = existing[0]?.Kind;
1540
+ if (add) {
1541
+ const guidStream = streams.get("#GUID");
1542
+ if (!guidStream) throw new FormatError("Portable PDB metadata is missing its #GUID heap");
1543
+ if (guidStream.bytes.length % 16 !== 0) {
1544
+ throw new FormatError("Portable PDB GUID heap size is not a multiple of 16 bytes");
1545
+ }
1546
+ kindIndex = guidStream.bytes.length / 16 + 1;
1547
+ const expandedGuidStream = new Uint8Array(guidStream.bytes.length + 16);
1548
+ expandedGuidStream.set(guidStream.bytes);
1549
+ expandedGuidStream.set(typeDefinitionDocumentGuidBytes, guidStream.bytes.length);
1550
+ guidStream.bytes = expandedGuidStream;
1551
+ }
1552
+
1553
+ const rows = new Map(pdb.tables.rows);
1554
+ const updatedRows = add
1555
+ ? [
1556
+ ...debugRows.map((row) => ({ ...row })),
1557
+ {
1558
+ table: 55,
1559
+ tableName: "CustomDebugInformation",
1560
+ rid: 0,
1561
+ Parent: {
1562
+ raw: encodeCodedIndex(2, typeRid, "HasCustomDebugInformation"),
1563
+ target: { table: 2, tableName: "TypeDef", rid: typeRid }
1564
+ },
1565
+ Kind: kindIndex,
1566
+ Value: appendedValue.index
1567
+ }
1568
+ ]
1569
+ : debugRows.map((row) =>
1570
+ row.rid === targetRecord.rid
1571
+ ? { ...row, Value: appendedValue.index }
1572
+ : { ...row }
1573
+ );
1574
+ const sortedRows = sortPortablePdbCustomDebugInformation(updatedRows);
1575
+ rows.set(55, sortedRows);
1576
+ const counts = [...pdb.tables.counts];
1577
+ counts[55] = sortedRows.length;
1578
+ return serializeEmbeddedPortablePdb({
1579
+ ...pdb,
1580
+ metadataRoot: { ...pdb.metadataRoot, streams },
1581
+ tables: {
1582
+ ...pdb.tables,
1583
+ valid: sortedRows.length > 0
1584
+ ? pdb.tables.valid | (1n << 55n)
1585
+ : pdb.tables.valid & ~(1n << 55n),
1586
+ counts,
1587
+ rows
1588
+ }
1589
+ });
1590
+ }
1591
+
1592
+ export function addEmbeddedPortablePdbTypeDefinitionDocuments(
1593
+ pdb,
1594
+ typeRid,
1595
+ documentRids
1596
+ ) {
1597
+ return editEmbeddedPortablePdbTypeDefinitionDocuments(
1598
+ pdb,
1599
+ typeRid,
1600
+ documentRids,
1601
+ true
1602
+ );
1603
+ }
1604
+
1605
+ export function replaceEmbeddedPortablePdbTypeDefinitionDocuments(
1606
+ pdb,
1607
+ typeRid,
1608
+ documentRids
1609
+ ) {
1610
+ return editEmbeddedPortablePdbTypeDefinitionDocuments(
1611
+ pdb,
1612
+ typeRid,
1613
+ documentRids,
1614
+ false
1615
+ );
1616
+ }
1617
+
1618
+ export function removeEmbeddedPortablePdbDocument(pdb, documentRid) {
1619
+ if (!pdb?.metadataRoot?.streams || !(pdb.tables?.rows instanceof Map) ||
1620
+ !Array.isArray(pdb.tables.counts) || !Array.isArray(pdb.documents) ||
1621
+ !Array.isArray(pdb.methodDebugInformation) ||
1622
+ !Array.isArray(pdb.customDebugInformation)) {
1623
+ throw new TypeError("Portable PDB document editing requires parsed PDB data");
1624
+ }
1625
+ const documentRows = pdb.tables.rows.get(48) ?? [];
1626
+ if (!Number.isInteger(documentRid) || documentRid < 1 ||
1627
+ documentRid > documentRows.length) {
1628
+ throw new RangeError(`Invalid Portable PDB Document row ID ${documentRid}`);
1629
+ }
1630
+ if (documentRows.length !== pdb.tables.counts[48] ||
1631
+ pdb.documents.length !== documentRows.length ||
1632
+ pdb.methodDebugInformation.length !== (pdb.tables.rows.get(49) ?? []).length) {
1633
+ throw new FormatError("Portable PDB document or method-debug row count is inconsistent");
1634
+ }
1635
+ if (pdb.methodDebugInformation.some((method) =>
1636
+ method.Document.rid === documentRid ||
1637
+ method.sequencePoints.some((point) => point.documentRid === documentRid)
1638
+ )) {
1639
+ throw new UnsupportedFormatError(
1640
+ `Portable PDB Document row ${documentRid} is referenced by method debug information`
1641
+ );
1642
+ }
1643
+ if (pdb.customDebugInformation.some((record) =>
1644
+ record.Parent.target?.table === 48 &&
1645
+ record.embeddedSource === undefined
1646
+ )) {
1647
+ throw new UnsupportedFormatError(
1648
+ "Portable PDB Document removal cannot preserve opaque document-scoped custom debug information"
1649
+ );
1650
+ }
1651
+
1652
+ const rows = new Map(pdb.tables.rows);
1653
+ const remainingDocumentRows = documentRows
1654
+ .filter((row) => row.rid !== documentRid)
1655
+ .map((row) => row.rid > documentRid ? { ...row, rid: row.rid - 1 } : { ...row });
1656
+ if (remainingDocumentRows.length > 0) rows.set(48, remainingDocumentRows);
1657
+ else rows.delete(48);
1658
+
1659
+ let streams = null;
1660
+ const methodRows = rows.get(49) ?? [];
1661
+ const updatedMethodRows = methodRows.map((row) => {
1662
+ const method = pdb.methodDebugInformation[row.rid - 1];
1663
+ if (!method || method.rid !== row.rid) {
1664
+ throw new FormatError(
1665
+ `Portable PDB MethodDebugInformation row ${row.rid} is missing`
1666
+ );
1667
+ }
1668
+ const document = row.Document.rid > documentRid
1669
+ ? { ...row.Document, raw: row.Document.rid - 1, rid: row.Document.rid - 1 }
1670
+ : { ...row.Document };
1671
+ const points = method.sequencePoints.map((point) => ({
1672
+ ...point,
1673
+ ...(point.documentRid > documentRid
1674
+ ? { documentRid: point.documentRid - 1 }
1675
+ : {})
1676
+ }));
1677
+ const pointsChanged = points.some((point, index) =>
1678
+ point.documentRid !== method.sequencePoints[index].documentRid
1679
+ );
1680
+ if (row.SequencePoints === 0 || !pointsChanged) {
1681
+ return { ...row, Document: document };
1682
+ }
1683
+ streams ??= new Map([...pdb.metadataRoot.streams].map(([name, stream]) =>
1684
+ [name, { ...stream, bytes: Uint8Array.from(stream.bytes) }]
1685
+ ));
1686
+ const blobStream = streams.get("#Blob");
1687
+ if (!blobStream) throw new FormatError("Portable PDB metadata is missing its #Blob heap");
1688
+ const encoded = encodePortablePdbSequencePoints({
1689
+ localSignatureRid: method.localSignatureRid,
1690
+ points
1691
+ }, {
1692
+ documentRid: document.rid,
1693
+ documentCount: remainingDocumentRows.length,
1694
+ localSignatureCount: pdb.rowCounts.get(17) ?? 0
1695
+ });
1696
+ const appended = appendBlobHeap(blobStream.bytes, encoded);
1697
+ blobStream.bytes = appended.heap;
1698
+ return { ...row, Document: document, SequencePoints: appended.index };
1699
+ });
1700
+ if (methodRows.length > 0) rows.set(49, updatedMethodRows);
1701
+
1702
+ const customDebugRows = rows.get(55) ?? [];
1703
+ const customDebugRecords = new Map(
1704
+ pdb.customDebugInformation.map((record) => [record.rid, record])
1705
+ );
1706
+ const remappedCustomDebugRows = sortPortablePdbCustomDebugInformation(
1707
+ customDebugRows.flatMap((row) => {
1708
+ const record = customDebugRecords.get(row.rid);
1709
+ if (!record) {
1710
+ throw new FormatError(
1711
+ `Portable PDB CustomDebugInformation row ${row.rid} is missing its decoded record`
1712
+ );
1713
+ }
1714
+ const target = record.Parent.target;
1715
+ if (target?.table === 2 && Array.isArray(record.typeDefinitionDocuments)) {
1716
+ const documentRids = record.typeDefinitionDocuments
1717
+ .filter((rid) => rid !== documentRid)
1718
+ .map((rid) => rid > documentRid ? rid - 1 : rid);
1719
+ const changed = documentRids.length !== record.typeDefinitionDocuments.length ||
1720
+ documentRids.some((rid, index) =>
1721
+ rid !== record.typeDefinitionDocuments[index]
1722
+ );
1723
+ if (!changed) return [{ ...row }];
1724
+ if (documentRids.length === 0) return [];
1725
+ streams ??= new Map([...pdb.metadataRoot.streams].map(([name, stream]) =>
1726
+ [name, { ...stream, bytes: Uint8Array.from(stream.bytes) }]
1727
+ ));
1728
+ const blobStream = streams.get("#Blob");
1729
+ if (!blobStream) {
1730
+ throw new FormatError("Portable PDB metadata is missing its #Blob heap");
1731
+ }
1732
+ const encoded = encodeTypeDefinitionDocumentValue(
1733
+ documentRids,
1734
+ remainingDocumentRows.length
1735
+ );
1736
+ const appended = appendBlobHeap(blobStream.bytes, encoded);
1737
+ blobStream.bytes = appended.heap;
1738
+ return [{ ...row, Value: appended.index }];
1739
+ }
1740
+ if (target?.table !== 48) return [{ ...row }];
1741
+ if (target.rid === documentRid) return [];
1742
+ if (target.rid < documentRid) return [{ ...row }];
1743
+ const rid = target.rid - 1;
1744
+ return [{
1745
+ ...row,
1746
+ Parent: {
1747
+ raw: encodeCodedIndex(48, rid, "HasCustomDebugInformation"),
1748
+ target: { ...target, rid }
1749
+ }
1750
+ }];
1751
+ })
1752
+ );
1753
+ if (remappedCustomDebugRows.length > 0) rows.set(55, remappedCustomDebugRows);
1754
+ else rows.delete(55);
1755
+
1756
+ const counts = [...pdb.tables.counts];
1757
+ counts[48] = remainingDocumentRows.length;
1758
+ counts[55] = remappedCustomDebugRows.length;
1759
+ let valid = pdb.tables.valid;
1760
+ valid = remainingDocumentRows.length > 0
1761
+ ? valid | (1n << 48n)
1762
+ : valid & ~(1n << 48n);
1763
+ valid = remappedCustomDebugRows.length > 0
1764
+ ? valid | (1n << 55n)
1765
+ : valid & ~(1n << 55n);
1766
+ return serializeEmbeddedPortablePdb({
1767
+ ...pdb,
1768
+ ...(streams ? { metadataRoot: { ...pdb.metadataRoot, streams } } : {}),
1769
+ tables: { ...pdb.tables, rows, counts, valid }
1770
+ });
1771
+ }
1772
+
1773
+ export function replaceEmbeddedPortablePdbSequencePoints(pdb, methodRid, sequencePoints) {
1774
+ if (!pdb?.metadataRoot?.streams || !(pdb.tables?.rows instanceof Map) ||
1775
+ !Array.isArray(pdb.tables.counts) || !(pdb.rowCounts instanceof Map) ||
1776
+ !Array.isArray(pdb.methodDebugInformation)) {
1777
+ throw new TypeError("Portable PDB sequence-point editing requires parsed PDB data");
1778
+ }
1779
+ if (!Number.isInteger(methodRid) || methodRid < 1 ||
1780
+ methodRid > pdb.methodDebugInformation.length) {
1781
+ throw new RangeError(`Invalid Portable PDB MethodDebugInformation row ID ${methodRid}`);
1782
+ }
1783
+ const rowsForMethods = pdb.tables.rows.get(49) ?? [];
1784
+ const row = rowsForMethods[methodRid - 1];
1785
+ if (!row || row.rid !== methodRid) {
1786
+ throw new FormatError(`Portable PDB MethodDebugInformation row ${methodRid} is missing`);
1787
+ }
1788
+ const encoded = encodePortablePdbSequencePoints(sequencePoints, {
1789
+ documentRid: row.Document.rid,
1790
+ documentCount: pdb.tables.counts[48],
1791
+ localSignatureCount: pdb.rowCounts.get(17) ?? 0
1792
+ });
1793
+ const streams = new Map([...pdb.metadataRoot.streams].map(([streamName, stream]) =>
1794
+ [streamName, { ...stream, bytes: Uint8Array.from(stream.bytes) }]
1795
+ ));
1796
+ const blobStream = streams.get("#Blob");
1797
+ if (!blobStream) throw new FormatError("Portable PDB metadata is missing its #Blob heap");
1798
+ const appended = appendBlobHeap(blobStream.bytes, encoded);
1799
+ blobStream.bytes = appended.heap;
1800
+ const rows = new Map(pdb.tables.rows);
1801
+ rows.set(49, rowsForMethods.map((methodRow) =>
1802
+ methodRow.rid === methodRid
1803
+ ? { ...methodRow, SequencePoints: appended.index }
1804
+ : { ...methodRow }
1805
+ ));
1806
+ return serializeEmbeddedPortablePdb({
1807
+ ...pdb,
1808
+ metadataRoot: { ...pdb.metadataRoot, streams },
1809
+ tables: { ...pdb.tables, rows }
1810
+ });
1811
+ }
1812
+
1813
+ export function replaceEmbeddedPortablePdbMethodDocument(pdb, methodRid, documentRid) {
1814
+ if (!pdb?.metadataRoot?.streams || !(pdb.tables?.rows instanceof Map) ||
1815
+ !Array.isArray(pdb.tables.counts) || !(pdb.rowCounts instanceof Map) ||
1816
+ !Array.isArray(pdb.methodDebugInformation) || !Array.isArray(pdb.documents)) {
1817
+ throw new TypeError("Portable PDB method-document editing requires parsed PDB data");
1818
+ }
1819
+ if (!Number.isInteger(methodRid) || methodRid < 1 ||
1820
+ methodRid > pdb.methodDebugInformation.length) {
1821
+ throw new RangeError(`Invalid Portable PDB MethodDebugInformation row ID ${methodRid}`);
1822
+ }
1823
+ if (!Number.isInteger(documentRid) || documentRid < 0 ||
1824
+ documentRid > pdb.documents.length) {
1825
+ throw new RangeError(`Invalid Portable PDB Document row ID ${documentRid}`);
1826
+ }
1827
+ const methodRows = pdb.tables.rows.get(49) ?? [];
1828
+ const row = methodRows[methodRid - 1];
1829
+ const method = pdb.methodDebugInformation[methodRid - 1];
1830
+ if (!row || row.rid !== methodRid || !method || method.rid !== methodRid) {
1831
+ throw new FormatError(
1832
+ `Portable PDB MethodDebugInformation row ${methodRid} is missing`
1833
+ );
1834
+ }
1835
+ const points = method.sequencePoints.map((point) => ({
1836
+ ...point,
1837
+ documentRid: documentRid || point.documentRid
1838
+ }));
1839
+ const streams = new Map([...pdb.metadataRoot.streams].map(([name, stream]) =>
1840
+ [name, { ...stream, bytes: Uint8Array.from(stream.bytes) }]
1841
+ ));
1842
+ let sequencePointsIndex = row.SequencePoints;
1843
+ if (row.SequencePoints !== 0 && points.length > 0) {
1844
+ const blobStream = streams.get("#Blob");
1845
+ if (!blobStream) throw new FormatError("Portable PDB metadata is missing its #Blob heap");
1846
+ const encoded = encodePortablePdbSequencePoints({
1847
+ localSignatureRid: method.localSignatureRid,
1848
+ points
1849
+ }, {
1850
+ documentRid,
1851
+ documentCount: pdb.documents.length,
1852
+ localSignatureCount: pdb.rowCounts.get(17) ?? 0
1853
+ });
1854
+ const appended = appendBlobHeap(blobStream.bytes, encoded);
1855
+ blobStream.bytes = appended.heap;
1856
+ sequencePointsIndex = appended.index;
1857
+ }
1858
+ const document = { ...row.Document, raw: documentRid, rid: documentRid };
1859
+ const rows = new Map(pdb.tables.rows);
1860
+ rows.set(49, methodRows.map((methodRow) =>
1861
+ methodRow.rid === methodRid
1862
+ ? { ...methodRow, Document: document, SequencePoints: sequencePointsIndex }
1863
+ : { ...methodRow }
1864
+ ));
1865
+ return serializeEmbeddedPortablePdb({
1866
+ ...pdb,
1867
+ methodDebugInformation: pdb.methodDebugInformation.map((methodRow) =>
1868
+ methodRow.rid === methodRid
1869
+ ? { ...methodRow, Document: document, sequencePoints: points }
1870
+ : { ...methodRow }
1871
+ ),
1872
+ metadataRoot: { ...pdb.metadataRoot, streams },
1873
+ tables: { ...pdb.tables, rows }
1874
+ });
1875
+ }
1876
+
1877
+ export function replaceEmbeddedPortablePdbCustomDebugInformation(pdb, customDebugInformationRid, value) {
1878
+ if (!pdb?.metadataRoot?.streams || !(pdb.tables?.rows instanceof Map) ||
1879
+ !Array.isArray(pdb.tables.counts) || !(pdb.rowCounts instanceof Map) ||
1880
+ !pdb.heaps?.getBlob || !pdb.heaps?.getGuid ||
1881
+ !Array.isArray(pdb.customDebugInformation)) {
1882
+ throw new TypeError("Portable PDB custom-debug editing requires parsed PDB data");
1883
+ }
1884
+ if (!Number.isInteger(customDebugInformationRid) || customDebugInformationRid < 1 ||
1885
+ customDebugInformationRid > (pdb.tables.counts[55] ?? 0)) {
1886
+ throw new RangeError(
1887
+ `Invalid Portable PDB CustomDebugInformation row ID ${customDebugInformationRid}`
1888
+ );
1889
+ }
1890
+ if (!(value instanceof Uint8Array)) {
1891
+ throw new TypeError("Portable PDB custom-debug values must be a Uint8Array");
1892
+ }
1893
+ const records = pdb.tables.rows.get(55) ?? [];
1894
+ const original = records[customDebugInformationRid - 1];
1895
+ if (!original || original.rid !== customDebugInformationRid) {
1896
+ throw new FormatError(
1897
+ `Portable PDB CustomDebugInformation row ${customDebugInformationRid} is missing`
1898
+ );
1899
+ }
1900
+ const streams = new Map([...pdb.metadataRoot.streams].map(([name, stream]) =>
1901
+ [name, { ...stream, bytes: Uint8Array.from(stream.bytes) }]
1902
+ ));
1903
+ const blobStream = streams.get("#Blob");
1904
+ if (!blobStream) throw new FormatError("Portable PDB metadata is missing its #Blob heap");
1905
+ const appended = appendBlobHeap(blobStream.bytes, value);
1906
+ blobStream.bytes = appended.heap;
1907
+ const replacement = { ...original, Value: appended.index };
1908
+ const validationHeaps = {
1909
+ getBlob: (index) => index === appended.index ? value : pdb.heaps.getBlob(index),
1910
+ getGuid: (index) => pdb.heaps.getGuid(index),
1911
+ getString: (index) => pdb.heaps.getString(index)
1912
+ };
1913
+ const decoded = decodePortablePdbCustomDebugInformation(
1914
+ replacement,
1915
+ validationHeaps,
1916
+ pdb.tables.counts[48] ?? 0,
1917
+ pdb.rowCounts.get(6) ?? 0
1918
+ );
1919
+ const updatedRecords = records.map((record) =>
1920
+ record.rid === customDebugInformationRid ? replacement : { ...record }
1921
+ );
1922
+ const customDebugInformation = pdb.customDebugInformation.map((record) =>
1923
+ record.rid === customDebugInformationRid
1924
+ ? { ...record, ...decoded }
1925
+ : { ...record }
1926
+ );
1927
+ const rows = new Map(pdb.tables.rows);
1928
+ rows.set(55, updatedRecords);
1929
+ return serializeEmbeddedPortablePdb({
1930
+ ...pdb,
1931
+ customDebugInformation,
1932
+ metadataRoot: { ...pdb.metadataRoot, streams },
1933
+ tables: { ...pdb.tables, rows }
1934
+ });
1935
+ }
1936
+
1937
+ export function replaceEmbeddedPortablePdbLocalScope(pdb, localScopeRid, options) {
1938
+ if (!pdb?.metadataRoot?.streams || !(pdb.tables?.rows instanceof Map) ||
1939
+ !Array.isArray(pdb.tables.counts) || !pdb.heaps?.getString || !pdb.heaps?.getBlob) {
1940
+ throw new TypeError("Portable PDB LocalScope editing requires parsed PDB data");
1941
+ }
1942
+ if (!Number.isInteger(localScopeRid) || localScopeRid < 1 ||
1943
+ localScopeRid > (pdb.tables.counts[50] ?? 0)) {
1944
+ throw new RangeError(`Invalid Portable PDB LocalScope row ID ${localScopeRid}`);
1945
+ }
1946
+ if (!options || typeof options !== "object" || Array.isArray(options)) {
1947
+ throw new TypeError("Portable PDB LocalScope replacement options must be an object");
1948
+ }
1949
+ const keys = Object.keys(options);
1950
+ if (keys.length === 0 ||
1951
+ keys.some((key) => !["startOffset", "length", "importScopeRid"].includes(key))) {
1952
+ throw new TypeError(
1953
+ "Portable PDB LocalScope replacement requires startOffset, length, and/or importScopeRid"
1954
+ );
1955
+ }
1956
+ const scopes = pdb.tables.rows.get(50) ?? [];
1957
+ const original = scopes[localScopeRid - 1];
1958
+ if (!original || original.rid !== localScopeRid) {
1959
+ throw new FormatError(`Portable PDB LocalScope row ${localScopeRid} is missing`);
1960
+ }
1961
+ if (Object.hasOwn(options, "startOffset") &&
1962
+ (!Number.isInteger(options.startOffset) || options.startOffset < 0 ||
1963
+ options.startOffset >= 0x80000000)) {
1964
+ throw new RangeError("Portable PDB LocalScope startOffset must be a nonnegative 31-bit integer");
1965
+ }
1966
+ if (Object.hasOwn(options, "length") &&
1967
+ (!Number.isInteger(options.length) || options.length < 1 ||
1968
+ options.length >= 0x80000000)) {
1969
+ throw new RangeError("Portable PDB LocalScope length must be a positive 31-bit integer");
1970
+ }
1971
+ if (Object.hasOwn(options, "importScopeRid") &&
1972
+ (!Number.isInteger(options.importScopeRid) || options.importScopeRid < 0 ||
1973
+ options.importScopeRid > (pdb.tables.counts[53] ?? 0))) {
1974
+ throw new RangeError("Portable PDB LocalScope importScopeRid is out of range");
1975
+ }
1976
+ if (Object.hasOwn(options, "importScopeRid") && options.importScopeRid > 0) {
1977
+ const importScope = pdb.tables.rows.get(53)?.[options.importScopeRid - 1];
1978
+ if (!importScope || importScope.rid !== options.importScopeRid) {
1979
+ throw new FormatError(
1980
+ `Portable PDB ImportScope row ${options.importScopeRid} is missing`
1981
+ );
1982
+ }
1983
+ }
1984
+ const replacement = {
1985
+ ...original,
1986
+ ...(Object.hasOwn(options, "startOffset") ? { StartOffset: options.startOffset } : {}),
1987
+ ...(Object.hasOwn(options, "length") ? { Length: options.length } : {}),
1988
+ ...(Object.hasOwn(options, "importScopeRid")
1989
+ ? { ImportScope: { rid: options.importScopeRid } }
1990
+ : {})
1991
+ };
1992
+ const updatedScopes = scopes.map((scope) =>
1993
+ scope.rid === localScopeRid ? replacement : { ...scope }
1994
+ );
1995
+ validateLocalScopes(
1996
+ updatedScopes,
1997
+ pdb.tables.rows.get(51) ?? [],
1998
+ pdb.tables.rows.get(52) ?? [],
1999
+ pdb.heaps
2000
+ );
2001
+ const rows = new Map(pdb.tables.rows);
2002
+ rows.set(50, updatedScopes);
2003
+ return serializeEmbeddedPortablePdb({
2004
+ ...pdb,
2005
+ tables: { ...pdb.tables, rows }
2006
+ });
2007
+ }
2008
+
2009
+ export function replaceEmbeddedPortablePdbLocalVariable(pdb, localVariableRid, options) {
2010
+ if (!pdb?.metadataRoot?.streams || !(pdb.tables?.rows instanceof Map) ||
2011
+ !Array.isArray(pdb.tables.counts) || !pdb.heaps?.getString || !pdb.heaps?.getBlob) {
2012
+ throw new TypeError("Portable PDB LocalVariable editing requires parsed PDB data");
2013
+ }
2014
+ if (!Number.isInteger(localVariableRid) || localVariableRid < 1 ||
2015
+ localVariableRid > (pdb.tables.counts[51] ?? 0)) {
2016
+ throw new RangeError(`Invalid Portable PDB LocalVariable row ID ${localVariableRid}`);
2017
+ }
2018
+ if (!options || typeof options !== "object" || Array.isArray(options)) {
2019
+ throw new TypeError("Portable PDB LocalVariable replacement options must be an object");
2020
+ }
2021
+ const keys = Object.keys(options);
2022
+ if (keys.length === 0 ||
2023
+ keys.some((key) => key !== "name" && key !== "attributes" && key !== "index")) {
2024
+ throw new TypeError(
2025
+ "Portable PDB LocalVariable replacement requires name, attributes, and/or index"
2026
+ );
2027
+ }
2028
+ const variables = pdb.tables.rows.get(51) ?? [];
2029
+ const original = variables[localVariableRid - 1];
2030
+ if (!original || original.rid !== localVariableRid) {
2031
+ throw new FormatError(`Portable PDB LocalVariable row ${localVariableRid} is missing`);
2032
+ }
2033
+ if (Object.hasOwn(options, "attributes") &&
2034
+ (!Number.isInteger(options.attributes) || options.attributes < 0 ||
2035
+ (options.attributes & ~1) !== 0)) {
2036
+ throw new RangeError("Portable PDB LocalVariable attributes may only set the pinned flag");
2037
+ }
2038
+ if (Object.hasOwn(options, "index") &&
2039
+ (!Number.isInteger(options.index) || options.index < 0 || options.index > 0xffff)) {
2040
+ throw new RangeError("Portable PDB LocalVariable index must be an unsigned 16-bit integer");
2041
+ }
2042
+ const streams = new Map([...pdb.metadataRoot.streams].map(([name, stream]) =>
2043
+ [name, { ...stream, bytes: Uint8Array.from(stream.bytes) }]
2044
+ ));
2045
+ let nameIndex = original.Name;
2046
+ if (Object.hasOwn(options, "name")) {
2047
+ if (typeof options.name !== "string" || options.name.includes("\0")) {
2048
+ throw new TypeError("Portable PDB LocalVariable names must be null-free strings");
2049
+ }
2050
+ const encodedName = new TextEncoder().encode(options.name);
2051
+ if (new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(encodedName) !== options.name) {
2052
+ throw new TypeError("Portable PDB LocalVariable names must contain valid Unicode");
2053
+ }
2054
+ const stringStream = streams.get("#Strings");
2055
+ if (!stringStream) throw new FormatError("Portable PDB metadata is missing its #Strings heap");
2056
+ const appended = appendStringHeap(stringStream.bytes, options.name);
2057
+ stringStream.bytes = appended.heap;
2058
+ nameIndex = appended.index;
2059
+ }
2060
+ const replacement = {
2061
+ ...original,
2062
+ ...(Object.hasOwn(options, "attributes") ? { Attributes: options.attributes } : {}),
2063
+ ...(Object.hasOwn(options, "index") ? { Index: options.index } : {}),
2064
+ ...(Object.hasOwn(options, "name") ? { Name: nameIndex } : {})
2065
+ };
2066
+ const updatedVariables = variables.map((variable) =>
2067
+ variable.rid === localVariableRid ? replacement : { ...variable }
2068
+ );
2069
+ const validationHeaps = {
2070
+ getString: (index) => Object.hasOwn(options, "name") && index === nameIndex
2071
+ ? options.name
2072
+ : pdb.heaps.getString(index),
2073
+ getBlob: (index) => pdb.heaps.getBlob(index)
2074
+ };
2075
+ validateLocalScopes(
2076
+ pdb.tables.rows.get(50) ?? [],
2077
+ updatedVariables,
2078
+ pdb.tables.rows.get(52) ?? [],
2079
+ validationHeaps
2080
+ );
2081
+ const rows = new Map(pdb.tables.rows);
2082
+ rows.set(51, updatedVariables);
2083
+ return serializeEmbeddedPortablePdb({
2084
+ ...pdb,
2085
+ metadataRoot: { ...pdb.metadataRoot, streams },
2086
+ tables: { ...pdb.tables, rows }
2087
+ });
2088
+ }
2089
+
2090
+ export function replaceEmbeddedPortablePdbLocalConstant(pdb, localConstantRid, options) {
2091
+ if (!pdb?.metadataRoot?.streams || !(pdb.tables?.rows instanceof Map) ||
2092
+ !Array.isArray(pdb.tables.counts) || !pdb.heaps?.getString || !pdb.heaps?.getBlob) {
2093
+ throw new TypeError("Portable PDB LocalConstant editing requires parsed PDB data");
2094
+ }
2095
+ if (!Number.isInteger(localConstantRid) || localConstantRid < 1 ||
2096
+ localConstantRid > (pdb.tables.counts[52] ?? 0)) {
2097
+ throw new RangeError(`Invalid Portable PDB LocalConstant row ID ${localConstantRid}`);
2098
+ }
2099
+ if (!options || typeof options !== "object" || Array.isArray(options) ||
2100
+ Object.keys(options).length !== 1 || !Object.hasOwn(options, "name")) {
2101
+ throw new TypeError("Portable PDB LocalConstant replacement requires a name");
2102
+ }
2103
+ if (typeof options.name !== "string" || options.name.includes("\0")) {
2104
+ throw new TypeError("Portable PDB LocalConstant names must be null-free strings");
2105
+ }
2106
+ const encodedName = new TextEncoder().encode(options.name);
2107
+ if (new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(encodedName) !== options.name) {
2108
+ throw new TypeError("Portable PDB LocalConstant names must contain valid Unicode");
2109
+ }
2110
+ const constants = pdb.tables.rows.get(52) ?? [];
2111
+ const original = constants[localConstantRid - 1];
2112
+ if (!original || original.rid !== localConstantRid) {
2113
+ throw new FormatError(`Portable PDB LocalConstant row ${localConstantRid} is missing`);
2114
+ }
2115
+ const streams = new Map([...pdb.metadataRoot.streams].map(([name, stream]) =>
2116
+ [name, { ...stream, bytes: Uint8Array.from(stream.bytes) }]
2117
+ ));
2118
+ const stringStream = streams.get("#Strings");
2119
+ if (!stringStream) throw new FormatError("Portable PDB metadata is missing its #Strings heap");
2120
+ const appended = appendStringHeap(stringStream.bytes, options.name);
2121
+ stringStream.bytes = appended.heap;
2122
+ const updatedConstants = constants.map((constant) =>
2123
+ constant.rid === localConstantRid
2124
+ ? { ...constant, Name: appended.index }
2125
+ : { ...constant }
2126
+ );
2127
+ const validationHeaps = {
2128
+ getString: (index) => index === appended.index
2129
+ ? options.name
2130
+ : pdb.heaps.getString(index),
2131
+ getBlob: (index) => pdb.heaps.getBlob(index)
2132
+ };
2133
+ validateLocalScopes(
2134
+ pdb.tables.rows.get(50) ?? [],
2135
+ pdb.tables.rows.get(51) ?? [],
2136
+ updatedConstants,
2137
+ validationHeaps
2138
+ );
2139
+ const rows = new Map(pdb.tables.rows);
2140
+ rows.set(52, updatedConstants);
2141
+ return serializeEmbeddedPortablePdb({
2142
+ ...pdb,
2143
+ metadataRoot: { ...pdb.metadataRoot, streams },
2144
+ tables: { ...pdb.tables, rows }
2145
+ });
2146
+ }
2147
+
2148
+ export function replaceEmbeddedPortablePdbImportScope(pdb, importScopeRid, options) {
2149
+ if (!pdb?.metadataRoot?.streams || !(pdb.tables?.rows instanceof Map) ||
2150
+ !Array.isArray(pdb.tables.counts)) {
2151
+ throw new TypeError("Portable PDB ImportScope editing requires parsed PDB data");
2152
+ }
2153
+ if (!Number.isInteger(importScopeRid) || importScopeRid < 1 ||
2154
+ importScopeRid > (pdb.tables.counts[53] ?? 0)) {
2155
+ throw new RangeError(`Invalid Portable PDB ImportScope row ID ${importScopeRid}`);
2156
+ }
2157
+ if (!options || typeof options !== "object" || Array.isArray(options) ||
2158
+ Object.keys(options).length !== 1 || !Object.hasOwn(options, "parentRid")) {
2159
+ throw new TypeError("Portable PDB ImportScope replacement requires parentRid");
2160
+ }
2161
+ const scopes = pdb.tables.rows.get(53) ?? [];
2162
+ const original = scopes[importScopeRid - 1];
2163
+ if (!original || original.rid !== importScopeRid) {
2164
+ throw new FormatError(`Portable PDB ImportScope row ${importScopeRid} is missing`);
2165
+ }
2166
+ if (!Number.isInteger(options.parentRid) || options.parentRid < 0 ||
2167
+ options.parentRid > scopes.length) {
2168
+ throw new RangeError("Portable PDB ImportScope parentRid is out of range");
2169
+ }
2170
+ const updatedScopes = scopes.map((scope) =>
2171
+ scope.rid === importScopeRid
2172
+ ? { ...scope, Parent: { ...scope.Parent, raw: options.parentRid, rid: options.parentRid } }
2173
+ : { ...scope }
2174
+ );
2175
+ validateImportScopes(updatedScopes);
2176
+ const rows = new Map(pdb.tables.rows);
2177
+ rows.set(53, updatedScopes);
2178
+ return serializeEmbeddedPortablePdb({
2179
+ ...pdb,
2180
+ tables: { ...pdb.tables, rows }
2181
+ });
2182
+ }
2183
+
2184
+ export function replaceEmbeddedPortablePdbMethodOffsets(
2185
+ pdb,
2186
+ methodRid,
2187
+ {
2188
+ sequencePoints = null,
2189
+ localSignatureRid = 0,
2190
+ scopes = [],
2191
+ stateMachineHoistedLocalScopes = [],
2192
+ asyncMethodSteppingInformation = []
2193
+ } = {}
2194
+ ) {
2195
+ if (!pdb?.metadataRoot?.streams || !(pdb.tables?.rows instanceof Map) ||
2196
+ !Array.isArray(pdb.tables.counts) || !(pdb.rowCounts instanceof Map)) {
2197
+ throw new TypeError("Portable PDB method-offset editing requires parsed PDB data");
2198
+ }
2199
+ if (!Number.isInteger(methodRid) || methodRid < 1 ||
2200
+ methodRid > (pdb.rowCounts.get(6) ?? 0)) {
2201
+ throw new RangeError(`Invalid Portable PDB MethodDef row ID ${methodRid}`);
2202
+ }
2203
+ if (!Array.isArray(scopes) || !Array.isArray(stateMachineHoistedLocalScopes) ||
2204
+ !Array.isArray(asyncMethodSteppingInformation)) {
2205
+ throw new TypeError("Portable PDB offset edits must be arrays");
2206
+ }
2207
+ const scopeRows = pdb.tables.rows.get(50) ?? [];
2208
+ const scopeUpdates = new Map(scopes.map((scope) => [scope.rid, scope]));
2209
+ if (scopeUpdates.size !== scopes.length || scopes.some((scope) =>
2210
+ !Number.isInteger(scope.rid) || !Number.isInteger(scope.startOffset) ||
2211
+ !Number.isInteger(scope.length) || scope.startOffset < 0 || scope.length < 0 ||
2212
+ scope.startOffset + scope.length > 0xffffffff ||
2213
+ !scopeRows.some((row) => row.rid === scope.rid && row.Method.rid === methodRid)
2214
+ )) {
2215
+ throw new FormatError("Portable PDB local-scope edits contain invalid rows or ranges");
2216
+ }
2217
+ const customDebugRows = pdb.tables.rows.get(55) ?? [];
2218
+ const hoistedScopeUpdates = new Map(
2219
+ stateMachineHoistedLocalScopes.map((update) => [update.rid, update])
2220
+ );
2221
+ const asyncSteppingUpdates = new Map(
2222
+ asyncMethodSteppingInformation.map((update) => [update.rid, update])
2223
+ );
2224
+ if (hoistedScopeUpdates.size !== stateMachineHoistedLocalScopes.length ||
2225
+ stateMachineHoistedLocalScopes.some((update) =>
2226
+ !Number.isInteger(update.rid) || !Array.isArray(update.scopes) ||
2227
+ !pdb.customDebugInformation.some((record) =>
2228
+ record.rid === update.rid &&
2229
+ record.Parent.target?.table === 6 &&
2230
+ record.Parent.target.rid === methodRid &&
2231
+ Array.isArray(record.stateMachineHoistedLocalScopes)
2232
+ ) ||
2233
+ !customDebugRows.some((record) => record.rid === update.rid) ||
2234
+ update.scopes.some((scope) =>
2235
+ !Number.isInteger(scope.startOffset) || !Number.isInteger(scope.length) ||
2236
+ scope.startOffset < 0 || scope.startOffset >= 0x80000000 ||
2237
+ scope.length <= 0 || scope.length >= 0x80000000 ||
2238
+ scope.startOffset + scope.length > 0x80000000
2239
+ )
2240
+ )) {
2241
+ throw new FormatError(
2242
+ "Portable PDB state-machine hoisted-local scope edits contain invalid records or ranges"
2243
+ );
2244
+ }
2245
+ if (asyncSteppingUpdates.size !== asyncMethodSteppingInformation.length ||
2246
+ asyncMethodSteppingInformation.some((update) =>
2247
+ !Number.isInteger(update.rid) || !Array.isArray(update.steps) ||
2248
+ !pdb.customDebugInformation.some((record) =>
2249
+ record.rid === update.rid &&
2250
+ record.Parent.target?.table === 6 &&
2251
+ record.Parent.target.rid === methodRid &&
2252
+ record.asyncMethodSteppingInformation
2253
+ ) ||
2254
+ !customDebugRows.some((record) => record.rid === update.rid) ||
2255
+ update.steps.some((step) =>
2256
+ !Number.isInteger(step.yieldOffset) || !Number.isInteger(step.resumeOffset) ||
2257
+ step.yieldOffset < 0 || step.resumeOffset < 0 ||
2258
+ step.yieldOffset >= 0x80000000 || step.resumeOffset >= 0x80000000 ||
2259
+ !Number.isInteger(step.methodRid) || step.methodRid < 1 ||
2260
+ step.methodRid > (pdb.rowCounts.get(6) ?? 0)
2261
+ )
2262
+ )) {
2263
+ throw new FormatError(
2264
+ "Portable PDB async stepping edits contain invalid records or offsets"
2265
+ );
2266
+ }
2267
+ const rows = new Map(pdb.tables.rows);
2268
+ let streams = null;
2269
+ const appendBlob = (value) => {
2270
+ streams ??= new Map([...pdb.metadataRoot.streams].map(([name, stream]) =>
2271
+ [name, { ...stream, bytes: Uint8Array.from(stream.bytes) }]
2272
+ ));
2273
+ const blobStream = streams.get("#Blob");
2274
+ if (!blobStream) throw new FormatError("Portable PDB metadata is missing its #Blob heap");
2275
+ const appended = appendBlobHeap(blobStream.bytes, value);
2276
+ blobStream.bytes = appended.heap;
2277
+ return appended.index;
2278
+ };
2279
+ const methodRows = pdb.tables.rows.get(49) ?? [];
2280
+ if (sequencePoints !== null) {
2281
+ const row = methodRows[methodRid - 1];
2282
+ if (!row || row.rid !== methodRid) {
2283
+ throw new FormatError(
2284
+ `Portable PDB MethodDebugInformation row ${methodRid} is missing`
2285
+ );
2286
+ }
2287
+ const encoded = encodePortablePdbSequencePoints({
2288
+ localSignatureRid,
2289
+ points: sequencePoints
2290
+ }, {
2291
+ documentRid: row.Document.rid,
2292
+ documentCount: pdb.tables.counts[48],
2293
+ localSignatureCount: pdb.rowCounts.get(17) ?? 0
2294
+ });
2295
+ const sequencePointsIndex = appendBlob(encoded);
2296
+ rows.set(49, methodRows.map((methodRow) =>
2297
+ methodRow.rid === methodRid
2298
+ ? { ...methodRow, SequencePoints: sequencePointsIndex }
2299
+ : { ...methodRow }
2300
+ ));
2301
+ }
2302
+ if (scopes.length > 0) {
2303
+ rows.set(50, scopeRows.map((scopeRow) => {
2304
+ const update = scopeUpdates.get(scopeRow.rid);
2305
+ return update
2306
+ ? { ...scopeRow, StartOffset: update.startOffset, Length: update.length }
2307
+ : { ...scopeRow };
2308
+ }));
2309
+ }
2310
+ if (stateMachineHoistedLocalScopes.length > 0 ||
2311
+ asyncMethodSteppingInformation.length > 0) {
2312
+ rows.set(55, customDebugRows.map((record) => {
2313
+ const update = hoistedScopeUpdates.get(record.rid);
2314
+ if (update) {
2315
+ const value = new Uint8Array(update.scopes.length * 8);
2316
+ const view = new DataView(value.buffer);
2317
+ update.scopes.forEach((scope, index) => {
2318
+ view.setUint32(index * 8, scope.startOffset, true);
2319
+ view.setUint32(index * 8 + 4, scope.length, true);
2320
+ });
2321
+ return { ...record, Value: appendBlob(value) };
2322
+ }
2323
+ const asyncUpdate = asyncSteppingUpdates.get(record.rid);
2324
+ return asyncUpdate
2325
+ ? {
2326
+ ...record,
2327
+ Value: appendBlob(encodeAsyncMethodSteppingInformation(asyncUpdate))
2328
+ }
2329
+ : { ...record };
2330
+ }));
2331
+ }
2332
+ return serializeEmbeddedPortablePdb({
2333
+ ...pdb,
2334
+ ...(streams ? { metadataRoot: { ...pdb.metadataRoot, streams } } : {}),
2335
+ tables: { ...pdb.tables, rows }
2336
+ });
2337
+ }
2338
+
2339
+ export function insertEmbeddedPortablePdbMethod(pdb, methodRid, oldCounts, newCounts) {
2340
+ if (!pdb?.metadataRoot?.streams || !(pdb.tables?.rows instanceof Map) ||
2341
+ !Array.isArray(pdb.tables.counts) || !(pdb.rowCounts instanceof Map)) {
2342
+ throw new TypeError("Portable PDB method insertion requires parsed PDB data");
2343
+ }
2344
+ if (!Array.isArray(oldCounts) || !Array.isArray(newCounts) ||
2345
+ oldCounts.length < 45 || newCounts.length < 45 ||
2346
+ !Number.isInteger(methodRid) || methodRid < 1 ||
2347
+ methodRid > newCounts[6] || newCounts[6] !== oldCounts[6] + 1) {
2348
+ throw new UnsupportedFormatError(
2349
+ "Portable PDB method insertion requires one valid MethodDef row to be inserted"
2350
+ );
2351
+ }
2352
+ for (const [table, count] of pdb.rowCounts) {
2353
+ if (count !== oldCounts[table]) {
2354
+ throw new FormatError(
2355
+ `Portable PDB row count for type-system table ${table} does not match assembly metadata`
2356
+ );
2357
+ }
2358
+ }
2359
+
2360
+ const entryPointRid = pdb.entryPointToken & 0x00ffffff;
2361
+ if (pdb.entryPointToken !== 0 && entryPointRid > oldCounts[6]) {
2362
+ throw new FormatError("Portable PDB entry point references a missing MethodDef");
2363
+ }
2364
+ const entryPointToken = pdb.entryPointToken !== 0 &&
2365
+ (pdb.entryPointToken >>> 24) === 6 && entryPointRid >= methodRid
2366
+ ? pdb.entryPointToken + 1
2367
+ : pdb.entryPointToken;
2368
+
2369
+ const rows = new Map(pdb.tables.rows);
2370
+ const methodRows = rows.get(49) ?? [];
2371
+ if (methodRows.length !== 0 && methodRows.length !== oldCounts[6]) {
2372
+ throw new FormatError("Portable PDB MethodDebugInformation row count is inconsistent");
2373
+ }
2374
+ if (methodRows.length > 0) {
2375
+ if (methodRows.some((row, index) => row.rid !== index + 1)) {
2376
+ throw new FormatError("Portable PDB MethodDebugInformation rows are not RID-aligned");
2377
+ }
2378
+ const remappedMethodRows = methodRows.map((row) => ({
2379
+ ...row,
2380
+ rid: row.rid >= methodRid ? row.rid + 1 : row.rid
2381
+ }));
2382
+ remappedMethodRows.splice(methodRid - 1, 0, {
2383
+ table: 49,
2384
+ tableName: "MethodDebugInformation",
2385
+ rid: methodRid,
2386
+ Document: { raw: 0, table: 48, tableName: "Document", rid: 0 },
2387
+ SequencePoints: 0
2388
+ });
2389
+ rows.set(49, remappedMethodRows);
2390
+ } else {
2391
+ rows.delete(49);
2392
+ }
2393
+
2394
+ const remapMethodReference = (reference) => reference.rid >= methodRid
2395
+ ? { ...reference, raw: reference.rid + 1, rid: reference.rid + 1 }
2396
+ : { ...reference };
2397
+ const scopeRows = rows.get(50) ?? [];
2398
+ if (rows.has(50)) {
2399
+ rows.set(50, scopeRows.map((scope) => ({
2400
+ ...scope,
2401
+ Method: remapMethodReference(scope.Method)
2402
+ })));
2403
+ }
2404
+ const stateMachineRows = rows.get(54) ?? [];
2405
+ if (rows.has(54)) {
2406
+ rows.set(54, stateMachineRows.map((stateMachine) => ({
2407
+ ...stateMachine,
2408
+ MoveNextMethod: remapMethodReference(stateMachine.MoveNextMethod),
2409
+ KickoffMethod: remapMethodReference(stateMachine.KickoffMethod)
2410
+ })));
2411
+ }
2412
+ const customDebugRows = rows.get(55) ?? [];
2413
+ if (!canRemapMethodScopedCustomDebugInformation(pdb)) {
2414
+ throw new UnsupportedFormatError(
2415
+ "Portable PDB method remapping cannot update method-scoped CustomDebugInformation"
2416
+ );
2417
+ }
2418
+ const asyncRemapped = remapAsyncMethodReferences(
2419
+ pdb,
2420
+ customDebugRows,
2421
+ (rid) => rid >= methodRid ? rid + 1 : rid
2422
+ );
2423
+ const remappedCustomDebugRows = sortPortablePdbCustomDebugInformation(
2424
+ asyncRemapped.records.map((record) => {
2425
+ const target = record.Parent.target;
2426
+ if (target?.table !== 6 || target.rid < methodRid) return { ...record };
2427
+ const rid = target.rid + 1;
2428
+ return {
2429
+ ...record,
2430
+ Parent: {
2431
+ raw: encodeCodedIndex(6, rid, "HasCustomDebugInformation"),
2432
+ target: { ...target, rid }
2433
+ }
2434
+ };
2435
+ })
2436
+ );
2437
+ if (rows.has(55)) rows.set(55, remappedCustomDebugRows);
2438
+
2439
+ const rowCounts = new Map([...pdb.rowCounts].map(([table]) =>
2440
+ [table, newCounts[table]]
2441
+ ));
2442
+ const tableCounts = [...pdb.tables.counts];
2443
+ for (let table = 0; table < 45; table++) {
2444
+ tableCounts[table] = newCounts[table];
2445
+ }
2446
+ tableCounts[49] = methodRows.length > 0 ? methodRows.length + 1 : 0;
2447
+ const valid = tableCounts[49] === 0
2448
+ ? pdb.tables.valid & ~(1n << 49n)
2449
+ : pdb.tables.valid | (1n << 49n);
2450
+
2451
+ const pdbStream = new Uint8Array(32 + rowCounts.size * 4);
2452
+ pdbStream.set(pdb.id);
2453
+ const pdbView = new DataView(pdbStream.buffer);
2454
+ pdbView.setUint32(20, entryPointToken, true);
2455
+ pdbView.setUint32(24, Number(pdb.referencedTypeSystemTables & 0xffffffffn), true);
2456
+ pdbView.setUint32(28, Number(pdb.referencedTypeSystemTables >> 32n), true);
2457
+ let countOffset = 32;
2458
+ for (const [table, count] of [...rowCounts].sort(([left], [right]) => left - right)) {
2459
+ pdbView.setUint32(countOffset, count, true);
2460
+ countOffset += 4;
2461
+ }
2462
+
2463
+ const streams = new Map([...(asyncRemapped.streams ?? pdb.metadataRoot.streams)].map(([name, stream]) =>
2464
+ [name, { ...stream, bytes: Uint8Array.from(stream.bytes) }]
2465
+ ));
2466
+ const pdbMetadataStream = streams.get("#Pdb");
2467
+ if (!pdbMetadataStream) {
2468
+ throw new FormatError("Portable PDB is missing its #Pdb stream");
2469
+ }
2470
+ pdbMetadataStream.bytes = pdbStream;
2471
+ return serializeEmbeddedPortablePdb({
2472
+ ...pdb,
2473
+ metadataRoot: { ...pdb.metadataRoot, streams },
2474
+ rowCounts,
2475
+ tables: { ...pdb.tables, rows, counts: tableCounts, valid }
2476
+ });
2477
+ }
2478
+
2479
+ export function remapEmbeddedPortablePdbLocalSignatures(
2480
+ pdb,
2481
+ signatureRid,
2482
+ oldCounts,
2483
+ newCounts,
2484
+ operation
2485
+ ) {
2486
+ if (!pdb?.metadataRoot?.streams || !(pdb.tables?.rows instanceof Map) ||
2487
+ !Array.isArray(pdb.tables.counts) || !(pdb.rowCounts instanceof Map) ||
2488
+ !Array.isArray(pdb.methodDebugInformation)) {
2489
+ throw new TypeError("Portable PDB local-signature remapping requires parsed PDB data");
2490
+ }
2491
+ if (!Array.isArray(oldCounts) || !Array.isArray(newCounts) ||
2492
+ oldCounts.length < 45 || newCounts.length < 45 ||
2493
+ !Number.isInteger(signatureRid) || signatureRid < 1 ||
2494
+ !["insert", "remove"].includes(operation) ||
2495
+ (operation === "insert" &&
2496
+ (signatureRid > newCounts[17] || newCounts[17] !== oldCounts[17] + 1)) ||
2497
+ (operation === "remove" &&
2498
+ (signatureRid > oldCounts[17] || newCounts[17] !== oldCounts[17] - 1))) {
2499
+ throw new UnsupportedFormatError(
2500
+ "Portable PDB local-signature remapping requires one valid StandAloneSig edit"
2501
+ );
2502
+ }
2503
+ for (const [table, count] of pdb.rowCounts) {
2504
+ if (count !== oldCounts[table]) {
2505
+ throw new FormatError(
2506
+ `Portable PDB row count for type-system table ${table} does not match assembly metadata`
2507
+ );
2508
+ }
2509
+ }
2510
+
2511
+ const rows = new Map(pdb.tables.rows);
2512
+ const methodRows = rows.get(49) ?? [];
2513
+ if (methodRows.length !== 0 &&
2514
+ (methodRows.length !== oldCounts[6] ||
2515
+ pdb.methodDebugInformation.length !== methodRows.length)) {
2516
+ throw new FormatError("Portable PDB MethodDebugInformation row count is inconsistent");
2517
+ }
2518
+ const streams = new Map([...pdb.metadataRoot.streams].map(([name, stream]) =>
2519
+ [name, { ...stream, bytes: Uint8Array.from(stream.bytes) }]
2520
+ ));
2521
+ const blobStream = streams.get("#Blob");
2522
+ if (!blobStream) {
2523
+ throw new FormatError("Portable PDB metadata is missing its #Blob heap");
2524
+ }
2525
+ let blobHeap = blobStream.bytes;
2526
+ let changed = false;
2527
+ const methodRowsWithRemappedSignatures = methodRows.map((row) => {
2528
+ if (row.SequencePoints === 0) return { ...row };
2529
+ const debugInformation = pdb.methodDebugInformation[row.rid - 1];
2530
+ if (!debugInformation || debugInformation.rid !== row.rid) {
2531
+ throw new FormatError(
2532
+ `Portable PDB MethodDebugInformation row ${row.rid} is missing`
2533
+ );
2534
+ }
2535
+ const localSignatureRid = debugInformation.localSignatureRid;
2536
+ if (!Number.isInteger(localSignatureRid) || localSignatureRid < 0 ||
2537
+ localSignatureRid > oldCounts[17]) {
2538
+ throw new FormatError(
2539
+ `Portable PDB MethodDebugInformation row ${row.rid} references a missing StandAloneSig`
2540
+ );
2541
+ }
2542
+ if (operation === "remove" && localSignatureRid === signatureRid) {
2543
+ throw new FormatError(
2544
+ `StandAloneSig row ${signatureRid} is referenced by Portable PDB MethodDebugInformation row ${row.rid}`
2545
+ );
2546
+ }
2547
+ let remappedLocalSignatureRid = localSignatureRid;
2548
+ if (operation === "insert" && localSignatureRid >= signatureRid) {
2549
+ remappedLocalSignatureRid++;
2550
+ } else if (operation === "remove" && localSignatureRid > signatureRid) {
2551
+ remappedLocalSignatureRid--;
2552
+ }
2553
+ if (remappedLocalSignatureRid === localSignatureRid) return { ...row };
2554
+ const encoded = encodePortablePdbSequencePoints({
2555
+ localSignatureRid: remappedLocalSignatureRid,
2556
+ points: debugInformation.sequencePoints
2557
+ }, {
2558
+ documentRid: row.Document.rid,
2559
+ documentCount: pdb.tables.counts[48],
2560
+ localSignatureCount: newCounts[17]
2561
+ });
2562
+ const appended = appendBlobHeap(blobHeap, encoded);
2563
+ blobHeap = appended.heap;
2564
+ changed = true;
2565
+ return { ...row, SequencePoints: appended.index };
2566
+ });
2567
+
2568
+ const rowCounts = new Map([...pdb.rowCounts].map(([table]) =>
2569
+ [table, newCounts[table]]
2570
+ ));
2571
+ const tableCounts = [...pdb.tables.counts];
2572
+ for (let table = 0; table < 45; table++) {
2573
+ tableCounts[table] = newCounts[table];
2574
+ }
2575
+ if (methodRows.length > 0) rows.set(49, methodRowsWithRemappedSignatures);
2576
+ if (changed) blobStream.bytes = blobHeap;
2577
+
2578
+ const pdbStream = new Uint8Array(32 + rowCounts.size * 4);
2579
+ pdbStream.set(pdb.id);
2580
+ const pdbView = new DataView(pdbStream.buffer);
2581
+ pdbView.setUint32(20, pdb.entryPointToken, true);
2582
+ pdbView.setUint32(24, Number(pdb.referencedTypeSystemTables & 0xffffffffn), true);
2583
+ pdbView.setUint32(28, Number(pdb.referencedTypeSystemTables >> 32n), true);
2584
+ let countOffset = 32;
2585
+ for (const [table, count] of [...rowCounts].sort(([left], [right]) => left - right)) {
2586
+ pdbView.setUint32(countOffset, count, true);
2587
+ countOffset += 4;
2588
+ }
2589
+ const pdbMetadataStream = streams.get("#Pdb");
2590
+ if (!pdbMetadataStream) {
2591
+ throw new FormatError("Portable PDB is missing its #Pdb stream");
2592
+ }
2593
+ pdbMetadataStream.bytes = pdbStream;
2594
+ return serializeEmbeddedPortablePdb({
2595
+ ...pdb,
2596
+ metadataRoot: { ...pdb.metadataRoot, streams },
2597
+ rowCounts,
2598
+ tables: { ...pdb.tables, rows, counts: tableCounts }
2599
+ });
2600
+ }
2601
+
2602
+ export function remapEmbeddedPortablePdbTypeDefinitions(
2603
+ pdb,
2604
+ typeRid,
2605
+ oldCounts,
2606
+ newCounts,
2607
+ operation
2608
+ ) {
2609
+ if (!pdb?.metadataRoot?.streams || !(pdb.tables?.rows instanceof Map) ||
2610
+ !Array.isArray(pdb.tables.counts) || !(pdb.rowCounts instanceof Map) ||
2611
+ !Array.isArray(pdb.customDebugInformation)) {
2612
+ throw new TypeError("Portable PDB TypeDef remapping requires parsed PDB data");
2613
+ }
2614
+ if (!Array.isArray(oldCounts) || !Array.isArray(newCounts) ||
2615
+ oldCounts.length < 45 || newCounts.length < 45 ||
2616
+ !Number.isInteger(typeRid) || typeRid < 1 ||
2617
+ !["insert", "remove"].includes(operation) ||
2618
+ (operation === "insert" &&
2619
+ (typeRid > newCounts[2] || newCounts[2] !== oldCounts[2] + 1)) ||
2620
+ (operation === "remove" &&
2621
+ (typeRid > oldCounts[2] || newCounts[2] !== oldCounts[2] - 1))) {
2622
+ throw new UnsupportedFormatError(
2623
+ "Portable PDB TypeDef remapping requires one valid TypeDef insertion or removal"
2624
+ );
2625
+ }
2626
+ for (const [table, count] of pdb.rowCounts) {
2627
+ if (count !== oldCounts[table]) {
2628
+ throw new FormatError(
2629
+ `Portable PDB row count for type-system table ${table} does not match assembly metadata`
2630
+ );
2631
+ }
2632
+ if (table !== 2 && newCounts[table] !== count) {
2633
+ throw new FormatError(
2634
+ `TypeDef remapping unexpectedly changed type-system table ${table}`
2635
+ );
2636
+ }
2637
+ }
2638
+
2639
+ const rows = new Map(pdb.tables.rows);
2640
+ const customDebugRows = rows.get(55) ?? [];
2641
+ if (customDebugRows.length !== pdb.customDebugInformation.length) {
2642
+ throw new FormatError("Portable PDB CustomDebugInformation row count is inconsistent");
2643
+ }
2644
+ const customDebugRecords = new Map(
2645
+ pdb.customDebugInformation.map((record) => [record.rid, record])
2646
+ );
2647
+ const remappedCustomDebugRows = sortPortablePdbCustomDebugInformation(
2648
+ customDebugRows.flatMap((row) => {
2649
+ const record = customDebugRecords.get(row.rid);
2650
+ if (!record) {
2651
+ throw new FormatError(
2652
+ `Portable PDB CustomDebugInformation row ${row.rid} is missing its decoded record`
2653
+ );
2654
+ }
2655
+ const target = record.Parent.target;
2656
+ if (target?.table !== 2) return [{ ...row }];
2657
+ if (operation === "remove" && target.rid === typeRid) return [];
2658
+ const rid = operation === "insert"
2659
+ ? target.rid >= typeRid ? target.rid + 1 : target.rid
2660
+ : target.rid > typeRid ? target.rid - 1 : target.rid;
2661
+ if (rid === target.rid) return [{ ...row }];
2662
+ return [{
2663
+ ...row,
2664
+ Parent: {
2665
+ raw: encodeCodedIndex(2, rid, "HasCustomDebugInformation"),
2666
+ target: { ...target, rid }
2667
+ }
2668
+ }];
2669
+ })
2670
+ );
2671
+ if (remappedCustomDebugRows.length > 0) rows.set(55, remappedCustomDebugRows);
2672
+ else rows.delete(55);
2673
+
2674
+ const rowCounts = new Map([...pdb.rowCounts].map(([table]) =>
2675
+ [table, newCounts[table]]
2676
+ ));
2677
+ const tableCounts = [...pdb.tables.counts];
2678
+ for (let table = 0; table < 45; table++) {
2679
+ tableCounts[table] = newCounts[table];
2680
+ }
2681
+ tableCounts[55] = remappedCustomDebugRows.length;
2682
+ const valid = remappedCustomDebugRows.length > 0
2683
+ ? pdb.tables.valid | (1n << 55n)
2684
+ : pdb.tables.valid & ~(1n << 55n);
2685
+
2686
+ const pdbStream = new Uint8Array(32 + rowCounts.size * 4);
2687
+ pdbStream.set(pdb.id);
2688
+ const pdbView = new DataView(pdbStream.buffer);
2689
+ pdbView.setUint32(20, pdb.entryPointToken, true);
2690
+ pdbView.setUint32(24, Number(pdb.referencedTypeSystemTables & 0xffffffffn), true);
2691
+ pdbView.setUint32(28, Number(pdb.referencedTypeSystemTables >> 32n), true);
2692
+ let countOffset = 32;
2693
+ for (const [table, count] of [...rowCounts].sort(([left], [right]) => left - right)) {
2694
+ pdbView.setUint32(countOffset, count, true);
2695
+ countOffset += 4;
2696
+ }
2697
+ const streams = new Map([...pdb.metadataRoot.streams].map(([name, stream]) =>
2698
+ [name, { ...stream, bytes: Uint8Array.from(stream.bytes) }]
2699
+ ));
2700
+ const pdbMetadataStream = streams.get("#Pdb");
2701
+ if (!pdbMetadataStream) throw new FormatError("Portable PDB is missing its #Pdb stream");
2702
+ pdbMetadataStream.bytes = pdbStream;
2703
+ return serializeEmbeddedPortablePdb({
2704
+ ...pdb,
2705
+ metadataRoot: { ...pdb.metadataRoot, streams },
2706
+ rowCounts,
2707
+ tables: { ...pdb.tables, rows, counts: tableCounts, valid }
2708
+ });
2709
+ }
2710
+
2711
+ export function removeEmbeddedPortablePdbMethod(pdb, methodRid, oldCounts, newCounts) {
2712
+ if (!pdb?.metadataRoot?.streams || !(pdb.tables?.rows instanceof Map) ||
2713
+ !Array.isArray(pdb.tables.counts) || !(pdb.rowCounts instanceof Map)) {
2714
+ throw new TypeError("Portable PDB method removal requires parsed PDB data");
2715
+ }
2716
+ if (!Array.isArray(oldCounts) || !Array.isArray(newCounts) ||
2717
+ oldCounts.length < 45 || newCounts.length < 45 ||
2718
+ !Number.isInteger(methodRid) || methodRid < 1 || methodRid > oldCounts[6] ||
2719
+ newCounts[6] !== oldCounts[6] - 1) {
2720
+ throw new UnsupportedFormatError(
2721
+ "Portable PDB method removal requires one valid MethodDef row to be removed"
2722
+ );
2723
+ }
2724
+ for (const [table, count] of pdb.rowCounts) {
2725
+ if (count !== oldCounts[table]) {
2726
+ throw new FormatError(
2727
+ `Portable PDB row count for type-system table ${table} does not match assembly metadata`
2728
+ );
2729
+ }
2730
+ }
2731
+ const entryPointRid = pdb.entryPointToken & 0x00ffffff;
2732
+ if (pdb.entryPointToken === (0x06000000 | methodRid)) {
2733
+ throw new UnsupportedFormatError("Portable PDB entry point cannot be removed");
2734
+ }
2735
+ if (pdb.entryPointToken !== 0 && entryPointRid > oldCounts[6]) {
2736
+ throw new FormatError("Portable PDB entry point references a missing MethodDef");
2737
+ }
2738
+ const entryPointToken = pdb.entryPointToken !== 0 && entryPointRid > methodRid
2739
+ ? pdb.entryPointToken - 1
2740
+ : pdb.entryPointToken;
2741
+ if (entryPointToken !== 0 && (entryPointToken & 0x00ffffff) > newCounts[6]) {
2742
+ throw new FormatError("Portable PDB entry point references a removed MethodDef");
2743
+ }
2744
+
2745
+ const rows = new Map(pdb.tables.rows);
2746
+ const methodRows = rows.get(49) ?? [];
2747
+ if (methodRows.length !== 0 && methodRows.length !== oldCounts[6]) {
2748
+ throw new FormatError("Portable PDB MethodDebugInformation row count is inconsistent");
2749
+ }
2750
+ if (methodRows.length === 0) rows.delete(49);
2751
+ if (methodRows.length > 0) {
2752
+ if (methodRows.some((row, index) => row.rid !== index + 1)) {
2753
+ throw new FormatError("Portable PDB MethodDebugInformation rows are not RID-aligned");
2754
+ }
2755
+ const remainingMethodRows = methodRows
2756
+ .filter((row) => row.rid !== methodRid)
2757
+ .map((row) => row.rid > methodRid ? { ...row, rid: row.rid - 1 } : { ...row });
2758
+ if (remainingMethodRows.length > 0) rows.set(49, remainingMethodRows);
2759
+ else rows.delete(49);
2760
+ }
2761
+ const scopeRows = rows.get(50) ?? [];
2762
+ const variableRows = rows.get(51) ?? [];
2763
+ const constantRows = rows.get(52) ?? [];
2764
+ const removedScopeRids = new Set(scopeRows
2765
+ .filter((scope) => scope.Method.rid === methodRid)
2766
+ .map((scope) => scope.rid));
2767
+ const collectOwnedRows = (column, childRows) => {
2768
+ const removed = new Set();
2769
+ for (let index = 0; index < scopeRows.length; index++) {
2770
+ const scope = scopeRows[index];
2771
+ if (scope.Method.rid !== methodRid) continue;
2772
+ const firstRid = scope[column]?.rid;
2773
+ const endRid = scopeRows[index + 1]?.[column]?.rid ?? childRows.length + 1;
2774
+ if (!Number.isInteger(firstRid) || !Number.isInteger(endRid) ||
2775
+ firstRid < 1 || firstRid > childRows.length + 1 ||
2776
+ endRid < firstRid || endRid > childRows.length + 1) {
2777
+ throw new FormatError(`Portable PDB LocalScope ${column} range is invalid`);
2778
+ }
2779
+ for (let rid = firstRid; rid < endRid; rid++) removed.add(rid);
2780
+ }
2781
+ return removed;
2782
+ };
2783
+ const removedVariableRids = collectOwnedRows("VariableList", variableRows);
2784
+ const removedConstantRids = collectOwnedRows("ConstantList", constantRows);
2785
+ const remapListRid = (rid, removedRids) => {
2786
+ let removedBefore = 0;
2787
+ for (const removedRid of removedRids) {
2788
+ if (removedRid < rid) removedBefore++;
2789
+ }
2790
+ return rid - removedBefore;
2791
+ };
2792
+ const removeOwnedRows = (table, childRows, removedRids) => {
2793
+ const remaining = childRows
2794
+ .filter((row) => !removedRids.has(row.rid))
2795
+ .map((row) => ({
2796
+ ...row,
2797
+ rid: remapListRid(row.rid, removedRids)
2798
+ }));
2799
+ if (remaining.length > 0) rows.set(table, remaining);
2800
+ else rows.delete(table);
2801
+ return remaining;
2802
+ };
2803
+ const remainingVariables = removeOwnedRows(51, variableRows, removedVariableRids);
2804
+ const remainingConstants = removeOwnedRows(52, constantRows, removedConstantRids);
2805
+ const remapMethodReference = (reference) => reference.rid > methodRid
2806
+ ? { ...reference, raw: reference.rid - 1, rid: reference.rid - 1 }
2807
+ : { ...reference };
2808
+ const remainingScopes = scopeRows
2809
+ .filter((scope) => !removedScopeRids.has(scope.rid))
2810
+ .map((scope) => ({
2811
+ ...scope,
2812
+ rid: remapListRid(scope.rid, removedScopeRids),
2813
+ Method: remapMethodReference(scope.Method),
2814
+ VariableList: {
2815
+ ...scope.VariableList,
2816
+ raw: remapListRid(scope.VariableList.rid, removedVariableRids),
2817
+ rid: remapListRid(scope.VariableList.rid, removedVariableRids)
2818
+ },
2819
+ ConstantList: {
2820
+ ...scope.ConstantList,
2821
+ raw: remapListRid(scope.ConstantList.rid, removedConstantRids),
2822
+ rid: remapListRid(scope.ConstantList.rid, removedConstantRids)
2823
+ }
2824
+ }));
2825
+ if (remainingScopes.length > 0) rows.set(50, remainingScopes);
2826
+ else rows.delete(50);
2827
+ const stateMachineRows = rows.get(54) ?? [];
2828
+ if (stateMachineRows.length !== (pdb.tables.counts[54] ?? 0)) {
2829
+ throw new FormatError("Portable PDB StateMachineMethod row count is inconsistent");
2830
+ }
2831
+ const remainingStateMachineRows = stateMachineRows
2832
+ .filter((stateMachine) =>
2833
+ stateMachine.MoveNextMethod.rid !== methodRid &&
2834
+ stateMachine.KickoffMethod.rid !== methodRid
2835
+ )
2836
+ .map((stateMachine, index) => ({
2837
+ ...stateMachine,
2838
+ rid: index + 1,
2839
+ MoveNextMethod: remapMethodReference(stateMachine.MoveNextMethod),
2840
+ KickoffMethod: remapMethodReference(stateMachine.KickoffMethod)
2841
+ }));
2842
+ if (remainingStateMachineRows.length > 0) rows.set(54, remainingStateMachineRows);
2843
+ else rows.delete(54);
2844
+ const customDebugRows = rows.get(55) ?? [];
2845
+ if (!canRemapMethodScopedCustomDebugInformation(pdb)) {
2846
+ throw new UnsupportedFormatError(
2847
+ "Portable PDB method remapping cannot update method-scoped CustomDebugInformation"
2848
+ );
2849
+ }
2850
+ const asyncRecordsReferencingRemovedMethod = new Set(
2851
+ pdb.customDebugInformation
2852
+ .filter((record) =>
2853
+ record.asyncMethodSteppingInformation?.steps.some((step) =>
2854
+ step.methodRid === methodRid
2855
+ )
2856
+ )
2857
+ .map((record) => record.rid)
2858
+ );
2859
+ const survivingCustomDebugRows = customDebugRows.filter((record) => {
2860
+ const target = record.Parent.target;
2861
+ if (asyncRecordsReferencingRemovedMethod.has(record.rid)) return false;
2862
+ if (target?.table === 6 && target.rid === methodRid) return false;
2863
+ if (target?.table === 50 && removedScopeRids.has(target.rid)) return false;
2864
+ if (target?.table === 51 && removedVariableRids.has(target.rid)) return false;
2865
+ if (target?.table === 52 && removedConstantRids.has(target.rid)) return false;
2866
+ return true;
2867
+ });
2868
+ const asyncRemapped = remapAsyncMethodReferences(
2869
+ pdb,
2870
+ survivingCustomDebugRows,
2871
+ (rid) => {
2872
+ if (rid === methodRid) {
2873
+ throw new UnsupportedFormatError(
2874
+ "Portable PDB method removal cannot discard a method referenced by async stepping information"
2875
+ );
2876
+ }
2877
+ return rid > methodRid ? rid - 1 : rid;
2878
+ }
2879
+ );
2880
+ const remappedCustomDebugRows = sortPortablePdbCustomDebugInformation(
2881
+ asyncRemapped.records.map((record) => {
2882
+ const target = record.Parent.target;
2883
+ if (!target) return { ...record };
2884
+ const rid = target.table === 6 && target.rid > methodRid
2885
+ ? target.rid - 1
2886
+ : target.table === 50
2887
+ ? remapListRid(target.rid, removedScopeRids)
2888
+ : target.table === 51
2889
+ ? remapListRid(target.rid, removedVariableRids)
2890
+ : target.table === 52
2891
+ ? remapListRid(target.rid, removedConstantRids)
2892
+ : target.rid;
2893
+ return rid === target.rid
2894
+ ? { ...record }
2895
+ : {
2896
+ ...record,
2897
+ Parent: {
2898
+ raw: encodeCodedIndex(
2899
+ target.table,
2900
+ rid,
2901
+ "HasCustomDebugInformation"
2902
+ ),
2903
+ target: { ...target, rid }
2904
+ }
2905
+ };
2906
+ })
2907
+ );
2908
+ if (remappedCustomDebugRows.length > 0) rows.set(55, remappedCustomDebugRows);
2909
+ else rows.delete(55);
2910
+
2911
+ const rowCounts = new Map([...pdb.rowCounts].map(([table]) =>
2912
+ [table, newCounts[table]]
2913
+ ));
2914
+ const tableCounts = [...pdb.tables.counts];
2915
+ for (let table = 0; table < 45; table++) {
2916
+ tableCounts[table] = newCounts[table];
2917
+ }
2918
+ tableCounts[49] = methodRows.length > 0 ? methodRows.length - 1 : 0;
2919
+ tableCounts[50] = remainingScopes.length;
2920
+ tableCounts[51] = remainingVariables.length;
2921
+ tableCounts[52] = remainingConstants.length;
2922
+ tableCounts[54] = remainingStateMachineRows.length;
2923
+ tableCounts[55] = remappedCustomDebugRows.length;
2924
+ let updatedValid = pdb.tables.valid;
2925
+ for (const [table, count] of [
2926
+ [49, tableCounts[49]],
2927
+ [50, tableCounts[50]],
2928
+ [51, tableCounts[51]],
2929
+ [52, tableCounts[52]],
2930
+ [54, tableCounts[54]],
2931
+ [55, tableCounts[55]]
2932
+ ]) {
2933
+ updatedValid = count === 0
2934
+ ? updatedValid & ~(1n << BigInt(table))
2935
+ : updatedValid | (1n << BigInt(table));
2936
+ }
2937
+
2938
+ const pdbStream = new Uint8Array(32 + rowCounts.size * 4);
2939
+ pdbStream.set(pdb.id);
2940
+ const pdbView = new DataView(pdbStream.buffer);
2941
+ pdbView.setUint32(20, entryPointToken, true);
2942
+ pdbView.setUint32(24, Number(pdb.referencedTypeSystemTables & 0xffffffffn), true);
2943
+ pdbView.setUint32(28, Number(pdb.referencedTypeSystemTables >> 32n), true);
2944
+ let countOffset = 32;
2945
+ for (const [table, count] of [...rowCounts].sort(([left], [right]) => left - right)) {
2946
+ pdbView.setUint32(countOffset, count, true);
2947
+ countOffset += 4;
2948
+ }
2949
+
2950
+ const streams = new Map([...(asyncRemapped.streams ?? pdb.metadataRoot.streams)].map(([name, stream]) =>
2951
+ [name, { ...stream, bytes: Uint8Array.from(stream.bytes) }]
2952
+ ));
2953
+ const pdbMetadataStream = streams.get("#Pdb");
2954
+ if (!pdbMetadataStream) {
2955
+ throw new FormatError("Portable PDB is missing its #Pdb stream");
2956
+ }
2957
+ pdbMetadataStream.bytes = pdbStream;
2958
+ return serializeEmbeddedPortablePdb({
2959
+ ...pdb,
2960
+ metadataRoot: { ...pdb.metadataRoot, streams },
2961
+ rowCounts,
2962
+ tables: { ...pdb.tables, rows, counts: tableCounts, valid: updatedValid }
2963
+ });
2964
+ }
2965
+
2966
+ export function replaceEmbeddedPortablePdbSource(pdb, documentRid, source, { compress = true } = {}) {
2967
+ if (!pdb?.metadataRoot?.streams || !(pdb.tables?.rows instanceof Map) ||
2968
+ !Array.isArray(pdb.tables.counts) || !Array.isArray(pdb.customDebugInformation)) {
2969
+ throw new TypeError("Portable PDB EmbeddedSource editing requires parsed PDB data");
2970
+ }
2971
+ if (!Number.isInteger(documentRid) || documentRid < 1 ||
2972
+ documentRid > pdb.tables.counts[48]) {
2973
+ throw new RangeError(`Invalid Portable PDB Document row ID ${documentRid}`);
2974
+ }
2975
+ if (!(source instanceof Uint8Array)) {
2976
+ throw new TypeError("Portable PDB embedded source must be a Uint8Array");
2977
+ }
2978
+ if (source.length > maxEmbeddedSourceSize) {
2979
+ throw new UnsupportedFormatError(
2980
+ `EmbeddedSource exceeds the ${maxEmbeddedSourceSize}-byte size limit`
2981
+ );
2982
+ }
2983
+ if (typeof compress !== "boolean") {
2984
+ throw new TypeError("Portable PDB EmbeddedSource compression option must be a boolean");
2985
+ }
2986
+ const debugInfo = pdb.customDebugInformation.find((row) =>
2987
+ row.kind === embeddedSourceKind &&
2988
+ row.Parent.target?.table === 48 &&
2989
+ row.Parent.target.rid === documentRid
2990
+ );
2991
+ if (!debugInfo) {
2992
+ throw new UnsupportedFormatError(
2993
+ `Portable PDB Document row ${documentRid} has no EmbeddedSource record`
2994
+ );
2995
+ }
2996
+ const documentRow = pdb.tables.rows.get(48)?.[documentRid - 1];
2997
+ if (!documentRow) {
2998
+ throw new FormatError(`Portable PDB Document row ${documentRid} is missing`);
2999
+ }
3000
+ const hashName = getPortablePdbDocumentHashName(pdb, documentRow);
3001
+ const rawSource = Uint8Array.from(source);
3002
+ const sourceHash = Uint8Array.from(createHash(hashName).update(rawSource).digest());
3003
+ const value = encodeEmbeddedSourceValue(rawSource, compress);
3004
+
3005
+ return replacePortablePdbCustomDebugInformationValue(
3006
+ pdb,
3007
+ debugInfo,
3008
+ value,
3009
+ { documentRid, hash: sourceHash }
3010
+ );
3011
+ }
3012
+
3013
+ export function addEmbeddedPortablePdbSource(pdb, documentRid, source, { compress = true } = {}) {
3014
+ if (!pdb?.metadataRoot?.streams || !(pdb.tables?.rows instanceof Map) ||
3015
+ !Array.isArray(pdb.tables.counts) || !Array.isArray(pdb.customDebugInformation)) {
3016
+ throw new TypeError("Portable PDB EmbeddedSource editing requires parsed PDB data");
3017
+ }
3018
+ if (!Number.isInteger(documentRid) || documentRid < 1 ||
3019
+ documentRid > pdb.tables.counts[48]) {
3020
+ throw new RangeError(`Invalid Portable PDB Document row ID ${documentRid}`);
3021
+ }
3022
+ if (!(source instanceof Uint8Array)) {
3023
+ throw new TypeError("Portable PDB embedded source must be a Uint8Array");
3024
+ }
3025
+ if (source.length > maxEmbeddedSourceSize) {
3026
+ throw new UnsupportedFormatError(
3027
+ `EmbeddedSource exceeds the ${maxEmbeddedSourceSize}-byte size limit`
3028
+ );
3029
+ }
3030
+ if (typeof compress !== "boolean") {
3031
+ throw new TypeError("Portable PDB EmbeddedSource compression option must be a boolean");
3032
+ }
3033
+ if (pdb.customDebugInformation.some((row) =>
3034
+ row.kind === embeddedSourceKind &&
3035
+ row.Parent.target?.table === 48 &&
3036
+ row.Parent.target.rid === documentRid
3037
+ )) {
3038
+ throw new UnsupportedFormatError(
3039
+ `Portable PDB Document row ${documentRid} already has an EmbeddedSource record`
3040
+ );
3041
+ }
3042
+ const documentRow = pdb.tables.rows.get(48)?.[documentRid - 1];
3043
+ if (!documentRow) {
3044
+ throw new FormatError(`Portable PDB Document row ${documentRid} is missing`);
3045
+ }
3046
+ const hashName = getPortablePdbDocumentHashName(pdb, documentRow);
3047
+ const rawSource = Uint8Array.from(source);
3048
+ const sourceHash = Uint8Array.from(createHash(hashName).update(rawSource).digest());
3049
+ const value = encodeEmbeddedSourceValue(rawSource, compress);
3050
+ const streams = new Map([...pdb.metadataRoot.streams].map(([streamName, stream]) =>
3051
+ [streamName, { ...stream, bytes: Uint8Array.from(stream.bytes) }]
3052
+ ));
3053
+ const guidStream = streams.get("#GUID");
3054
+ const blobStream = streams.get("#Blob");
3055
+ if (!guidStream || !blobStream) {
3056
+ throw new FormatError("Portable PDB metadata is missing its GUID or Blob heap");
3057
+ }
3058
+ if (guidStream.bytes.length % 16 !== 0) {
3059
+ throw new FormatError("Portable PDB GUID heap size is not a multiple of 16 bytes");
3060
+ }
3061
+ const kindIndex = guidStream.bytes.length / 16 + 1;
3062
+ const expandedGuidStream = new Uint8Array(guidStream.bytes.length + 16);
3063
+ expandedGuidStream.set(guidStream.bytes);
3064
+ expandedGuidStream.set(embeddedSourceGuidBytes, guidStream.bytes.length);
3065
+ guidStream.bytes = expandedGuidStream;
3066
+ const appendedValue = appendBlobHeap(blobStream.bytes, value);
3067
+ const appendedHash = appendBlobHeap(appendedValue.heap, sourceHash);
3068
+ blobStream.bytes = appendedHash.heap;
3069
+
3070
+ const parent = {
3071
+ raw: encodeCodedIndex(48, documentRid, "HasCustomDebugInformation"),
3072
+ target: { table: 48, tableName: "Document", rid: documentRid }
3073
+ };
3074
+ const rows = new Map(pdb.tables.rows);
3075
+ rows.set(48, (pdb.tables.rows.get(48) ?? []).map((row) =>
3076
+ row.rid === documentRid ? { ...row, Hash: appendedHash.index } : { ...row }
3077
+ ));
3078
+ const debugRows = [...(pdb.tables.rows.get(55) ?? []), {
3079
+ table: 55,
3080
+ tableName: "CustomDebugInformation",
3081
+ rid: 0,
3082
+ Parent: parent,
3083
+ Kind: kindIndex,
3084
+ Value: appendedValue.index
3085
+ }].sort((left, right) => left.Parent.raw - right.Parent.raw)
3086
+ .map((row, index) => ({ ...row, rid: index + 1 }));
3087
+ rows.set(55, debugRows);
3088
+ const counts = [...pdb.tables.counts];
3089
+ counts[55] = debugRows.length;
3090
+ return serializeEmbeddedPortablePdb({
3091
+ ...pdb,
3092
+ metadataRoot: { ...pdb.metadataRoot, streams },
3093
+ tables: {
3094
+ ...pdb.tables,
3095
+ valid: debugRows.length > 0
3096
+ ? pdb.tables.valid | (1n << 55n)
3097
+ : pdb.tables.valid & ~(1n << 55n),
3098
+ counts,
3099
+ rows
3100
+ }
3101
+ });
3102
+ }
3103
+
3104
+ export function removeEmbeddedPortablePdbSource(pdb, documentRid) {
3105
+ if (!pdb?.metadataRoot?.streams || !(pdb.tables?.rows instanceof Map) ||
3106
+ !Array.isArray(pdb.tables.counts) || !Array.isArray(pdb.customDebugInformation)) {
3107
+ throw new TypeError("Portable PDB EmbeddedSource editing requires parsed PDB data");
3108
+ }
3109
+ if (!Number.isInteger(documentRid) || documentRid < 1 ||
3110
+ documentRid > pdb.tables.counts[48]) {
3111
+ throw new RangeError(`Invalid Portable PDB Document row ID ${documentRid}`);
3112
+ }
3113
+ const debugInfo = pdb.customDebugInformation.find((row) =>
3114
+ row.kind === embeddedSourceKind &&
3115
+ row.Parent.target?.table === 48 &&
3116
+ row.Parent.target.rid === documentRid
3117
+ );
3118
+ if (!debugInfo) {
3119
+ throw new UnsupportedFormatError(
3120
+ `Portable PDB Document row ${documentRid} has no EmbeddedSource record`
3121
+ );
3122
+ }
3123
+ return removePortablePdbCustomDebugInformation(pdb, debugInfo);
3124
+ }
3125
+
3126
+ export function removeEmbeddedPortablePdbCustomDebugInformation(pdb, recordRid) {
3127
+ if (!pdb?.metadataRoot?.streams || !(pdb.tables?.rows instanceof Map) ||
3128
+ !Array.isArray(pdb.tables.counts) || !Array.isArray(pdb.customDebugInformation)) {
3129
+ throw new TypeError("Portable PDB custom-debug editing requires parsed PDB data");
3130
+ }
3131
+ if (!Number.isInteger(recordRid) || recordRid < 1 ||
3132
+ recordRid > pdb.tables.counts[55]) {
3133
+ throw new RangeError(
3134
+ `Invalid Portable PDB CustomDebugInformation row ID ${recordRid}`
3135
+ );
3136
+ }
3137
+ const debugInfo = pdb.customDebugInformation.find((record) => record.rid === recordRid);
3138
+ if (!debugInfo) {
3139
+ throw new FormatError(
3140
+ `Portable PDB CustomDebugInformation row ${recordRid} is missing its decoded record`
3141
+ );
3142
+ }
3143
+ return removePortablePdbCustomDebugInformation(pdb, debugInfo);
3144
+ }
3145
+
3146
+ function removePortablePdbCustomDebugInformation(pdb, debugInfo) {
3147
+ const rowsForDebugInformation = pdb.tables.rows.get(55) ?? [];
3148
+ if (rowsForDebugInformation.length !== pdb.tables.counts[55]) {
3149
+ throw new FormatError("Portable PDB CustomDebugInformation row count is inconsistent");
3150
+ }
3151
+ const row = rowsForDebugInformation[debugInfo.rid - 1];
3152
+ if (!row || row.rid !== debugInfo.rid || row.Kind === 0) {
3153
+ throw new FormatError(
3154
+ `Portable PDB CustomDebugInformation row ${debugInfo.rid} is missing`
3155
+ );
3156
+ }
3157
+ const debugRows = rowsForDebugInformation
3158
+ .filter((record) => record.rid !== debugInfo.rid)
3159
+ .map((record, index) => ({ ...record, rid: index + 1 }));
3160
+ const rows = new Map(pdb.tables.rows);
3161
+ if (debugRows.length > 0) rows.set(55, debugRows);
3162
+ else rows.delete(55);
3163
+ const counts = [...pdb.tables.counts];
3164
+ counts[55] = debugRows.length;
3165
+ return serializeEmbeddedPortablePdb({
3166
+ ...pdb,
3167
+ tables: {
3168
+ ...pdb.tables,
3169
+ valid: debugRows.length > 0
3170
+ ? pdb.tables.valid | (1n << 55n)
3171
+ : pdb.tables.valid & ~(1n << 55n),
3172
+ counts,
3173
+ rows
3174
+ }
3175
+ });
3176
+ }
3177
+
3178
+ function getPortablePdbDocumentHashName(pdb, documentRow) {
3179
+ const hashAlgorithmBytes = pdb.heaps.getGuid(documentRow.HashAlgorithm);
3180
+ const hashAlgorithm = hashAlgorithmBytes ? formatGuid(hashAlgorithmBytes) : null;
3181
+ const hashName = documentHashAlgorithms.get(hashAlgorithm);
3182
+ if (!hashName) {
3183
+ throw new UnsupportedFormatError(
3184
+ `Portable PDB Document hash algorithm ${hashAlgorithm ?? "(nil)"} is unsupported`
3185
+ );
3186
+ }
3187
+ return hashName;
3188
+ }
3189
+
3190
+ function encodeEmbeddedSourceValue(source, compress) {
3191
+ const compressed = compress && source.length > 0
3192
+ ? Uint8Array.from(deflateRawSync(source))
3193
+ : null;
3194
+ const useCompressed = compressed !== null && compressed.length < source.length;
3195
+ const payload = useCompressed ? compressed : source;
3196
+ const value = new Uint8Array(4 + payload.length);
3197
+ new DataView(value.buffer).setUint32(0, useCompressed ? source.length : 0, true);
3198
+ value.set(payload, 4);
3199
+ return value;
3200
+ }
3201
+
3202
+ function replacePortablePdbCustomDebugInformationValue(
3203
+ pdb,
3204
+ debugInfo,
3205
+ value,
3206
+ documentHashUpdate = null
3207
+ ) {
3208
+ const streams = new Map([...pdb.metadataRoot.streams].map(([streamName, stream]) =>
3209
+ [streamName, { ...stream, bytes: Uint8Array.from(stream.bytes) }]
3210
+ ));
3211
+ const blobStream = streams.get("#Blob");
3212
+ if (!blobStream) throw new FormatError("Portable PDB metadata is missing its #Blob heap");
3213
+ const appended = appendBlobHeap(blobStream.bytes, value);
3214
+ blobStream.bytes = appended.heap;
3215
+ const rows = new Map(pdb.tables.rows);
3216
+ const debugRows = pdb.tables.rows.get(55) ?? [];
3217
+ rows.set(55, debugRows.map((row) =>
3218
+ row.rid === debugInfo.rid ? { ...row, Value: appended.index } : { ...row }
3219
+ ));
3220
+ if (documentHashUpdate) {
3221
+ const documentRows = pdb.tables.rows.get(48) ?? [];
3222
+ const appendedHash = appendBlobHeap(blobStream.bytes, documentHashUpdate.hash);
3223
+ blobStream.bytes = appendedHash.heap;
3224
+ rows.set(48, documentRows.map((row) =>
3225
+ row.rid === documentHashUpdate.documentRid
3226
+ ? { ...row, Hash: appendedHash.index }
3227
+ : { ...row }
3228
+ ));
3229
+ }
3230
+ return serializeEmbeddedPortablePdb({
3231
+ ...pdb,
3232
+ metadataRoot: { ...pdb.metadataRoot, streams },
3233
+ tables: { ...pdb.tables, rows }
3234
+ });
3235
+ }
3236
+
3237
+ function encodeSourceLinkValue(sourceLink) {
3238
+ if (sourceLink === null || Array.isArray(sourceLink) || typeof sourceLink !== "object" ||
3239
+ sourceLink.documents === null || Array.isArray(sourceLink.documents) ||
3240
+ typeof sourceLink.documents !== "object" ||
3241
+ Object.values(sourceLink.documents).some((mapping) => typeof mapping !== "string")) {
3242
+ throw new TypeError("SourceLink requires a documents object with string mappings");
3243
+ }
3244
+ const json = JSON.stringify(sourceLink);
3245
+ if (typeof json !== "string") {
3246
+ throw new TypeError("SourceLink value could not be serialized as JSON");
3247
+ }
3248
+ const encoded = new TextEncoder().encode(json);
3249
+ if (decodeUtf8(encoded, "Portable PDB SourceLink") !== json) {
3250
+ throw new TypeError("SourceLink JSON must contain valid Unicode text");
3251
+ }
3252
+ return encoded;
3253
+ }
3254
+
3255
+ export function addEmbeddedPortablePdbSourceLink(pdb, sourceLink) {
3256
+ if (!pdb?.metadataRoot?.streams || !(pdb.tables?.rows instanceof Map) ||
3257
+ !Array.isArray(pdb.tables.counts) || !Array.isArray(pdb.customDebugInformation)) {
3258
+ throw new TypeError("Portable PDB SourceLink editing requires parsed PDB data");
3259
+ }
3260
+ if (pdb.customDebugInformation.some((row) =>
3261
+ row.kind === sourceLinkKind && row.Parent.target?.table === 0
3262
+ )) {
3263
+ throw new UnsupportedFormatError("Portable PDB already has a module-scoped SourceLink record");
3264
+ }
3265
+ const value = encodeSourceLinkValue(sourceLink);
3266
+ const streams = new Map([...pdb.metadataRoot.streams].map(([streamName, stream]) =>
3267
+ [streamName, { ...stream, bytes: Uint8Array.from(stream.bytes) }]
3268
+ ));
3269
+ const guidStream = streams.get("#GUID");
3270
+ const blobStream = streams.get("#Blob");
3271
+ if (!guidStream || !blobStream) {
3272
+ throw new FormatError("Portable PDB metadata is missing its GUID or Blob heap");
3273
+ }
3274
+ if (guidStream.bytes.length % 16 !== 0) {
3275
+ throw new FormatError("Portable PDB GUID heap size is not a multiple of 16 bytes");
3276
+ }
3277
+ const kindIndex = guidStream.bytes.length / 16 + 1;
3278
+ const expandedGuidStream = new Uint8Array(guidStream.bytes.length + 16);
3279
+ expandedGuidStream.set(guidStream.bytes);
3280
+ expandedGuidStream.set(sourceLinkGuidBytes, guidStream.bytes.length);
3281
+ guidStream.bytes = expandedGuidStream;
3282
+ const appendedValue = appendBlobHeap(blobStream.bytes, value);
3283
+ blobStream.bytes = appendedValue.heap;
3284
+
3285
+ const parent = {
3286
+ raw: encodeCodedIndex(0, 1, "HasCustomDebugInformation"),
3287
+ target: { table: 0, tableName: "Module", rid: 1 }
3288
+ };
3289
+ const rows = new Map(pdb.tables.rows);
3290
+ const debugRows = [...(pdb.tables.rows.get(55) ?? []), {
3291
+ table: 55,
3292
+ tableName: "CustomDebugInformation",
3293
+ rid: 0,
3294
+ Parent: parent,
3295
+ Kind: kindIndex,
3296
+ Value: appendedValue.index
3297
+ }].sort((left, right) => left.Parent.raw - right.Parent.raw)
3298
+ .map((row, index) => ({ ...row, rid: index + 1 }));
3299
+ rows.set(55, debugRows);
3300
+ const counts = [...pdb.tables.counts];
3301
+ counts[55] = debugRows.length;
3302
+ return serializeEmbeddedPortablePdb({
3303
+ ...pdb,
3304
+ metadataRoot: { ...pdb.metadataRoot, streams },
3305
+ tables: {
3306
+ ...pdb.tables,
3307
+ valid: debugRows.length > 0
3308
+ ? pdb.tables.valid | (1n << 55n)
3309
+ : pdb.tables.valid & ~(1n << 55n),
3310
+ counts,
3311
+ rows
3312
+ }
3313
+ });
3314
+ }
3315
+
3316
+ export function replaceEmbeddedPortablePdbSourceLink(pdb, sourceLink) {
3317
+ if (!pdb?.metadataRoot?.streams || !(pdb.tables?.rows instanceof Map) ||
3318
+ !Array.isArray(pdb.tables.counts) || !Array.isArray(pdb.customDebugInformation)) {
3319
+ throw new TypeError("Portable PDB SourceLink editing requires parsed PDB data");
3320
+ }
3321
+ const encoded = encodeSourceLinkValue(sourceLink);
3322
+ const debugInfo = pdb.customDebugInformation.find((row) =>
3323
+ row.kind === sourceLinkKind && row.Parent.target?.table === 0
3324
+ );
3325
+ if (!debugInfo) {
3326
+ throw new UnsupportedFormatError("Portable PDB has no module-scoped SourceLink record");
3327
+ }
3328
+ return replacePortablePdbCustomDebugInformationValue(
3329
+ pdb,
3330
+ debugInfo,
3331
+ encoded
3332
+ );
3333
+ }
3334
+
3335
+ export function removeEmbeddedPortablePdbSourceLink(pdb) {
3336
+ if (!pdb?.metadataRoot?.streams || !(pdb.tables?.rows instanceof Map) ||
3337
+ !Array.isArray(pdb.tables.counts) || !Array.isArray(pdb.customDebugInformation)) {
3338
+ throw new TypeError("Portable PDB SourceLink editing requires parsed PDB data");
3339
+ }
3340
+ const debugInfo = pdb.customDebugInformation.find((row) =>
3341
+ row.kind === sourceLinkKind && row.Parent.target?.table === 0
3342
+ );
3343
+ if (!debugInfo) {
3344
+ throw new UnsupportedFormatError("Portable PDB has no module-scoped SourceLink record");
3345
+ }
3346
+ return removePortablePdbCustomDebugInformation(pdb, debugInfo);
3347
+ }