exoproc-accessors 0.1.0 → 0.6.2

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.
package/dist/index.js CHANGED
@@ -37,9 +37,9 @@ import {
37
37
  isModuleLoadedInProcessSync,
38
38
  MiddlewareAccessor,
39
39
  InittableMiddlewareAccessor,
40
- isMiddlewareAccessor
40
+ isInittableAccessor,
41
+ HostAccessor
41
42
  } from "bun-xffi";
42
-
43
43
  class MsvcrtDependentMiddlewareAccessor extends InittableMiddlewareAccessor {
44
44
  async onInit() {
45
45
  if (this.isLocal)
@@ -61,27 +61,6 @@ class MsvcrtDependentMiddlewareAccessor extends InittableMiddlewareAccessor {
61
61
  }
62
62
  }
63
63
 
64
- class HostAccessor extends InittableMiddlewareAccessor {
65
- get processId() {
66
- return this._processId;
67
- }
68
- constructor(backend, root) {
69
- super(backend, root ?? null);
70
- if (!root) {
71
- this.root = this;
72
- }
73
- let b = backend;
74
- while (isMiddlewareAccessor(b)) {
75
- b = b.backend;
76
- }
77
- if (b) {
78
- this._processId = b.processId;
79
- }
80
- }
81
- async onInit() {}
82
- onInitSync() {}
83
- }
84
-
85
64
  class ThrowingHostAccessor extends HostAccessor {
86
65
  constructor(processId, root) {
87
66
  super(new ThrowingMemoryAccessor(processId), root);
@@ -92,7 +71,7 @@ class RedirectorHostAccessor extends HostAccessor {
92
71
  _target;
93
72
  constructor(processId, root) {
94
73
  super(new ThrowingMemoryAccessor(processId), root);
95
- this._target = new ThrowingHostAccessor(processId, this.root);
74
+ this._target = new ThrowingHostAccessor(processId);
96
75
  }
97
76
  get target() {
98
77
  return this._target;
@@ -238,9 +217,51 @@ class RedirectorHostAccessor extends HostAccessor {
238
217
  }
239
218
  }
240
219
  async onInit() {}
220
+ async onDeinit() {
221
+ const t = this.getTarget();
222
+ if (t !== this && t !== this.root && isInittableAccessor(t)) {
223
+ await t.deinit();
224
+ }
225
+ }
241
226
  onInitSync() {}
242
227
  }
243
228
 
229
+ class RacingHostAccessor extends RedirectorHostAccessor {
230
+ racers = [];
231
+ registerChild(child) {
232
+ if (!this.racers || !isInittableAccessor(child))
233
+ return;
234
+ this.racers.push(child);
235
+ }
236
+ startRacer(racer) {
237
+ return racer.init();
238
+ }
239
+ async race() {
240
+ if (this.racers.length === 0) {
241
+ throw new Error("RacingHostAccessor: no racers registered to race");
242
+ }
243
+ const attempts = this.racers.map((racer) => ({
244
+ racer,
245
+ settled: this.startRacer(racer)
246
+ }));
247
+ let winner;
248
+ try {
249
+ winner = await Promise.any(attempts.map((a) => a.settled.then(() => a.racer)));
250
+ } catch (err) {
251
+ throw new Error(`RacingHostAccessor: none of ${attempts.length} racer(s) could be initialized`, { cause: err });
252
+ }
253
+ for (const a of attempts) {
254
+ if (a.racer === winner)
255
+ continue;
256
+ this.releaseLoser(a.racer, a.settled);
257
+ }
258
+ return winner;
259
+ }
260
+ releaseLoser(racer, settled) {
261
+ settled.then(() => racer.deinit().catch(() => {})).catch(() => {});
262
+ }
263
+ }
264
+
244
265
  class BootstrapHostAccessor extends RedirectorHostAccessor {
245
266
  constructor(processId, root) {
246
267
  super(processId, root);
@@ -258,7 +279,7 @@ class BootstrapHostAccessor extends RedirectorHostAccessor {
258
279
 
259
280
  class CallRedirectorAccessor extends MiddlewareAccessor {
260
281
  async alloc(size, address = null, protection = MemoryProtection.READWRITE, allocationType = MemoryState.COMMIT | MemoryState.RESERVE) {
261
- const addressVal = address ? resolveAddress(address) : null;
282
+ const addressVal = address ? resolveAddress(address) : 0;
262
283
  const result = await this.root.call(Kernel32Impl.VirtualAlloc, addressVal, size, allocationType, protection);
263
284
  if (!result || Number(result) === 0) {
264
285
  throw new Error(`VirtualAlloc failed in remote process for size ${size}`);
@@ -266,7 +287,7 @@ class CallRedirectorAccessor extends MiddlewareAccessor {
266
287
  return result;
267
288
  }
268
289
  allocSync(size, address = null, protection = MemoryProtection.READWRITE, allocationType = MemoryState.COMMIT | MemoryState.RESERVE) {
269
- const addressVal = address ? resolveAddress(address) : null;
290
+ const addressVal = address ? resolveAddress(address) : 0;
270
291
  const result = this.root.callSync(Kernel32Impl.VirtualAlloc, addressVal, size, allocationType, protection);
271
292
  if (!result || Number(result) === 0) {
272
293
  throw new Error(`VirtualAlloc failed in remote process for size ${size}`);
@@ -456,27 +477,56 @@ class IndirectCallRedirectorAccessor extends CallRedirectorAccessor {
456
477
  });
457
478
  return info;
458
479
  }
480
+ hasMsvcrt = null;
481
+ async checkMsvcrt() {
482
+ if (this.hasMsvcrt !== null)
483
+ return this.hasMsvcrt;
484
+ const msvcrtBase = MsvcrtLibrary.baseAddress;
485
+ if (msvcrtBase.isNull() || Number(msvcrtBase) === 0) {
486
+ this.hasMsvcrt = false;
487
+ return false;
488
+ }
489
+ this.hasMsvcrt = await isModuleLoadedInProcess(this.root, msvcrtBase);
490
+ return this.hasMsvcrt;
491
+ }
492
+ checkMsvcrtSync() {
493
+ if (this.hasMsvcrt !== null)
494
+ return this.hasMsvcrt;
495
+ const msvcrtBase = MsvcrtLibrary.baseAddress;
496
+ if (msvcrtBase.isNull() || Number(msvcrtBase) === 0) {
497
+ this.hasMsvcrt = false;
498
+ return false;
499
+ }
500
+ this.hasMsvcrt = isModuleLoadedInProcessSync(this.root, msvcrtBase);
501
+ return this.hasMsvcrt;
502
+ }
459
503
  async alloc(size, address = null, protection = MemoryProtection.READWRITE, allocationType = MemoryState.COMMIT | MemoryState.RESERVE) {
460
504
  if (protection === MemoryProtection.READWRITE && (address === null || address === undefined)) {
461
- const result = await this.root.call(MsvcrtImpl.malloc, size);
462
- if (!result || Number(result) === 0) {
463
- throw new Error(`malloc failed in remote process for size ${size}`);
464
- }
465
- const allocatedAddr = Number(result);
466
- this.mallocs.set(allocatedAddr, size);
467
- return allocatedAddr;
505
+ try {
506
+ if (await this.checkMsvcrt()) {
507
+ const result = await this.root.call(MsvcrtImpl.malloc, size);
508
+ if (result && Number(result) !== 0) {
509
+ const allocatedAddr = Number(result);
510
+ this.mallocs.set(allocatedAddr, size);
511
+ return allocatedAddr;
512
+ }
513
+ }
514
+ } catch {}
468
515
  }
469
516
  return super.alloc(size, address, protection, allocationType);
470
517
  }
471
518
  allocSync(size, address = null, protection = MemoryProtection.READWRITE, allocationType = MemoryState.COMMIT | MemoryState.RESERVE) {
472
519
  if (protection === MemoryProtection.READWRITE && (address === null || address === undefined)) {
473
- const result = this.root.callSync(MsvcrtImpl.malloc, size);
474
- if (!result || Number(result) === 0) {
475
- throw new Error(`malloc failed in remote process for size ${size}`);
476
- }
477
- const allocatedAddr = Number(result);
478
- this.mallocs.set(allocatedAddr, size);
479
- return allocatedAddr;
520
+ try {
521
+ if (this.checkMsvcrtSync()) {
522
+ const result = this.root.callSync(MsvcrtImpl.malloc, size);
523
+ if (result && Number(result) !== 0) {
524
+ const allocatedAddr = Number(result);
525
+ this.mallocs.set(allocatedAddr, size);
526
+ return allocatedAddr;
527
+ }
528
+ }
529
+ } catch {}
480
530
  }
481
531
  return super.allocSync(size, address, protection, allocationType);
482
532
  }
@@ -539,14 +589,35 @@ class IndirectCallRedirectorAccessor extends CallRedirectorAccessor {
539
589
  }
540
590
 
541
591
  class MarshallingCallableAccessor extends MiddlewareAccessor {
542
- static bufferToMarshal(type, val) {
592
+ static marshalInfo(funcName, type, val) {
543
593
  const normType = normalizeType(type);
544
594
  if (normType === "cstring" && typeof val === "string") {
545
- return Buffer.from(val + "\x00", "utf8");
546
- } else if (normType === "cwstring" && typeof val === "string") {
547
- return Buffer.from(val + "\x00", "utf16le");
548
- } else if (val && typeof val === "object" && Buffer.isBuffer(val)) {
549
- return val;
595
+ return { buf: Buffer.from(val + "\x00", "utf8") };
596
+ }
597
+ if (normType === "cwstring" && typeof val === "string") {
598
+ return { buf: Buffer.from(val + "\x00", "utf16le") };
599
+ }
600
+ if (typeof val === "string" && (normType === "ptr" || normType === "i64" || normType === "u64" || normType === "usize" || normType === "size_t")) {
601
+ const trimmed = val.trim();
602
+ if (!/^-?0x[0-9a-fA-F]+$/.test(trimmed)) {
603
+ const isAnsi = funcName ? funcName.endsWith("A") : false;
604
+ return {
605
+ buf: Buffer.from(val + "\x00", isAnsi ? "utf8" : "utf16le")
606
+ };
607
+ }
608
+ }
609
+ if (val && typeof val === "object") {
610
+ if (Buffer.isBuffer(val)) {
611
+ return { buf: val, writeBackTarget: val };
612
+ }
613
+ if (Buffer.isBuffer(val._backingBuffer)) {
614
+ const backing = val._backingBuffer;
615
+ return { buf: backing, writeBackTarget: backing };
616
+ }
617
+ if (ArrayBuffer.isView(val)) {
618
+ const viewBuf = Buffer.from(val.buffer, val.byteOffset, val.byteLength);
619
+ return { buf: viewBuf, writeBackTarget: viewBuf };
620
+ }
550
621
  }
551
622
  return null;
552
623
  }
@@ -555,16 +626,39 @@ class MarshallingCallableAccessor extends MiddlewareAccessor {
555
626
  const tempAllocations = [];
556
627
  try {
557
628
  const signatureArgs = func.args || [];
558
- for (let i = 0;i < signatureArgs.length; i++) {
559
- const buf = MarshallingCallableAccessor.bufferToMarshal(signatureArgs[i], args[i]);
560
- if (!buf)
629
+ const totalArgs = Math.max(signatureArgs.length, args.length);
630
+ for (let i = 0;i < totalArgs; i++) {
631
+ const info = MarshallingCallableAccessor.marshalInfo(func.name, signatureArgs[i] || "", args[i]);
632
+ if (!info) {
633
+ if (typeof args[i] === "string" && /^-?0x[0-9a-fA-F]+$/.test(args[i].trim())) {
634
+ modifiedArgs[i] = Number(BigInt(args[i].trim()));
635
+ }
561
636
  continue;
562
- const remoteAddr = Number(await this.root.alloc(buf.length));
563
- await this.root.write(remoteAddr, buf);
564
- tempAllocations.push({ address: remoteAddr, size: buf.length });
637
+ }
638
+ const allocLen = Math.max(info.buf.length, 1);
639
+ const remoteAddr = Number(await this.root.alloc(allocLen));
640
+ if (info.buf.length > 0) {
641
+ await this.root.write(remoteAddr, info.buf);
642
+ }
643
+ tempAllocations.push({
644
+ address: remoteAddr,
645
+ size: allocLen,
646
+ writeBackTarget: info.writeBackTarget
647
+ });
565
648
  modifiedArgs[i] = remoteAddr;
566
649
  }
567
- return await this.backend.call(func, ...modifiedArgs);
650
+ const result = await this.backend.call(func, ...modifiedArgs);
651
+ for (const alloc of tempAllocations) {
652
+ if (alloc.writeBackTarget && alloc.writeBackTarget.length > 0) {
653
+ try {
654
+ const remoteBytes = await this.root.read(alloc.address, alloc.writeBackTarget.length);
655
+ if (remoteBytes && remoteBytes.length > 0) {
656
+ remoteBytes.copy(alloc.writeBackTarget, 0, 0, Math.min(remoteBytes.length, alloc.writeBackTarget.length));
657
+ }
658
+ } catch {}
659
+ }
660
+ }
661
+ return result;
568
662
  } finally {
569
663
  for (const alloc of tempAllocations) {
570
664
  try {
@@ -578,16 +672,39 @@ class MarshallingCallableAccessor extends MiddlewareAccessor {
578
672
  const tempAllocations = [];
579
673
  try {
580
674
  const signatureArgs = func.args || [];
581
- for (let i = 0;i < signatureArgs.length; i++) {
582
- const buf = MarshallingCallableAccessor.bufferToMarshal(signatureArgs[i], args[i]);
583
- if (!buf)
675
+ const totalArgs = Math.max(signatureArgs.length, args.length);
676
+ for (let i = 0;i < totalArgs; i++) {
677
+ const info = MarshallingCallableAccessor.marshalInfo(func.name, signatureArgs[i] || "", args[i]);
678
+ if (!info) {
679
+ if (typeof args[i] === "string" && /^-?0x[0-9a-fA-F]+$/.test(args[i].trim())) {
680
+ modifiedArgs[i] = Number(BigInt(args[i].trim()));
681
+ }
584
682
  continue;
585
- const remoteAddr = Number(this.root.allocSync(buf.length));
586
- this.root.writeSync(remoteAddr, buf);
587
- tempAllocations.push({ address: remoteAddr, size: buf.length });
683
+ }
684
+ const allocLen = Math.max(info.buf.length, 1);
685
+ const remoteAddr = Number(this.root.allocSync(allocLen));
686
+ if (info.buf.length > 0) {
687
+ this.root.writeSync(remoteAddr, info.buf);
688
+ }
689
+ tempAllocations.push({
690
+ address: remoteAddr,
691
+ size: allocLen,
692
+ writeBackTarget: info.writeBackTarget
693
+ });
588
694
  modifiedArgs[i] = remoteAddr;
589
695
  }
590
- return this.backend.callSync(func, ...modifiedArgs);
696
+ const result = this.backend.callSync(func, ...modifiedArgs);
697
+ for (const alloc of tempAllocations) {
698
+ if (alloc.writeBackTarget && alloc.writeBackTarget.length > 0) {
699
+ try {
700
+ const remoteBytes = this.root.readSync(alloc.address, alloc.writeBackTarget.length);
701
+ if (remoteBytes && remoteBytes.length > 0) {
702
+ remoteBytes.copy(alloc.writeBackTarget, 0, 0, Math.min(remoteBytes.length, alloc.writeBackTarget.length));
703
+ }
704
+ } catch {}
705
+ }
706
+ }
707
+ return result;
591
708
  } finally {
592
709
  for (const alloc of tempAllocations) {
593
710
  try {
@@ -1022,29 +1139,33 @@ class FileTransferReadAccessor extends MsvcrtDependentMiddlewareAccessor {
1022
1139
  if (this.isInitializing || !this.tempFilePath) {
1023
1140
  return super.readAfterInit(address, size, offset);
1024
1141
  }
1025
- await this.ensureOpen();
1026
- const targetAddr = resolveAddress(address) + offset;
1027
- await this.backend.call(MsvcrtImpl.rewind, this.hFile);
1028
- const written = await this.backend.call(MsvcrtImpl.fwrite, targetAddr, 1, size, this.hFile);
1029
- if (Number(written) !== size) {
1030
- throw new Error(`fwrite failed to write entire memory block of size ${size} (wrote ${written})`);
1031
- }
1032
- await this.backend.call(MsvcrtImpl.fflush, this.hFile);
1033
- return fs.readFileSync(this.tempFilePath).subarray(0, size);
1142
+ try {
1143
+ await this.ensureOpen();
1144
+ const targetAddr = resolveAddress(address) + offset;
1145
+ await this.backend.call(MsvcrtImpl.rewind, this.hFile);
1146
+ const written = await this.backend.call(MsvcrtImpl.fwrite, targetAddr, 1, size, this.hFile);
1147
+ if (Number(written) === size) {
1148
+ await this.backend.call(MsvcrtImpl.fflush, this.hFile);
1149
+ return fs.readFileSync(this.tempFilePath).subarray(0, size);
1150
+ }
1151
+ } catch {}
1152
+ return super.readAfterInit(address, size, offset);
1034
1153
  }
1035
1154
  readAfterInitSync(address, size, offset = 0) {
1036
1155
  if (this.isInitializingSync || !this.tempFilePath) {
1037
1156
  return super.readAfterInitSync(address, size, offset);
1038
1157
  }
1039
- this.ensureOpenSync();
1040
- const targetAddr = resolveAddress(address) + offset;
1041
- this.backend.callSync(MsvcrtImpl.rewind, this.hFile);
1042
- const written = this.backend.callSync(MsvcrtImpl.fwrite, targetAddr, 1, size, this.hFile);
1043
- if (Number(written) !== size) {
1044
- throw new Error(`fwrite failed to write entire memory block of size ${size} (wrote ${written})`);
1045
- }
1046
- this.backend.callSync(MsvcrtImpl.fflush, this.hFile);
1047
- return fs.readFileSync(this.tempFilePath).subarray(0, size);
1158
+ try {
1159
+ this.ensureOpenSync();
1160
+ const targetAddr = resolveAddress(address) + offset;
1161
+ this.backend.callSync(MsvcrtImpl.rewind, this.hFile);
1162
+ const written = this.backend.callSync(MsvcrtImpl.fwrite, targetAddr, 1, size, this.hFile);
1163
+ if (Number(written) === size) {
1164
+ this.backend.callSync(MsvcrtImpl.fflush, this.hFile);
1165
+ return fs.readFileSync(this.tempFilePath).subarray(0, size);
1166
+ }
1167
+ } catch {}
1168
+ return super.readAfterInitSync(address, size, offset);
1048
1169
  }
1049
1170
  }
1050
1171
 
@@ -1129,14 +1250,16 @@ class FileTransferWriteAccessor extends MsvcrtDependentMiddlewareAccessor {
1129
1250
  }
1130
1251
  const targetAddr = resolveAddress(address) + offset;
1131
1252
  const buffer = data instanceof Buffer ? data : Buffer.from(data);
1132
- fs.writeFileSync(this.tempFilePath, buffer);
1133
- await this.ensureOpen();
1134
- await this.backend.call(MsvcrtImpl.rewind, this.hFile);
1135
- const readBytes = await this.backend.call(MsvcrtImpl.fread, targetAddr, 1, buffer.length, this.hFile);
1136
- if (Number(readBytes) !== buffer.length) {
1137
- throw new Error(`fread failed to read entire file block of size ${buffer.length} (read ${readBytes})`);
1138
- }
1139
- return Number(readBytes);
1253
+ try {
1254
+ fs.writeFileSync(this.tempFilePath, buffer);
1255
+ await this.ensureOpen();
1256
+ await this.backend.call(MsvcrtImpl.rewind, this.hFile);
1257
+ const readBytes = await this.backend.call(MsvcrtImpl.fread, targetAddr, 1, buffer.length, this.hFile);
1258
+ if (Number(readBytes) === buffer.length) {
1259
+ return buffer.length;
1260
+ }
1261
+ } catch {}
1262
+ return super.writeAfterInit(address, data, offset);
1140
1263
  }
1141
1264
  writeAfterInitSync(address, data, offset = 0) {
1142
1265
  if (this.isInitializingSync || !this.tempFilePath) {
@@ -1144,14 +1267,16 @@ class FileTransferWriteAccessor extends MsvcrtDependentMiddlewareAccessor {
1144
1267
  }
1145
1268
  const targetAddr = resolveAddress(address) + offset;
1146
1269
  const buffer = data instanceof Buffer ? data : Buffer.from(data);
1147
- fs.writeFileSync(this.tempFilePath, buffer);
1148
- this.ensureOpenSync();
1149
- this.backend.callSync(MsvcrtImpl.rewind, this.hFile);
1150
- const readBytes = this.backend.callSync(MsvcrtImpl.fread, targetAddr, 1, buffer.length, this.hFile);
1151
- if (Number(readBytes) !== buffer.length) {
1152
- throw new Error(`fread failed to read entire file block of size ${buffer.length} (read ${readBytes})`);
1153
- }
1154
- return Number(readBytes);
1270
+ try {
1271
+ fs.writeFileSync(this.tempFilePath, buffer);
1272
+ this.ensureOpenSync();
1273
+ this.backend.callSync(MsvcrtImpl.rewind, this.hFile);
1274
+ const readBytes = this.backend.callSync(MsvcrtImpl.fread, targetAddr, 1, buffer.length, this.hFile);
1275
+ if (Number(readBytes) === buffer.length) {
1276
+ return buffer.length;
1277
+ }
1278
+ } catch {}
1279
+ return super.writeAfterInitSync(address, data, offset);
1155
1280
  }
1156
1281
  }
1157
1282
 
@@ -1640,21 +1765,512 @@ class ScannerMiddleware extends MiddlewareAccessor {
1640
1765
  }
1641
1766
  }
1642
1767
  }
1768
+ // packages/accessors/src/indirect-nthread-host-accessor.ts
1769
+ import { AsyncLocalStorage } from "async_hooks";
1770
+ import {
1771
+ isMiddlewareAccessor
1772
+ } from "bun-xffi";
1773
+ import { Thread } from "bun-winapi";
1774
+ import { NThread } from "bun-nthread";
1775
+ class IndirectNThreadHostAccessor extends HostAccessor {
1776
+ nthreadRef;
1777
+ bootstrapRoot;
1778
+ get nthread() {
1779
+ return this.nthreadRef instanceof NThreadRaceAccessor ? this.nthreadRef.target : this.nthreadRef;
1780
+ }
1781
+ constructor(backendOrPid, threadId, options = {}) {
1782
+ let nthread;
1783
+ let nthreadRoot;
1784
+ if (typeof backendOrPid === "number") {
1785
+ nthreadRoot = new RedirectorHostAccessor(backendOrPid);
1786
+ nthread = new NThread(backendOrPid, threadId, options, nthreadRoot);
1787
+ } else {
1788
+ nthread = backendOrPid;
1789
+ }
1790
+ const pid = nthread.processId;
1791
+ super(new ThrowingMemoryAccessor(pid));
1792
+ const bootstrap = new BootstrapHostAccessor(pid, this);
1793
+ this.bootstrapRoot = bootstrap;
1794
+ bootstrap.backend = nthread;
1795
+ const redirector = new IndirectCallRedirectorAccessor(nthread, bootstrap);
1796
+ const machineCodePool = new MachineCodePoolMiddleware(redirector, bootstrap);
1797
+ const memsetWrite = new MemsetWriteAccessor(machineCodePool, bootstrap);
1798
+ const memcmpRead = new MemcmpReadAccessor(memsetWrite, bootstrap);
1799
+ const fileWriter = new FileTransferWriteAccessor(memcmpRead, bootstrap);
1800
+ const fileReader = new FileTransferReadAccessor(fileWriter, bootstrap);
1801
+ const scanner = new ScannerMiddleware(fileReader, bootstrap);
1802
+ const marshalling = new MarshallingCallableAccessor(scanner, bootstrap);
1803
+ this.backend = marshalling;
1804
+ let b = marshalling;
1805
+ while (isMiddlewareAccessor(b)) {
1806
+ b = b.backend;
1807
+ }
1808
+ if (b) {
1809
+ this._processId = b.processId;
1810
+ }
1811
+ if (nthreadRoot) {
1812
+ nthreadRoot.target = this;
1813
+ }
1814
+ this.nthreadRef = nthread;
1815
+ }
1816
+ async onInit() {
1817
+ await this.bootstrapRoot.init();
1818
+ }
1819
+ onInitSync() {
1820
+ this.bootstrapRoot.initSync();
1821
+ }
1822
+ async onDeinit() {
1823
+ try {
1824
+ await this.bootstrapRoot.deinit();
1825
+ } catch {}
1826
+ try {
1827
+ const nt = this.nthread;
1828
+ if (nt && typeof nt.deinit === "function") {
1829
+ await nt.deinit();
1830
+ }
1831
+ } catch {}
1832
+ }
1833
+ onDeinitSync() {
1834
+ try {
1835
+ this.bootstrapRoot.deinitSync();
1836
+ } catch {}
1837
+ try {
1838
+ const nt = this.nthread;
1839
+ if (nt && typeof nt.deinitSync === "function") {
1840
+ nt.deinitSync();
1841
+ }
1842
+ } catch {}
1843
+ }
1844
+ toJSON() {
1845
+ const session = super.toJSON();
1846
+ session.accessorType = "IndirectNThreadHostAccessor";
1847
+ try {
1848
+ const nt = this.nthread;
1849
+ if (nt && typeof nt.threadId === "number") {
1850
+ session.threadId = nt.threadId;
1851
+ }
1852
+ if (nt && nt.stubs) {
1853
+ const stubs = {};
1854
+ for (const [k, v] of Object.entries(nt.stubs)) {
1855
+ if (v && typeof v.address === "bigint") {
1856
+ stubs[k] = `0x${v.address.toString(16)}`;
1857
+ }
1858
+ }
1859
+ if (Object.keys(stubs).length > 0) {
1860
+ session.stubs = stubs;
1861
+ }
1862
+ }
1863
+ } catch {}
1864
+ return session;
1865
+ }
1866
+ }
1867
+ var currentCandidate = new AsyncLocalStorage;
1868
+
1869
+ class NThreadRaceAccessor extends RacingHostAccessor {
1870
+ aborts = new Map;
1871
+ constructor(pid, nthreadOptions = {}, root) {
1872
+ super(pid, root);
1873
+ this.target = this;
1874
+ const threads = Thread.getThreads(pid);
1875
+ if (threads.length === 0) {
1876
+ throw new Error(`createAccessor: process ${pid} has no threads to redirect`);
1877
+ }
1878
+ const callerSignal = nthreadOptions.signal;
1879
+ for (const t of threads) {
1880
+ const controller = new AbortController;
1881
+ if (callerSignal) {
1882
+ if (callerSignal.aborted)
1883
+ controller.abort(callerSignal.reason);
1884
+ else
1885
+ callerSignal.addEventListener("abort", () => controller.abort(callerSignal.reason), { once: true });
1886
+ }
1887
+ const nthread = new NThread(pid, t.tid, { ...nthreadOptions, signal: controller.signal }, this);
1888
+ this.aborts.set(nthread, () => controller.abort());
1889
+ }
1890
+ }
1891
+ startRacer(racer) {
1892
+ return currentCandidate.run(racer, () => racer.init());
1893
+ }
1894
+ async call(func, ...args) {
1895
+ if (this.target === this) {
1896
+ const caller = currentCandidate.getStore();
1897
+ if (caller)
1898
+ return caller.call(func, ...args);
1899
+ }
1900
+ return super.call(func, ...args);
1901
+ }
1902
+ releaseLoser(racer, settled) {
1903
+ this.aborts.get(racer)?.();
1904
+ super.releaseLoser(racer, settled);
1905
+ }
1906
+ async onInit() {
1907
+ const winner = await this.race();
1908
+ this.target = winner;
1909
+ }
1910
+ onInitSync() {
1911
+ throw new Error("NThreadRaceAccessor does not support initSync()");
1912
+ }
1913
+ }
1914
+ // packages/accessors/src/accessors.ts
1915
+ import { Thread as Thread3, Module } from "bun-winapi";
1916
+ import { Kernel32Impl as Kernel32Impl2 } from "bun-xffi";
1917
+ import { CallThreadDiedError, NThread as NThread2 } from "bun-nthread";
1918
+ import { NShm } from "bun-nshm";
1919
+ import {
1920
+ attachNJava as attachNJava2,
1921
+ findNJava,
1922
+ getNJava,
1923
+ getReflection,
1924
+ findReflection,
1925
+ getJavaClass
1926
+ } from "bun-njava";
1927
+
1928
+ // packages/accessors/src/stealth-java.ts
1929
+ import { Thread as Thread2 } from "bun-winapi";
1930
+ import { attachNJava } from "bun-njava";
1931
+ async function spawnStealthWorkerThread(bootstrapHost) {
1932
+ const pid = bootstrapHost.processId;
1933
+ const beforeThreads = new Set(Thread2.getThreads(pid).map((t) => t.tid));
1934
+ const njava = await attachNJava(bootstrapHost);
1935
+ const reflection = await njava.getReflection();
1936
+ const functions = await njava.getFunctions();
1937
+ const executorsClass = await reflection.getClass("java.util.concurrent.Executors");
1938
+ const dtfMethod = await executorsClass.getMethod("defaultThreadFactory");
1939
+ const threadFactory = await dtfMethod.call(0n);
1940
+ const allExecMethods = await reflection.getAllReflectedMethods(executorsClass.classObj);
1941
+ const newPoolMethod = allExecMethods.find((m) => m.name === "newSingleThreadScheduledExecutor" && m.paramTypes.length === 1) ?? allExecMethods.find((m) => m.name === "newSingleThreadScheduledExecutor");
1942
+ const pool = newPoolMethod?.paramTypes.length === 1 ? await newPoolMethod.call(0n, threadFactory) : await newPoolMethod.call(0n);
1943
+ const cdlClass = await reflection.getClass("java.util.concurrent.CountDownLatch");
1944
+ const cdlCtor = await reflection.findConstructor(cdlClass.classObj, (p) => p.length === 1 && p[0] === "int");
1945
+ const cdlInstance = await reflection.construct(cdlCtor, [0]);
1946
+ const eventHandlerClass = await reflection.getClass("java.beans.EventHandler");
1947
+ const ehCtor = await reflection.findConstructor(eventHandlerClass.classObj, (p) => p.length === 4 && p[0] === "java.lang.Object" && p[1] === "java.lang.String" && p[2] === "java.lang.String" && p[3] === "java.lang.String");
1948
+ const handler = await reflection.construct(ehCtor, [
1949
+ cdlInstance,
1950
+ "countDown",
1951
+ 0n,
1952
+ "run"
1953
+ ]);
1954
+ const proxyClass = await reflection.getClass("java.lang.reflect.Proxy");
1955
+ const newProxyInstanceMethod = await proxyClass.getMethod("newProxyInstance");
1956
+ const classLoaderClass = await reflection.getClass("java.lang.ClassLoader");
1957
+ const getSysLoaderMethod = await classLoaderClass.getMethod("getSystemClassLoader");
1958
+ const sysLoader = await getSysLoaderMethod.call(0n);
1959
+ const classClass = await reflection.getClass("java.lang.Class");
1960
+ const runnableClass = await reflection.getClass("java.lang.Runnable");
1961
+ const interfacesArray = BigInt(await functions.NewObjectArray(1, classClass.classObj, runnableClass.classObj));
1962
+ const runnable = await newProxyInstanceMethod.invokeRaw(0n, [
1963
+ sysLoader,
1964
+ interfacesArray,
1965
+ handler
1966
+ ]);
1967
+ const timeUnitClass = await reflection.getClass("java.util.concurrent.TimeUnit");
1968
+ const msField = await timeUnitClass.getField("MILLISECONDS");
1969
+ const msUnit = await msField.get();
1970
+ const poolClass = await reflection.getClass("java.util.concurrent.ScheduledExecutorService");
1971
+ const scheduleMethod = await poolClass.getMethod("scheduleWithFixedDelay");
1972
+ await scheduleMethod.invokeRaw(pool, [
1973
+ runnable,
1974
+ 0n,
1975
+ 50n,
1976
+ msUnit
1977
+ ]);
1978
+ const spawnDeadline = Date.now() + 5000;
1979
+ let newTid;
1980
+ while (Date.now() < spawnDeadline && !newTid) {
1981
+ const afterThreads = Thread2.getThreads(pid);
1982
+ const found = afterThreads.find((t) => !beforeThreads.has(t.tid));
1983
+ if (found) {
1984
+ newTid = found.tid;
1985
+ break;
1986
+ }
1987
+ await Bun.sleep(20);
1988
+ }
1989
+ if (!newTid) {
1990
+ throw new Error("spawnStealthWorkerThread: spawned worker thread did not appear in target process thread list");
1991
+ }
1992
+ return { osTid: newTid };
1993
+ }
1994
+ async function createStealthJavaAccessor(pid, options = {}) {
1995
+ const { njava: _njavaOpt, ...passThroughOptions } = options;
1996
+ const bootstrapHost = await createAccessor(pid, {
1997
+ ...passThroughOptions,
1998
+ njava: true
1999
+ });
2000
+ try {
2001
+ const { osTid: workerTid } = await spawnStealthWorkerThread(bootstrapHost);
2002
+ await bootstrapHost.deinit();
2003
+ return await createAccessor(workerTid, {
2004
+ ...passThroughOptions,
2005
+ idType: "thread",
2006
+ njava: true
2007
+ });
2008
+ } catch (err) {
2009
+ await bootstrapHost.deinit().catch(() => {});
2010
+ throw err;
2011
+ }
2012
+ }
2013
+
2014
+ // packages/accessors/src/accessors.ts
2015
+ var DEFAULT_ID_TYPE = "processAllThreadIds";
2016
+ function resolveFromThreadId(id) {
2017
+ const entry = Thread3.getThreads(0).find((t) => t.tid === id);
2018
+ if (!entry) {
2019
+ throw new Error(`createAccessor: no thread with id ${id} found`);
2020
+ }
2021
+ return { pid: entry.ownerPid, threadId: id };
2022
+ }
2023
+ function resolveFromProcessId(id) {
2024
+ const threadId = Thread3.getThreads(id)[0]?.tid;
2025
+ if (threadId === undefined) {
2026
+ throw new Error(`createAccessor: process ${id} has no threads to redirect`);
2027
+ }
2028
+ return { pid: id, threadId };
2029
+ }
2030
+ function resolveBaseAccessor(id, options) {
2031
+ if (options.backend) {
2032
+ return options.backend;
2033
+ }
2034
+ const Host = options.host ?? IndirectNThreadHostAccessor;
2035
+ const idType = options.idType ?? DEFAULT_ID_TYPE;
2036
+ if (idType === "processAllThreadIds") {
2037
+ if (Host !== IndirectNThreadHostAccessor) {
2038
+ throw new Error('createAccessor: idType "processAllThreadIds" is only supported ' + "with the default host (IndirectNThreadHostAccessor) -- there's " + "no generic way to race across an arbitrary host class's threads.");
2039
+ }
2040
+ return new IndirectNThreadHostAccessor(new NThreadRaceAccessor(id, options.hostOptions));
2041
+ }
2042
+ const { pid, threadId } = idType === "thread" ? resolveFromThreadId(id) : resolveFromProcessId(id);
2043
+ return new Host(pid, threadId, options.hostOptions);
2044
+ }
2045
+ function createAccessorWithoutInit(id, options = {}) {
2046
+ const base = resolveBaseAccessor(id, options);
2047
+ base._options = options;
2048
+ if (!options.sharedMemory) {
2049
+ return base;
2050
+ }
2051
+ base.backend = new (options.sharedMemoryMiddleware ?? NShm)(base.backend, base, options.sharedMemoryOptions);
2052
+ return base;
2053
+ }
2054
+ async function remoteGetModuleHandleA(accessor, name) {
2055
+ const buf = Buffer.from(`${name}\x00`);
2056
+ const ptr = await accessor.alloc(buf.length);
2057
+ try {
2058
+ await accessor.write(ptr, buf);
2059
+ return BigInt(await accessor.call(Kernel32Impl2.GetModuleHandleA, ptr));
2060
+ } finally {
2061
+ await accessor.free(ptr).catch(() => {});
2062
+ }
2063
+ }
2064
+ function isThreadDiedError(err) {
2065
+ if (!(err instanceof Error))
2066
+ return false;
2067
+ return err instanceof CallThreadDiedError || err.name === "CallThreadDiedError" || err.constructor?.name === "CallThreadDiedError" || /Thread died/i.test(err.message);
2068
+ }
2069
+ async function createAccessor(idOrPath, options = {}) {
2070
+ if (typeof idOrPath === "string") {
2071
+ return await HostAccessor.load(idOrPath, options);
2072
+ }
2073
+ const id = idOrPath;
2074
+ const initOne = async (targetId, opt) => {
2075
+ const accessor = createAccessorWithoutInit(targetId, opt);
2076
+ accessor._options = opt;
2077
+ try {
2078
+ await accessor.init();
2079
+ const njavaOpt = opt.njava ?? "auto";
2080
+ if (njavaOpt === true) {
2081
+ await attachNJava2(accessor);
2082
+ } else if (njavaOpt === "auto") {
2083
+ try {
2084
+ let hJvm = await remoteGetModuleHandleA(accessor, "jvm.dll");
2085
+ if (hJvm === 0n) {
2086
+ const modules = Module.getModules(accessor.processId);
2087
+ const isJavaLauncher = modules.some((m) => /java(\.exe|w\.exe|\.dll)/i.test(m.name));
2088
+ if (isJavaLauncher) {
2089
+ const deadline = Date.now() + 1e4;
2090
+ while (hJvm === 0n && Date.now() < deadline) {
2091
+ await Bun.sleep(100);
2092
+ hJvm = await remoteGetModuleHandleA(accessor, "jvm.dll");
2093
+ }
2094
+ }
2095
+ }
2096
+ if (hJvm !== 0n) {
2097
+ await attachNJava2(accessor);
2098
+ }
2099
+ } catch (err) {
2100
+ if (isThreadDiedError(err)) {
2101
+ accessor.close();
2102
+ throw err;
2103
+ }
2104
+ }
2105
+ }
2106
+ return accessor;
2107
+ } catch (err) {
2108
+ accessor.close();
2109
+ throw err;
2110
+ }
2111
+ };
2112
+ if (options.njava === "stealth") {
2113
+ return await createStealthJavaAccessor(id, options);
2114
+ }
2115
+ if (options.backend || options.idType === "thread") {
2116
+ return await initOne(id, options);
2117
+ }
2118
+ if (!options.idType) {
2119
+ try {
2120
+ const modules = Module.getModules(id);
2121
+ const isJava = modules.some((m) => /java(\.exe|w\.exe|\.dll)/i.test(m.name));
2122
+ if (isJava) {
2123
+ const threads = Thread3.getThreads(id);
2124
+ const candidateThreads = threads.length > 1 ? [...threads.slice(1), threads[0]] : threads;
2125
+ for (const t of candidateThreads) {
2126
+ try {
2127
+ return await initOne(t.tid, {
2128
+ ...options,
2129
+ idType: "thread"
2130
+ });
2131
+ } catch {}
2132
+ }
2133
+ }
2134
+ } catch {}
2135
+ }
2136
+ try {
2137
+ return await initOne(id, options);
2138
+ } catch (err) {
2139
+ if (isThreadDiedError(err)) {
2140
+ const threads = Thread3.getThreads(id);
2141
+ for (const t of threads) {
2142
+ try {
2143
+ return await initOne(t.tid, {
2144
+ ...options,
2145
+ idType: "thread"
2146
+ });
2147
+ } catch {}
2148
+ }
2149
+ }
2150
+ throw err;
2151
+ }
2152
+ }
2153
+ var AGGRESSIVENESS_PRESETS = {
2154
+ 1: {
2155
+ hostOptions: { timeoutMs: 20000, pollIntervalMs: 2 },
2156
+ sharedMemory: false
2157
+ },
2158
+ 2: {
2159
+ hostOptions: { timeoutMs: 5000, pollIntervalMs: 2 },
2160
+ sharedMemory: true
2161
+ }
2162
+ };
2163
+ function createAccessorOptions(aggressiveness = 1) {
2164
+ const preset = AGGRESSIVENESS_PRESETS[aggressiveness];
2165
+ return {
2166
+ hostOptions: { ...preset.hostOptions },
2167
+ sharedMemory: preset.sharedMemory
2168
+ };
2169
+ }
2170
+ async function restoreAccessorFromSession(session, overrideOptions = {}) {
2171
+ const baseOptions = {
2172
+ ...session.options,
2173
+ ...overrideOptions
2174
+ };
2175
+ const nthreadLayer = session.pipeline?.find((p) => p.type === "NThread");
2176
+ if (nthreadLayer?.state && nthreadLayer.state.threadId) {
2177
+ try {
2178
+ const testThread = Thread3.open(nthreadLayer.state.threadId);
2179
+ const isAlive = testThread.isValid() && testThread.getExitCode() === 259;
2180
+ testThread.close();
2181
+ if (isAlive) {
2182
+ const redirector = new RedirectorHostAccessor(session.processId);
2183
+ const nthread = new NThread2(session.processId, nthreadLayer.state.threadId, baseOptions.hostOptions ?? {}, redirector);
2184
+ nthread.resumeFromHibernation(nthreadLayer.state);
2185
+ const accessor = new IndirectNThreadHostAccessor(nthread);
2186
+ redirector.target = accessor;
2187
+ const hasNShm = session.pipeline?.some((p) => p.type === "NShm") || baseOptions.sharedMemory;
2188
+ if (hasNShm) {
2189
+ accessor.backend = new (baseOptions.sharedMemoryMiddleware ?? NShm)(accessor.backend, accessor, baseOptions.sharedMemoryOptions);
2190
+ }
2191
+ await accessor.init();
2192
+ const hasNJava = session.pipeline?.some((p) => p.type === "NJavaAccessor") || Boolean(baseOptions.njava);
2193
+ if (hasNJava) {
2194
+ await attachNJava2(accessor);
2195
+ }
2196
+ accessor._options = baseOptions;
2197
+ if (session.metadata) {
2198
+ for (const [key, value] of Object.entries(session.metadata)) {
2199
+ accessor.setSessionMetadata(key, value);
2200
+ }
2201
+ }
2202
+ return accessor;
2203
+ }
2204
+ } catch {}
2205
+ }
2206
+ let targetId = session.processId;
2207
+ let useThread = false;
2208
+ try {
2209
+ const threads = Thread3.getThreads(session.processId);
2210
+ if (threads.length === 0) {
2211
+ throw new Error(`Cannot restore accessor: process ${session.processId} is not running (no threads found).`);
2212
+ }
2213
+ if (session.threadId !== undefined && threads.some((t) => t.tid === session.threadId)) {
2214
+ targetId = session.threadId;
2215
+ useThread = true;
2216
+ }
2217
+ } catch (err) {
2218
+ if (err.message?.includes("Cannot restore accessor:")) {
2219
+ throw err;
2220
+ }
2221
+ }
2222
+ const finalOptions = {
2223
+ ...baseOptions,
2224
+ idType: useThread ? "thread" : baseOptions.idType ?? "processAllThreadIds"
2225
+ };
2226
+ const accessor = await createAccessor(targetId, finalOptions);
2227
+ if (session.metadata) {
2228
+ for (const [key, value] of Object.entries(session.metadata)) {
2229
+ accessor.setSessionMetadata(key, value);
2230
+ }
2231
+ }
2232
+ return accessor;
2233
+ }
2234
+ async function createAccessorFromFile(filePath, overrideOptions) {
2235
+ return await HostAccessor.restoreFromFile(filePath, overrideOptions);
2236
+ }
2237
+ HostAccessor.registerRestorer("IndirectNThreadHostAccessor", async (session, overrideOptions) => {
2238
+ return await restoreAccessorFromSession(session, overrideOptions);
2239
+ });
2240
+ HostAccessor.registerRestorer("default", async (session, overrideOptions) => {
2241
+ return await restoreAccessorFromSession(session, overrideOptions);
2242
+ });
1643
2243
  export {
1644
- ThrowingMemoryAccessor,
1645
- ThrowingHostAccessor,
1646
- ScannerMiddleware,
1647
- RedirectorHostAccessor,
1648
- ProcessCacheAccessor,
1649
- MsvcrtDependentMiddlewareAccessor,
1650
- MemsetWriteAccessor,
1651
- MemcmpReadAccessor,
1652
- MarshallingCallableAccessor,
1653
- MachineCodePoolMiddleware,
1654
- IndirectCallRedirectorAccessor,
1655
- HostAccessor,
1656
- FileTransferWriteAccessor,
1657
- FileTransferReadAccessor,
2244
+ BootstrapHostAccessor,
1658
2245
  CallRedirectorAccessor,
1659
- BootstrapHostAccessor
2246
+ FileTransferReadAccessor,
2247
+ FileTransferWriteAccessor,
2248
+ HostAccessor,
2249
+ IndirectCallRedirectorAccessor,
2250
+ IndirectNThreadHostAccessor,
2251
+ MachineCodePoolMiddleware,
2252
+ MarshallingCallableAccessor,
2253
+ MemcmpReadAccessor,
2254
+ MemsetWriteAccessor,
2255
+ MsvcrtDependentMiddlewareAccessor,
2256
+ NThreadRaceAccessor,
2257
+ ProcessCacheAccessor,
2258
+ RacingHostAccessor,
2259
+ RedirectorHostAccessor,
2260
+ ScannerMiddleware,
2261
+ ThrowingHostAccessor,
2262
+ ThrowingMemoryAccessor,
2263
+ attachNJava2 as attachNJava,
2264
+ createAccessor,
2265
+ createAccessorFromFile,
2266
+ createAccessorOptions,
2267
+ createAccessorWithoutInit,
2268
+ createStealthJavaAccessor,
2269
+ findNJava,
2270
+ findReflection,
2271
+ getJavaClass,
2272
+ getNJava,
2273
+ getReflection,
2274
+ restoreAccessorFromSession,
2275
+ spawnStealthWorkerThread
1660
2276
  };