koffi 2.16.0 → 2.16.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.
Files changed (36) hide show
  1. package/CHANGELOG.md +14 -1
  2. package/build/koffi/darwin_arm64/koffi.node +0 -0
  3. package/build/koffi/darwin_x64/koffi.node +0 -0
  4. package/build/koffi/freebsd_arm64/koffi.node +0 -0
  5. package/build/koffi/freebsd_ia32/koffi.node +0 -0
  6. package/build/koffi/freebsd_x64/koffi.node +0 -0
  7. package/build/koffi/linux_arm64/koffi.node +0 -0
  8. package/build/koffi/linux_armhf/koffi.node +0 -0
  9. package/build/koffi/linux_ia32/koffi.node +0 -0
  10. package/build/koffi/linux_loong64/koffi.node +0 -0
  11. package/build/koffi/linux_riscv64d/koffi.node +0 -0
  12. package/build/koffi/linux_x64/koffi.node +0 -0
  13. package/build/koffi/musl_arm64/koffi.node +0 -0
  14. package/build/koffi/musl_x64/koffi.node +0 -0
  15. package/build/koffi/openbsd_ia32/koffi.node +0 -0
  16. package/build/koffi/openbsd_x64/koffi.node +0 -0
  17. package/build/koffi/win32_arm64/koffi.node +0 -0
  18. package/build/koffi/win32_ia32/koffi.node +0 -0
  19. package/build/koffi/win32_x64/koffi.node +0 -0
  20. package/index.js +1 -1
  21. package/indirect.js +1 -1
  22. package/package.json +1 -1
  23. package/src/koffi/src/abi_arm32.cc +2 -10
  24. package/src/koffi/src/abi_arm64.cc +2 -28
  25. package/src/koffi/src/abi_loong64_asm.S +2 -2
  26. package/src/koffi/src/abi_riscv64.cc +2 -10
  27. package/src/koffi/src/abi_riscv64_asm.S +2 -2
  28. package/src/koffi/src/abi_x64_sysv.cc +2 -10
  29. package/src/koffi/src/abi_x64_win.cc +2 -26
  30. package/src/koffi/src/abi_x64_win_asm.S +2 -2
  31. package/src/koffi/src/abi_x86.cc +2 -28
  32. package/src/koffi/src/abi_x86_asm.S +4 -2
  33. package/src/koffi/src/abi_x86_asm.asm +2 -0
  34. package/src/koffi/src/call.cc +15 -8
  35. package/src/koffi/src/ffi.cc +32 -5
  36. package/src/koffi/src/ffi.hh +1 -3
package/CHANGELOG.md CHANGED
@@ -7,6 +7,19 @@
7
7
 
8
8
  ### Koffi 2.16
9
9
 
