quickwin 3.0.1 → 3.0.3

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 (163) hide show
  1. package/README.en.md +3 -0
  2. package/README.md +2 -0
  3. package/package.json +2 -3
  4. package/qwin-nowasm-x86.exe +0 -0
  5. package/qwin-nowasm.exe +0 -0
  6. package/qwin-x86.exe +0 -0
  7. package/qwin.exe +0 -0
  8. package/examples/PdfCanvas.js +0 -38
  9. package/examples/PdfCanvas.tsx +0 -54
  10. package/examples/execd/main.js +0 -23379
  11. package/examples/execd/tray.js +0 -192
  12. package/examples/execd/worker.js +0 -122
  13. package/examples/pdf_preview2.js +0 -398
  14. package/examples/pdf_preview2.ts +0 -353
  15. package/examples/pdf_viewer.js +0 -22515
  16. package/examples/pdf_viewer.tsx +0 -228
  17. package/examples/test_gallery.js +0 -18692
  18. package/examples/test_gallery.tsx +0 -291
  19. package/examples/test_react_lazy.js +0 -18560
  20. package/examples/test_react_lazy.tsx +0 -65
  21. package/examples/test_react_listbox.js +0 -18562
  22. package/examples/test_react_listbox.tsx +0 -73
  23. package/examples/test_root_window.js +0 -18532
  24. package/examples/test_root_window.tsx +0 -41
  25. package/examples/test_tab.js +0 -18563
  26. package/examples/test_tab.tsx +0 -77
  27. package/examples/tray_demo.js +0 -86
  28. package/examples/tray_demo.tsx +0 -89
  29. package/test/add.wasm +0 -0
  30. package/test/bench_ffi_callraw.js +0 -235
  31. package/test/bench_ffi_callraw.ts +0 -235
  32. package/test/bench_ffi_overhead.js +0 -139
  33. package/test/bench_ffi_overhead.ts +0 -141
  34. package/test/bench_sendmessage.js +0 -104
  35. package/test/bench_sendmessage.ts +0 -100
  36. package/test/bench_sendmessage_nonnull.js +0 -187
  37. package/test/bench_sendmessage_nonnull.ts +0 -188
  38. package/test/complex.wasm +0 -0
  39. package/test/complex_imports.wasm +0 -0
  40. package/test/debug/http_server_e2e.js +0 -31
  41. package/test/debug/minwasm.js +0 -52
  42. package/test/debug/mupdf_debug.js +0 -86
  43. package/test/debug/wasm.js +0 -122
  44. package/test/debug/wasm_export_debug.js +0 -54
  45. package/test/debug/wasm_names.js +0 -36
  46. package/test/example.pdf +0 -0
  47. package/test/global_imports.wasm +0 -0
  48. package/test/import_func.wasm +0 -0
  49. package/test/imports.wasm +0 -0
  50. package/test/mupdf-cdn.js +0 -40
  51. package/test/mupdf-cdn.ts +0 -43
  52. package/test/run.js +0 -111
  53. package/test/run.ts +0 -116
  54. package/test/sjlj.wasm +0 -0
  55. package/test/test_basic.js +0 -7
  56. package/test/test_basic.ts +0 -9
  57. package/test/test_basic_fetch.js +0 -63
  58. package/test/test_basic_fetch.ts +0 -59
  59. package/test/test_brotli.js +0 -48
  60. package/test/test_brotli.ts +0 -52
  61. package/test/test_fetch_cache.js +0 -169
  62. package/test/test_fetch_cache.ts +0 -187
  63. package/test/test_ffi.js +0 -157
  64. package/test/test_ffi.ts +0 -170
  65. package/test/test_ffi_abi.js +0 -290
  66. package/test/test_ffi_abi.ts +0 -274
  67. package/test/test_ffi_array.js +0 -116
  68. package/test/test_ffi_array.ts +0 -141
  69. package/test/test_ffi_bind.js +0 -656
  70. package/test/test_ffi_bind.ts +0 -670
  71. package/test/test_ffi_struct.js +0 -779
  72. package/test/test_ffi_struct.ts +0 -828
  73. package/test/test_ffi_struct_layout.js +0 -495
  74. package/test/test_ffi_struct_layout.ts +0 -543
  75. package/test/test_ffi_user32.js +0 -103
  76. package/test/test_ffi_user32.ts +0 -113
  77. package/test/test_ffi_windows.js +0 -172
  78. package/test/test_ffi_windows.ts +0 -187
  79. package/test/test_files.js +0 -102
  80. package/test/test_files.ts +0 -121
  81. package/test/test_frame_encoding.js +0 -128
  82. package/test/test_frame_encoding.ts +0 -132
  83. package/test/test_gdi_draw.js +0 -681
  84. package/test/test_gdi_draw.ts +0 -725
  85. package/test/test_helper.js +0 -91
  86. package/test/test_helper.ts +0 -88
  87. package/test/test_http_import.js +0 -96
  88. package/test/test_http_import.ts +0 -93
  89. package/test/test_http_server.js +0 -214
  90. package/test/test_http_server.ts +0 -211
  91. package/test/test_import_dir.js +0 -4
  92. package/test/test_import_dir.ts +0 -5
  93. package/test/test_ipv6.js +0 -134
  94. package/test/test_ipv6.ts +0 -130
  95. package/test/test_jsx.js +0 -1314
  96. package/test/test_meta_url.js +0 -3
  97. package/test/test_meta_url.ts +0 -4
  98. package/test/test_mupdf_render.js +0 -51
  99. package/test/test_mupdf_render.ts +0 -55
  100. package/test/test_mupdf_twice.js +0 -62
  101. package/test/test_mupdf_twice.ts +0 -65
  102. package/test/test_mupdf_wasm.js +0 -39
  103. package/test/test_mupdf_wasm.ts +0 -32
  104. package/test/test_net_event.js +0 -57
  105. package/test/test_net_event.ts +0 -56
  106. package/test/test_net_fetch.js +0 -384
  107. package/test/test_net_fetch.ts +0 -373
  108. package/test/test_net_websocket.js +0 -180
  109. package/test/test_net_websocket.ts +0 -164
  110. package/test/test_polyfill.js +0 -144
  111. package/test/test_polyfill.ts +0 -159
  112. package/test/test_printer.js +0 -137
  113. package/test/test_printer.ts +0 -148
  114. package/test/test_proc.js +0 -141
  115. package/test/test_proc.ts +0 -163
  116. package/test/test_react_complex.js +0 -18678
  117. package/test/test_react_counter.js +0 -18555
  118. package/test/test_react_flex.js +0 -18871
  119. package/test/test_react_hidden.js +0 -18560
  120. package/test/test_react_listview_link.js +0 -18590
  121. package/test/test_react_notify.js +0 -18797
  122. package/test/test_react_render.js +0 -18550
  123. package/test/test_registry.js +0 -74
  124. package/test/test_registry.ts +0 -95
  125. package/test/test_sysinfo.js +0 -122
  126. package/test/test_sysinfo.ts +0 -141
  127. package/test/test_url.js +0 -173
  128. package/test/test_url.ts +0 -183
  129. package/test/test_wasm_basic.js +0 -85
  130. package/test/test_wasm_basic.ts +0 -73
  131. package/test/test_wasm_import_global.js +0 -41
  132. package/test/test_wasm_import_global.ts +0 -45
  133. package/test/test_wasm_sjlj.js +0 -145
  134. package/test/test_wasm_sjlj.ts +0 -125
  135. package/test/test_wasm_types.js +0 -97
  136. package/test/test_wasm_types.ts +0 -124
  137. package/test/test_wolfssl_read.js +0 -203
  138. package/test/test_wolfssl_read.ts +0 -184
  139. package/test/test_worker.js +0 -21
  140. package/test/test_worker.ts +0 -24
  141. package/test/test_worker_concurrent.js +0 -45
  142. package/test/test_worker_concurrent.ts +0 -56
  143. package/test/test_worker_data_url.js +0 -46
  144. package/test/test_worker_data_url.ts +0 -51
  145. package/test/test_worker_fetch.js +0 -26
  146. package/test/test_worker_fetch.ts +0 -27
  147. package/test/test_worker_net.js +0 -69
  148. package/test/test_worker_net.ts +0 -66
  149. package/test/test_worker_wasm_concurrent.js +0 -44
  150. package/test/test_worker_wasm_concurrent.ts +0 -45
  151. package/test/types.wasm +0 -0
  152. package/test/worker_fetch.js +0 -13
  153. package/test/worker_fetch.ts +0 -13
  154. package/test/worker_helper.js +0 -1
  155. package/test/worker_helper.ts +0 -1
  156. package/test/worker_http.js +0 -18
  157. package/test/worker_http.ts +0 -20
  158. package/test/worker_import_lib.js +0 -12
  159. package/test/worker_import_lib.ts +0 -13
  160. package/test/worker_module.js +0 -16
  161. package/test/worker_module.ts +0 -17
  162. package/test/worker_wasm_concurrent.js +0 -22
  163. package/test/worker_wasm_concurrent.ts +0 -22
@@ -1,274 +0,0 @@
1
- import * as std from 'std'
2
- import * as ffi from 'ffi'
3
- import * as os from 'os'
4
- import { bind } from '../lib/ffi/bind.js'
5
- import { struct } from '../lib/ffi/struct.js'
6
- import { NULL, PtrArrayBuffer, type MaybePtr } from '../lib/ffi/ctype.js'
7
- import { POINT, RECT } from '../lib/windows/structs.js'
8
- import { Tester } from './test_helper.js'
9
-
10
- // ABI 边界回归:覆盖 REMOVE_LIBFFI_PLAN.md §4.2 的维度,全部用真实 Win32/CRT
11
- // 函数(不新增测试专用导出)。缺函数的平台记 SKIP 而非 FAIL,避免环境差异误报。
12
- //
13
- // 维度 → 用例:
14
- // 参数个数 0/1/3/4/5/6/8/12:GetTickCount/fabs/memcpy/GetLocaleInfoA/
15
- // SetRect/CompareStringW/WideCharToMultiByte/SetDIBitsToDevice
16
- // 整浮混合:ldexp(f64,i32)、frexp(f64,ptr)、modf(f64,ptr)
17
- // 前 4 全整数(RCX-R9):GetLocaleInfoA 的 4 个参数
18
- // 负数返回 i32/i16/i8:lstrcmpW;i64:InterlockedIncrement64
19
- // 窄整型负值传参:abs(i8/i16/u8/u16)
20
- // 大 64 位传参:InterlockedExchange64
21
- // 指针 NULL:GetDC(NULL)、IsWindow(NULL);null/undefined 不是指针合法值,应拒绝
22
-
23
- function readAscii(buf: ArrayBuffer, len: number): string {
24
- const dv = new DataView(buf)
25
- let s = ''
26
- for (let i = 0; i < len; i++) {
27
- const c = dv.getUint8(i)
28
- if (c === 0) break
29
- s += String.fromCharCode(c)
30
- }
31
- return s
32
- }
33
-
34
- function has(dll: MaybePtr<'HMODULE'>, name: string): boolean {
35
- return !!dll && !!ffi.GetProcAddress(dll, name)
36
- }
37
-
38
- export const suite = {
39
- name: 'ffi-abi',
40
- run: (t: Tester) => {
41
- const k32 = ffi.LoadLibrary('kernel32.dll')
42
- const usr = ffi.LoadLibrary('user32.dll')
43
- const gdi = ffi.LoadLibrary('gdi32.dll')
44
- const crt = ffi.LoadLibrary('msvcrt.dll')
45
-
46
- t.section('param count 0 ( -> u32)')
47
- if (has(k32, 'GetTickCount')) {
48
- const getTickCount = bind('kernel32.dll', 'GetTickCount', ' -> u32')
49
- const tc = getTickCount()
50
- t.checkTrue('GetTickCount() > 0', tc > 0)
51
- } else t.skipCase('GetTickCount missing')
52
-
53
- t.section('param count 1 (f64 -> f64)')
54
- if (has(crt, 'fabs')) {
55
- const fabs = bind('msvcrt.dll', 'fabs', 'f64 -> f64')
56
- t.check('fabs(-3.5)', 3.5, fabs(-3.5))
57
- } else t.skipCase('fabs missing')
58
-
59
- t.section('param count 3 (<BYTE>ptr <BYTE>ptr u32 -> ptr)')
60
- if (has(crt, 'memcpy')) {
61
- const memcpy = bind('msvcrt.dll', 'memcpy', '<BYTE>ptr <BYTE>ptr u32 -> <>ptr')
62
- const srcB = new PtrArrayBuffer(8)
63
- const src = new Uint8Array(srcB)
64
- src.set([1, 2, 3, 4, 5, 6, 7, 8])
65
- const dstB = new PtrArrayBuffer(8)
66
- const dst = new Uint8Array(dstB)
67
- const r = memcpy(dstB, srcB, 8)
68
- const rd = Array.from(dst).join(',')
69
- t.check('memcpy copied bytes', '1,2,3,4,5,6,7,8', rd)
70
- t.checkTrue('memcpy returns dest ptr', typeof r === 'number' && r !== 0)
71
- } else t.skipCase('memcpy missing')
72
-
73
- t.section('param count 4 + all-int-regs (u32 u32 <BYTE>ptr i32 -> i32)')
74
- if (has(k32, 'GetLocaleInfoA')) {
75
- const getLocaleInfoA = bind('kernel32.dll', 'GetLocaleInfoA', 'u32 u32 <BYTE>ptr i32 -> i32')
76
- const buf = new PtrArrayBuffer(64)
77
- const n = getLocaleInfoA(0x0409, 0x00001001 /* LOCALE_SENGLANGUAGE */, buf, 64)
78
- if (n > 0) {
79
- const s = readAscii(buf, n)
80
- std.printf(' GetLocaleInfoA(0x0409, LOCALE_SENGLANGUAGE) = "%s" (%d)\n', s, n)
81
- t.check('en-US language name', 'English', s)
82
- } else t.skipCase('en-US locale unavailable')
83
- } else t.skipCase('GetLocaleInfoA missing')
84
-
85
- t.section('param count 5 (<BYTE>ptr i32 i32 i32 i32 -> i32)')
86
- if (has(usr, 'SetRect')) {
87
- const setRect = bind('user32.dll', 'SetRect', '<BYTE>ptr i32 i32 i32 i32 -> i32')
88
- const rc = new PtrArrayBuffer(16)
89
- const ok = setRect(rc, 1, 2, 30, 40)
90
- const dv = new DataView(rc)
91
- t.checkTrue('SetRect returns nonzero', ok !== 0)
92
- t.check('SetRect left', 1, dv.getInt32(0, true))
93
- t.check('SetRect top', 2, dv.getInt32(4, true))
94
- t.check('SetRect right', 30, dv.getInt32(8, true))
95
- t.check('SetRect bottom', 40, dv.getInt32(12, true))
96
- } else t.skipCase('SetRect missing')
97
-
98
- t.section('param count 6 (u32 u32 <WCHAR>ptr i32 <WCHAR>ptr i32 -> i32)')
99
- if (has(k32, 'CompareStringW')) {
100
- const compareStringW = bind('kernel32.dll', 'CompareStringW', 'u32 u32 <WCHAR>ptr i32 <WCHAR>ptr i32 -> i32')
101
- const r = compareStringW(0x0400 /* LOCALE_USER_DEFAULT */, 0, 'abc', -1, 'abd', -1)
102
- t.check('CompareStringW("abc","abd") = CSTR_LESS_THAN', 1, r)
103
- } else t.skipCase('CompareStringW missing')
104
-
105
- t.section('param count 8 (u32 u32 <WCHAR>ptr i32 <BYTE>ptr i32 <>ptr <>ptr -> i32)')
106
- if (has(k32, 'WideCharToMultiByte')) {
107
- const wideCharToMultiByte = bind('kernel32.dll', 'WideCharToMultiByte', 'u32 u32 <WCHAR>ptr i32 <BYTE>ptr i32 <>ptr <>ptr -> i32')
108
- const out = new PtrArrayBuffer(16)
109
- // 显式给长度(cchWideChar=2)避免 -1 空终止语义下返回值是否含 '\0' 的版本差异。
110
- const n = wideCharToMultiByte(0 /* CP_ACP */, 0, 'AB', 2, out, 16, NULL, NULL)
111
- t.check('WideCharToMultiByte char count', 2, n)
112
- t.check('converted byte 0 = A', 65, new DataView(out).getUint8(0))
113
- t.check('converted byte 1 = B', 66, new DataView(out).getUint8(1))
114
- } else t.skipCase('WideCharToMultiByte missing')
115
-
116
- t.section('param count 12 (SetDIBitsToDevice on memory DC)')
117
- if (has(gdi, 'SetDIBitsToDevice') && has(gdi, 'CreateCompatibleDC') && has(gdi, 'DeleteDC')) {
118
- const createCompatibleDC = bind('gdi32.dll', 'CreateCompatibleDC', '<>ptr -> <>ptr')
119
- const deleteDC = bind('gdi32.dll', 'DeleteDC', '<>ptr -> i32')
120
- const setDIBitsToDevice = bind('gdi32.dll', 'SetDIBitsToDevice',
121
- '<>ptr i32 i32 u32 u32 i32 i32 u32 u32 <BYTE>ptr <BYTE>ptr u32 -> i32')
122
- const hdc = createCompatibleDC(0)
123
- if (hdc) {
124
- const bmi = new PtrArrayBuffer(40)
125
- const bdv = new DataView(bmi)
126
- bdv.setUint32(0, 40, true) // biSize
127
- bdv.setInt32(4, 1, true) // biWidth
128
- bdv.setInt32(8, 1, true) // biHeight (bottom-up)
129
- bdv.setUint16(12, 1, true) // biPlanes
130
- bdv.setUint16(14, 32, true) // biBitCount
131
- bdv.setUint32(16, 0, true) // biCompression = BI_RGB
132
- const bits = new PtrArrayBuffer(4)
133
- const lines = setDIBitsToDevice(hdc, 0, 0, 1, 1, 0, 0, 0, 1, bits, bmi, 0 /* DIB_RGB_COLORS */)
134
- t.check('SetDIBitsToDevice scanlines copied', 1, lines)
135
- deleteDC(hdc)
136
- } else t.skipCase('CreateCompatibleDC returned NULL')
137
- } else t.skipCase('SetDIBitsToDevice/CreateCompatibleDC missing')
138
-
139
- t.section('int/float mix: f64 i32 -> f64')
140
- if (has(crt, 'ldexp')) {
141
- const ldexp = bind('msvcrt.dll', 'ldexp', 'f64 i32 -> f64')
142
- t.check('ldexp(1.5, 3)', 12.0, ldexp(1.5, 3))
143
- } else t.skipCase('ldexp missing')
144
-
145
- t.section('int/float mix: f64 <BYTE>ptr -> f64')
146
- if (has(crt, 'frexp')) {
147
- const frexp = bind('msvcrt.dll', 'frexp', 'f64 <BYTE>ptr -> f64')
148
- const e = new PtrArrayBuffer(4)
149
- const m = frexp(12.0, e)
150
- t.check('frexp(12).mantissa', 0.75, m)
151
- t.check('frexp(12).exponent', 4, new DataView(e).getInt32(0, true))
152
- } else t.skipCase('frexp missing')
153
- if (has(crt, 'modf')) {
154
- const modf = bind('msvcrt.dll', 'modf', 'f64 <BYTE>ptr -> f64')
155
- const ip = new PtrArrayBuffer(8)
156
- const frac = modf(3.75, ip)
157
- t.check('modf(3.75).frac', 0.75, frac)
158
- t.check('modf(3.75).int', 3.0, new DataView(ip).getFloat64(0, true))
159
- } else t.skipCase('modf missing')
160
-
161
- t.section('narrow signed args (msvcrt!abs)')
162
- if (has(crt, 'abs')) {
163
- const absI8 = bind('msvcrt.dll', 'abs', 'i8 -> i32')
164
- const absU8 = bind('msvcrt.dll', 'abs', 'u8 -> i32')
165
- const absI16 = bind('msvcrt.dll', 'abs', 'i16 -> i32')
166
- const absU16 = bind('msvcrt.dll', 'abs', 'u16 -> i32')
167
- t.check('abs(i8 -128)', 128, absI8(-128))
168
- t.check('abs(i8 -5)', 5, absI8(-5))
169
- t.check('abs(u8 200)', 200, absU8(200))
170
- t.check('abs(i16 -300)', 300, absI16(-300))
171
- t.check('abs(u16 60000)', 60000, absU16(60000))
172
- } else t.skipCase('abs missing')
173
-
174
- t.section('negative return i32/i16/i8 (lstrcmpW)')
175
- if (has(k32, 'lstrcmpW')) {
176
- const cmpI32 = bind('kernel32.dll', 'lstrcmpW', '<WCHAR>ptr <WCHAR>ptr -> i32')
177
- const cmpI16 = bind('kernel32.dll', 'lstrcmpW', '<WCHAR>ptr <WCHAR>ptr -> i16')
178
- const cmpI8 = bind('kernel32.dll', 'lstrcmpW', '<WCHAR>ptr <WCHAR>ptr -> i8')
179
- t.checkTrue('lstrcmpW i32 < 0', cmpI32('a', 'b') < 0)
180
- t.checkTrue('lstrcmpW i16 < 0', cmpI16('a', 'b') < 0)
181
- t.checkTrue('lstrcmpW i8 < 0', cmpI8('a', 'b') < 0)
182
- } else t.skipCase('lstrcmpW missing')
183
-
184
- t.section('negative return i64 (InterlockedIncrement64)')
185
- if (has(k32, 'InterlockedExchange64') && has(k32, 'InterlockedIncrement64')) {
186
- const exchange64 = bind('kernel32.dll', 'InterlockedExchange64', '<BYTE>ptr i64 -> i64')
187
- const increment64 = bind('kernel32.dll', 'InterlockedIncrement64', '<BYTE>ptr -> i64')
188
- const cell = new PtrArrayBuffer(8)
189
- exchange64(cell, -2n)
190
- t.check('InterlockedIncrement64(-2)', -1n, increment64(cell))
191
- } else t.skipCase('Interlocked*64 missing')
192
-
193
- t.section('64-bit arg transport (InterlockedExchange64)')
194
- if (has(k32, 'InterlockedExchange64')) {
195
- const exchange64 = bind('kernel32.dll', 'InterlockedExchange64', '<BYTE>ptr i64 -> i64')
196
- const cell = new PtrArrayBuffer(8)
197
- const prev0 = exchange64(cell, 4294967297n) // 2^32+1:超过 32 位
198
- const prev1 = exchange64(cell, -1n) // 64 位全 1
199
- const prev2 = exchange64(cell, 0n)
200
- t.check('exchange empty cell returns 0', 0n, prev0)
201
- t.check('exchange returns 2^32+1', 4294967297n, prev1)
202
- t.check('exchange returns -1', -1n, prev2)
203
- } else t.skipCase('InterlockedExchange64 missing')
204
-
205
- t.section('ptr NULL/undefined')
206
- if (has(usr, 'GetDC') && has(usr, 'IsWindow')) {
207
- const getDC = bind('user32.dll', 'GetDC', '<>ptr -> <>ptr')
208
- const isWindow = bind('user32.dll', 'IsWindow', '<>ptr -> i32')
209
- t.checkTrue('GetDC(NULL) non-null', !!getDC(NULL))
210
- // undefined / null 不是指针合法值(指针只收 NULL 或有效地址),必须拒绝而非静默当 0。
211
- let errUndef = ''
212
- try {
213
- (getDC as unknown as (v: unknown) => number)(undefined)
214
- } catch (e) {
215
- errUndef = String(e)
216
- }
217
- t.checkTrue('GetDC(undefined) rejected', errUndef.includes('undefined'))
218
- t.check('IsWindow(NULL) = FALSE', 0, isWindow(NULL))
219
- } else t.skipCase('GetDC/IsWindow missing')
220
-
221
- t.section('by-value struct arg: <POINT> (slot value, not a pointer)')
222
- if (has(usr, 'PtInRect') && has(usr, 'WindowFromPoint') && has(usr, 'ChildWindowFromPointEx')) {
223
- // PtInRect(rect, pt):RECT 按指针、POINT 按值(x64 位置寄存器 / ia32 压 8 字节上栈)。
224
- // 非对称矩形 {0,0,10,100} —— 若 x/y 在打包中交换,(99,5) 与 (5,99) 的判定会互换,
225
- // 本段即可抓出「槽内是地址而非值」「字段顺序/位置错」两类 ABI 错误。
226
- const ptInRect = bind('user32.dll', 'PtInRect', '<RECT>ptr <POINT> -> i32', { RECT, POINT })
227
- const rectOut = RECT.alloc()
228
- const rdv = new DataView(rectOut)
229
- rdv.setInt32(8, 10, true) // right(left/top = 0,零填充)
230
- rdv.setInt32(12, 100, true) // bottom
231
- t.check('PtInRect inside (5,5)', 1, ptInRect(rectOut.ptr, { x: 5, y: 5 }))
232
- t.check('PtInRect inside (5,99)', 1, ptInRect(rectOut.ptr, { x: 5, y: 99 }))
233
- t.check('PtInRect outside (99,5) — swap-sensitive', 0, ptInRect(rectOut.ptr, { x: 99, y: 5 }))
234
- t.check('PtInRect outside x=10 (right edge exclusive)', 0, ptInRect(rectOut.ptr, { x: 10, y: 5 }))
235
-
236
- // 位置 1(首参即结构):WindowFromPoint(POINT) → 屏内必非 NULL
237
- const getSystemMetrics = bind('user32.dll', 'GetSystemMetrics', 'i32 -> i32')
238
- const cx = getSystemMetrics(0) // SM_CXSCREEN
239
- const cy = getSystemMetrics(1) // SM_CYSCREEN
240
- const windowFromPoint = bind('user32.dll', 'WindowFromPoint', '<POINT> -> <>ptr', { POINT })
241
- t.checkTrue('WindowFromPoint(screen center) non-null', !!windowFromPoint({ x: cx >> 1, y: cy >> 1 }))
242
-
243
- // 位置 2 + 其后参数推移(POINT 之后还有 UINT flags —— 槽宽算错会把 flags 读错位)
244
- const childFromPointEx = bind('user32.dll', 'ChildWindowFromPointEx', '<>ptr <POINT> u32 -> <>ptr', { POINT })
245
- const getDesktopWindow = bind('user32.dll', 'GetDesktopWindow', ' -> <>ptr')
246
- const desk = getDesktopWindow()
247
- t.checkTrue('ChildWindowFromPointEx inside → non-null', !!childFromPointEx(desk, { x: 1, y: 1 }, 0 /* CWP_ALL */))
248
- t.check('ChildWindowFromPointEx outside → NULL', 0, childFromPointEx(desk, { x: cx + 100, y: cy + 100 }, 0 /* CWP_ALL */))
249
- } else t.skipCase('PtInRect/WindowFromPoint/ChildWindowFromPointEx missing')
250
-
251
- t.section('by-value struct >8B / odd size: x64 by-reference (caller temp)')
252
- if (os.arch === 'x64' && has(crt, 'memcpy')) {
253
- // MS x64:size ∉ {1,2,4,8} 的聚合体按引用传 —— 槽内是指向 caller 分配的
254
- // 16 字节对齐临时副本的指针(单参数永不跨寄存器)。memcpy 第 2 参语义即
255
- // 「源地址」,借它白盒验证整条 by-ref 路径:若槽内不是合法副本地址、或
256
- // 字节未写进副本,拷回内容必错。ia32 按值恒把字节压栈(callee 读地址会
257
- // 拿到垃圾值),故本段 x64 限定;ia32 侧由上面真实 API 段覆盖栈上传值。
258
- const BYVAL12 = struct('BYVAL12', { a: 'u32', b: 'u32', c: 'u32' })
259
- const BYVAL3 = struct('BYVAL3', { p: 'u8', q: 'u8', r: 'u8' })
260
- const cp12 = bind('msvcrt.dll', 'memcpy', '<BYTE>ptr <BYVAL12> u32 -> <>ptr', { BYVAL12 })
261
- const cp3 = bind('msvcrt.dll', 'memcpy', '<BYTE>ptr <BYVAL3> u32 -> <>ptr', { BYVAL3 })
262
- const d12 = new PtrArrayBuffer(12)
263
- cp12(d12, { a: 1, b: 0x11223344, c: 0x7fffffff }, 12)
264
- const dv12 = new DataView(d12)
265
- t.check('12B (>8B) by-ref word 0', 1, dv12.getUint32(0, true))
266
- t.check('12B (>8B) by-ref word 1', 0x11223344, dv12.getUint32(4, true))
267
- t.check('12B (>8B) by-ref word 2', 0x7fffffff, dv12.getUint32(8, true))
268
- const d3 = new PtrArrayBuffer(3)
269
- cp3(d3, { p: 0xaa, q: 0xbb, r: 0xcc }, 3)
270
- t.check('3B (odd size) by-ref bytes', 'AA,BB,CC',
271
- Array.from(new Uint8Array(d3)).map(x => x.toString(16).toUpperCase()).join(','))
272
- } else t.skipCase('by-ref path: x64-only whitebox (ia32 pushes value bytes verbatim)')
273
- },
274
- }
@@ -1,116 +0,0 @@
1
- import { bind } from '../lib/ffi/bind.js';
2
- import { struct, structArray } from '../lib/ffi/struct.js';
3
- import { NULL, normToken, PtrArrayBuffer } from '../lib/ffi/ctype.js';
4
- function expectType(_value) { }
5
- // 运行时 throw 捕获:返回错误文本(无异常 → 空串)
6
- function throws(fn) {
7
- try {
8
- fn();
9
- return '';
10
- }
11
- catch (e) {
12
- return String(e);
13
- }
14
- }
15
- const POINT = struct('POINT', { x: 'i32', y: 'i32' });
16
- const RECT = struct('RECT', { l: 'i32', t: 'i32', r: 'i32', b: 'i32' });
17
- // 数组编解码对:encode 注册进 encoders 表后 <POINT>ptr 位收元素数组;
18
- // decode 是出参数组读回(双态:buffer 自算/显式 count、裸地址 count 必填)
19
- const POINTArr = structArray(POINT);
20
- export const suite = {
21
- name: 'ffi-array',
22
- run: (t) => {
23
- t.section("normToken: '*' is an ordinary out-of-lexicon char (same as '+')");
24
- t.checkTrue("'<X*>ptr' rejected", throws(() => normToken('<POINT*>ptr')).includes('Unknown token'));
25
- t.checkTrue("'*'+'!' rejected", throws(() => normToken('<POINT*>ptr!')).includes('Unknown token'));
26
- t.checkTrue("'*'+'@' rejected", throws(() => normToken('<POINT*>ptr@Foo')).includes('Unknown token'));
27
- t.checkTrue('bare <*>ptr rejected', throws(() => normToken('<*>ptr')).includes('Unknown token'));
28
- t.checkTrue("'+' not in lexicon: generic Unknown token", throws(() => normToken('<POINT+>ptr')).includes('Unknown token'));
29
- t.checkTrue('name charset tightened (non-identifier rejected)', throws(() => normToken('<1abc>ptr')).includes('Unknown token'));
30
- t.check('plain <X>ptr unchanged', '<X>ptr', normToken('<X>ptr'));
31
- t.check('!-form unchanged', '<X>ptr!', normToken('<X>ptr!'));
32
- t.check('@-suffix stripped (enum annotation is type-layer only)', '<X>ptr', normToken('<X>ptr@Foo'));
33
- t.section('type layer: registered encoder derives the array value domain');
34
- // 注册数组 encoder 后:<POINT>ptr 位 = 非空元素数组(值域由 encoder 推导)∪ number 直通
35
- const PolylineT = bind('gdi32.dll', 'Polyline', '<HDC>ptr! <POINT>ptr i32 -> i32', { POINT: POINTArr });
36
- expectType();
37
- expectType();
38
- // 类型专用块(只过 tsc、不执行)
39
- const typeOnly = () => {
40
- // @ts-expect-error 单对象不是元素数组(encoder 值域收数组,不收裸 POINT)
41
- PolylineT(0, { x: 1, y: 2 }, 1);
42
- // @ts-expect-error 品牌拦错布局直传(RECT PAB 不是 POINT 位的值域——传 .ptr 走 number 直通)
43
- PolylineT(0, RECT.alloc(), 1);
44
- // @ts-expect-error decode 的裸地址形 count 必填(地址无长度信息)
45
- POINTArr.decode(POINT.alloc().ptr);
46
- };
47
- t.checkTrue('type-only block wired', typeof typeOnly === 'function');
48
- t.section('structArray.encode: contiguous branded materialization');
49
- const pab = POINTArr.encode([{ x: 1, y: 2 }, { x: -3, y: 4 }]);
50
- t.check('byteLength = n * size', 16, pab.byteLength);
51
- const pdv = new DataView(pab);
52
- t.check('[0].x', 1, pdv.getInt32(0, true));
53
- t.check('[0].y', 2, pdv.getInt32(4, true));
54
- t.check('[1].x', -3, pdv.getInt32(8, true));
55
- t.check('[1].y', 4, pdv.getInt32(12, true));
56
- t.checkTrue('PAB .ptr non-null (branded)', pab.ptr !== 0);
57
- // fail-loud:值域刻意收普通数组(直传变量),空/非数组/单对象全靠运行时
58
- // throw —— 任何路径都不产出 PAB(0)(其 .ptr 是"非 0 的 1 字节堆指针",会骗过
59
- // C 的 NULL 防御)。
60
- t.checkTrue('[] fail-loud (never produces PAB(0))', throws(() => POINTArr.encode([])).includes('non-empty'));
61
- t.checkTrue('non-array fail-loud', throws(() => POINTArr.encode(123)).includes('non-empty'));
62
- t.section('structArray.decode: self-evident / explicit count / address form');
63
- // 自算形态:纯元素序列 buffer(alloc)byteLength/size 整除自证
64
- t.check('self-computed n = byteLength/size', 3, POINTArr.decode(POINT.alloc(3)).length);
65
- const rt = POINTArr.decode(POINTArr.encode([{ x: 1, y: -2 }, { x: 3, y: 4 }]));
66
- t.check('roundtrip [0].x', 1, rt[0].x);
67
- t.check('roundtrip [1].y', 4, rt[1].y);
68
- // 混合缓冲(byteLength 非整数倍,如 EnumPrinters 的结构 + 尾部字符串)自算 fail-loud
69
- t.checkTrue('non-multiple byteLength fail-loud', throws(() => POINTArr.decode(new PtrArrayBuffer(7))).includes('multiple'));
70
- // 显式 count 越界防线:count*size 超 byteLength → 拦死(写错 count 的越界读入口)
71
- t.checkTrue('count beyond byteLength fail-loud', throws(() => POINTArr.decode(new PtrArrayBuffer(12), 2)).includes('exceeds'));
72
- t.checkTrue('negative count fail-loud', throws(() => POINTArr.decode(new PtrArrayBuffer(16), -1)).includes('non-negative'));
73
- t.check('count = 0 → []', 0, POINTArr.decode(POINT.alloc(), 0).length);
74
- // 裸地址形(C 返回的 T* / 预建缓冲 .ptr):count 必填,逐字节拷读
75
- const addrBack = POINTArr.decode(pab.ptr, 2);
76
- t.check('address form count', 2, addrBack.length);
77
- t.check('address form [0].x', 1, addrBack[0].x);
78
- t.checkTrue('address without count throws at runtime (dynamic path)', throws(() => POINTArr.decode(pab.ptr)).includes('count'));
79
- t.section('StructDef.alloc(n): n-slot zero-filled (default 1)');
80
- t.check('alloc() default = 1 slot', POINT.size, POINT.alloc().byteLength);
81
- t.check('alloc(3) = 3 slots', POINT.size * 3, POINT.alloc(3).byteLength);
82
- t.checkTrue('alloc slots zero-filled', new Int32Array(POINT.alloc(2))[3] === 0);
83
- t.checkTrue('alloc(0) fail-loud (PAB(0) never escapes)', throws(() => POINT.alloc(0)).includes('n >= 1'));
84
- t.checkTrue('alloc(-1) fail-loud', throws(() => POINT.alloc(-1)).includes('n >= 1'));
85
- t.checkTrue('alloc(1.5) fail-loud', throws(() => POINT.alloc(1.5)).includes('n >= 1'));
86
- t.section('StructDef.encode(v, buf, offset): positioned write primitive');
87
- const big = POINT.alloc(2);
88
- POINT.encode({ x: 7, y: -8 }, big, POINT.size); // 写入第 2 槽
89
- const bdv = new DataView(big);
90
- t.check('slot 0 stays zero', 0, bdv.getInt32(0, true));
91
- t.check('slot 1.x written at offset', 7, bdv.getInt32(POINT.size, true));
92
- t.check('slot 1.y written at offset', -8, bdv.getInt32(POINT.size + 4, true));
93
- t.checkTrue('offset form returns the buf itself', POINT.encode({ x: 1, y: 2 }, big, 0) === big);
94
- t.section('registered encoder dispatch: kernel32!IsBadReadPtr observes the pointer C gets');
95
- const isBad = bind('kernel32.dll', 'IsBadReadPtr', '<POINT>ptr u64 -> i32', { POINT: POINTArr });
96
- t.check('3-point array: whole 24-byte region readable', 0, isBad([{ x: 1, y: 2 }, { x: 3, y: 4 }, { x: 5, y: 6 }], 24n));
97
- t.check('number passthrough: prebuilt PAB .ptr', 0, isBad(pab.ptr, 16n));
98
- t.check('number passthrough: NULL literal', 1, isBad(NULL, 1n));
99
- t.checkTrue('bare object rejected at encoder', throws(() => isBad({ x: 1, y: 2 }, 8n)).includes('non-empty'));
100
- t.checkTrue('[] rejected at encoder (NULL stays explicit)', throws(() => isBad([], 8n)).includes('non-empty'));
101
- t.checkTrue('null rejected with NULL hint', throws(() => isBad(null, 8n)).includes('NULL'));
102
- t.section('content fidelity: msvcrt!memcpy reads encoder array source');
103
- const memcpy = bind('msvcrt.dll', 'memcpy', '<POINT>ptr <POINT>ptr u64 -> <>ptr', { POINT: POINTArr });
104
- const dst = POINT.alloc();
105
- memcpy(dst.ptr, [{ x: 7, y: -8 }], 8n);
106
- const back = POINT.decode(dst.ptr);
107
- t.check('copied x', 7, back.x);
108
- t.check('copied y', -8, back.y);
109
- t.section("decode(alloc()): '!' unfilled field fails (C did not fill)");
110
- const OutH = struct('OutH', { h: '<HANDLE>ptr!' });
111
- t.checkTrue("'!' 0 slot throws on decode", throws(() => OutH.decode(OutH.alloc())).includes("'!'"));
112
- t.section('bind-time lexicon checks (fail at module load)');
113
- t.checkTrue('<POINT+>ptr rejected at bind (not in lexicon)', throws(() => bind('kernel32.dll', 'IsBadReadPtr', '<POINT+>ptr u64 -> i32', { POINT: POINTArr })).includes('Unknown token'));
114
- t.checkTrue('<POINT*>ptr rejected at bind (out-of-lexicon)', throws(() => bind('kernel32.dll', 'IsBadReadPtr', '<POINT*>ptr u64 -> i32', { POINT: POINTArr })).includes('Unknown token'));
115
- }
116
- };
@@ -1,141 +0,0 @@
1
- import { Tester } from './test_helper.js'
2
- import { bind } from '../lib/ffi/bind.js'
3
- import { struct, structArray } from '../lib/ffi/struct.js'
4
- import { NULL, normToken, PtrArrayBuffer, type MaybePtr, type Ptr } from '../lib/ffi/ctype.js'
5
-
6
- // 编译期断言工具(仅类型层,运行时无开销)
7
- type Equal<A, B> = (<G>() => G extends A ? 1 : 2) extends (<G>() => G extends B ? 1 : 2) ? true : false
8
- function expectType<T extends true>(_value?: T): void {}
9
-
10
- // 运行时 throw 捕获:返回错误文本(无异常 → 空串)
11
- function throws(fn: () => unknown): string {
12
- try { fn(); return '' } catch (e) { return String(e) }
13
- }
14
-
15
- const POINT = struct('POINT', { x: 'i32', y: 'i32' })
16
- const RECT = struct('RECT', { l: 'i32', t: 'i32', r: 'i32', b: 'i32' })
17
- // 数组编解码对:encode 注册进 encoders 表后 <POINT>ptr 位收元素数组;
18
- // decode 是出参数组读回(双态:buffer 自算/显式 count、裸地址 count 必填)
19
- const POINTArr = structArray(POINT)
20
-
21
- export const suite = {
22
- name: 'ffi-array',
23
- run: (t: Tester) => {
24
- t.section("normToken: '*' is an ordinary out-of-lexicon char (same as '+')")
25
- t.checkTrue("'<X*>ptr' rejected", throws(() => normToken('<POINT*>ptr')).includes('Unknown token'))
26
- t.checkTrue("'*'+'!' rejected", throws(() => normToken('<POINT*>ptr!')).includes('Unknown token'))
27
- t.checkTrue("'*'+'@' rejected", throws(() => normToken('<POINT*>ptr@Foo')).includes('Unknown token'))
28
- t.checkTrue('bare <*>ptr rejected', throws(() => normToken('<*>ptr')).includes('Unknown token'))
29
- t.checkTrue("'+' not in lexicon: generic Unknown token", throws(() => normToken('<POINT+>ptr')).includes('Unknown token'))
30
- t.checkTrue('name charset tightened (non-identifier rejected)',
31
- throws(() => normToken('<1abc>ptr')).includes('Unknown token'))
32
- t.check('plain <X>ptr unchanged', '<X>ptr', normToken('<X>ptr'))
33
- t.check('!-form unchanged', '<X>ptr!', normToken('<X>ptr!'))
34
- t.check('@-suffix stripped (enum annotation is type-layer only)', '<X>ptr', normToken('<X>ptr@Foo'))
35
-
36
- t.section('type layer: registered encoder derives the array value domain')
37
- // 注册数组 encoder 后:<POINT>ptr 位 = 非空元素数组(值域由 encoder 推导)∪ number 直通
38
- const PolylineT = bind('gdi32.dll', 'Polyline', '<HDC>ptr! <POINT>ptr i32 -> i32', { POINT: POINTArr })
39
- expectType<Equal<Parameters<typeof PolylineT>[1],
40
- readonly { readonly x?: number | undefined; readonly y?: number | undefined }[] | MaybePtr<'POINT'>>>()
41
- expectType<Equal<Parameters<typeof PolylineT>[0], Ptr<'HDC'>>>()
42
- // 类型专用块(只过 tsc、不执行)
43
- const typeOnly: unknown = () => {
44
- // @ts-expect-error 单对象不是元素数组(encoder 值域收数组,不收裸 POINT)
45
- PolylineT(0 as Ptr<'HDC'>, { x: 1, y: 2 }, 1)
46
- // @ts-expect-error 品牌拦错布局直传(RECT PAB 不是 POINT 位的值域——传 .ptr 走 number 直通)
47
- PolylineT(0 as Ptr<'HDC'>, RECT.alloc(), 1)
48
- // @ts-expect-error decode 的裸地址形 count 必填(地址无长度信息)
49
- POINTArr.decode(POINT.alloc().ptr)
50
- }
51
- t.checkTrue('type-only block wired', typeof typeOnly === 'function')
52
-
53
- t.section('structArray.encode: contiguous branded materialization')
54
- const pab = POINTArr.encode([{ x: 1, y: 2 }, { x: -3, y: 4 }])
55
- t.check('byteLength = n * size', 16, pab.byteLength)
56
- const pdv = new DataView(pab)
57
- t.check('[0].x', 1, pdv.getInt32(0, true))
58
- t.check('[0].y', 2, pdv.getInt32(4, true))
59
- t.check('[1].x', -3, pdv.getInt32(8, true))
60
- t.check('[1].y', 4, pdv.getInt32(12, true))
61
- t.checkTrue('PAB .ptr non-null (branded)', pab.ptr !== 0)
62
- // fail-loud:值域刻意收普通数组(直传变量),空/非数组/单对象全靠运行时
63
- // throw —— 任何路径都不产出 PAB(0)(其 .ptr 是"非 0 的 1 字节堆指针",会骗过
64
- // C 的 NULL 防御)。
65
- t.checkTrue('[] fail-loud (never produces PAB(0))',
66
- throws(() => POINTArr.encode([])).includes('non-empty'))
67
- t.checkTrue('non-array fail-loud',
68
- throws(() => POINTArr.encode(123 as never)).includes('non-empty'))
69
-
70
- t.section('structArray.decode: self-evident / explicit count / address form')
71
- // 自算形态:纯元素序列 buffer(alloc)byteLength/size 整除自证
72
- t.check('self-computed n = byteLength/size', 3, POINTArr.decode(POINT.alloc(3)).length)
73
- const rt = POINTArr.decode(POINTArr.encode([{ x: 1, y: -2 }, { x: 3, y: 4 }]))
74
- t.check('roundtrip [0].x', 1, rt[0]!.x)
75
- t.check('roundtrip [1].y', 4, rt[1]!.y)
76
- // 混合缓冲(byteLength 非整数倍,如 EnumPrinters 的结构 + 尾部字符串)自算 fail-loud
77
- t.checkTrue('non-multiple byteLength fail-loud',
78
- throws(() => POINTArr.decode(new PtrArrayBuffer(7))).includes('multiple'))
79
- // 显式 count 越界防线:count*size 超 byteLength → 拦死(写错 count 的越界读入口)
80
- t.checkTrue('count beyond byteLength fail-loud',
81
- throws(() => POINTArr.decode(new PtrArrayBuffer(12), 2)).includes('exceeds'))
82
- t.checkTrue('negative count fail-loud',
83
- throws(() => POINTArr.decode(new PtrArrayBuffer(16), -1)).includes('non-negative'))
84
- t.check('count = 0 → []', 0, POINTArr.decode(POINT.alloc(), 0).length)
85
- // 裸地址形(C 返回的 T* / 预建缓冲 .ptr):count 必填,逐字节拷读
86
- const addrBack = POINTArr.decode(pab.ptr, 2)
87
- t.check('address form count', 2, addrBack.length)
88
- t.check('address form [0].x', 1, addrBack[0]!.x)
89
- t.checkTrue('address without count throws at runtime (dynamic path)',
90
- throws(() => (POINTArr.decode as unknown as (p: number) => unknown[])(pab.ptr)).includes('count'))
91
-
92
- t.section('StructDef.alloc(n): n-slot zero-filled (default 1)')
93
- t.check('alloc() default = 1 slot', POINT.size, POINT.alloc().byteLength)
94
- t.check('alloc(3) = 3 slots', POINT.size * 3, POINT.alloc(3).byteLength)
95
- t.checkTrue('alloc slots zero-filled', new Int32Array(POINT.alloc(2))[3] === 0)
96
- t.checkTrue('alloc(0) fail-loud (PAB(0) never escapes)',
97
- throws(() => POINT.alloc(0)).includes('n >= 1'))
98
- t.checkTrue('alloc(-1) fail-loud', throws(() => POINT.alloc(-1)).includes('n >= 1'))
99
- t.checkTrue('alloc(1.5) fail-loud', throws(() => POINT.alloc(1.5)).includes('n >= 1'))
100
-
101
- t.section('StructDef.encode(v, buf, offset): positioned write primitive')
102
- const big = POINT.alloc(2)
103
- POINT.encode({ x: 7, y: -8 }, big, POINT.size) // 写入第 2 槽
104
- const bdv = new DataView(big)
105
- t.check('slot 0 stays zero', 0, bdv.getInt32(0, true))
106
- t.check('slot 1.x written at offset', 7, bdv.getInt32(POINT.size, true))
107
- t.check('slot 1.y written at offset', -8, bdv.getInt32(POINT.size + 4, true))
108
- t.checkTrue('offset form returns the buf itself', POINT.encode({ x: 1, y: 2 }, big, 0) === big)
109
-
110
- t.section('registered encoder dispatch: kernel32!IsBadReadPtr observes the pointer C gets')
111
- const isBad = bind('kernel32.dll', 'IsBadReadPtr', '<POINT>ptr u64 -> i32', { POINT: POINTArr })
112
- t.check('3-point array: whole 24-byte region readable', 0, isBad([{ x: 1, y: 2 }, { x: 3, y: 4 }, { x: 5, y: 6 }], 24n))
113
- t.check('number passthrough: prebuilt PAB .ptr', 0, isBad(pab.ptr, 16n))
114
- t.check('number passthrough: NULL literal', 1, isBad(NULL, 1n))
115
- t.checkTrue('bare object rejected at encoder',
116
- throws(() => isBad({ x: 1, y: 2 } as never, 8n)).includes('non-empty'))
117
- t.checkTrue('[] rejected at encoder (NULL stays explicit)',
118
- throws(() => isBad([], 8n)).includes('non-empty'))
119
- t.checkTrue('null rejected with NULL hint',
120
- throws(() => isBad(null as never, 8n)).includes('NULL'))
121
-
122
- t.section('content fidelity: msvcrt!memcpy reads encoder array source')
123
- const memcpy = bind('msvcrt.dll', 'memcpy', '<POINT>ptr <POINT>ptr u64 -> <>ptr', { POINT: POINTArr })
124
- const dst = POINT.alloc()
125
- memcpy(dst.ptr, [{ x: 7, y: -8 }], 8n)
126
- const back = POINT.decode(dst.ptr)
127
- t.check('copied x', 7, back.x)
128
- t.check('copied y', -8, back.y)
129
-
130
- t.section("decode(alloc()): '!' unfilled field fails (C did not fill)")
131
- const OutH = struct('OutH', { h: '<HANDLE>ptr!' })
132
- t.checkTrue("'!' 0 slot throws on decode",
133
- throws(() => OutH.decode(OutH.alloc())).includes("'!'"))
134
-
135
- t.section('bind-time lexicon checks (fail at module load)')
136
- t.checkTrue('<POINT+>ptr rejected at bind (not in lexicon)',
137
- throws(() => bind('kernel32.dll', 'IsBadReadPtr', '<POINT+>ptr u64 -> i32', { POINT: POINTArr })).includes('Unknown token'))
138
- t.checkTrue('<POINT*>ptr rejected at bind (out-of-lexicon)',
139
- throws(() => bind('kernel32.dll', 'IsBadReadPtr', '<POINT*>ptr u64 -> i32', { POINT: POINTArr })).includes('Unknown token'))
140
- }
141
- }