10
+ #### Koffi 2.16.2
11
+
12
+ *Released on 2026-05-06*
13
+
14
+ - Fix string truncation bugs when passing some kinds of long V8 strings (see [Koromix/koffi#266](https://github.com/Koromix/koffi/issues/266))
15
+
16
+ #### Koffi 2.16.1
17
+
18
+ *Released on 2026-04-17*
19
+
20
+ - Fix possible stack check crash when relaying callbacks on Windows
21
+ - Improve exception handling inside callbacks
22
+
10
23
  #### Koffi 2.16.0
11
24
 
12
25
  *Released on 2026-04-14*
@@ -14,7 +27,7 @@
14
27
  - Support buffers and typed arrays in `koffi.address()`
15
28
  - Put x86 SEH record at the top of stack frame
16
29
  - Optimize Koffi binaries produced by GCC (with `-fno-semantic-interposition`)
17
- - Fix nonsensical addresses in Koffi call dumps (when using DUMP_CALLS=1 environement variable)
30
+ - Fix nonsensical addresses in Koffi call dumps (when using DUMP_CALLS=1 environment variable)
18
31
 
19
32
  - Reduce Koffi package size even more:
20
33
  * Replace table of callback trampoline pointers with simple offset computation
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
package/index.js CHANGED
@@ -398,7 +398,7 @@ var require_package = __commonJS({
398
398
  "../../bin/Koffi/package/src/koffi/package.json"(exports2, module2) {
399
399
  module2.exports = {
400
400
  name: "koffi",
401
- version: "2.16.0",
401
+ version: "2.16.2",
402
402
  description: "Fast and simple C FFI (foreign function interface) for Node.js",
403
403
  keywords: [
404
404
  "foreign",
package/indirect.js CHANGED
@@ -398,7 +398,7 @@ var require_package = __commonJS({
398
398
  "../../bin/Koffi/package/src/koffi/package.json"(exports2, module2) {
399
399
  module2.exports = {
400
400
  name: "koffi",
401
- version: "2.16.0",
401
+ version: "2.16.2",
402
402
  description: "Fast and simple C FFI (foreign function interface) for Node.js",
403
403
  keywords: [
404
404
  "foreign",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "koffi",
3
- "version": "2.16.0",
3
+ "version": "2.16.2",
4
4
  "description": "Fast and simple C FFI (foreign function interface) for Node.js",
5
5
  "keywords": [
6
6
  "foreign",
@@ -544,15 +544,12 @@ Napi::Value CallData::Complete(const FunctionInfo *func)
544
544
 
545
545
  void CallData::Relay(Size idx, uint8_t *sp)
546
546
  {
547
+ const TrampolineInfo &trampoline = shared.trampolines[idx];
548
+
547
549
  uint8_t *own_sp = sp;
548
550
  uint8_t *caller_sp = sp + 128;
549
551
  BackRegisters *out_reg = (BackRegisters *)(sp + 80);
550
552
 
551
- if (env.IsExceptionPending()) [[unlikely]]
552
- return;
553
-
554
- const TrampolineInfo &trampoline = shared.trampolines[idx];
555
-
556
553
  const FunctionInfo *proto = trampoline.proto;
557
554
  Napi::Function func = trampoline.func.Value();
558
555
 
@@ -565,11 +562,6 @@ void CallData::Relay(Size idx, uint8_t *sp)
565
562
 
566
563
  K_DEFER_N(err_guard) { memset(out_reg, 0, K_SIZE(*out_reg)); };
567
564
 
568
- if (trampoline.generation >= 0 && trampoline.generation != (int32_t)mem->generation) [[unlikely]] {
569
- ThrowError<Napi::Error>(env, "Cannot use non-registered callback beyond FFI call");
570
- return;
571
- }
572
-
573
565
  LocalArray<napi_value, MaxParameters + 1> arguments;
574
566
 
575
567
  arguments.Append(!trampoline.recv.IsEmpty() ? trampoline.recv.Value() : env.Undefined());
@@ -693,33 +693,12 @@ Napi::Value CallData::Complete(const FunctionInfo *func)
693
693
 
694
694
  void CallData::Relay(Size idx, uint8_t *sp)
695
695
  {
696
+ const TrampolineInfo &trampoline = shared.trampolines[idx];
697
+
696
698
  uint8_t *own_sp = sp;
697
699
  uint8_t *caller_sp = sp + 208;
698
700
  BackRegisters *out_reg = (BackRegisters *)(sp + 136);
699
701
 
700
- if (env.IsExceptionPending()) [[unlikely]]
701
- return;
702
-
703
- #if defined(_WIN32)
704
- TEB *teb = GetTEB();
705
-
706
- // Restore previous stack limits at the end
707
- K_DEFER_C(base = teb->StackBase,
708
- limit = teb->StackLimit,
709
- dealloc = teb->DeallocationStack) {
710
- teb->StackBase = base;
711
- teb->StackLimit = limit;
712
- teb->DeallocationStack = dealloc;
713
- };
714
-
715
- // Adjust stack limits so SEH works correctly
716
- teb->StackBase = instance->main_stack_max;
717
- teb->StackLimit = instance->main_stack_min;
718
- teb->DeallocationStack = instance->main_stack_min;
719
- #endif
720
-
721
- const TrampolineInfo &trampoline = shared.trampolines[idx];
722
-
723
702
  const FunctionInfo *proto = trampoline.proto;
724
703
  Napi::Function func = trampoline.func.Value();
725
704
 
@@ -731,11 +710,6 @@ void CallData::Relay(Size idx, uint8_t *sp)
731
710
 
732
711
  K_DEFER_N(err_guard) { memset(out_reg, 0, K_SIZE(*out_reg)); };
733
712
 
734
- if (trampoline.generation >= 0 && trampoline.generation != (int32_t)mem->generation) [[unlikely]] {
735
- ThrowError<Napi::Error>(env, "Cannot use non-registered callback beyond FFI call");
736
- return;
737
- }
738
-
739
713
  LocalArray<napi_value, MaxParameters + 1> arguments;
740
714
 
741
715
  arguments.Append(!trampoline.recv.IsEmpty() ? trampoline.recv.Value() : env.Undefined());
@@ -141,7 +141,7 @@ ForwardCallXDD:
141
141
  .macro trampoline id
142
142
  li.d $t0, \id
143
143
  b RelayTrampoline
144
- .align 4
144
+ .balign 16
145
145
  .endm
146
146
 
147
147
  RelayTrampoline:
@@ -197,7 +197,7 @@ SwitchAndRelay:
197
197
  addi.d $sp, $sp, 16
198
198
  jr $ra
199
199
 
200
- .align 4
200
+ .balign 16
201
201
  #include "gnu.inc"
202
202
 
203
203
  TrampolineEnd:
@@ -485,15 +485,12 @@ Napi::Value CallData::Complete(const FunctionInfo *func)
485
485
 
486
486
  void CallData::Relay(Size idx, uint8_t *sp)
487
487
  {
488
+ const TrampolineInfo &trampoline = shared.trampolines[idx];
489
+
488
490
  uint8_t *own_sp = sp;
489
491
  uint8_t *caller_sp = sp + 168;
490
492
  BackRegisters *out_reg = (BackRegisters *)(sp + 128);
491
493
 
492
- if (env.IsExceptionPending()) [[unlikely]]
493
- return;
494
-
495
- const TrampolineInfo &trampoline = shared.trampolines[idx];
496
-
497
494
  const FunctionInfo *proto = trampoline.proto;
498
495
  Napi::Function func = trampoline.func.Value();
499
496
 
@@ -506,11 +503,6 @@ void CallData::Relay(Size idx, uint8_t *sp)
506
503
 
507
504
  K_DEFER_N(err_guard) { memset(out_reg, 0, K_SIZE(*out_reg)); };
508
505
 
509
- if (trampoline.generation >= 0 && trampoline.generation != (int32_t)mem->generation) [[unlikely]] {
510
- ThrowError<Napi::Error>(env, "Cannot use non-registered callback beyond FFI call");
511
- return;
512
- }
513
-
514
506
  LocalArray<napi_value, MaxParameters + 1> arguments;
515
507
 
516
508
  arguments.Append(!trampoline.recv.IsEmpty() ? trampoline.recv.Value() : env.Undefined());
@@ -141,7 +141,7 @@ ForwardCallXDD:
141
141
  .macro trampoline id
142
142
  li t0, \id
143
143
  j RelayTrampoline
144
- .align 4
144
+ .balign 16
145
145
  .endm
146
146
 
147
147
  RelayTrampoline:
@@ -196,7 +196,7 @@ SwitchAndRelay:
196
196
  addi sp, sp, 16
197
197
  ret
198
198
 
199
- .align 4
199
+ .balign 16
200
200
  #include "gnu.inc"
201
201
 
202
202
  TrampolineEnd:
@@ -521,15 +521,12 @@ Napi::Value CallData::Complete(const FunctionInfo *func)
521
521
 
522
522
  void CallData::Relay(Size idx, uint8_t *sp)
523
523
  {
524
+ const TrampolineInfo &trampoline = shared.trampolines[idx];
525
+
524
526
  uint8_t *own_sp = sp;
525
527
  uint8_t *caller_sp = sp + 160;
526
528
  BackRegisters *out_reg = (BackRegisters *)(sp + 112);
527
529
 
528
- if (env.IsExceptionPending()) [[unlikely]]
529
- return;
530
-
531
- const TrampolineInfo &trampoline = shared.trampolines[idx];
532
-
533
530
  const FunctionInfo *proto = trampoline.proto;
534
531
  Napi::Function func = trampoline.func.Value();
535
532
 
@@ -542,11 +539,6 @@ void CallData::Relay(Size idx, uint8_t *sp)
542
539
 
543
540
  K_DEFER_N(err_guard) { memset(out_reg, 0, K_SIZE(*out_reg)); };
544
541
 
545
- if (trampoline.generation >= 0 && trampoline.generation != (int32_t)mem->generation) [[unlikely]] {
546
- ThrowError<Napi::Error>(env, "Cannot use non-registered callback beyond FFI call");
547
- return;
548
- }
549
-
550
542
  LocalArray<napi_value, MaxParameters + 1> arguments;
551
543
 
552
544
  arguments.Append(!trampoline.recv.IsEmpty() ? trampoline.recv.Value() : env.Undefined());
@@ -327,31 +327,12 @@ Napi::Value CallData::Complete(const FunctionInfo *func)
327
327
 
328
328
  void CallData::Relay(Size idx, uint8_t *sp)
329
329
  {
330
+ const TrampolineInfo &trampoline = shared.trampolines[idx];
331
+
330
332
  uint8_t *own_sp = sp;
331
333
  uint8_t *caller_sp = sp + 128;
332
334
  BackRegisters *out_reg = (BackRegisters *)(sp + 64);
333
335
 
334
- if (env.IsExceptionPending()) [[unlikely]]
335
- return;
336
-
337
- TEB *teb = GetTEB();
338
-
339
- // Restore previous stack limits at the end
340
- K_DEFER_C(base = teb->StackBase,
341
- limit = teb->StackLimit,
342
- dealloc = teb->DeallocationStack) {
343
- teb->StackBase = base;
344
- teb->StackLimit = limit;
345
- teb->DeallocationStack = dealloc;
346
- };
347
-
348
- // Adjust stack limits so SEH works correctly
349
- teb->StackBase = instance->main_stack_max;
350
- teb->StackLimit = instance->main_stack_min;
351
- teb->DeallocationStack = instance->main_stack_min;
352
-
353
- const TrampolineInfo &trampoline = shared.trampolines[idx];
354
-
355
336
  const FunctionInfo *proto = trampoline.proto;
356
337
  Napi::Function func = trampoline.func.Value();
357
338
 
@@ -363,11 +344,6 @@ void CallData::Relay(Size idx, uint8_t *sp)
363
344
 
364
345
  K_DEFER_N(err_guard) { memset(out_reg, 0, K_SIZE(*out_reg)); };
365
346
 
366
- if (trampoline.generation >= 0 && trampoline.generation != (int32_t)mem->generation) [[unlikely]] {
367
- ThrowError<Napi::Error>(env, "Cannot use non-registered callback beyond FFI call");
368
- return;
369
- }
370
-
371
347
  LocalArray<napi_value, MaxParameters + 1> arguments;
372
348
 
373
349
  arguments.Append(!trampoline.recv.IsEmpty() ? trampoline.recv.Value() : env.Undefined());
@@ -152,12 +152,12 @@ SwitchAndRelay:
152
152
  pop %rbp
153
153
  ret
154
154
 
155
- .align 4
155
+ .balign 16
156
156
  FindTrampolineStart:
157
157
  leaq 16(%rip), %rax
158
158
  andq $0xFFFFFFFFFFFFFFF0, %rax
159
159
  ret
160
- .align 4
160
+ .balign 16
161
161
 
162
162
  #include "gnu.inc"
163
163
 
@@ -441,32 +441,11 @@ Napi::Value CallData::Complete(const FunctionInfo *func)
441
441
 
442
442
  void CallData::Relay(Size idx, uint8_t *sp)
443
443
  {
444
+ const TrampolineInfo &trampoline = shared.trampolines[idx];
445
+
444
446
  uint8_t *caller_sp = sp + 48;
445
447
  BackRegisters *out_reg = (BackRegisters *)(sp + 16);
446
448
 
447
- if (env.IsExceptionPending()) [[unlikely]]
448
- return;
449
-
450
- #if defined(_WIN32)
451
- TEB *teb = GetTEB();
452
-
453
- // Restore previous stack limits at the end
454
- K_DEFER_C(base = teb->StackBase,
455
- limit = teb->StackLimit,
456
- dealloc = teb->DeallocationStack) {
457
- teb->StackBase = base;
458
- teb->StackLimit = limit;
459
- teb->DeallocationStack = dealloc;
460
- };
461
-
462
- // Adjust stack limits so SEH works correctly
463
- teb->StackBase = instance->main_stack_max;
464
- teb->StackLimit = instance->main_stack_min;
465
- teb->DeallocationStack = instance->main_stack_min;
466
- #endif
467
-
468
- const TrampolineInfo &trampoline = shared.trampolines[idx];
469
-
470
449
  const FunctionInfo *proto = trampoline.proto;
471
450
  Napi::Function func = trampoline.func.Value();
472
451
 
@@ -492,11 +471,6 @@ void CallData::Relay(Size idx, uint8_t *sp)
492
471
  out_reg->ret_pop = pop;
493
472
  };
494
473
 
495
- if (trampoline.generation >= 0 && trampoline.generation != (int32_t)mem->generation) [[unlikely]] {
496
- ThrowError<Napi::Error>(env, "Cannot use non-registered callback beyond FFI call");
497
- return;
498
- }
499
-
500
474
  LocalArray<napi_value, MaxParameters + 1> arguments;
501
475
 
502
476
  arguments.Append(!trampoline.recv.IsEmpty() ? trampoline.recv.Value() : env.Undefined());
@@ -109,6 +109,8 @@ RelayTrampoline:
109
109
  .cfi_def_cfa esp, 48
110
110
  movl %eax, 0(%esp)
111
111
  movl %esp, 4(%esp)
112
+ movl $0, 32(%esp)
113
+ movl $0, 36(%esp)
112
114
  call GetEIP
113
115
  addl $_GLOBAL_OFFSET_TABLE_, %eax
114
116
  call *RelayCallback@GOT(%eax)
@@ -172,13 +174,13 @@ SwitchAndRelay:
172
174
  ret
173
175
  .cfi_endproc
174
176
 
175
- .align 4
177
+ .balign 16
176
178
  FindTrampolineStart:
177
179
  call GetEIP
178
180
  addl $16, %eax
179
181
  andl $0xFFFFFFF0, %eax
180
182
  ret
181
- .align 4
183
+ .balign 16
182
184
 
183
185
  #include "gnu.inc"
184
186
 
@@ -101,6 +101,8 @@ RelayTrampoline proc
101
101
  sub esp, 44
102
102
  mov dword ptr [esp+0], eax
103
103
  mov dword ptr [esp+4], esp
104
+ mov dword ptr [esp+32], 0
105
+ mov dword ptr [esp+36], 0
104
106
  call RelayCallback
105
107
  mov edx, dword ptr [esp+44]
106
108
  mov ecx, dword ptr [esp+36]
@@ -31,7 +31,6 @@ CallData::CallData(Napi::Env env, InstanceData *instance, InstanceMemory *mem)
31
31
  : env(env), instance(instance),
32
32
  mem(mem), old_stack_mem(mem->stack), old_heap_mem(mem->heap)
33
33
  {
34
- mem->generation += !mem->depth;
35
34
  mem->depth++;
36
35
 
37
36
  K_ASSERT(AlignUp(mem->stack.ptr, 16) == mem->stack.ptr);
@@ -64,9 +63,9 @@ void CallData::Dispose()
64
63
  K_ASSERT(trampoline->instance == instance);
65
64
  K_ASSERT(!trampoline->func.IsEmpty());
66
65
 
67
- trampoline->instance = nullptr;
68
66
  trampoline->func.Reset();
69
67
  trampoline->recv.Reset();
68
+ trampoline->used = false;
70
69
 
71
70
  shared.available.Append(idx);
72
71
  }
@@ -152,14 +151,22 @@ Size CallData::PushStringValue(Napi::Value value, const char **out_str)
152
151
  napi_status status;
153
152
 
154
153
  buf.ptr = (char *)mem->heap.ptr;
155
- buf.len = std::max((Size)0, mem->heap.len - Kibibytes(32));
154
+ buf.len = mem->heap.len;
156
155
 
157
156
  status = napi_get_value_string_utf8(env, value, buf.ptr, (size_t)buf.len, &len);
158
157
  K_ASSERT(status == napi_ok);
159
158
 
160
159
  len++;
161
160
 
162
- if (len < (size_t)buf.len) [[likely]] {
161
+ // V8 can truncate the string and return a length that is less than the real string
162
+ // length in several cases, such as when it flattens string ropes.
163
+ // This was the cause of a truncation bug (see https://github.com/Koromix/koffi/issues/266),
164
+ // which went unnoticed for a long time.
165
+ // We don't want to query the length beforehand, because it's slow. Instead, check that the
166
+ // returned lengfth is much shorter than the available buffer capacity, and if so, we know
167
+ // we're okay because V8 flattens strings ~32 KiB at a time.
168
+
169
+ if ((Size)len < buf.len - Kibibytes(64)) [[likely]] {
163
170
  mem->heap.ptr += (Size)len;
164
171
  mem->heap.len -= (Size)len;
165
172
  } else {
@@ -187,14 +194,14 @@ Size CallData::PushString16Value(Napi::Value value, const char16_t **out_str16)
187
194
 
188
195
  mem->heap.ptr = AlignUp(mem->heap.ptr, 2);
189
196
  buf.ptr = (char16_t *)mem->heap.ptr;
190
- buf.len = std::max((Size)0, mem->heap.len - Kibibytes(32)) / 2;
197
+ buf.len = mem->heap.len / 2;
191
198
 
192
199
  status = napi_get_value_string_utf16(env, value, buf.ptr, (size_t)buf.len, &len);
193
200
  K_ASSERT(status == napi_ok);
194
201
 
195
202
  len++;
196
203
 
197
- if (len < (size_t)buf.len) [[likely]] {
204
+ if ((Size)len < buf.len - Kibibytes(64) / 2) [[likely]] {
198
205
  mem->heap.ptr += (Size)len * 2;
199
206
  mem->heap.len -= (Size)len * 2;
200
207
  } else {
@@ -227,7 +234,7 @@ Size CallData::PushString32Value(Napi::Value value, const char32_t **out_str32)
227
234
 
228
235
  mem->heap.ptr = AlignUp(mem->heap.ptr, 4);
229
236
  buf.ptr = (char32_t *)mem->heap.ptr;
230
- buf.len = std::max((Size)0, mem->heap.len - Kibibytes(32)) / 4;
237
+ buf.len = mem->heap.len / 4;
231
238
 
232
239
  if (buf16.len < buf.len) [[likely]] {
233
240
  mem->heap.ptr += buf16.len * 4;
@@ -1142,7 +1149,7 @@ void *CallData::ReserveTrampoline(const FunctionInfo *proto, Napi::Function func
1142
1149
  trampoline->proto = proto;
1143
1150
  trampoline->func.Reset(func, 1);
1144
1151
  trampoline->recv.Reset();
1145
- trampoline->generation = (int32_t)mem->generation;
1152
+ trampoline->used = true;
1146
1153
 
1147
1154
  void *ptr = GetTrampoline(idx);
1148
1155
 
@@ -1728,10 +1728,6 @@ extern "C" void RelayCallback(Size idx, uint8_t *sp)
1728
1728
  return;
1729
1729
  }
1730
1730
 
1731
- // Avoid triggering the "use callback beyond FFI" check
1732
- K_DEFER_C(generation = trampoline->generation) { trampoline->generation = generation; };
1733
- trampoline->generation = -1;
1734
-
1735
1731
  if (std::this_thread::get_id() == instance->main_thread_id) {
1736
1732
  CallData call(env, instance, mem);
1737
1733
 
@@ -1745,6 +1741,35 @@ extern "C" void RelayCallback(Size idx, uint8_t *sp)
1745
1741
 
1746
1742
  extern "C" void RelayDirect(CallData *call, Size idx, uint8_t *sp)
1747
1743
  {
1744
+ TrampolineInfo *trampoline = &shared.trampolines[idx];
1745
+ Napi::Env env = trampoline->func.Env();
1746
+
1747
+ #if defined(_WIN32)
1748
+ TEB *teb = GetTEB();
1749
+ InstanceData *instance = trampoline->instance;
1750
+
1751
+ // Restore previous stack limits at the end
1752
+ K_DEFER_C(base = teb->StackBase,
1753
+ limit = teb->StackLimit,
1754
+ dealloc = teb->DeallocationStack) {
1755
+ teb->StackBase = base;
1756
+ teb->StackLimit = limit;
1757
+ teb->DeallocationStack = dealloc;
1758
+ };
1759
+
1760
+ // Adjust stack limits so SEH works correctly
1761
+ teb->StackBase = instance->main_stack_max;
1762
+ teb->StackLimit = instance->main_stack_min;
1763
+ teb->DeallocationStack = instance->main_stack_min;
1764
+ #endif
1765
+
1766
+ if (env.IsExceptionPending()) [[unlikely]]
1767
+ return;
1768
+ if (!trampoline->used) [[unlikely]] {
1769
+ ThrowError<Napi::Error>(env, "Cannot use non-registered callback beyond FFI call");
1770
+ return;
1771
+ }
1772
+
1748
1773
  Napi::HandleScope scope(call->env);
1749
1774
  call->Relay(idx, sp);
1750
1775
  }
@@ -2020,7 +2045,7 @@ static Napi::Value RegisterCallback(const Napi::CallbackInfo &info)
2020
2045
  } else {
2021
2046
  trampoline->recv.Reset();
2022
2047
  }
2023
- trampoline->generation = -1;
2048
+ trampoline->used = true;
2024
2049
 
2025
2050
  void *ptr = GetTrampoline(idx);
2026
2051
 
@@ -2072,6 +2097,7 @@ static Napi::Value UnregisterCallback(const Napi::CallbackInfo &info)
2072
2097
 
2073
2098
  trampoline->func.Reset();
2074
2099
  trampoline->recv.Reset();
2100
+ trampoline->used = false;
2075
2101
 
2076
2102
  shared.available.Append(idx);
2077
2103
  }
@@ -2665,6 +2691,7 @@ InstanceData::~InstanceData()
2665
2691
  trampoline->instance = nullptr;
2666
2692
  trampoline->func.Reset();
2667
2693
  trampoline->recv.Reset();
2694
+ trampoline->used = false;
2668
2695
  }
2669
2696
  }
2670
2697
  }
@@ -249,8 +249,6 @@ struct InstanceMemory {
249
249
  Span<uint8_t> stack0;
250
250
  Span<uint8_t> heap;
251
251
 
252
- uint16_t generation; // Can wrap without risk
253
-
254
252
  bool busy;
255
253
  bool temporary;
256
254
  int depth;
@@ -322,7 +320,7 @@ struct TrampolineInfo {
322
320
  Napi::FunctionReference func;
323
321
  Napi::Reference<Napi::Value> recv;
324
322
 
325
- int32_t generation;
323
+ bool used;
326
324
  };
327
325
 
328
326
  struct SharedData {