1 /* 2 * FFI module for ucode - LibFFI-based Foreign Function Interface 3 * 4 * Copyright (C) 2005-2025 Mike Pall (LuaJIT FFI implementation) 5 * Copyright (C) 2023-2026 Jo-Philipp Wich <jo@mein.io> (ucode integration) 6 * 7 * Permission to use, copy, modify, and/or distribute this software for any 8 * purpose with or without fee is hereby granted, provided that the above 9 * copyright notice and this permission notice appear in all copies. 10 * 11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 18 * 19 * This module contains derived work from LuaJIT's FFI implementation. 20 * 21 * Modifications from LuaJIT: 22 * - Replaced LuaJIT's own call infrastructure (lj_ccall.c, lj_ccallback.c) with libffi 23 * - Adapted VM interactions to use ucode's API (uc_vm_t, uc_value_t, etc.) 24 * - Removed JIT-specific code and dependencies 25 * - Consolidated library loading logic into this module 26 * 27 * See NOTICE and ATTRIBUTION.md for complete attribution details. 28 */ 29 30 /** 31 * # Foreign Function Interface (FFI) 32 * 33 * The `ffi` module provides a foreign function interface for ucode, allowing 34 * direct interaction with C libraries. It combines a C declaration parser with 35 * libffi-based function calling to enable seamless interop between ucode and C. 36 * 37 * The module can be imported using the wildcard import syntax: 38 * 39 * ``` 40 * import * as ffi from 'ffi'; 41 * ``` 42 * 43 * ## Synopsis 44 * 45 * ```javascript 46 * import * as ffi from 'ffi'; 47 * 48 * // 1. Declare C types and functions 49 * ffi.cdef(` 50 * struct point { int x; int y; }; 51 * extern char **environ; 52 * `); 53 * 54 * // 2. Call C functions via the global C namespace 55 * // Primitive return values are auto-converted to ucode types 56 * let strcmp = ffi.C.wrap('int strcmp(const char *, const char *)'); 57 * print(strcmp("hello", "world"), "\n"); // => non-zero (number) 58 * 59 * // 3. String return values remain as cdata - use ffi.string() to convert 60 * let getenv = ffi.C.wrap('char *getenv(char *)'); 61 * let path_ptr = getenv('PATH'); // Returns char* cdata 62 * let path_str = ffi.string(path_ptr); // Convert to ucode string 63 * 64 * // 4. Create C data instances 65 * ffi.cdef('struct point { int x; int y; };'); 66 * let p = ffi.ctype('struct point', 10, 20); 67 * print(p.get('x'), p.get('y'), "\n"); // => 10 20 68 * 69 * // 5. Access global variables 70 * print(ffi.C.dlsym('environ').get(0), "\n"); 71 * 72 * // 6. Query type information 73 * print(ffi.sizeof('int'), "\n"); // => 4 74 * print(ffi.alignof('double'), "\n"); // => 8 75 * print(ffi.offsetof('struct point', 'y'), "\n"); // => 4 76 * 77 * // 7. Load external libraries 78 * let libz = ffi.dlopen('z'); 79 * let zlibVersion = libz.wrap('const char *zlibVersion(void)'); 80 * print(zlibVersion().slice(), "\n"); // => "1.2.11" (or similar) 81 * 82 * // Use in callbacks (primitives auto-converted) 83 * let qsort = ffi.C.wrap('void qsort(void *, size_t, size_t, int (*)(const void *, const void *))'); 84 * let cmp = ffi.C.wrap('int strcmp(const char *, const char *)'); 85 * let arr = ffi.ctype('char *[5]', ["zebra", "apple", "banana", "cherry", "date"]); 86 * // cmp() returns ucode number directly (primitives auto-converted) 87 * qsort(arr.ptr(), arr.length(), arr.itemsize(), 88 * (a, b) => cmp(a.deref('const char *'), b.deref('const char *'))); 89 * ``` 90 * 91 * ## Memory Management for char* Return Values 92 * 93 * When a wrapped C function returns `char*`, the return value is a **cdata pointer 94 * object**, not an auto-converted ucode string. This design prevents memory leaks 95 * and gives you explicit control over memory management. 96 * 97 * ### Converting char* to ucode Strings 98 * 99 * Use `ffi.string()` or `slice()` to convert a char* cdata to a ucode string: 100 * 101 * ```javascript 102 * let getenv = ffi.C.wrap('char *getenv(char *)'); 103 * 104 * let path_ptr = getenv('PATH'); // Returns char* cdata 105 * let path = ffi.string(path_ptr); // Convert to ucode string 106 * // or equivalently: 107 * let path = path_ptr.slice(); // slice() without args = string() 108 * ``` 109 * 110 * **Note**: Both `ffi.string()` and `slice()` create a **copy** of the C string. 111 * The original C memory remains untouched. 112 * 113 * ### Memory Ownership Patterns 114 * 115 * #### Pattern 1: C Manages Memory (No Free Required) 116 * 117 * Functions like `getenv()`, `strerror()` return pointers to **static/internal 118 * memory** managed by the C library. Do NOT free these. 119 * 120 * ```javascript 121 * let getenv = ffi.C.wrap('char *getenv(char *)'); 122 * 123 * let path_ptr = getenv('PATH'); 124 * let path = ffi.string(path_ptr); // Copies to ucode string 125 * 126 * // path_ptr points to C internal memory - DO NOT free 127 * // path is a ucode string - managed by ucode GC 128 * ``` 129 * 130 * #### Pattern 2: Caller Must Free (malloc'd Memory) 131 * 132 * Functions like `strdup()`, `asprintf()`, `getline()` return **malloc'd memory** 133 * that you must free to avoid leaks. 134 * 135 * ```javascript 136 * let strdup = ffi.C.wrap('char *strdup(const char *)'); 137 * let free = ffi.C.wrap('void free(void *)'); 138 * 139 * let ptr = strdup("hello"); // malloc'd by strdup 140 * let str = ffi.string(ptr); // Copies to ucode string 141 * free(ptr); // NOW you can safely free 142 * 143 * // str is safe - it's a ucode string copy 144 * // ptr memory is freed - no leak 145 * ``` 146 * 147 * **Key**: Keep the cdata pointer until you're done copying, then free it. 148 * 149 * #### Pattern 3: Stack-Allocated Buffers 150 * 151 * When C writes into a buffer you provide (e.g., `sprintf`), the buffer is 152 * managed by ucode. 153 * 154 * ```javascript 155 * let sprintf = ffi.C.wrap('int sprintf(char *, const char *, ...)'); 156 * 157 * let buf = ffi.ctype('char[256]'); // ucode-managed array 158 * sprintf(buf, "Hello %s", "World"); 159 * 160 * let msg = ffi.string(buf); // Copies to ucode string 161 * 162 * // buf is managed by ucode GC - no manual free needed 163 * ``` 164 * 165 * ### Substring Operations with slice() 166 * 167 * For char* pointers, `slice()` supports substring extraction: 168 * 169 * ```javascript 170 * let getenv = ffi.C.wrap('char *getenv(char *)'); 171 * let ptr = getenv('PATH'); 172 * 173 * // From start to end (same as ffi.string()) 174 * let full = ptr.slice(); 175 * 176 * // From start index to end 177 * let rest = ptr.slice(5); 178 * 179 * // Specific range 180 * let part = ptr.slice(0, 10); 181 * 182 * // Negative indices (from end) 183 * let last = ptr.slice(-5); 184 * ``` 185 * 186 * ### Common Functions Reference 187 * 188 * | Function | Memory Owner | Pattern | 189 * |----------|--------------|---------| 190 * | `getenv()` | C (static) | No free needed | 191 * | `strerror()` | C (static) | No free needed | 192 * | `strdup()` | Caller | Must `free()` | 193 * | `asprintf()` | Caller | Must `free()` | 194 * | `getline()` | Caller | Must `free()` | 195 * | `sprintf()` | Caller (buffer) | Buffer managed by you | 196 * | `strtok()` | C (static) | No free needed | 197 * 198 * ### Best Practices 199 * 200 * 1. **Always use `ffi.string()` or `slice()`** when you need a ucode string from `char*` 201 * 2. **Track ownership**: Does C manage the memory or do you? 202 * 3. **Free after copying**: Call `free(ptr)` only after `ffi.string(ptr)` or `ptr.slice()` 203 * 4. **Never free static memory**: `getenv()`, `strerror()` return static pointers 204 * 205 * ## Limitations 206 * 207 * - **No vararg closures**: `wrap()` cannot create closures with variable arguments 208 * - **Fixed ABI**: Calling convention determined at closure creation time 209 * - **Platform constraints**: Some architectures have limited support for certain type combinations 210 * 211 * ## The `ffi.C` Namespace 212 * 213 * `ffi.C` is a special CLib instance representing the process's global symbol table. 214 * It provides access to standard C library functions without explicit `dlopen()`: 215 * 216 * ```javascript 217 * // These are equivalent: 218 * let strlen1 = ffi.C.wrap('size_t strlen(const char *)'); 219 * 220 * ffi.cdef('size_t strlen(const char *);'); 221 * let strlen2 = ffi.C.wrap('strlen'); 222 * ``` 223 * 224 * Functions declared via `cdef()` are automatically registered in `ffi.C`'s symbol table. 225 * 226 * ## Pointer Arithmetic and Memory Access 227 * 228 * C data objects (cdata) provide methods for pointer arithmetic and memory access: 229 * 230 * ### Creating Pointers with ptr() 231 * 232 * Use `ptr()` to get a pointer to a cdata value: 233 * 234 * ```javascript 235 * let x = ffi.ctype('int', 42); 236 * let px = x.ptr(); // int* pointer to x 237 * 238 * // Pass to C functions expecting pointers 239 * ffi.cdef('int atoi(const char *)'); 240 * let num = ffi.ctype('char[4]', "123"); 241 * let result = atoi(num.ptr()); // => 123 242 * ``` 243 * 244 * ### Array Indexing with get() and set() 245 * 246 * Access array elements using `get(index)` and `set(index, value)`: 247 * 248 * ```javascript 249 * let arr = ffi.ctype('int[5]', [10, 20, 30, 40, 50]); 250 * 251 * // Read elements 252 * let first = arr.get(0); // => 10 (ucode number) 253 * let third = arr.get(2); // => 30 (ucode number) 254 * 255 * // Modify elements 256 * arr.set(0, 100); 257 * arr.set(4, 200); 258 * 259 * // Negative indices work too 260 * let last = arr.get(-1); // => 200 (ucode number) 261 * ``` 262 * 263 * ### Understanding get() vs index() 264 * 265 * **`get()` returns converted ucode values**, while **`index()` returns 266 * raw cdata references**. This is the key distinction between the two methods. 267 * 268 * #### get() - Converted Values 269 * 270 * The `get()` method immediately converts C values to ucode types: 271 * 272 * ```javascript 273 * let arr = ffi.ctype('int[5]', [10, 20, 30, 40, 50]); 274 * 275 * // Returns ucode number directly 276 * let val1 = arr.get(0); // => 10 (number) 277 * let val2 = arr.get(2); // => 30 (number) 278 * 279 * // Struct field access - returns converted value 280 * ffi.cdef('struct point { int x; int y; };'); 281 * let p = ffi.ctype('struct point', 10, 20); 282 * p.get('x'); // => 10 (number) 283 * p.get('y'); // => 20 (number) 284 * ``` 285 * 286 * #### index() - Raw cdata References 287 * 288 * The `index()` method returns a cdata reference for further manipulation: 289 * 290 * ```javascript 291 * let arr = ffi.ctype('int[5]', [10, 20, 30, 40, 50]); 292 * 293 * // Returns cdata reference (unconverted) 294 * let ref1 = arr.index(0); // => cdata (int) 295 * let ref2 = arr.index(2); // => cdata (int) 296 * 297 * // Convert to ucode value explicitly 298 * ref1.get(); // => 10 (number) 299 * 300 * // Or modify through the reference 301 * arr.index(0).set(100); // Set arr[0] = 100 302 * ``` 303 * 304 * #### Pointer Arithmetic 305 * 306 * Both methods work with pointers, but return different types: 307 * 308 * ```javascript 309 * let ptr = ffi.ctype('int *', arr.ptr()); 310 * 311 * // index() returns cdata reference 312 * ptr.index(0); // => cdata at ptr[0] 313 * ptr.index(1); // => cdata at ptr[1] 314 * ptr.index(0).get(); // => 10 (number) 315 * 316 * // get() returns converted value 317 * ptr.get(0); // => 10 (number) 318 * ptr.get(1); // => 20 (number) 319 * ``` 320 * 321 * #### Path Syntax Support 322 * 323 * Both methods support path notation for nested access: 324 * 325 * ```javascript 326 * ffi.cdef('struct rect { struct point min; struct point max; };'); 327 * let r = ffi.ctype('struct rect', { 328 * min: {x: 0, y: 0}, 329 * max: {x: 100, y: 100} 330 * }); 331 * 332 * // get() returns converted value 333 * r.get('min.x'); // => 0 (number) 334 * 335 * // index() returns cdata reference 336 * r.index('min.x'); // => cdata (int) 337 * r.index('min.x').get() // => 0 (number) 338 * ``` 339 * 340 * #### Practical Guidance 341 * 342 * **Use `get()` when:** 343 * - You need the value immediately as a ucode type 344 * - Reading values for computation: `let x = arr.get(i)` 345 * - Accessing struct fields: `let y = struct.get('field')` 346 * - Most common use cases 347 * 348 * **Use `index()` when:** 349 * - You need a reference for further manipulation 350 * - Chaining operations: `arr.index(i).set(val)` 351 * - Pointer arithmetic with cdata: `ptr.index(n).deref()` 352 * - Passing references to other C functions 353 * 354 * **For writing values:** 355 * - Use `set()` for both arrays and structs: `arr.set(i, val)`, `struct.set('f', val)` 356 * 357 * **For getting pointers (not values):** 358 * - Use `ptr()` on scalars: `x.ptr()` gives you `int*` 359 * - Arrays are already pointers: `arr` can be passed to C functions 360 * 361 * ### Pointer Arithmetic via get() and index() 362 * 363 * Both `get(n)` and `index(n)` work for pointer arithmetic on pointer types: 364 * 365 * ```javascript 366 * ffi.cdef('char *strdup(const char *)'); 367 * let strdup = ffi.C.wrap('char *strdup(const char *)'); 368 * 369 * let ptr = strdup("hello world"); 370 * 371 * // get() returns converted value (number for char) 372 * let first_char = ptr.get(0); // 'h' (number 104) 373 * let sixth_char = ptr.get(6); // 'w' (number 119) 374 * 375 * // index() returns cdata reference 376 * ptr.index(6); // => cdata (char) 377 * ptr.index(6).get() // => 119 (number) 378 * 379 * // Get substring from offset 380 * let substring = ffi.string(ptr.get(6)); // "world" 381 * 382 * free(ptr); 383 * ``` 384 * 385 * ### Path-Based Access for Nested Structures 386 * 387 * Use dot notation and array indexing in paths for complex access: 388 * 389 * ```javascript 390 * ffi.cdef(` 391 * struct point { int x; int y; }; 392 * struct rect { struct point min; struct point max; }; 393 * `); 394 * 395 * let r = ffi.ctype('struct rect', { 396 * min: {x: 0, y: 0}, 397 * max: {x: 100, y: 100} 398 * }); 399 * 400 * // Nested field access 401 * r.get('min.x'); // => 0 402 * r.set('max.y', 50); 403 * 404 * // Array of structs 405 * ffi.cdef('struct point points[3];'); 406 * let arr = ffi.ctype('struct point[3]', [ 407 * {x: 1, y: 2}, 408 * {x: 3, y: 4}, 409 * {x: 5, y: 6} 410 * ]); 411 * 412 * arr.get('[1].x'); // => 3 413 * arr.set('[2].y', 10); 414 * ``` 415 * 416 * ### Dereferencing Pointers with deref() 417 * 418 * Use `deref(type)` to read the value pointed to: 419 * 420 * ```javascript 421 * let x = ffi.ctype('int', 42); 422 * let px = x.ptr(); 423 * 424 * let value = px.deref('int'); // => 42 425 * 426 * // With char* pointers 427 * ffi.cdef('char *strdup(const char *)'); 428 * let strdup = ffi.C.wrap('char *strdup(const char *)'); 429 * 430 * let ptr = strdup("hello"); 431 * let first_byte = ptr.deref('char'); // => 'h' (as number 104) 432 * 433 * free(ptr); 434 * ``` 435 * 436 * ### Querying Array Properties 437 * 438 * Use `length()` and `itemsize()` for array information: 439 * 440 * ```javascript 441 * let arr = ffi.ctype('int[10]'); 442 * 443 * arr.length(); // => 10 (number of elements) 444 * arr.itemsize(); // => 4 (size of each element in bytes) 445 * 446 * // Calculate total size 447 * let total = arr.length() * arr.itemsize(); // => 40 bytes 448 * ``` 449 * 450 * ### Working with Byte Arrays 451 * 452 * For `char[]` or `uint8_t[]`, use `slice()` to extract strings: 453 * 454 * ```javascript 455 * let buf = ffi.ctype('char[10]', "hello"); 456 * 457 * // Extract as ucode string 458 * let str = buf.slice(); // => "hello" 459 * let part = buf.slice(0, 3); // => "hel" 460 * 461 * // Or use ffi.string() 462 * let str2 = ffi.string(buf); // => "hello" 463 * ``` 464 * 465 * ### Complete Example: String Manipulation 466 * 467 * ```javascript 468 * ffi.cdef(` 469 * char *strdup(const char *); 470 * void free(void *); 471 * size_t strlen(const char *); 472 * `); 473 * 474 * let strdup = ffi.C.wrap('char *strdup(const char *)'); 475 * let free = ffi.C.wrap('void free(void *)'); 476 * let strlen = ffi.C.wrap('size_t strlen(const char *)'); 477 * 478 * // Create a duplicatable string 479 * let ptr = strdup("hello world"); 480 * 481 * // Get length 482 * let len = strlen(ptr).get(); // => 11 483 * 484 * // Access individual characters via indexing 485 * let first = ptr.get(0); // 'h' 486 * let sixth = ptr.get(6); // 'w' 487 * 488 * // Extract substrings 489 * let hello = ptr.slice(0, 5); // "hello" 490 * let world = ptr.slice(6); // "world" 491 * 492 * // Modify in place 493 * ptr.set(5, 0); // Null-terminate at space 494 * 495 * let str = ffi.string(ptr); // => "hello" 496 * 497 * // Clean up 498 * free(ptr); 499 * ``` 500 * 501 * @module ffi 502 */ 503 504 #include <syslog.h> 505 #include <errno.h> 506 #include <dlfcn.h> 507 508 #include <assert.h> 509 #include <stdio.h> 510 #include <ffi.h> 511 #include <ucode/module.h> 512 #include <ucode/util.h> 513 514 #ifdef HAVE_ULOG 515 #include <libubox/ulog.h> 516 #endif 517 518 #include "uc_cdata.h" 519 #include "uc_ctype.h" 520 #include "uc_cparse.h" 521 #include "uc_cconv.h" 522 523 #define CLNS_INDEX ((1u<<CT_FUNC)|(1u<<CT_EXTERN)|(1u<<CT_CONSTVAL)) 524 525 typedef struct { 526 void *dlh; 527 char *name; 528 uc_value_t *cache; 529 } uc_ffi_clib_t; 530 531 typedef struct { 532 ffi_cif cif; 533 ffi_abi abi; 534 size_t nargs; 535 ffi_type *rtype; 536 ffi_type **atypes; 537 } uc_ffi_cc_t; 538 539 540 541 /* Check first argument for a C type and returns its ID. */ 542 static CTypeID ffi_checkctype(uc_vm_t *vm, size_t nargs, size_t narg, CTState *cts, uc_value_t **param) 543 { 544 uc_value_t *arg = uc_fn_arg(narg); 545 546 if (narg >= nargs) { 547 uc_vm_raise_exception(vm, EXCEPTION_TYPE, 548 "C type expected, got no value"); 549 550 return 0; 551 } 552 553 if (ucv_type(arg) == UC_STRING) 554 { /* Parse an abstract C type declaration. */ 555 CPState cp = { 556 .uv_vm = vm, 557 .cts = cts, 558 .srcname = ucv_string_get(arg), 559 .p = ucv_string_get(arg), 560 .uv_param = param, 561 .mode = CPARSE_MODE_ABSTRACT | CPARSE_MODE_NOIMPLICIT 562 }; 563 564 if (!uc_cparse(&cp)) 565 return 0; 566 567 return cp.val.id; 568 } 569 else 570 { 571 GCcdata *cd = ucv_resource_data(arg, "ffi.ctype"); 572 573 if (!cd) { 574 uc_vm_raise_exception(vm, EXCEPTION_TYPE, 575 "C type expected, got %s", 576 (narg < nargs) ? ucv_typename(arg) : "no value"); 577 578 return 0; 579 } 580 581 if (param && param < uc_vector_last(&vm->stack)) { 582 uc_vm_raise_exception(vm, EXCEPTION_TYPE, 583 "wrong number of type parameters"); 584 585 return 0; 586 } 587 //cd = cdataV(o); 588 return cd->ctypeid == CTID_CTYPEID ? *(CTypeID *)cdataptr(cd) : cd->ctypeid; 589 } 590 } 591 592 /* Convert given value to C type. */ 593 static CTypeID 594 uv_to_ct(uc_vm_t *vm, uint32_t mode, uc_value_t *uv, GCcdata **cdp) 595 { 596 GCcdata *cd = ucv_resource_data(uv, "ffi.ctype"); 597 598 if (cd && cd->ctypeid == CTID_CTYPEID) { 599 return *(CTypeID *)cdataptr(cd); 600 } 601 else if (cd) { 602 if (cdp) 603 *cdp = cd; 604 605 return cd->ctypeid; 606 } 607 else if (ucv_type(uv) == UC_STRING) { 608 /* Parse an abstract C type declaration. */ 609 CPState cp = { 610 .uv_vm = vm, 611 .cts = ctype_cts(vm), 612 .srcname = ucv_string_get(uv), 613 .p = ucv_string_get(uv), 614 .mode = mode 615 }; 616 617 if (!uc_cparse(&cp)) 618 return 0; 619 620 return cp.val.id; 621 } 622 else { 623 uc_vm_raise_exception(vm, EXCEPTION_TYPE, 624 "C type or string expected, got %s", 625 ucv_typename(uv)); 626 627 return 0; 628 } 629 } 630 631 /* Convert argument to C pointer. */ 632 static void *ffi_checkptr(uc_vm_t *vm, size_t nargs, size_t narg, CTypeID id) 633 { 634 uc_value_t *arg = uc_fn_arg(narg); 635 CTState *cts = ctype_cts(vm); 636 void *p; 637 638 if (narg >= nargs) { 639 uc_vm_raise_exception(vm, EXCEPTION_TYPE, "value expected"); 640 641 return NULL; 642 } 643 644 uc_cconv_ct_tv(cts, ctype_get(cts, id), (uint8_t *)&p, 645 arg, CCF_ARG(narg), NULL); 646 647 return p; 648 } 649 650 /* Get buffer size from cdata object. Returns SIZE_MAX for non-array types. */ 651 static size_t 652 ffi_cdata_bufsize(CTState *cts, uc_value_t *uv) 653 { 654 GCcdata *cd = ucv_resource_data(uv, "ffi.ctype"); 655 CType *ct; 656 657 if (!cd) 658 return SIZE_MAX; 659 660 ct = ctype_get(cts, cd->ctypeid); 661 662 if (ctype_isptr(ct->info)) 663 ct = ctype_rawchild(cts, ct); 664 665 if (ctype_isrefarray(ct->info)) 666 return ct->size; 667 668 return SIZE_MAX; 669 } 670 671 /* Get redirected or mangled external symbol. */ 672 static uc_value_t * 673 clib_extsym(CTState *cts, CType *ct, uc_value_t *name) 674 { 675 if (ct->sib) { 676 CType *ctf = ctype_get(cts, ct->sib); 677 678 if (ctype_isxattrib(ctf->info, CTA_REDIR)) 679 return ctf->uv_name; 680 } 681 682 return name; 683 } 684 685 686 static bool 687 uc_ctype_requires_ffi_struct(CTState *cts, CTypeID cid) 688 { 689 CType *ct = ctype_get(cts, cid); 690 CTInfo info = ct->info; 691 692 switch (ctype_type(info)) { 693 case CT_ARRAY: 694 switch (cid) { 695 case CTID_COMPLEX_FLOAT: 696 case CTID_COMPLEX_DOUBLE: 697 return false; 698 } 699 700 /* fall through */ 701 702 case CT_STRUCT: 703 return true; 704 } 705 706 return false; 707 } 708 709 static ffi_type * 710 uc_ctype_to_ffi_type(CTState *cts, CTypeID cid, ffi_type *st) 711 { 712 CType *ct = ctype_get(cts, cid); 713 CTInfo info = ct->info; 714 CTSize size = ct->size; 715 716 switch (ctype_type(info)) { 717 case CT_NUM: 718 if (info & CTF_BOOL) 719 return &ffi_type_uint8; 720 721 if (info & CTF_FP) { 722 if (size == sizeof(double)) 723 return &ffi_type_double; 724 725 if (size == sizeof(float)) 726 return &ffi_type_float; 727 728 return &ffi_type_longdouble; 729 } 730 731 switch (size) { 732 case 1: 733 return (info & CTF_UNSIGNED) ? &ffi_type_uchar : &ffi_type_schar; 734 735 case 2: 736 return (info & CTF_UNSIGNED) ? &ffi_type_uint16 : &ffi_type_sint16; 737 738 case 4: 739 return (info & CTF_UNSIGNED) ? &ffi_type_uint32 : &ffi_type_sint32; 740 741 case 8: 742 return (info & CTF_UNSIGNED) ? &ffi_type_uint64 : &ffi_type_sint64; 743 } 744 745 assert(0); 746 return NULL; 747 748 case CT_VOID: 749 return &ffi_type_void; 750 751 case CT_ENUM: 752 switch (ctype_cid(info)) { 753 case CTID_INT32: 754 return &ffi_type_sint32; 755 756 case CTID_UINT32: 757 return &ffi_type_uint32; 758 } 759 760 assert(0); 761 return NULL; 762 763 case CT_PTR: 764 return &ffi_type_pointer; 765 766 case CT_ARRAY: 767 switch (cid) { 768 case CTID_COMPLEX_FLOAT: 769 return &ffi_type_complex_float; 770 771 case CTID_COMPLEX_DOUBLE: 772 return &ffi_type_complex_double; 773 } 774 775 /* fall through */ 776 777 case CT_STRUCT: 778 if (!st) 779 st = xalloc(sizeof(ffi_type)); 780 781 st->type = FFI_TYPE_STRUCT; 782 st->size = size; 783 st->alignment = ctype_align(info); 784 785 return st; 786 } 787 788 return NULL; 789 } 790 791 static uc_value_t * 792 clib_dlsym(uc_vm_t *vm, uc_ffi_clib_t *lib, uc_value_t *name) 793 { 794 uc_value_t *sym; 795 bool exists; 796 797 if (!lib || ucv_type(name) != UC_STRING) 798 return NULL; 799 800 sym = ucv_object_get(lib->cache, ucv_string_get(name), &exists); 801 802 if (!exists) { 803 CTState *cts = ctype_cts(vm); 804 CType *ct; 805 CTypeID id = uc_ctype_getname(cts, &ct, name, CLNS_INDEX); 806 807 if (!id) { 808 uc_vm_raise_exception(vm, EXCEPTION_TYPE, 809 "missing declaration for symbol '%s'", 810 ucv_string_get(name)); 811 812 return NULL; 813 } 814 815 if (ctype_isconstval(ct->info)) { 816 sym = ucv_uint64_new(ct->size); 817 } 818 else { 819 uc_value_t *extname = clib_extsym(cts, ct, name); 820 821 if (!ctype_isfunc(ct->info) && !ctype_isextern(ct->info)) { 822 uc_vm_raise_exception(vm, EXCEPTION_TYPE, 823 "unexpected ctype %08x for symbol '%s' in clib", 824 ct->info, ucv_string_get(name)); 825 826 return NULL; 827 } 828 829 #if UC_TARGET_WINDOWS 830 DWORD oldwerr = GetLastError(); 831 #endif 832 void *p = dlsym(lib->dlh, ucv_string_get(extname)); 833 834 #if UC_TARGET_WINDOWS 835 SetLastError(oldwerr); 836 #endif 837 838 if (!p) { 839 uc_vm_raise_exception(vm, EXCEPTION_REFERENCE, 840 "cannot resolve symbol '%s': %s", 841 ucv_string_get(name), 842 dlerror()); 843 844 return NULL; 845 } 846 847 /* dlsym returns a pointer to the symbol (not the value). 848 * Wrap the symbol's type in a pointer for correct semantics. */ 849 CTypeID ptr_id = uc_ctype_intern(cts, CTINFO(CT_PTR, CTALIGN_PTR) + id, CTSIZE_PTR); 850 851 sym = uc_cdata_new(vm, ptr_id, CTSIZE_PTR); 852 *(void **)uc_cdata_dataptr(sym) = p; 853 } 854 855 ucv_object_add(lib->cache, ucv_string_get(name), sym); 856 } 857 858 return ucv_get(sym); 859 } 860 861 static uc_value_t * 862 uc_ctype_call(uc_vm_t *vm, size_t nargs); 863 864 static uc_value_t * 865 ct_to_uv(uc_vm_t *vm, CTState *cts, CTypeID cid, void *cdata, size_t size, 866 uc_value_t *refs); 867 868 /* Path token types */ 869 typedef enum { 870 PATH_TOKEN_FIELD, /* Field name: "foo" */ 871 PATH_TOKEN_INDEX /* Array index: "[0]" */ 872 } path_token_type; 873 874 typedef struct { 875 path_token_type type; 876 union { 877 char *field; /* Allocated field name (for PATH_TOKEN_FIELD) */ 878 size_t index; /* For PATH_TOKEN_INDEX */ 879 }; 880 } path_token; 881 882 typedef struct { 883 path_token *entries; 884 size_t count; 885 } path_tokens; 886 887 /* Free path tokens */ 888 static void 889 path_tokens_free(path_tokens *tokens) 890 { 891 uc_vector_foreach(tokens, tok) 892 if (tok->type == PATH_TOKEN_FIELD) 893 free(tok->field); 894 895 uc_vector_clear(tokens); 896 } 897 898 /* Tokenize a path string like "foo.bar[0].baz" or "foo.0.bar" */ 899 static bool 900 path_tokenize(uc_vm_t *vm, uc_value_t *key_uv, path_tokens *tokens) 901 { 902 if (ucv_type(key_uv) != UC_STRING) 903 return false; 904 905 const char *path = ucv_string_get(key_uv); 906 size_t len = ucv_string_length(key_uv); 907 908 size_t i = 0; 909 while (i < len) { 910 /* Skip dots */ 911 if (path[i] == '.') { 912 i++; 913 continue; 914 } 915 916 /* Check for array index [n] */ 917 if (path[i] == '[') { 918 /* Find closing bracket */ 919 size_t j = i + 1; 920 while (j < len && path[j] != ']') 921 j++; 922 923 if (j >= len) { 924 uc_vm_raise_exception(vm, EXCEPTION_SYNTAX, 925 "Invalid path syntax: missing closing bracket"); 926 path_tokens_free(tokens); 927 return false; 928 } 929 930 /* Parse index */ 931 char *endptr; 932 size_t idx = strtoul(path + i + 1, &endptr, 10); 933 if (endptr != path + j) { 934 uc_vm_raise_exception(vm, EXCEPTION_SYNTAX, 935 "Invalid path syntax: invalid array index"); 936 path_tokens_free(tokens); 937 return false; 938 } 939 940 /* Add index token */ 941 uc_vector_push(tokens, (path_token){ .type = PATH_TOKEN_INDEX, .index = idx }); 942 943 i = j + 1; 944 continue; 945 } 946 947 /* Field name */ 948 size_t j = i; 949 while (j < len && path[j] != '.' && path[j] != '[') 950 j++; 951 952 if (j > i) { 953 /* Add field token */ 954 size_t field_len = j - i; 955 char *field = malloc(field_len + 1); 956 if (!field) { 957 uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, 958 "Out of memory"); 959 path_tokens_free(tokens); 960 return false; 961 } 962 memcpy(field, path + i, field_len); 963 field[field_len] = '\0'; 964 965 uc_vector_push(tokens, (path_token){ .type = PATH_TOKEN_FIELD, .field = field }); 966 } 967 968 i = j; 969 } 970 971 return tokens->count > 0; 972 } 973 974 /* Navigate through a cdata using path tokens. 975 * Returns final CType, updates pointer to final location. 976 * Sets *error to true on failure. 977 */ 978 static CType * 979 path_navigate(CTState *cts, GCcdata *start_cd, path_tokens *tokens, 980 uint8_t **pptr, CType **pct, bool *error) 981 { 982 *error = false; 983 uint8_t *p = cdataptr(start_cd); 984 CType *ct = ctype_get(cts, start_cd->ctypeid); 985 986 /* Skip extern and attribute wrappers */ 987 while (ctype_isextern(ct->info) || ctype_isattrib(ct->info)) 988 ct = ctype_child(cts, ct); 989 990 /* Handle reference indirection */ 991 if (ctype_isref(ct->info)) { 992 p = *(uint8_t **)p; 993 ct = ctype_child(cts, ct); 994 } 995 996 uc_vector_foreach(tokens, tok) { 997 if (tok->type == PATH_TOKEN_FIELD) { 998 /* String key - struct field access */ 999 if (!ctype_isstruct(ct->info)) { 1000 *error = true; 1001 return NULL; 1002 } 1003 1004 CTSize ofs; 1005 CTInfo fqual = 0; 1006 uc_value_t *field_key = ucv_string_new(tok->field); 1007 1008 CType *fct = uc_ctype_getfieldq(cts, ct, field_key, &ofs, &fqual); 1009 ucv_put(field_key); 1010 1011 if (!fct) { 1012 *error = true; 1013 return NULL; 1014 } 1015 1016 p += ofs; 1017 ct = fct; 1018 1019 /* Get the actual field type */ 1020 ct = ctype_child(cts, ct); 1021 1022 /* Skip attributes on field */ 1023 while (ctype_isattrib(ct->info)) 1024 ct = ctype_child(cts, ct); 1025 } 1026 else { 1027 /* Integer key - array/pointer access */ 1028 if (!ctype_ispointer(ct->info) && !ctype_isarray(ct->info)) { 1029 *error = true; 1030 return NULL; 1031 } 1032 1033 CTSize sz = uc_ctype_size(cts, ctype_cid(ct->info)); 1034 if (sz == CTSIZE_INVALID) { 1035 *error = true; 1036 return NULL; 1037 } 1038 1039 if (ctype_isptr(ct->info)) 1040 p = (uint8_t *)cdata_getptr(p, ct->size); 1041 1042 /* Check bounds for arrays */ 1043 if (ctype_isarray(ct->info)) { 1044 CTSize arr_len = ct->size / sz; 1045 if (tok->index >= arr_len) { 1046 *error = true; 1047 return NULL; 1048 } 1049 } 1050 1051 p += tok->index * sz; 1052 ct = ctype_rawchild(cts, ct); 1053 } 1054 } 1055 1056 *pct = ct; 1057 *pptr = p; 1058 return ct; 1059 } 1060 1061 static uc_value_t * 1062 clib_wrapped_call(uc_vm_t *vm, size_t nargs) 1063 { 1064 uc_callframe_t *call = uc_vector_last(&vm->callframes); 1065 uc_cfunction_t *cfn = call->cfunction; 1066 size_t off = ALIGN(sizeof(*cfn) + strlen(cfn->name) + 1); 1067 CTypeID cid = *(CTypeID *)((char *)cfn + off); 1068 void *fp = *(void **)((char *)cfn + off + sizeof(cid)); 1069 1070 uc_value_t *sym = uc_cdata_new(vm, cid, CTSIZE_PTR); 1071 *(void **)uc_cdata_dataptr(sym) = fp; 1072 1073 uc_value_t *ctx = call->ctx; 1074 call->ctx = sym; 1075 1076 uc_value_t *ret = uc_ctype_call(vm, nargs); 1077 1078 /* Auto-convert primitive return values for convenience */ 1079 if (ret) { 1080 GCcdata *cd = ucv_resource_data(ret, "ffi.ctype"); 1081 if (cd) { 1082 CTState *cts = ctype_cts(vm); 1083 CType *ct = ctype_get(cts, cd->ctypeid); 1084 1085 if (ct && !ctype_isfunc(ct->info) && !ctype_isptr(ct->info)) { 1086 /* Primitives: convert to ucode values */ 1087 uc_value_t *converted = ct_to_uv(vm, cts, cd->ctypeid, cdataptr(cd), ct->size, NULL); 1088 ucv_put(ret); 1089 ret = converted; 1090 } 1091 /* Pointers remain as cdata for explicit control: 1092 * - Avoid memory leaks from auto-copying char* 1093 * - Allow explicit ffi.string() conversion when needed 1094 * - Enable pointer arithmetic and dereferencing 1095 */ 1096 } 1097 } 1098 1099 ucv_put(call->ctx); 1100 call->ctx = ctx; 1101 1102 return ret; 1103 } 1104 1105 1106 /** 1107 * Represents a handle to a loaded shared library. 1108 * 1109 * @class module:ffi.CLib 1110 * @hideconstructor 1111 * 1112 * @see {@link module:ffi#dlopen|dlopen()} 1113 * 1114 * @example 1115 * 1116 * const lib = dlopen(…); 1117 * 1118 * lib.wrap(…); 1119 * lib.dlsym(…); 1120 */ 1121 1122 /** 1123 * Look up a symbol in the loaded library. 1124 * 1125 * The `dlsym()` method retrieves a symbol (function, variable, or constant) 1126 * from the loaded shared library or global symbol table. 1127 * 1128 * **Input patterns:** 1129 * 1130 * 1. **Bare symbol name**: Look up by symbol name directly. Returns a cdata 1131 * pointer for functions/variables, or a number for constants. 1132 * 1133 * 2. **Full declaration**: Provide a complete declaration string. The symbol 1134 * name is extracted and used for lookup. 1135 * 1136 * @function module:ffi.CLib#dlsym 1137 * 1138 * @param {string} name 1139 * The symbol name or full declaration string. 1140 * 1141 * @returns {?module:ffi.CData|number} 1142 * A cdata pointer for functions/variables, a number for constants, 1143 * or `null` if the symbol cannot be resolved. 1144 * 1145 * @throws {Error} 1146 * Throws an exception if the symbol cannot be found or the declaration 1147 * syntax is invalid. 1148 * 1149 * @example 1150 * // Pattern 1: Bare symbol name 1151 * ffi.cdef('extern char **environ;'); 1152 * let env = ffi.C.dlsym('environ'); 1153 * print(env.get(0), "\n"); 1154 * 1155 * @example 1156 * // Pattern 2: Full declaration 1157 * let getenv = ffi.C.dlsym('char *getenv(char *)'); 1158 * print(ffi.string(getenv.deref('char *'))); 1159 * 1160 * @example 1161 * // Access constant value (returns number) 1162 * ffi.cdef('const int INT_MAX;'); 1163 * let max = ffi.C.dlsym('INT_MAX'); // => number 1164 */ 1165 static uc_value_t * 1166 uc_clib_dlsym(uc_vm_t *vm, size_t nargs) 1167 { 1168 uc_ffi_clib_t *lib = uc_fn_thisval("ffi.clib"); 1169 uc_value_t *arg = uc_fn_arg(0); 1170 CTState *cts = ctype_cts(vm); 1171 1172 if (ucv_type(arg) == UC_STRING) { 1173 const char *s = ucv_string_get(arg); 1174 if (strpbrk(s, " \t\n\r")) { 1175 /* Parse the declaration */ 1176 CPState cp = { 1177 .uv_vm = vm, 1178 .cts = cts, 1179 .srcname = s, 1180 .p = s, 1181 .uv_param = NULL, 1182 .mode = CPARSE_MODE_ABSTRACT | CPARSE_MODE_NOIMPLICIT 1183 }; 1184 1185 if (!uc_cparse(&cp)) { 1186 uc_vm_raise_exception(vm, EXCEPTION_TYPE, 1187 "failed to parse C declaration: '%s'", s); 1188 return NULL; 1189 } 1190 1191 /* Get the symbol name from the parsed type */ 1192 CType *ct = ctype_raw(cts, cp.val.id); 1193 if (!ct || !ct->uv_name) { 1194 uc_vm_raise_exception(vm, EXCEPTION_TYPE, 1195 "declaration does not define a named symbol"); 1196 return NULL; 1197 } 1198 1199 /* Use the symbol name for lookup */ 1200 arg = ct->uv_name; 1201 } else { 1202 /* Bare symbol name: first check if there's a declaration */ 1203 CType *ct; 1204 CTypeID id = uc_ctype_getname(cts, &ct, arg, CLNS_INDEX); 1205 if (id) { 1206 /* Declaration exists, use normal clib_dlsym */ 1207 return clib_dlsym(vm, lib, arg); 1208 } else { 1209 /* No declaration: direct dlsym returning void* cdata */ 1210 void *p = dlsym(lib->dlh, s); 1211 if (!p) { 1212 /* Symbol not found */ 1213 return NULL; 1214 } 1215 /* Create a void* cdata */ 1216 CTypeID voidp = CTID_P_VOID; 1217 uc_value_t *cd = uc_cdata_new(vm, voidp, sizeof(void*)); 1218 void **ptr = (void**)uc_cdata_dataptr(cd); 1219 *ptr = p; 1220 return cd; 1221 } 1222 } 1223 } 1224 1225 return clib_dlsym(vm, lib, arg); 1226 } 1227 1228 static uc_value_t * 1229 uc_clib_resolve_common(CTState *cts, uc_ffi_clib_t *lib, uc_value_t *cdef, 1230 CType **ctp, GCcdata **cdp) 1231 { 1232 const char *spec; 1233 size_t spec_len, pos; 1234 GCcdata *cd; 1235 CType *ct; 1236 void *fp; 1237 1238 if (!lib) 1239 return NULL; 1240 1241 if (ucv_type(cdef) == UC_STRING) { 1242 spec = ucv_string_get(cdef); 1243 spec_len = ucv_string_length(cdef); 1244 1245 pos = strcspn(spec, " \t\r\n*[{("); 1246 1247 if (pos != spec_len) { 1248 CTypeID cid = uv_to_ct(cts->vm, CPARSE_MODE_DIRECT, cdef, NULL); 1249 1250 if (!cid) 1251 return NULL; 1252 1253 CType *ct = ctype_raw(cts, cid); 1254 1255 uc_value_t *sym_name = ct->uv_name; 1256 uc_value_t *sym = clib_dlsym(cts->vm, lib, sym_name); 1257 1258 if (!sym) { 1259 uc_value_t *repr = uc_ctype_repr(cts->vm, cid, ct->uv_name); 1260 1261 uc_vm_raise_exception(cts->vm, EXCEPTION_TYPE, 1262 "unable to resolve symbol '%s' for declaration '%s'", 1263 ucv_string_get(sym_name), ucv_string_get(repr)); 1264 1265 ucv_put(repr); 1266 1267 return NULL; 1268 } 1269 1270 GCcdata *cd = ucv_resource_data(sym, "ffi.ctype"); 1271 assert(cd); 1272 1273 /* dlsym returns a pointer to the symbol. Unwrap pointer to get actual type. 1274 * For function pointers, this gives us the function type which should match the declaration. */ 1275 CType *sym_ct = ctype_get(cts, cd->ctypeid); 1276 if (ctype_isptr(sym_ct->info)) 1277 sym_ct = ctype_rawchild(cts, sym_ct); 1278 1279 CType *decl_ct = ctype_get(cts, cid); 1280 1281 if (sym_ct != decl_ct) { 1282 uc_value_t *repr_decl = uc_ctype_repr(cts->vm, cid, ct->uv_name); 1283 uc_value_t *repr_sym = uc_ctype_repr(cts->vm, cd->ctypeid, NULL); 1284 1285 uc_vm_raise_exception(cts->vm, EXCEPTION_TYPE, 1286 "type mismatch between declaration (%s) and resolved symbol (%s)", 1287 ucv_string_get(repr_decl), 1288 ucv_string_get(repr_sym)); 1289 1290 ucv_put(repr_decl); 1291 ucv_put(repr_sym); 1292 ucv_put(sym); 1293 1294 return NULL; 1295 } 1296 1297 if (ctp) 1298 *ctp = ct; 1299 1300 if (cdp) 1301 *cdp = cd; 1302 1303 return sym; 1304 } 1305 else { 1306 CType *ct; 1307 uc_value_t *sym_uv = ucv_string_new(spec); 1308 CTypeID id = uc_ctype_getname(cts, &ct, sym_uv, CLNS_INDEX); 1309 ucv_put(sym_uv); 1310 1311 if (!id) { 1312 uc_vm_raise_exception(cts->vm, EXCEPTION_TYPE, 1313 "unknown symbol '%s'", spec); 1314 1315 return NULL; 1316 } 1317 1318 uc_value_t *sym = clib_dlsym(cts->vm, lib, cdef); 1319 1320 if (!sym) { 1321 uc_vm_raise_exception(cts->vm, EXCEPTION_TYPE, 1322 "unable to resolve symbol '%s'", spec); 1323 1324 return NULL; 1325 } 1326 1327 GCcdata *cd = ucv_resource_data(sym, "ffi.ctype"); 1328 assert(cd); 1329 1330 /* dlsym returns a pointer to the symbol. Unwrap pointer to get actual type. */ 1331 CType *sym_ct = ctype_get(cts, cd->ctypeid); 1332 if (ctype_isptr(sym_ct->info)) 1333 sym_ct = ctype_rawchild(cts, sym_ct); 1334 1335 CType *decl_ct = ctype_get(cts, id); 1336 1337 if (sym_ct != decl_ct) { 1338 uc_value_t *repr_decl = uc_ctype_repr(cts->vm, id, ct->uv_name); 1339 uc_value_t *repr_sym = uc_ctype_repr(cts->vm, cd->ctypeid, NULL); 1340 1341 uc_vm_raise_exception(cts->vm, EXCEPTION_TYPE, 1342 "type mismatch between declared type '%s' and resolved symbol type '%s'", 1343 ucv_string_get(repr_decl), 1344 ucv_string_get(repr_sym)); 1345 1346 ucv_put(repr_decl); 1347 ucv_put(repr_sym); 1348 ucv_put(sym); 1349 1350 return NULL; 1351 } 1352 1353 if (ctp) 1354 *ctp = ct; 1355 1356 if (cdp) 1357 *cdp = cd; 1358 1359 return sym; 1360 } 1361 } 1362 1363 cd = ucv_resource_data(cdef, "ffi.ctype"); 1364 if (!cd) 1365 return NULL; 1366 1367 /* Handle ctype resources containing function pointers */ 1368 uc_vm_t *vm = cts->vm; 1369 1370 ct = ctype_get(cts, cd->ctypeid); 1371 1372 if (!ct) 1373 return NULL; 1374 1375 /* Unwrap pointer types to get to the actual function type */ 1376 if (ctype_isptr(ct->info)) { 1377 ct = ctype_rawchild(cts, ct); 1378 } 1379 1380 if (!ct || !ctype_isfunc(ct->info)) { 1381 uc_value_t *repr = uc_ctype_repr(vm, cd->ctypeid, NULL); 1382 1383 uc_vm_raise_exception(vm, EXCEPTION_TYPE, 1384 "attempt to wrap non-function cdata type '%s'", 1385 ucv_string_get(repr)); 1386 1387 ucv_put(repr); 1388 1389 return NULL; 1390 } 1391 1392 /* Extract the function pointer from the cdata */ 1393 fp = *(void **)cdataptr(cd); 1394 1395 if (!fp) { 1396 uc_vm_raise_exception(vm, EXCEPTION_REFERENCE, 1397 "attempt to wrap NULL function pointer"); 1398 1399 return NULL; 1400 } 1401 1402 if (ctp) 1403 *ctp = ct; 1404 1405 if (cdp) 1406 *cdp = cd; 1407 1408 /* Create a temporary cdata to hold the function pointer for the caller */ 1409 uc_value_t *sym = uc_cdata_new(vm, ctype_typeid(cts, ct), CTSIZE_PTR); 1410 *(void **)uc_cdata_dataptr(sym) = fp; 1411 1412 return sym; 1413 } 1414 1415 /** 1416 * Resolve a symbol to a cdata pointer. 1417 * 1418 * The `resolve()` method retrieves a symbol from the loaded library and 1419 * returns a cdata pointer. Unlike `wrap()`, it does not create a callable 1420 * wrapper - it returns the raw pointer for manual handling. 1421 * 1422 * @function module:ffi.CLib#resolve 1423 * 1424 * @param {string|module:ffi.CData} decl 1425 * The function declaration or cdata function pointer. 1426 * 1427 * @returns {?module:ffi.CData} 1428 * A cdata pointer to the symbol, or `null` if resolution fails. 1429 * 1430 * @throws {Error} 1431 * Throws an exception if the symbol cannot be resolved. 1432 * 1433 * @example 1434 * // Resolve function pointer 1435 * ffi.cdef('int strcmp(const char *, const char *)'); 1436 * let ptr = ffi.C.resolve('strcmp'); 1437 * // ptr is a cdata, not callable directly 1438 */ 1439 static uc_value_t * 1440 uc_clib_resolve(uc_vm_t *vm, size_t nargs) 1441 { 1442 uc_ffi_clib_t *this = uc_fn_thisval("ffi.clib"); 1443 uc_value_t *cdef = uc_fn_arg(0); 1444 CTState *cts = ctype_cts(vm); 1445 1446 return uc_clib_resolve_common(cts, this, cdef, NULL, NULL); 1447 } 1448 1449 /** 1450 * Wrap a C function symbol into a callable ucode function. 1451 * 1452 * The `wrap()` method retrieves a function symbol from the library and returns 1453 * a callable wrapper that handles argument marshaling and function invocation 1454 * via libffi. 1455 * 1456 * **Input patterns:** 1457 * 1458 * 1. **Full declaration**: Provide a complete function declaration string. 1459 * The symbol name is extracted automatically. 1460 * 1461 * 2. **Bare symbol**: Provide just the symbol name. Requires that the type 1462 * was previously declared via `cdef()`. 1463 * 1464 * 3. **cdata pointer**: Provide a cdata containing a function pointer 1465 * (e.g., from `dlsym()`). The type must match the cdata's type. 1466 * 1467 * @function module:ffi.CLib#wrap 1468 * 1469 * @param {string|module:ffi.CData} decl 1470 * The function declaration string, bare symbol name, or cdata function pointer. 1471 * 1472 * @returns {?function} 1473 * A callable function wrapper, or `null` if resolution fails. 1474 * 1475 * @throws {Error} 1476 * Throws an exception if the symbol cannot be resolved or is not a function. 1477 * 1478 * @example 1479 * // Pattern 1: Full declaration (no cdef needed) 1480 * let strcmp = ffi.C.wrap('int strcmp(const char *, const char *)'); 1481 * print(strcmp("hello", "world")); // => number (auto-converted) 1482 * 1483 * @example 1484 * // Pattern 2: Bare symbol (requires cdef) 1485 * ffi.cdef('int strcmp(const char *, const char *)'); 1486 * let strcmp = ffi.C.wrap('strcmp'); 1487 * print(strcmp("hello", "world")); // => number (auto-converted) 1488 * 1489 * @example 1490 * // Pattern 3: cdata function pointer 1491 * ffi.cdef('size_t strlen(const char *)'); 1492 * let strlen_sym = ffi.C.dlsym('strlen'); 1493 * let strlen_fn = ffi.C.wrap(strlen_sym); 1494 * print(strlen_fn("hello")); // => number (auto-converted) 1495 * 1496 * @example 1497 * // Pointer returns remain as cdata for explicit control 1498 * let getenv = ffi.C.wrap('char *getenv(char *)'); 1499 * let path_ptr = getenv('PATH'); // => cdata (char*) 1500 * let path = ffi.string(path_ptr); // Convert to ucode string 1501 */ 1502 static uc_value_t * 1503 uc_clib_wrap(uc_vm_t *vm, size_t nargs) 1504 { 1505 uc_ffi_clib_t *this = uc_fn_thisval("ffi.clib"); 1506 uc_value_t *cdef = uc_fn_arg(0); 1507 CTState *cts = ctype_cts(vm); 1508 GCcdata *cd; 1509 CType *ct; 1510 1511 uc_value_t *sym = uc_clib_resolve_common(cts, this, cdef, &ct, &cd); 1512 1513 if (!sym) 1514 return NULL; 1515 1516 CTypeID cid = ctype_typeid(cts, ct); 1517 uc_value_t *sym_name = ct->uv_name; 1518 1519 if (ctype_isptr(ct->info)) 1520 ct = ctype_rawchild(cts, ct); 1521 1522 if (!ct || !ctype_isfunc(ct->info)) { 1523 uc_value_t *repr_sym = uc_ctype_repr(vm, cd->ctypeid, NULL); 1524 1525 uc_vm_raise_exception(vm, EXCEPTION_TYPE, 1526 "attempt to wrap non-function value type '%s'", 1527 ucv_string_get(repr_sym)); 1528 1529 ucv_put(repr_sym); 1530 ucv_put(sym); 1531 1532 return NULL; 1533 } 1534 1535 void *fp = *(void **)uc_cdata_dataptr(sym); 1536 uc_cfunction_t *cfn = NULL; 1537 size_t namelen, off; 1538 1539 namelen = snprintf(NULL, 0, "ffi.%s.%s", 1540 this->name ? this->name : "C", ucv_string_get(sym_name)); 1541 1542 off = ALIGN(sizeof(*cfn) + namelen + 1); 1543 1544 cfn = xalloc(off + sizeof(cid) + sizeof(fp)); 1545 cfn->header.type = UC_CFUNCTION; 1546 cfn->cfn = clib_wrapped_call; 1547 1548 snprintf(cfn->name, namelen + 1, "ffi.%s.%s", 1549 this->name ? this->name : "C", ucv_string_get(sym_name)); 1550 1551 memcpy((char *)cfn + off, &cid, sizeof(cid)); 1552 memcpy((char *)cfn + off + sizeof(cid), &fp, sizeof(fp)); 1553 1554 ucv_put(sym); 1555 1556 return ucv_get(&cfn->header); 1557 } 1558 1559 1560 static size_t 1561 uc_ctype_count_custom_types(CTState *cts, CType *funcspec, CTypeID argtype) 1562 { 1563 size_t n_custom_types = 0; 1564 1565 /* check whether return value requires a custom ffi type */ 1566 if (uc_ctype_requires_ffi_struct(cts, ctype_cid(funcspec->info))) 1567 n_custom_types++; 1568 1569 while (true) { 1570 if (!argtype) 1571 break; 1572 1573 CType *ctf = ctype_get(cts, argtype); 1574 1575 assert(ctype_isfield(ctf->info)); 1576 1577 argtype = ctf->sib; 1578 1579 if (uc_ctype_requires_ffi_struct(cts, ctype_cid(ctf->info))) 1580 n_custom_types++; 1581 } 1582 1583 return n_custom_types; 1584 } 1585 1586 typedef struct { 1587 ffi_closure closure; 1588 ffi_cif cif; 1589 void *codeloc; 1590 uc_vm_t *vm; 1591 uc_value_t *func; 1592 CType *ct; 1593 ffi_type *argtypes[]; 1594 } uc_closure_context_t; 1595 1596 static void 1597 uc_ctype_closure_cb(ffi_cif *cif, void *ret, void *args[], void *ud) 1598 { 1599 uc_value_t *uv_arg, *uv_ret = ucv_uint64_new(0); 1600 uc_closure_context_t *context = ud; 1601 uc_exception_type_t ex; 1602 1603 CTState *cts = ctype_cts(context->vm); 1604 CType *ct_arg, *ct_ret; 1605 CTypeID id_arg; 1606 1607 uc_vm_stack_push(context->vm, ucv_get(context->func)); 1608 1609 /* skip attribute entries */ 1610 for (id_arg = context->ct->sib; 1611 id_arg && ctype_isattrib(ctype_get(cts, id_arg)->info); 1612 id_arg = ctype_get(cts, id_arg)->sib) 1613 ; 1614 1615 for (size_t i = 0; i < cif->nargs; i++) { 1616 uv_arg = NULL; 1617 1618 assert(id_arg); 1619 ct_arg = ctype_get(cts, id_arg); 1620 1621 assert(ctype_isfield(ct_arg->info)); 1622 id_arg = ct_arg->sib; 1623 1624 uc_cconv_tv_ct(cts, ctype_raw(cts, ctype_cid(ct_arg->info)), 1625 ctype_cid(ct_arg->info), &uv_arg, args[i]); 1626 1627 uc_vm_stack_push(context->vm, uv_arg); 1628 } 1629 1630 ex = uc_vm_call(context->vm, false, cif->nargs); 1631 1632 if (ex == EXCEPTION_NONE) 1633 uv_ret = uc_vm_stack_pop(context->vm); 1634 1635 ct_ret = ctype_get(cts, ctype_cid(context->ct->info)); 1636 1637 // FIXME: ret value ref 1638 uc_cconv_ct_init(cts, ct_ret, ct_ret->size, ret, &uv_ret, 1, NULL); 1639 ucv_put(uv_ret); 1640 } 1641 1642 static uc_closure_context_t * 1643 ct_to_closure(uc_vm_t *vm, CTState *cts, CType *ct, uc_value_t *func) 1644 { 1645 ffi_type *custom_type, **argument_type, *atype, *rtype; 1646 uc_closure_context_t *context; 1647 ffi_abi abi = FFI_DEFAULT_ABI; 1648 size_t context_size; 1649 CTypeID cid_arg; 1650 ffi_status st; 1651 void *codeloc; 1652 1653 if (!ucv_is_callable(func)) { 1654 char *repr = ucv_to_string(vm, func); 1655 1656 uc_vm_raise_exception(vm, EXCEPTION_TYPE, 1657 "attempt to bind non-function value '%s'", 1658 repr ? repr : "null"); 1659 1660 free(repr); 1661 1662 return NULL; 1663 } 1664 1665 /* resolve function type */ 1666 if (ct && ctype_isptr(ct->info)) 1667 ct = ctype_rawchild(cts, ct); 1668 1669 if (!ct || !ctype_isfunc(ct->info)) { 1670 uc_value_t *repr = uc_ctype_repr(vm, ctype_typeid(cts, ct), NULL); 1671 1672 uc_vm_raise_exception(vm, EXCEPTION_TYPE, 1673 "attempt to wrap non-function C type '%s'", 1674 repr ? ucv_string_get(repr) : "NULL"); 1675 1676 ucv_put(repr); 1677 1678 return NULL; 1679 } 1680 1681 /* can't wrap variadic functions */ 1682 if (ct->info & CTF_VARARG) { 1683 uc_vm_raise_exception(vm, EXCEPTION_TYPE, 1684 "wrapping variadic C function types is not supported"); 1685 1686 return NULL; 1687 } 1688 1689 /* skip attribute entries */ 1690 for (cid_arg = ct->sib; 1691 cid_arg && ctype_isattrib(ctype_get(cts, cid_arg)->info); 1692 cid_arg = ctype_get(cts, cid_arg)->sib) 1693 ; 1694 1695 /* compute required size & allocate storage for closure context */ 1696 context_size = sizeof(*context) 1697 + ct->size * sizeof(ffi_type *) 1698 + uc_ctype_count_custom_types(cts, ct, cid_arg) * sizeof(ffi_type); 1699 1700 context = ffi_closure_alloc(context_size, &codeloc); 1701 1702 if (!context) { 1703 uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, 1704 "unable to allocate FFI closure context"); 1705 1706 return NULL; 1707 } 1708 1709 context->codeloc = codeloc; 1710 context->func = ucv_get(func); 1711 context->vm = vm; 1712 context->ct = ct; 1713 1714 argument_type = (ffi_type **)context->argtypes; 1715 custom_type = (ffi_type *)&argument_type[ct->size]; 1716 1717 /* select ABI */ 1718 #ifdef X86 1719 switch (ctype_cconv(ct->info)) { 1720 case CTCC_FASTCALL: abi = FFI_FASTCALL; break; 1721 case CTCC_THISCALL: abi = FFI_THISCALL; break; 1722 case CTCC_STDCALL: abi = FFI_STDCALL; break; 1723 case CTCC_CDECL: abi = FFI_MS_CDECL; break; 1724 } 1725 #endif 1726 1727 if (ctype_isvector(ct->info)) { 1728 #if defined(X86) || defined(X86_WIN32) || defined(X86_WIN64) 1729 if (ct->size != 8 && ct->size != 16) { 1730 uc_value_t *repr = uc_ctype_repr(vm, ctype_cid(ct->info), NULL); 1731 1732 uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, 1733 "vector return type '%s' is not supported", 1734 ucv_string_get(repr)); 1735 1736 ucv_put(repr); 1737 1738 return NULL; 1739 } 1740 #endif 1741 } 1742 1743 rtype = uc_ctype_to_ffi_type(cts, ctype_cid(ct->info), custom_type); 1744 1745 if (!rtype) { 1746 uc_value_t *repr = uc_ctype_repr(vm, ctype_cid(ct->info), NULL); 1747 1748 uc_vm_raise_exception(vm, EXCEPTION_TYPE, 1749 "don't know how to handle return type '%s'", 1750 ucv_string_get(repr)); 1751 1752 ucv_put(repr); 1753 1754 goto out; 1755 } 1756 1757 if (rtype->type == FFI_TYPE_STRUCT) 1758 custom_type++; 1759 1760 for (size_t i = 0; i < ct->size; i++) { 1761 assert(cid_arg); 1762 1763 CType *ct_arg = ctype_get(cts, cid_arg); 1764 1765 assert(ctype_isfield(ct_arg->info)); 1766 1767 cid_arg = ct_arg->sib; 1768 atype = uc_ctype_to_ffi_type(cts, ctype_cid(ct_arg->info), custom_type); 1769 1770 if (!atype) { 1771 uc_value_t *repr = uc_ctype_repr(vm, ctype_cid(ct->info), NULL); 1772 1773 uc_vm_raise_exception(vm, EXCEPTION_TYPE, 1774 "don't know how to handle argument type '%s'", 1775 ucv_string_get(repr)); 1776 1777 ucv_put(repr); 1778 1779 goto out; 1780 } 1781 1782 if (atype->type == FFI_TYPE_STRUCT) 1783 custom_type++; 1784 1785 *(argument_type++) = atype; 1786 } 1787 1788 st = ffi_prep_cif(&context->cif, abi, ct->size, rtype, context->argtypes); 1789 1790 if (st == FFI_OK) { 1791 st = ffi_prep_closure_loc(&context->closure, &context->cif, 1792 uc_ctype_closure_cb, context, 1793 context->codeloc); 1794 } 1795 1796 switch (st) { 1797 case FFI_BAD_TYPEDEF: 1798 uc_vm_raise_exception(vm, EXCEPTION_TYPE, "invalid FFI type"); 1799 goto out; 1800 1801 case FFI_BAD_ABI: 1802 uc_vm_raise_exception(vm, EXCEPTION_TYPE, "invalid FFI ABI"); 1803 goto out; 1804 1805 #ifdef HAVE_FFI_BAD_ARGTYPE 1806 case FFI_BAD_ARGTYPE: 1807 uc_vm_raise_exception(vm, EXCEPTION_TYPE, "invalid variadic argument type"); 1808 goto out; 1809 #endif 1810 1811 case FFI_OK: 1812 return context; 1813 } 1814 1815 out: 1816 ffi_closure_free(context); 1817 1818 return NULL; 1819 } 1820 1821 static uc_value_t * 1822 uc_ctype_call(uc_vm_t *vm, size_t nargs) 1823 { 1824 GCcdata *cd = uc_fn_thisval("ffi.ctype"); 1825 CTState *cts = ctype_cts(vm); 1826 CType *ct = cd ? ctype_get(cts, cd->ctypeid) : NULL; 1827 CTSize sz = CTSIZE_PTR; 1828 1829 if (ct && ctype_isptr(ct->info)) { 1830 sz = ct->size; 1831 ct = ctype_rawchild(cts, ct); 1832 } 1833 1834 if (!ct || !ctype_isfunc(ct->info)) { 1835 uc_value_t *repr = cd ? uc_ctype_repr(vm, cd->ctypeid, NULL) : NULL; 1836 1837 uc_vm_raise_exception(vm, EXCEPTION_TYPE, 1838 "attempt to call non-function value type '%s'", 1839 repr ? ucv_string_get(repr) : "NULL"); 1840 1841 ucv_put(repr); 1842 1843 return NULL; 1844 } 1845 1846 ffi_cif cif; 1847 ffi_abi abi = FFI_DEFAULT_ABI; 1848 ffi_type *rtype = &ffi_type_void; 1849 1850 /* select ABI */ 1851 #ifdef X86 1852 switch (ctype_cconv(ct->info)) { 1853 case CTCC_FASTCALL: abi = FFI_FASTCALL; break; 1854 case CTCC_THISCALL: abi = FFI_THISCALL; break; 1855 case CTCC_STDCALL: abi = FFI_STDCALL; break; 1856 case CTCC_CDECL: abi = FFI_MS_CDECL; break; 1857 } 1858 #endif 1859 1860 CType *ct_ret = ct; //ctype_child(cts, ct); 1861 1862 if (ctype_isvector(ct_ret->info)) { 1863 #if defined(X86) || defined(X86_WIN32) || defined(X86_WIN64) 1864 if (ct_ret->size != 8 && ct_ret->size != 16) { 1865 uc_value_t *repr = uc_ctype_repr(vm, ctype_cid(ct_ret->info), NULL); 1866 1867 uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, 1868 "vector return type '%s' is not supported", 1869 ucv_string_get(repr)); 1870 1871 ucv_put(repr); 1872 1873 return NULL; 1874 } 1875 #endif 1876 } 1877 1878 rtype = uc_ctype_to_ffi_type(cts, ctype_cid(ct_ret->info), NULL); 1879 1880 if (!rtype) { 1881 uc_value_t *repr = uc_ctype_repr(vm, ctype_cid(ct_ret->info), NULL); 1882 1883 uc_vm_raise_exception(vm, EXCEPTION_TYPE, 1884 "don't know how to handle return type '%s'", 1885 ucv_string_get(repr)); 1886 1887 ucv_put(repr); 1888 1889 return NULL; 1890 } 1891 1892 /* skip attribute entries */ 1893 CTypeID fid = ct->sib; 1894 1895 while (fid) { 1896 CType *ctf = ctype_get(cts, fid); 1897 1898 if (!ctype_isattrib(ctf->info)) 1899 break; 1900 1901 fid = ctf->sib; 1902 } 1903 1904 struct { 1905 size_t count; 1906 ffi_type **entries; 1907 } argtypes = { 0 }; 1908 1909 struct { 1910 size_t count; 1911 void **entries; 1912 } argvalues = { 0 }; 1913 1914 struct { 1915 size_t count; 1916 void **entries; 1917 } argmem = { 0 }; 1918 1919 uc_value_t *rv = NULL; 1920 size_t nfixedargs = 0; 1921 1922 /* Count fixed arguments from declaration (before ...) */ 1923 CTypeID temp_fid = ct->sib; 1924 while (temp_fid) { 1925 CType *ctf = ctype_get(cts, temp_fid); 1926 if (!ctype_isattrib(ctf->info)) 1927 nfixedargs++; 1928 temp_fid = ctf->sib; 1929 } 1930 1931 for (size_t i = 0; i < nargs; i++) { 1932 CTypeID did; 1933 bool is_vararg = false; 1934 1935 if (fid) { 1936 CType *ctf = ctype_get(cts, fid); 1937 1938 assert(ctype_isfield(ctf->info)); 1939 1940 fid = ctf->sib; 1941 did = ctype_cid(ctf->info); 1942 } 1943 else if (ct->info & CTF_VARARG) { 1944 is_vararg = true; 1945 /* For variadic args, infer type from ucode value */ 1946 uc_value_t **argp = &vm->stack.entries[vm->stack.count - nargs + i]; 1947 GCcdata *arg_cd = ucv_resource_data(*argp, "ffi.ctype"); 1948 1949 if (arg_cd && arg_cd->ctypeid != CTID_CTYPEID) { 1950 /* cdata argument: use its type directly */ 1951 did = arg_cd->ctypeid; 1952 } 1953 else if (ucv_type(*argp) == UC_STRING) { 1954 /* string -> char* */ 1955 did = CTID_P_CCHAR; 1956 } 1957 else if (ucv_type(*argp) == UC_INTEGER) { 1958 /* integer -> int (promoted from smaller types) */ 1959 did = CTID_INT32; 1960 } 1961 else if (ucv_type(*argp) == UC_DOUBLE) { 1962 /* double stays double (float would be promoted) */ 1963 did = CTID_DOUBLE; 1964 } 1965 else if (ucv_is_callable(*argp)) { 1966 /* callback -> function pointer (void*) */ 1967 did = CTID_P_VOID; 1968 } 1969 else { 1970 uc_vm_raise_exception(vm, EXCEPTION_TYPE, 1971 "unsupported variadic argument type %s", 1972 ucv_typename(*argp)); 1973 goto out; 1974 } 1975 } 1976 else { 1977 uc_vm_raise_exception(vm, EXCEPTION_TYPE, 1978 "too many arguments for called function"); 1979 1980 goto out; 1981 } 1982 1983 CType *d = ctype_raw(cts, did); 1984 CTSize sz = d->size; 1985 ffi_type *atype = uc_ctype_to_ffi_type(cts, did, NULL); 1986 1987 /* Apply default argument promotions for variadic arguments */ 1988 if (is_vararg && atype) { 1989 /* Float promotes to double */ 1990 if (atype == &ffi_type_float) { 1991 atype = &ffi_type_double; 1992 sz = sizeof(double); 1993 did = CTID_DOUBLE; 1994 d = ctype_get(cts, did); 1995 } 1996 /* Small integers promote to int */ 1997 else if (atype == &ffi_type_schar || atype == &ffi_type_uchar || 1998 atype == &ffi_type_sint16 || atype == &ffi_type_uint16) { 1999 #if UC_SIZEOF_INT == 4 2000 atype = (atype == &ffi_type_uchar || atype == &ffi_type_uint16) 2001 ? &ffi_type_uint : &ffi_type_sint; 2002 sz = sizeof(int); 2003 did = (atype == &ffi_type_uint) ? CTID_UINT32 : CTID_INT32; 2004 #else 2005 atype = (atype == &ffi_type_uchar || atype == &ffi_type_uint16) 2006 ? &ffi_type_uint64 : &ffi_type_sint64; 2007 sz = sizeof(int64_t); 2008 did = (atype == &ffi_type_uint64) ? CTID_UINT64 : CTID_INT64; 2009 #endif 2010 d = ctype_get(cts, did); 2011 } 2012 } 2013 2014 if (!atype) { 2015 uc_value_t *repr = uc_ctype_repr(vm, ctype_cid(ct_ret->info), NULL); 2016 2017 uc_vm_raise_exception(vm, EXCEPTION_TYPE, 2018 "don't know how to handle argument type '%s'", 2019 ucv_string_get(repr)); 2020 2021 ucv_put(repr); 2022 2023 goto out; 2024 } 2025 2026 uc_value_t **argp = &vm->stack.entries[vm->stack.count - nargs + i]; 2027 GCcdata *arg_cd = ucv_resource_data(*argp, "ffi.ctype"); 2028 2029 if (arg_cd && arg_cd->ctypeid != CTID_CTYPEID) { 2030 /* Check if this is an array cdata - if so, wrap pointer in pointer-sized slot */ 2031 CType *arg_ct = ctype_get(cts, arg_cd->ctypeid); 2032 if (ctype_isarray(arg_ct->info)) { 2033 void *memp, *valp; 2034 memp = valp = xalloc(sizeof(void *)); 2035 *(void **)valp = cdataptr(arg_cd); 2036 uc_vector_push(&argmem, memp); 2037 uc_vector_push(&argvalues, valp); 2038 } 2039 else { 2040 uc_vector_push(&argvalues, cdataptr(arg_cd)); 2041 } 2042 } 2043 else if (ctype_isptr(d->info) && ucv_type(*argp) == UC_OBJECT) { 2044 CType *child = ctype_rawchild(cts, d); 2045 if (ctype_isstruct(child->info)) { 2046 void *struct_mem = xalloc(child->size); 2047 uc_cconv_ct_init(cts, child, child->size, struct_mem, argp, 1, NULL); 2048 void *ptr_mem = xalloc(sizeof(void*)); 2049 *(void**)ptr_mem = struct_mem; 2050 uc_vector_push(&argmem, struct_mem); 2051 uc_vector_push(&argmem, ptr_mem); 2052 uc_vector_push(&argvalues, ptr_mem); 2053 } 2054 else { 2055 void *memp, *valp; 2056 memp = valp = xalloc(sz); 2057 uc_cconv_ct_tv(cts, d, valp, *argp, CCF_ARG(i), NULL); 2058 uc_vector_push(&argmem, memp); 2059 uc_vector_push(&argvalues, valp); 2060 } 2061 } 2062 else { 2063 void *memp, *valp; 2064 2065 if (ucv_type(*argp) == UC_STRING) { 2066 memp = valp = xalloc(sz); 2067 *(char **)valp = ucv_string_get(*argp); 2068 } 2069 else if (ucv_is_callable(*argp)) { 2070 uc_closure_context_t *cc = ct_to_closure(vm, cts, d, *argp); 2071 2072 memp = (void *)((uintptr_t)cc | 1u); 2073 valp = &cc->codeloc; 2074 } 2075 else { 2076 memp = valp = xalloc(sz); 2077 uc_cconv_ct_tv(cts, d, valp, *argp, CCF_ARG(i), NULL); 2078 } 2079 2080 uc_vector_push(&argmem, memp); 2081 uc_vector_push(&argvalues, valp); 2082 } 2083 2084 uc_vector_push(&argtypes, atype); 2085 } 2086 2087 if (fid) { 2088 uc_vm_raise_exception(vm, EXCEPTION_TYPE, 2089 "too few arguments for called function"); 2090 2091 goto out; 2092 } 2093 2094 ffi_status st; 2095 2096 if (ct->info & CTF_VARARG) 2097 st = ffi_prep_cif_var(&cif, abi, nfixedargs, argtypes.count, rtype, argtypes.entries); 2098 else 2099 st = ffi_prep_cif(&cif, abi, argtypes.count, rtype, argtypes.entries); 2100 2101 switch (st) { 2102 case FFI_BAD_TYPEDEF: 2103 uc_vm_raise_exception(vm, EXCEPTION_TYPE, "invalid FFI type"); 2104 goto out; 2105 2106 case FFI_BAD_ABI: 2107 uc_vm_raise_exception(vm, EXCEPTION_TYPE, "invalid FFI ABI"); 2108 goto out; 2109 2110 #ifdef HAVE_FFI_BAD_ARGTYPE 2111 case FFI_BAD_ARGTYPE: 2112 uc_vm_raise_exception(vm, EXCEPTION_TYPE, "invalid variadic argument type"); 2113 goto out; 2114 #endif 2115 2116 case FFI_OK: 2117 if (rtype != &ffi_type_void) { 2118 CTSize rsz = ctype_get(cts, ctype_cid(ct_ret->info))->size; 2119 2120 if (rsz < sizeof(ffi_arg) || rsz == CTSIZE_INVALID) 2121 rsz = sizeof(ffi_arg); 2122 2123 rv = uc_cdata_new(vm, ctype_cid(ct_ret->info), rsz); 2124 } 2125 2126 ffi_call(&cif, 2127 (void (*)(void))cdata_getptr(cdataptr(cd), sz), 2128 uc_cdata_dataptr(rv), 2129 argvalues.entries); 2130 } 2131 2132 out: 2133 #ifdef __clang_analyzer__ 2134 /* Clang static analyzer does not understand that rtype is either a static 2135 * ffi_type or a heap-allocated value that is freed here. Pretend to free 2136 * it unconditionally to suppress the false positive memory leak warning. */ 2137 free(rtype); 2138 #else 2139 if (rtype->type == FFI_TYPE_STRUCT) 2140 free(rtype); 2141 #endif 2142 2143 while (argtypes.count) 2144 if (argtypes.entries[--argtypes.count]->type == FFI_TYPE_STRUCT) 2145 free(argtypes.entries[argtypes.count]); 2146 2147 while (argmem.count) { 2148 void *ptr = argmem.entries[--argmem.count]; 2149 2150 if ((uintptr_t)ptr & 1u) { 2151 uc_closure_context_t *cc = 2152 (uc_closure_context_t *)((uintptr_t)ptr & ~(uintptr_t)1u); 2153 2154 ucv_put(cc->func); 2155 ffi_closure_free(cc); 2156 } 2157 else { 2158 free(ptr); 2159 } 2160 } 2161 2162 uc_vector_clear(&argvalues); 2163 uc_vector_clear(&argtypes); 2164 uc_vector_clear(&argmem); 2165 2166 return rv; 2167 } 2168 2169 static uc_value_t * 2170 uc_ctype_free(uc_vm_t *vm, size_t nargs) 2171 { 2172 GCcdata **cd = uc_fn_this("ffi.ctype"); 2173 2174 if (cd) { 2175 if (UC_UNLIKELY(*cd && cdataisv(*cd))) 2176 free(memcdatav(*cd)); 2177 else 2178 free(*cd); 2179 2180 *cd = NULL; 2181 } 2182 2183 return NULL; 2184 } 2185 2186 static uc_value_t * 2187 ct_to_uv(uc_vm_t *vm, CTState *cts, CTypeID cid, void *cdata, size_t size, 2188 uc_value_t *refs); 2189 2190 static uc_value_t * 2191 ct_to_uv(uc_vm_t *vm, CTState *cts, CTypeID cid, void *cdata, size_t size, 2192 uc_value_t *refs) 2193 { 2194 CType *ct = ctype_get(cts, cid); 2195 CTInfo info = ct->info; 2196 uc_value_t *s; 2197 2198 switch (ctype_type(info)) { 2199 case CT_PTR: 2200 switch (ctype_cid(info)) { 2201 case CTID_INT8: 2202 case CTID_UINT8: 2203 if ((info ^ CTF_UCHAR) & CTF_UNSIGNED) 2204 goto generic_ptr; 2205 2206 /* fall through */ 2207 2208 case CTID_CCHAR: 2209 /* special optimization case: when retrieving the ucode equivalent value of 2210 a `const char *` pointer, attempt to return a reference to the original 2211 uv string (if any) nstead of constructing a new heap string */ 2212 if (ctype_cid(info) == CTID_CCHAR) { 2213 for (size_t i = 0; i < ucv_array_length(refs); i++) { 2214 uc_string_t *us = (uc_string_t *)ucv_array_get(refs, i); 2215 2216 if (us->str == *(char **)cdata) { 2217 return ucv_get(&us->header); 2218 } 2219 } 2220 } 2221 2222 return *(char **)cdata ? ucv_string_new(*(char **)cdata) : NULL; 2223 2224 default: 2225 generic_ptr: 2226 /* Return pointer value as integer for all pointer types */ 2227 return ucv_uint64_new((uintptr_t)*(void **)cdata); 2228 } 2229 2230 break; 2231 2232 case CT_NUM: 2233 if (info & CTF_BOOL) { 2234 return ucv_boolean_new(*(bool *)cdata); 2235 } 2236 else if ((info & CTF_FP)) { 2237 if (size == sizeof(double)) 2238 return ucv_double_new(*(double *)cdata); 2239 else if (size == sizeof(float)) 2240 return ucv_double_new(*(float *)cdata); 2241 } 2242 else if (size == 1) { 2243 if (info & CTF_UNSIGNED) 2244 return ucv_uint64_new(*(uint8_t *)cdata); 2245 else 2246 return ucv_int64_new(*(int8_t *)cdata); 2247 } 2248 else if (size == 2) { 2249 if (info & CTF_UNSIGNED) 2250 return ucv_uint64_new(*(uint16_t *)cdata); 2251 else 2252 return ucv_int64_new(*(int16_t *)cdata); 2253 } 2254 else if (size == 4) { 2255 if (info & CTF_UNSIGNED) 2256 return ucv_uint64_new(*(uint32_t *)cdata); 2257 else 2258 return ucv_int64_new(*(int32_t *)cdata); 2259 } 2260 else if (size == 8) { 2261 if (info & CTF_UNSIGNED) 2262 return ucv_uint64_new(*(uint64_t *)cdata); 2263 else 2264 return ucv_int64_new(*(int64_t *)cdata); 2265 } 2266 2267 break; 2268 2269 case CT_ENUM: 2270 /* attempt to return named enum choice name */ 2271 for (CTypeID choice_id = ct->sib; choice_id; ) { 2272 CType *choice_type = ctype_get(cts, choice_id); 2273 2274 choice_id = choice_type->sib; 2275 2276 if (!ctype_isconstval(choice_type->info) || !choice_type->uv_name) 2277 continue; 2278 2279 if (choice_type->size != *(CTSize *)cdata) 2280 continue; 2281 2282 return ucv_get(choice_type->uv_name); 2283 } 2284 2285 /* no matching constant name found, return numeric value */ 2286 if (ctype_cid(info) == CTID_UINT32) 2287 return ucv_uint64_new(*(uint32_t *)cdata); 2288 else 2289 return ucv_int64_new(*(int32_t *)cdata); 2290 2291 break; 2292 2293 case CT_ARRAY: 2294 if (info & CTF_COMPLEX) { 2295 if (size == 2 * sizeof(float)) { 2296 uc_value_t *a = ucv_array_new_length(vm, 2); 2297 float *f = (float *)cdata; 2298 2299 ucv_array_set(a, 0, ucv_double_new((double)f[0])); 2300 ucv_array_set(a, 1, ucv_double_new((double)f[1])); 2301 2302 return a; 2303 } 2304 else if (size == 2 * sizeof(double)) { 2305 uc_value_t *a = ucv_array_new_length(vm, 2); 2306 double *d = (double *)cdata; 2307 2308 ucv_array_set(a, 0, ucv_double_new(d[0])); 2309 ucv_array_set(a, 1, ucv_double_new(d[1])); 2310 2311 return a; 2312 } 2313 } 2314 else { 2315 CType *elem_type = ctype_rawchild(cts, ct); 2316 CTSize elem_size = elem_type->size; 2317 uc_value_t *a = ucv_array_new_length(vm, size / elem_size); 2318 2319 for (size_t off = 0; off < size; off += elem_size) 2320 ucv_array_push(a, 2321 ct_to_uv(vm, cts, ctype_typeid(cts, elem_type), 2322 (char *)cdata + off, elem_size, refs)); 2323 2324 return a; 2325 } 2326 2327 break; 2328 2329 case CT_STRUCT: 2330 s = ucv_object_new(vm); 2331 2332 for (CTypeID field_id = ct->sib; field_id; ) { 2333 CType *field_type = ctype_get(cts, field_id); 2334 2335 field_id = field_type->sib; 2336 2337 if (ctype_isfield(field_type->info) || ctype_isbitfield(field_type->info)) { 2338 if (!field_type->uv_name) 2339 continue; 2340 2341 ucv_object_add(s, ucv_string_get(field_type->uv_name), 2342 ct_to_uv(vm, cts, ctype_cid(field_type->info), 2343 (char *)cdata + field_type->size, 2344 ctype_rawchild(cts, field_type)->size, refs)); 2345 } 2346 } 2347 2348 return s; 2349 } 2350 2351 return NULL; 2352 } 2353 2354 2355 /** 2356 * Read a value from a C data object. 2357 * 2358 * The `get()` method reads values from cdata objects and returns them 2359 * converted to ucode types. It supports: 2360 * 2361 * - **Scalar values**: `int.get()` returns the scalar value directly 2362 * - **Array indexing**: `arr.get(n)` returns element at position n 2363 * - **Struct fields**: `struct.get('field')` returns field value 2364 * - **Path notation**: `struct.get('nested.field[0]')` for deep access 2365 * 2366 * For arrays and struct fields, `get()` behaves identically to `index()`. 2367 * Use `get()` as the primary method for reading values due to its 2368 * descriptive name. 2369 * 2370 * @function module:ffi.CData#get 2371 * 2372 * @param {string|number} [key] 2373 * The field name, array index, or path to read. Omit for scalar types 2374 * to get the value directly. 2375 * 2376 * @returns {*} 2377 * The value at the specified location, converted to a ucode type. 2378 * For structs without a key, returns an object with all field values. 2379 * 2380 * @throws {Error} 2381 * Throws an exception if the key is invalid for the type. 2382 * 2383 * @example 2384 * // Read scalar value (no key needed) 2385 * let x = ffi.ctype('int', 42); 2386 * x.get(); // => 42 (number) 2387 * 2388 * @example 2389 * // Read struct field 2390 * ffi.cdef('struct point { int x; int y; };'); 2391 * let p = ffi.ctype('struct point', 10, 20); 2392 * p.get('x'); // => 10 (number) 2393 * p.get('y'); // => 20 (number) 2394 * 2395 * @example 2396 * // Read entire struct as object 2397 * p.get(); // => {x: 10, y: 20} (ucode object) 2398 * 2399 * @example 2400 * // Read array element 2401 * let arr = ffi.ctype('int[5]', [1, 2, 3, 4, 5]); 2402 * arr.get(0); // => 1 (number) 2403 * arr.get(4); // => 5 (number) 2404 * 2405 * @example 2406 * // Path notation for nested access 2407 * ffi.cdef('struct rect { struct point min; struct point max; };'); 2408 * let r = ffi.ctype('struct rect', { 2409 * min: {x: 0, y: 0}, 2410 * max: {x: 100, y: 100} 2411 * }); 2412 * r.get('min.x'); // => 0 2413 * r.get('max.y'); // => 100 2414 * 2415 * @see {@link module:ffi.CData#index|index()} - Equivalent for array/field access 2416 * @see {@link module:ffi.CData#set|set()} - Write values to cdata 2417 */ 2418 static uc_value_t * 2419 uc_ctype_get(uc_vm_t *vm, size_t nargs) 2420 { 2421 GCcdata *cd = uc_fn_thisval("ffi.ctype"); 2422 CTState *cts = ctype_cts(vm); 2423 CTInfo qual = 0; 2424 uint8_t *p; 2425 CType *ct; 2426 CTSize sz; 2427 2428 if (!cd) 2429 return NULL; 2430 2431 ct = ctype_get(cts, cd->ctypeid); 2432 sz = cdataisv(cd) ? cdatavlen(cd) : ct->size; 2433 p = cdataptr(cd); 2434 2435 /* Handle reference types: dereference to get actual data pointer */ 2436 if (ctype_isref(ct->info)) { 2437 p = *(uint8_t **)p; 2438 ct = ctype_get(cts, ctype_cid(ct->info)); 2439 if (!ct) 2440 return NULL; 2441 sz = ct->size; 2442 if (sz == CTSIZE_INVALID) 2443 return NULL; 2444 } 2445 2446 if (nargs == 0 && ctype_isstruct(ct->info)) { 2447 return ct_to_uv(vm, cts, ctype_typeid(cts, ct), p, sz, 2448 cd->refs); 2449 } 2450 2451 if (nargs) { 2452 uc_value_t *key = uc_fn_arg(0); 2453 2454 /* Check for path syntax (contains '.' or '[') */ 2455 bool is_path = false; 2456 if (ucv_type(key) == UC_STRING) { 2457 const char *s = ucv_string_get(key); 2458 if (strpbrk(s, ".[")) 2459 is_path = true; 2460 } 2461 2462 if (is_path) { 2463 /* Use path parsing for nested access */ 2464 path_tokens tokens = {0}; 2465 bool error = false; 2466 2467 if (!path_tokenize(vm, key, &tokens)) 2468 return NULL; 2469 2470 ct = path_navigate(cts, cd, &tokens, &p, &ct, &error); 2471 path_tokens_free(&tokens); 2472 2473 if (error || !ct) { 2474 uc_value_t *repr = uc_ctype_repr(vm, cd->ctypeid, NULL); 2475 char *keystr = ucv_to_string(vm, key); 2476 2477 uc_vm_raise_exception(vm, EXCEPTION_REFERENCE, 2478 "Invalid path '%s' for type '%s'", 2479 keystr, ucv_string_get(repr)); 2480 2481 ucv_put(repr); 2482 free(keystr); 2483 2484 return NULL; 2485 } 2486 2487 /* path_navigate already returns the final type */ 2488 sz = ct->size; 2489 } 2490 else { 2491 /* Use original uc_cdata_index for single-level access */ 2492 ct = uc_cdata_index(cts, cd, key, &p, &qual); 2493 2494 if (!ct) 2495 return NULL; 2496 2497 if (qual & 1) { 2498 uc_value_t *repr = uc_ctype_repr(vm, cd->ctypeid, NULL); 2499 char *keystr = ucv_to_string(vm, key); 2500 2501 uc_vm_raise_exception(vm, EXCEPTION_REFERENCE, 2502 "Invalid index '%s' for type '%s' given", 2503 keystr, ucv_string_get(repr)); 2504 2505 ucv_put(repr); 2506 free(keystr); 2507 2508 return NULL; 2509 } 2510 2511 ct = ctype_child(cts, ct); 2512 sz = ct->size; 2513 } 2514 } 2515 2516 return ct_to_uv(vm, cts, ctype_typeid(cts, ct), p, sz, 2517 cd->refs); 2518 } 2519 2520 /** 2521 * Write a value to a C data object. 2522 * 2523 * The `set()` method writes a value to a cdata. For structs, it can write 2524 * individual fields by name. For arrays, it can write elements by index. 2525 * 2526 * @function module:ffi.CData#set 2527 * 2528 * @param {string|number} key 2529 * The field name or array index to write. 2530 * 2531 * @param {*} value 2532 * The value to write. Will be converted to the appropriate C type. 2533 * 2534 * @returns {undefined} 2535 * Returns `undefined`. 2536 * 2537 * @throws {Error} 2538 * Throws an exception if the key is invalid or the value cannot be converted. 2539 * 2540 * @example 2541 * // Write scalar value 2542 * let x = ffi.ctype('int'); 2543 * x.set(42); 2544 * 2545 * @example 2546 * // Write struct field 2547 * ffi.cdef('struct point { int x; int y; };'); 2548 * let p = ffi.ctype('struct point'); 2549 * p.set('x', 10); 2550 * p.set('y', 20); 2551 * 2552 * @example 2553 * // Write array element 2554 * let arr = ffi.ctype('int[5]'); 2555 * arr.set(0, 100); 2556 * arr.set(4, 200); 2557 */ 2558 static uc_value_t * 2559 uc_ctype_set(uc_vm_t *vm, size_t nargs) 2560 { 2561 GCcdata *cd = uc_fn_thisval("ffi.ctype"); 2562 CTState *cts = ctype_cts(vm); 2563 CTInfo qual = 0; 2564 uint8_t *p; 2565 CType *ct; 2566 2567 if (!cd) 2568 return NULL; 2569 2570 ct = ctype_get(cts, cd->ctypeid); 2571 p = cdataptr(cd); 2572 2573 /* Handle reference types: dereference to get actual data pointer */ 2574 if (ctype_isref(ct->info)) { 2575 p = *(uint8_t **)p; 2576 ct = ctype_get(cts, ctype_cid(ct->info)); 2577 if (!ct) 2578 return NULL; 2579 } 2580 2581 if (nargs > 1) { 2582 uc_value_t *key = uc_fn_arg(0); 2583 uc_value_t *val = uc_fn_arg(1); 2584 2585 /* Check for path syntax (contains '.' or '[') */ 2586 bool is_path = false; 2587 if (ucv_type(key) == UC_STRING) { 2588 const char *s = ucv_string_get(key); 2589 if (strpbrk(s, ".[")) 2590 is_path = true; 2591 } 2592 2593 if (is_path) { 2594 /* Use path parsing for nested access */ 2595 path_tokens tokens = {0}; 2596 bool error = false; 2597 2598 if (!path_tokenize(vm, key, &tokens)) 2599 return NULL; 2600 2601 ct = path_navigate(cts, cd, &tokens, &p, &ct, &error); 2602 path_tokens_free(&tokens); 2603 2604 if (error || !ct) { 2605 uc_value_t *repr = uc_ctype_repr(vm, cd->ctypeid, NULL); 2606 char *keystr = ucv_to_string(vm, key); 2607 2608 uc_vm_raise_exception(vm, EXCEPTION_REFERENCE, 2609 "Invalid path '%s' for type '%s'", 2610 keystr, ucv_string_get(repr)); 2611 2612 ucv_put(repr); 2613 free(keystr); 2614 2615 return NULL; 2616 } 2617 2618 /* path_navigate already returns the final type, no need to call ctype_child */ 2619 } 2620 else { 2621 /* Use original uc_cdata_index for single-level access */ 2622 ct = uc_cdata_index(cts, cd, key, &p, &qual); 2623 2624 if (!ct) 2625 return NULL; 2626 2627 if (qual & 1) { 2628 uc_value_t *repr = uc_ctype_repr(vm, cd->ctypeid, NULL); 2629 char *keystr = ucv_to_string(vm, key); 2630 2631 uc_vm_raise_exception(vm, EXCEPTION_REFERENCE, 2632 "Invalid index '%s' for type '%s' given", 2633 keystr, ucv_string_get(repr)); 2634 2635 ucv_put(repr); 2636 free(keystr); 2637 2638 return NULL; 2639 } 2640 2641 ct = ctype_child(cts, ct); 2642 } 2643 2644 /* Convert and store the value */ 2645 uc_cconv_ct_tv(cts, ct, p, val, CCF_ARG(0), NULL); 2646 } 2647 else if (nargs == 1) { 2648 /* No key: set the value directly (for reference types or scalar cdata) */ 2649 uc_value_t *val = uc_fn_arg(0); 2650 uc_cconv_ct_tv(cts, ct, p, val, CCF_ARG(0), NULL); 2651 } 2652 2653 return NULL; 2654 } 2655 2656 /** 2657 * Get a pointer to a C data object. 2658 * 2659 * The `ptr()` method returns a pointer cdata pointing to the memory of the 2660 * current cdata. This is useful for passing to C functions that expect 2661 * pointers. 2662 * 2663 * @function module:ffi.CData#ptr 2664 * 2665 * @returns {module:ffi.CData} 2666 * A pointer cdata pointing to this cdata's memory. 2667 * 2668 * @example 2669 * // Get pointer to scalar 2670 * let x = ffi.ctype('int', 42); 2671 * let px = x.ptr(); // int* pointer 2672 * 2673 * @example 2674 * // Pass to C function expecting pointer 2675 * ffi.cdef('void memset(void *, int, size_t)'); 2676 * let buf = ffi.ctype('char[10]'); 2677 * ffi.C.wrap('void memset(void *, int, size_t)')(buf.ptr(), 0, 10); 2678 */ 2679 static uc_value_t * 2680 uc_ctype_ptr(uc_vm_t *vm, size_t nargs) 2681 { 2682 GCcdata *cd = uc_fn_thisval("ffi.ctype"); 2683 2684 if (!cd) 2685 return NULL; 2686 2687 uc_value_t *pres = uc_cdata_new(vm, CTID_P_VOID, CTSIZE_PTR); 2688 *(void **)uc_cdata_dataptr(pres) = cdataptr(cd); 2689 2690 return pres; 2691 } 2692 2693 /** 2694 * Access array elements, struct fields, or perform pointer arithmetic. 2695 * 2696 * The `index()` method returns **raw cdata references** to the accessed 2697 * location, without converting to ucode types. This allows further 2698 * manipulation, pointer arithmetic, or explicit conversion. 2699 * 2700 * Supports: 2701 * 2702 * - **Array indexing**: `arr.index(n)` returns cdata reference to element 2703 * - **Struct fields**: `struct.index('field')` returns cdata reference 2704 * - **Pointer arithmetic**: `ptr.index(n)` returns cdata at *(ptr + n) 2705 * - **Path notation**: `struct.index('nested.field[0]')` for deep access 2706 * 2707 * **Key difference from `get()`**: `index()` returns raw cdata (unconverted), 2708 * while `get()` returns converted ucode values. 2709 * 2710 * @function module:ffi.CData#index 2711 * 2712 * @param {string|number} key 2713 * The array index, field name, or path to access. 2714 * 2715 * @returns {module:ffi.CData} 2716 * A cdata reference to the value at the specified location (unconverted). 2717 * Call `.get()` on the result to convert to a ucode value. 2718 * 2719 * @throws {Error} 2720 * Throws an exception if the key is invalid for the type. 2721 * 2722 * @example 2723 * // Array indexing - returns cdata, not number 2724 * let arr = ffi.ctype('int[5]', [10, 20, 30, 40, 50]); 2725 * arr.index(0); // => cdata (int) 2726 * arr.index(0).get() // => 10 (number) 2727 * 2728 * @example 2729 * // Struct field access - returns cdata reference 2730 * ffi.cdef('struct point { int x; int y; };'); 2731 * let p = ffi.ctype('struct point', 10, 20); 2732 * p.index('x'); // => cdata (int) 2733 * p.index('x').get() // => 10 (number) 2734 * 2735 * @example 2736 * // Pointer arithmetic - returns cdata at offset 2737 * let ptr = ffi.ctype('int *', arr.ptr()); 2738 * ptr.index(0); // => cdata (int) at ptr[0] 2739 * ptr.index(2); // => cdata (int) at ptr[2] 2740 * ptr.index(2).get() // => 30 (number) 2741 * 2742 * @example 2743 * // Chaining - modify through index() 2744 * arr.index(0).set(100); // Set arr[0] = 100 2745 * 2746 * @see {@link module:ffi.CData#get|get()} - Returns converted ucode values 2747 * @see {@link module:ffi.CData#ptr|ptr()} - Get a pointer, not a value 2748 */ 2749 static uc_value_t * 2750 uc_ctype_index(uc_vm_t *vm, size_t nargs) 2751 { 2752 CTState *cts = ctype_cts(vm); 2753 CTInfo qual = 0; 2754 uint8_t *p; 2755 GCcdata *cd = uc_fn_thisval("ffi.ctype"); 2756 uc_value_t *key = uc_fn_arg(0); 2757 2758 if (!cd) 2759 return NULL; 2760 2761 /* Check for path syntax (contains '.' or '[') */ 2762 bool is_path = false; 2763 if (ucv_type(key) == UC_STRING) { 2764 const char *s = ucv_string_get(key); 2765 if (strpbrk(s, ".[")) 2766 is_path = true; 2767 } 2768 2769 CType *ct = ctype_get(cts, cd->ctypeid); 2770 2771 /* Handle reference types: dereference to get actual type */ 2772 if (ctype_isref(ct->info)) { 2773 p = *(uint8_t **)cdataptr(cd); 2774 ct = ctype_get(cts, ctype_cid(ct->info)); 2775 if (!ct) 2776 return NULL; 2777 } 2778 else { 2779 p = cdataptr(cd); 2780 } 2781 2782 if (is_path) { 2783 /* Use path parsing for nested access */ 2784 path_tokens tokens = {0}; 2785 bool error = false; 2786 2787 if (!path_tokenize(vm, key, &tokens)) { 2788 path_tokens_free(&tokens); 2789 return NULL; 2790 } 2791 2792 ct = path_navigate(cts, cd, &tokens, &p, &ct, &error); 2793 path_tokens_free(&tokens); 2794 2795 if (error || !ct) 2796 return NULL; 2797 } 2798 else { 2799 /* Handle integer key for pointer/array indexing */ 2800 uc_type_t ut = ucv_type(key); 2801 bool is_integer_key = (ut == UC_INTEGER || ut == UC_DOUBLE); 2802 2803 if (is_integer_key && (ctype_ispointer(ct->info) || ctype_isarray(ct->info))) { 2804 /* Pointer/array indexing: ptr[index] or arr[index] */ 2805 ptrdiff_t idx; 2806 if (ut == UC_INTEGER) 2807 idx = (ptrdiff_t)ucv_int64_get(key); 2808 else 2809 idx = (ptrdiff_t)ucv_double_get(key); 2810 2811 CTSize sz = uc_ctype_size(cts, ctype_cid(ct->info)); 2812 if (sz == CTSIZE_INVALID) { 2813 uc_vm_raise_exception(vm, EXCEPTION_TYPE, 2814 "size of C type is unknown or too large"); 2815 return NULL; 2816 } 2817 2818 /* Get the pointer value (for ptr types) or use data directly (for arrays) */ 2819 if (ctype_isptr(ct->info)) 2820 p = (uint8_t *)cdata_getptr(p, ct->size); 2821 2822 /* Get element type */ 2823 CType *elt = ctype_rawchild(cts, ct); 2824 2825 /* Calculate offset */ 2826 p = p + idx * (int32_t)sz; 2827 2828 /* Return raw cdata reference (unconverted) */ 2829 CTypeID elt_id = ctype_typeid(cts, elt); 2830 CType *elt_ct = ctype_get(cts, elt_id); 2831 2832 /* For pointer element types, we need to store the pointer VALUE, not the address */ 2833 if (ctype_isptr(elt_ct->info)) { 2834 /* Read the pointer value from p and create a cdata containing it */ 2835 void *ptrval = cdata_getptr(p, elt_ct->size); 2836 uc_value_t *res = uc_cdata_new(vm, elt_id, elt_ct->size); 2837 *(void **)uc_cdata_dataptr(res) = ptrval; 2838 return res; 2839 } 2840 2841 return uc_cdata_newref(vm, p, elt_id); 2842 } 2843 else { 2844 /* Use original uc_cdata_index for single-level access */ 2845 ct = uc_cdata_index(cts, cd, key, &p, &qual); 2846 2847 if (!ct) 2848 return NULL; 2849 2850 if (qual & 1) 2851 return NULL; 2852 2853 /* Get the raw child type to avoid qualifier issues, 2854 but preserve pointer types */ 2855 if (!ctype_ispointer(ct->info)) 2856 ct = ctype_rawchild(cts, ct); 2857 2858 /* For pointer fields, create a cdata containing the pointer value */ 2859 if (ctype_ispointer(ct->info)) { 2860 void *ptrval = cdata_getptr(p, ct->size); 2861 uc_value_t *res = uc_cdata_new(vm, ctype_typeid(cts, ct), CTSIZE_PTR); 2862 *(void **)uc_cdata_dataptr(res) = ptrval; 2863 return res; 2864 } 2865 } 2866 } 2867 2868 /* Return raw cdata reference (unconverted) */ 2869 return uc_cdata_newref(vm, p, ctype_typeid(cts, ct)); 2870 } 2871 2872 /** 2873 * Read the value pointed to by a pointer cdata. 2874 * 2875 * The `deref()` method dereferences a pointer cdata and reads the value 2876 * at the pointed-to address. The target type can be specified explicitly 2877 * or inferred from the pointer type. 2878 * 2879 * @function module:ffi.CData#deref 2880 * 2881 * @param {string} [type] 2882 * The C type to read. If omitted, the pointer's element type is used. 2883 * 2884 * @returns {*} 2885 * The value at the pointer address, converted to a ucode type. 2886 * 2887 * @throws {Error} 2888 * Throws an exception if the pointer is NULL or the type is invalid. 2889 * 2890 * @example 2891 * // Dereference int pointer 2892 * let x = ffi.ctype('int', 42); 2893 * let px = x.ptr(); 2894 * print(px.deref('int')); // => 42 2895 * 2896 * @example 2897 * // Read first byte of char* 2898 * ffi.cdef('char *strdup(const char *)'); 2899 * let ptr = ffi.C.wrap('char *strdup(const char *)')("hello"); 2900 * print(ptr.deref('char')); // => 104 (ASCII for 'h') 2901 * ptr.deref(); // Also works, uses pointer's element type 2902 */ 2903 static uc_value_t * 2904 uc_ctype_deref(uc_vm_t *vm, size_t nargs) 2905 { 2906 GCcdata *cd = uc_fn_thisval("ffi.ctype"); 2907 2908 if (!cd) 2909 return NULL; 2910 2911 CTState *cts = ctype_cts(vm); 2912 CType *ct = ctype_get(cts, cd->ctypeid); 2913 2914 CTypeID ctid; 2915 uint8_t *p = NULL; 2916 2917 /* Skip extern and attribute wrappers to get the actual type */ 2918 while (ctype_isextern(ct->info) || ctype_isattrib(ct->info)) 2919 ct = ctype_child(cts, ct); 2920 2921 if (ctype_isptr(ct->info)) { 2922 ctid = nargs 2923 ? uv_to_ct(vm, CPARSE_MODE_ABSTRACT | CPARSE_MODE_NOIMPLICIT, 2924 uc_fn_arg(0), NULL) 2925 : ctype_cid(ct->info); 2926 2927 if (!ctid) 2928 return NULL; 2929 2930 p = *(uint8_t **)cdataptr(cd); 2931 2932 if (!p) { 2933 uc_vm_raise_exception(vm, EXCEPTION_REFERENCE, 2934 "Attempt to dereference a NULL pointer"); 2935 2936 return NULL; 2937 } 2938 } 2939 else if (ctype_isref(ct->info)) { 2940 /* Reference: dereference to get actual data pointer */ 2941 ctid = nargs 2942 ? uv_to_ct(vm, CPARSE_MODE_ABSTRACT | CPARSE_MODE_NOIMPLICIT, 2943 uc_fn_arg(0), NULL) 2944 : ctype_cid(ct->info); 2945 2946 if (!ctid) 2947 return NULL; 2948 2949 p = *(uint8_t **)cdataptr(cd); 2950 2951 if (!p) { 2952 uc_vm_raise_exception(vm, EXCEPTION_REFERENCE, 2953 "Attempt to dereference a NULL reference"); 2954 2955 return NULL; 2956 } 2957 } 2958 else if (ctype_isrefarray(ct->info)) { 2959 /* Array: dereference returns first element */ 2960 ctid = nargs 2961 ? uv_to_ct(vm, CPARSE_MODE_ABSTRACT | CPARSE_MODE_NOIMPLICIT, 2962 uc_fn_arg(0), NULL) 2963 : ctype_cid(ct->info); 2964 2965 if (!ctid) 2966 return NULL; 2967 2968 p = (uint8_t *)cdataptr(cd); 2969 2970 if (!p) { 2971 uc_vm_raise_exception(vm, EXCEPTION_REFERENCE, 2972 "Attempt to dereference empty array"); 2973 2974 return NULL; 2975 } 2976 } 2977 else { 2978 uc_value_t *repr = uc_ctype_repr(vm, cd->ctypeid, NULL); 2979 2980 uc_vm_raise_exception(vm, EXCEPTION_TYPE, 2981 "Attempt to dereference non-pointer type %s", 2982 ucv_string_get(repr)); 2983 2984 ucv_put(repr); 2985 2986 return NULL; 2987 } 2988 2989 CType *ctt = ctype_raw(cts, ctid); 2990 2991 if (ctt->size == CTSIZE_INVALID) { 2992 uc_value_t *repr = uc_ctype_repr(vm, ctid, NULL); 2993 2994 uc_vm_raise_exception(vm, EXCEPTION_TYPE, 2995 "C type '%s' has unknown storage size", 2996 ucv_string_get(repr)); 2997 2998 ucv_put(repr); 2999 3000 return NULL; 3001 } 3002 3003 uc_value_t *rv = NULL; 3004 3005 uc_cconv_tv_ct(cts, ctt, ctid, &rv, p); 3006 3007 return rv; 3008 } 3009 3010 static CTSize 3011 uc_ctype_sizeof_common(CTState *cts, uc_value_t *uv, uc_value_t *nelem) 3012 { 3013 GCcdata *cd = NULL; 3014 CTypeID id; 3015 CTSize sz; 3016 CType *ct; 3017 3018 id = uv_to_ct(cts->vm, CPARSE_MODE_ABSTRACT | CPARSE_MODE_NOIMPLICIT, 3019 uv, &cd); 3020 3021 if (!id) 3022 return CTSIZE_INVALID; 3023 3024 if (UC_UNLIKELY(cd && cdataisv(cd))) { 3025 ct = uc_ctype_rawref(cts, id); 3026 if (ctype_isarray(ct->info)) { 3027 CType *child = ctype_rawchild(cts, ct); 3028 return cdatavlen(cd) * child->size; 3029 } 3030 return cdatavlen(cd) * ct->size; 3031 } 3032 3033 ct = uc_ctype_rawref(cts, id); 3034 3035 if (ctype_isvltype(ct->info)) { 3036 // FIXME: transaprently handle cdata (ffi_checkint()) 3037 if (ucv_type(nelem) != UC_INTEGER) { 3038 uc_vm_raise_exception(cts->vm, EXCEPTION_TYPE, 3039 "integer argument expected, got %s", 3040 ucv_typename(nelem)); 3041 3042 return CTSIZE_INVALID; 3043 } 3044 3045 sz = uc_ctype_vlsize(cts, ct, (CTSize)ucv_int64_get(nelem)); 3046 } 3047 else { 3048 sz = ctype_hassize(ct->info) ? ct->size : CTSIZE_INVALID; 3049 } 3050 3051 return sz; 3052 } 3053 3054 /** 3055 * Get the size of a C data object in bytes. 3056 * 3057 * The `size()` method returns the total size in bytes of the cdata. For 3058 * arrays, this is the total size including all elements. 3059 * 3060 * @function module:ffi.CData#size 3061 * 3062 * @returns {number} 3063 * The size of the cdata in bytes. 3064 * 3065 * @example 3066 * // Get size of struct 3067 * ffi.cdef('struct point { int x; int y; };'); 3068 * let p = ffi.ctype('struct point'); 3069 * print(p.size()); // => 8 (on typical systems) 3070 * 3071 * @example 3072 * // Get size of array 3073 * let arr = ffi.ctype('int[10]'); 3074 * print(arr.size()); // => 40 (10 * sizeof(int)) 3075 */ 3076 static uc_value_t * 3077 uc_ctype_sizeof(uc_vm_t *vm, size_t nargs) 3078 { 3079 uc_value_t *this = _uc_fn_this_res(vm); 3080 CTSize sz = uc_ctype_sizeof_common(ctype_cts(vm), this, uc_fn_arg(0)); 3081 3082 return (sz != CTSIZE_INVALID) ? ucv_uint64_new(sz) : NULL; 3083 } 3084 3085 /** 3086 * Get the number of elements in an array cdata. 3087 * 3088 * The `length()` method returns the number of elements in an array. 3089 * For non-array types, returns `null`. 3090 * 3091 * @function module:ffi.CData#length 3092 * 3093 * @returns {?number} 3094 * The number of elements in the array, or `null` if not an array. 3095 * 3096 * @example 3097 * // Get array length 3098 * let arr = ffi.ctype('int[10]'); 3099 * print(arr.length()); // => 10 3100 * 3101 * @example 3102 * // Works with initialized arrays 3103 * let arr2 = ffi.ctype('char[5]', "hello"); 3104 * print(arr2.length()); // => 5 3105 */ 3106 static uc_value_t * 3107 uc_ctype_length(uc_vm_t *vm, size_t nargs) 3108 { 3109 uc_value_t *this = _uc_fn_this_res(vm); 3110 CTState *cts = ctype_cts(vm); 3111 CTSize sz = uc_ctype_sizeof_common(cts, this, uc_fn_arg(0)); 3112 3113 if (sz == CTSIZE_INVALID) 3114 return NULL; 3115 3116 GCcdata *cd = ucv_resource_data(this, "ffi.ctype"); 3117 CType *ct = ctype_raw(cts, cd->ctypeid); 3118 3119 if (!ctype_isarray(ct->info)) 3120 return NULL; 3121 3122 CTSize item_sz = ctype_rawchild(cts, ct)->size; 3123 3124 return (item_sz != CTSIZE_INVALID) ? ucv_uint64_new(sz / item_sz) : NULL; 3125 } 3126 3127 /** 3128 * Get the size of an array element or struct field in bytes. 3129 * 3130 * The `itemsize()` method returns the size in bytes of each element in an 3131 * array, or the size of a specified struct field. 3132 * 3133 * @function module:ffi.CData#itemsize 3134 * 3135 * @param {string} [fieldname] 3136 * For struct types, the field name to get the size of. 3137 * 3138 * @returns {number} 3139 * The size of each array element or the struct field in bytes. 3140 * 3141 * @example 3142 * // Get array element size 3143 * let arr = ffi.ctype('int[10]'); 3144 * print(arr.itemsize()); // => 4 (sizeof(int)) 3145 * 3146 * @example 3147 * // Get struct field size 3148 * ffi.cdef('struct foo { char a; int b; double c; };'); 3149 * let f = ffi.ctype('struct foo'); 3150 * print(f.itemsize('b')); // => 4 (size of int field) 3151 */ 3152 static uc_value_t * 3153 uc_ctype_itemsize(uc_vm_t *vm, size_t nargs) 3154 { 3155 uc_value_t *this = _uc_fn_this_res(vm); 3156 GCcdata *cd = NULL; 3157 CTypeID id = uv_to_ct(vm, CPARSE_MODE_ABSTRACT | CPARSE_MODE_NOIMPLICIT, 3158 this, &cd); 3159 3160 if (!id) 3161 return NULL; 3162 3163 CTState *cts = ctype_cts(vm); 3164 CTInfo info = ctype_raw(cts, id)->info; 3165 CTSize item_sz = CTSIZE_INVALID; 3166 3167 if (ctype_isstruct(info)) { 3168 uc_value_t *key = uc_fn_arg(0); 3169 3170 if (ucv_type(key) != UC_STRING) { 3171 uc_vm_raise_exception(vm, EXCEPTION_TYPE, 3172 "Expecting field name for struct type, got %s", 3173 nargs ? ucv_typename(key) : "no argument"); 3174 3175 return NULL; 3176 } 3177 3178 CTSize ofs; 3179 CType *fct; 3180 3181 fct = uc_ctype_getfieldq(cts, ctype_raw(cts, id), key, &ofs, NULL); 3182 3183 if (fct) 3184 item_sz = ctype_rawchild(cts, fct)->size; 3185 } 3186 else if (ctype_isarray(info)) { 3187 item_sz = ctype_rawchild(cts, ctype_get(cts, id))->size; 3188 } 3189 3190 return (item_sz != CTSIZE_INVALID) ? ucv_uint64_new(item_sz) : NULL; 3191 } 3192 3193 /** 3194 * Extract a substring from a char* or char[] cdata. 3195 * 3196 * The `slice()` method extracts a substring from a character pointer or 3197 * array. For char* pointers, it reads until the null terminator by default. 3198 * For char[] arrays, it uses the array length. 3199 * 3200 * @function module:ffi.CData#slice 3201 * 3202 * @param {number} [start=0] 3203 * The starting index (0-based). Negative values count from the end. 3204 * 3205 * @param {number} [end] 3206 * The ending index (exclusive). If omitted, uses the end of the string/array. 3207 * 3208 * @returns {string} 3209 * The extracted substring. 3210 * 3211 * @throws {Error} 3212 * Throws an exception if called without arguments on non-char* pointer types. 3213 * 3214 * @example 3215 * // Extract from char* pointer 3216 * ffi.cdef('char *strdup(const char *)'); 3217 * let ptr = ffi.C.wrap('char *strdup(const char *)')("hello world"); 3218 * print(ptr.slice()); // => "hello world" 3219 * print(ptr.slice(6)); // => "world" 3220 * print(ptr.slice(0, 5)); // => "hello" 3221 * 3222 * @example 3223 * // Extract from char[] array 3224 * let buf = ffi.ctype('char[10]', "hello"); 3225 * print(buf.slice()); // => "hello" 3226 * print(buf.slice(0, 3)); // => "hel" 3227 */ 3228 static uc_value_t * 3229 uc_ctype_slice(uc_vm_t *vm, size_t nargs) 3230 { 3231 GCcdata *cd = uc_fn_thisval("ffi.ctype"); 3232 3233 if (!cd) 3234 return NULL; 3235 3236 CTState *cts = ctype_cts(vm); 3237 CType *ct = ctype_get(cts, cd->ctypeid); 3238 CTSize sz = cdataisv(cd) ? cdatavlen(cd) : ct->size; 3239 uint8_t *p = cdataptr(cd); 3240 3241 /* Check if this is a char* pointer type */ 3242 bool is_charptr = false; 3243 uint8_t *charptr_data = p; 3244 size_t charptr_len = sz; 3245 3246 if (ctype_isptr(ct->info)) { 3247 CType *child = ctype_rawchild(cts, ct); 3248 /* Unwrap REF pointers to get the actual pointed-to type */ 3249 if (ctype_isptr(child->info)) { 3250 /* This is a pointer to pointer - check if inner points to char */ 3251 CType *inner = ctype_rawchild(cts, child); 3252 if (ctype_type(inner->info) == CT_NUM && inner->size == 1) { 3253 is_charptr = true; 3254 charptr_data = *(uint8_t **)p; 3255 if (charptr_data) 3256 charptr_len = strlen((char *)charptr_data); 3257 else 3258 charptr_len = 0; 3259 } 3260 } 3261 else if (ctype_type(child->info) == CT_NUM && child->size == 1) { 3262 is_charptr = true; 3263 charptr_data = *(uint8_t **)p; 3264 if (charptr_data) 3265 charptr_len = strlen((char *)charptr_data); 3266 else 3267 charptr_len = 0; 3268 } 3269 } 3270 3271 /* No arguments: treat as string() for char* pointers */ 3272 if (nargs == 0) { 3273 if (is_charptr) { 3274 /* char* pointer: read null-terminated string */ 3275 if (!charptr_data) 3276 return ucv_string_new(""); 3277 return ucv_string_new((char *)charptr_data); 3278 } 3279 /* For non-char* pointers, require explicit indices */ 3280 uc_vm_raise_exception(vm, EXCEPTION_TYPE, 3281 "slice() without arguments only supported for char* pointers"); 3282 return NULL; 3283 } 3284 3285 int64_t start_i = ucv_int64_get(uc_fn_arg(0)); 3286 size_t start; 3287 size_t end; 3288 3289 if (start_i < 0) 3290 start = (is_charptr ? charptr_len : sz) + start_i; 3291 else 3292 start = (size_t)start_i; 3293 3294 if (nargs >= 2) { 3295 int64_t end_i = ucv_int64_get(uc_fn_arg(1)); 3296 if (end_i < 0) 3297 end = (is_charptr ? charptr_len : sz) + end_i; 3298 else 3299 end = (size_t)end_i; 3300 } 3301 else { 3302 end = is_charptr ? charptr_len : sz; 3303 } 3304 3305 /* Clamp to valid range */ 3306 size_t max_len = is_charptr ? charptr_len : sz; 3307 if (start > max_len) 3308 start = max_len; 3309 if (end > max_len) 3310 end = max_len; 3311 if (start > end) 3312 start = end; 3313 3314 size_t len = end - start; 3315 3316 if (len == 0) 3317 return ucv_string_new_length("", 0); 3318 3319 return ucv_string_new_length((char *)charptr_data + start, len); 3320 } 3321 3322 static uc_value_t * 3323 uc_ctype_tostring(uc_vm_t *vm, size_t nargs) 3324 { 3325 GCcdata *cd = uc_fn_thisval("ffi.ctype"); 3326 3327 if (!cd) 3328 return NULL; 3329 3330 CTState *cts = ctype_cts(vm); 3331 CType *ct = ctype_get(cts, cd->ctypeid); 3332 uc_value_t *type_repr = uc_ctype_repr(vm, cd->ctypeid, NULL); 3333 uc_stringbuf_t *sb = ucv_stringbuf_new(); 3334 3335 ucv_stringbuf_addstr(sb, ucv_string_get(type_repr), ucv_string_length(type_repr)); 3336 ucv_put(type_repr); 3337 3338 /* Skip qualifiers and attributes to get the actual type */ 3339 while (ctype_isattrib(ct->info) || ctype_isref(ct->info)) 3340 ct = ctype_child(cts, ct); 3341 3342 /* Format value based on type */ 3343 switch (ctype_type(ct->info)) { 3344 case CT_NUM: 3345 case CT_ENUM: 3346 { 3347 uc_value_t *val = ct_to_uv(vm, cts, cd->ctypeid, cdataptr(cd), 3348 cdataisv(cd) ? cdatavlen(cd) : ct->size, 3349 cd->refs); 3350 if (val) { 3351 char *str = ucv_to_string(vm, val); 3352 if (str) { 3353 ucv_stringbuf_addstr(sb, ": ", 2); 3354 ucv_stringbuf_addstr(sb, str, strlen(str)); 3355 free(str); 3356 } 3357 ucv_put(val); 3358 } 3359 } 3360 break; 3361 3362 case CT_ARRAY: 3363 { 3364 CTSize clen = ct->size; 3365 CType *ctt = ctype_rawchild(cts, ct); 3366 3367 /* Complex number: show as re+imI */ 3368 if (ct->info & CTF_COMPLEX) 3369 { 3370 uc_value_t *val = uc_ctype_repr_complex(cdataptr(cd), 3371 cdataisv(cd) ? cdatavlen(cd) : ct->size); 3372 if (val) { 3373 ucv_stringbuf_addstr(sb, ": ", 2); 3374 ucv_stringbuf_addstr(sb, ucv_string_get(val), ucv_string_length(val)); 3375 ucv_put(val); 3376 } 3377 } 3378 /* String array: show contents */ 3379 else if (ctt->size == 1 && (ctt->info & CTF_UNSIGNED) == 0) 3380 { 3381 char *str = (char *)cdataptr(cd); 3382 ucv_stringbuf_addstr(sb, ": \"", 3); 3383 for (char *p = str; *p && (p - str) < 128; p++) { 3384 if (*p == '"') 3385 ucv_stringbuf_addstr(sb, "\\\"", 2); 3386 else if (*p == '\\') 3387 ucv_stringbuf_addstr(sb, "\\\\", 2); 3388 else if (*p == '\n') 3389 ucv_stringbuf_addstr(sb, "\\n", 2); 3390 else if (*p == '\r') 3391 ucv_stringbuf_addstr(sb, "\\r", 2); 3392 else if (*p == '\t') 3393 ucv_stringbuf_addstr(sb, "\\t", 2); 3394 else if (*p >= 32 && *p < 127) 3395 ucv_stringbuf_addstr(sb, p, 1); 3396 else 3397 ucv_stringbuf_printf(sb, "\\x%02x", (unsigned char)*p); 3398 } 3399 ucv_stringbuf_addstr(sb, "\"", 1); 3400 } 3401 else if (clen != CTSIZE_INVALID && ctt->size > 0) 3402 { 3403 ucv_stringbuf_printf(sb, " (len=%zu)", clen / ctt->size); 3404 } 3405 } 3406 break; 3407 3408 case CT_PTR: 3409 { 3410 void *ptr = *(void **)cdataptr(cd); 3411 if (ptr) 3412 ucv_stringbuf_printf(sb, " @ %p", ptr); 3413 else 3414 ucv_stringbuf_addstr(sb, ": NULL", 6); 3415 } 3416 break; 3417 3418 case CT_STRUCT: 3419 { 3420 CTSize sz = cdataisv(cd) ? cdatavlen(cd) : ct->size; 3421 uc_value_t *val = ct_to_uv(vm, cts, cd->ctypeid, cdataptr(cd), sz, cd->refs); 3422 if (val) { 3423 char *str = ucv_to_string(vm, val); 3424 if (str) { 3425 ucv_stringbuf_addstr(sb, ": ", 2); 3426 ucv_stringbuf_addstr(sb, str, strlen(str)); 3427 free(str); 3428 } 3429 ucv_put(val); 3430 } 3431 } 3432 break; 3433 3434 case CT_VOID: 3435 ucv_stringbuf_addstr(sb, ": void", 6); 3436 break; 3437 } 3438 3439 return ucv_stringbuf_finish(sb); 3440 } 3441 3442 3443 /** 3444 * Represents a C data object holding a value of a C type. 3445 * 3446 * @class module:ffi.CData 3447 * @hideconstructor 3448 * 3449 * @see {@link module:ffi#ctype|ctype()} 3450 * 3451 * @example 3452 * 3453 * const val = ctype(…); 3454 * 3455 * val.get(); 3456 * val.set(…); 3457 * val.ptr(); 3458 * val.index(…); 3459 * val.deref(…); 3460 * val.size(); 3461 * val.length(); 3462 * val.itemsize(…); 3463 * val.slice(…); 3464 */ 3465 3466 /** 3467 * Create a C data instance. 3468 * 3469 * The `ctype()` function creates a new C data object (cdata) of the specified 3470 * type. It can be called with optional initializer values that will be used 3471 * to initialize the object. 3472 * 3473 * **Usage patterns:** 3474 * 3475 * 1. **Without initializer**: Creates an uninitialized cdata of the given type. 3476 * For pointer types, the pointer is set to NULL. 3477 * 3478 * 2. **With initializer**: Creates and initializes a cdata. The initializer 3479 * values depend on the type: 3480 * - Scalar types: single value (number, boolean) 3481 * - Structs: positional arguments for each field or a ucode object 3482 * - Arrays: individual element values or a string for char arrays 3483 * 3484 * ```javascript 3485 * // Primitive type 3486 * let x = ffi.ctype('int', 42); 3487 * print(x.get()); // => 42 3488 * 3489 * // Struct type with positional arguments 3490 * ffi.cdef('struct point { int x; int y; };'); 3491 * let p1 = ffi.ctype('struct point', 10, 20); 3492 * print(p1.get('x'), p1.get('y')); // => 10 20 3493 * 3494 * // Struct type with object initializer 3495 * let p2 = ffi.ctype('struct point', { x: 30, y: 40 }); 3496 * print(p2.get('x'), p2.get('y')); // => 30 40 3497 * 3498 * // Nested struct with object initializer 3499 * ffi.cdef('struct rect { struct point tl; struct point br; };'); 3500 * let r = ffi.ctype('struct rect', { 3501 * tl: { x: 0, y: 0 }, 3502 * br: { x: 100, y: 200 } 3503 * }); 3504 * let tl = r.get('tl'); 3505 * print(tl.get('x'), tl.get('y')); // => 0 0 3506 * 3507 * // Array type 3508 * let arr = ffi.ctype('int[3]', 1, 2, 3); 3509 * print(arr.get(0), arr.get(1), arr.get(2)); // => 1 2 3 3510 * 3511 * // Char array from string 3512 * let buf = ffi.ctype('char[10]', 'hello'); 3513 * print(buf.deref()); // => "hello" 3514 * 3515 * // Pointer type (uninitialized) 3516 * let ptr = ffi.ctype('void *'); 3517 * ``` 3518 * 3519 * @function module:ffi#ctype 3520 * 3521 * @param {string} type 3522 * A C type declaration string. Can be a basic type, struct name, array type, 3523 * pointer type, etc. The type must have been declared via `cdef()` first. 3524 * 3525 * @param {...*} [init] 3526 * Optional initializer values. 3527 * 3528 * @returns {?module:ffi.CData} 3529 * A cdata of the specified type, or `null` if the type cannot be 3530 * parsed or has invalid size. For `typeof()` without initializer, 3531 * returns a CTypeID handle cdata. 3532 * 3533 * @throws {Error} 3534 * Throws an exception if the type declaration is invalid or wrong number 3535 * of initializers provided. 3536 * 3537 * @example 3538 * // Create integer 3539 * let x = ffi.ctype('int', 42); 3540 * print(x.get()); 3541 * 3542 * @example 3543 * // Create struct 3544 * ffi.cdef('struct point { int x; int y; };'); 3545 * let p = ffi.ctype('struct point', 10, 20); 3546 * print(p.get('x')); 3547 * 3548 * @example 3549 * // Create array 3550 * let arr = ffi.ctype('double[5]', 1.1, 2.2, 3.3, 4.4, 5.5); 3551 * print(arr.length()); 3552 */ 3553 static uc_value_t * 3554 uc_ffi_ctype(uc_vm_t *vm, size_t nargs) 3555 { 3556 uc_value_t *spec = uc_fn_arg(0); 3557 CTState *cts = ctype_cts(vm); 3558 uc_value_t *res; 3559 3560 if (ucv_type(spec) != UC_STRING) 3561 return NULL; 3562 3563 CPState cp = { 3564 .uv_vm = vm, 3565 .cts = cts, 3566 .srcname = ucv_string_get(spec), 3567 .p = ucv_string_get(spec), 3568 .uv_param = NULL, 3569 .mode = CPARSE_MODE_ABSTRACT | CPARSE_MODE_NOIMPLICIT 3570 }; 3571 3572 if (!uc_cparse(&cp)) 3573 return NULL; 3574 3575 /* initializer values provided... */ 3576 if (nargs > 1) { 3577 size_t init_arg_off = 1; 3578 CTSize sz; 3579 CType *ct = ctype_raw(cts, cp.val.id); 3580 CTInfo info = uc_ctype_info(cts, cp.val.id, &sz); 3581 uc_value_t *refs = NULL; 3582 3583 if (info & CTF_VLA) { 3584 CTSize vla_sz = ucv_uint64_get(uc_fn_arg(1)); 3585 3586 if (errno) { 3587 uc_vm_raise_exception(vm, EXCEPTION_TYPE, 3588 "invalid size argument provided"); 3589 3590 return NULL; 3591 } 3592 3593 init_arg_off++; 3594 sz = uc_ctype_vlsize(cts, ct, vla_sz); 3595 } 3596 3597 if (sz == CTSIZE_INVALID) { 3598 uc_vm_raise_exception(vm, EXCEPTION_TYPE, 3599 "C type has invalid size"); 3600 3601 return NULL; 3602 } 3603 3604 res = uc_cdata_newx(vm, cp.val.id, sz, info); 3605 3606 /* Special handling: char array initialized with a string */ 3607 if (ctype_isarray(info) && ctype_isinteger(ctype_child(cts, ct)->info) && 3608 nargs - init_arg_off == 1 && ucv_type(vm->stack.entries[vm->stack.count - nargs + init_arg_off]) == UC_STRING) { 3609 /* Convert string to array of char values */ 3610 uc_value_t *str = vm->stack.entries[vm->stack.count - nargs + init_arg_off]; 3611 const char *s = ucv_string_get(str); 3612 size_t len = strlen(s); 3613 CType *child = ctype_child(cts, ct); 3614 CTSize elem_sz = child->size; 3615 GCcdata *cd_tmp = ucv_resource_data(res, "ffi.ctype"); 3616 uint8_t *data = (uint8_t *)cdataptr(cd_tmp); 3617 3618 /* Copy string including null terminator if array is large enough */ 3619 for (size_t i = 0; i < len && i * elem_sz < sz; i++) { 3620 if (elem_sz == 1) { 3621 data[i] = s[i]; 3622 } else { 3623 /* For wider character types (e.g., char16_t) - truncate for now */ 3624 data[i * elem_sz] = s[i]; 3625 } 3626 } 3627 /* Null-terminate if there's space */ 3628 if (len < sz / elem_sz) { 3629 if (elem_sz == 1) { 3630 data[len] = '\0'; 3631 } else { 3632 data[len * elem_sz] = '\0'; 3633 } 3634 } 3635 } else { 3636 if (nargs - init_arg_off > 0) { 3637 GCcdata *cd_tmp = ucv_resource_data(res, "ffi.ctype"); 3638 uint8_t *data = (uint8_t *)cdataptr(cd_tmp); 3639 uc_cconv_ct_init(cts, ct, sz, data, 3640 &vm->stack.entries[vm->stack.count - nargs + init_arg_off], 3641 nargs - init_arg_off, &refs); 3642 } 3643 } 3644 3645 GCcdata *cd = ucv_resource_data(res, "ffi.ctype"); 3646 cd->refs = refs; 3647 } 3648 else { 3649 CTSize sz; 3650 CTInfo info = uc_ctype_info(cts, cp.val.id, &sz); 3651 3652 if (sz == CTSIZE_INVALID) { 3653 uc_vm_raise_exception(vm, EXCEPTION_TYPE, 3654 "C type has invalid size"); 3655 3656 return NULL; 3657 } 3658 3659 res = uc_cdata_newx(vm, cp.val.id, sz, info); 3660 } 3661 3662 return res; 3663 } 3664 3665 /** 3666 * Declare C types and functions. 3667 * 3668 * The `cdef()` function parses C declaration strings and registers the types 3669 * with the FFI system. This is required before using types with `ctype()`, 3670 * wrapping functions with `wrap()`, or resolving symbols with `dlsym()`. 3671 * 3672 * Multiple declarations can be provided in a single call, separated by 3673 * semicolons. The parser supports most C declaration syntax including: 3674 * 3675 * - Basic types (`int`, `char`, `float`, `double`, etc.) 3676 * - Type modifiers (`const`, `volatile`, `unsigned`, `signed`) 3677 * - Pointers and arrays (`int *`, `char **`, `int[10]`) 3678 * - Structs and unions (`struct foo { ... }`, `union bar { ... }`) 3679 * - Enums (`enum baz { ... }`) 3680 * - Function declarations (`int foo(int, char *)`) 3681 * - Typedefs (`typedef ...`) 3682 * - Extern declarations (`extern int var;`) 3683 * 3684 * ```javascript 3685 * // Declare a struct type 3686 * ffi.cdef('struct point { int x; int y; };'); 3687 * 3688 * // Declare a function 3689 * ffi.cdef('int strcmp(const char *, const char *);'); 3690 * 3691 * // Declare multiple items 3692 * ffi.cdef(` 3693 * typedef unsigned int uint32_t; 3694 * struct sockaddr { 3695 * sa_family_t sa_family; 3696 * char sa_data[14]; 3697 * }; 3698 * extern char **environ; 3699 * `); 3700 * ``` 3701 * 3702 * After declaring types, you can create instances with `ctype()`, wrap 3703 * functions with `wrap()`, or access global variables with `dlsym()`. 3704 * 3705 * @function module:ffi#cdef 3706 * 3707 * @param {string} spec 3708 * A C declaration string or multiple declarations separated by semicolons. 3709 * 3710 * @returns {module:ffi.CData} 3711 * A cdata holding the CTypeID handle for the last declared type. 3712 * 3713 * @throws {Error} 3714 * Throws an exception if the declaration syntax is invalid. 3715 * 3716 * @example 3717 * // Declare struct and create instance 3718 * ffi.cdef('struct point { int x; int y; };'); 3719 * let p = ffi.ctype('struct point', 10, 20); 3720 * print(p.get('x'), p.get('y')); 3721 * 3722 * @example 3723 * // Declare function and wrap it 3724 * ffi.cdef('size_t strlen(const char *);'); 3725 * let strlen = ffi.C.wrap('size_t strlen(const char *)'); 3726 * print(strlen("hello").get()); 3727 */ 3728 static uc_value_t * 3729 uc_ffi_cdef(uc_vm_t *vm, size_t nargs) 3730 { 3731 uc_value_t *spec = uc_fn_arg(0); 3732 CTState *cts = ctype_cts(vm); 3733 3734 if (ucv_type(spec) != UC_STRING) 3735 return NULL; 3736 3737 if (!vm->callframes.count) 3738 return NULL; 3739 3740 CPState cp = { 3741 .uv_vm = vm, 3742 .cts = cts, 3743 .srcname = ucv_string_get(spec), 3744 .p = ucv_string_get(spec), 3745 .uv_param = &vm->stack.entries[uc_vector_last(&vm->callframes)->stackframe + 2], 3746 .mode = CPARSE_MODE_MULTI | CPARSE_MODE_DIRECT 3747 }; 3748 3749 if (!uc_cparse(&cp)) 3750 return NULL; 3751 3752 uc_value_t *res = uc_cdata_new(vm, CTID_CTYPEID, 4); 3753 *(CTypeID *)uc_cdata_dataptr(res) = cp.val.id; 3754 3755 return res; 3756 } 3757 3758 /** 3759 * Get the CTypeID for a C type. 3760 * 3761 * The `typeof()` function returns a CTypeID handle for the specified type. 3762 * This is useful for storing type references or passing to other FFI functions. 3763 * 3764 * @function module:ffi#typeof 3765 * 3766 * @param {string} type 3767 * The C type declaration. 3768 * 3769 * @returns {module:ffi.CData} 3770 * A cdata holding the CTypeID handle (an integer type ID). 3771 * 3772 * @throws {Error} 3773 * Throws an exception if the type declaration is invalid. 3774 * 3775 * @example 3776 * // Get type ID for struct 3777 * ffi.cdef('struct point { int x; int y; };'); 3778 * let point_type = ffi.typeof('struct point'); 3779 * 3780 * @example 3781 * // Get type ID for function pointer 3782 * ffi.cdef('int callback(int, char *);'); 3783 * let cb_type = ffi.typeof('int (*)(int, char *)'); 3784 */ 3785 static uc_value_t * 3786 uc_ffi_typeof(uc_vm_t *vm, size_t nargs) 3787 { 3788 CTState *cts = ctype_cts(vm); 3789 CTypeID id = ffi_checkctype(vm, nargs, 0, cts, NULL); 3790 3791 uc_value_t *res = uc_cdata_new(vm, CTID_CTYPEID, 4); 3792 3793 *(CTypeID *)uc_cdata_dataptr(res) = id; 3794 3795 return res; 3796 } 3797 3798 /** 3799 * Get the size of a C type in bytes. 3800 * 3801 * The `sizeof()` function returns the size in bytes of a C type or cdata 3802 * expression. For variable-length arrays, an element count can be provided. 3803 * 3804 * @function module:ffi#sizeof 3805 * 3806 * @param {string|module:ffi.CData} type 3807 * The C type declaration or cdata expression to measure. 3808 * 3809 * @param {number} [nelem] 3810 * For variable-length arrays, the number of elements. 3811 * 3812 * @returns {?number} 3813 * The size in bytes, or `null` if the size is unknown. 3814 * 3815 * @throws {Error} 3816 * Throws an exception if the type is invalid or nelem is required but missing. 3817 * 3818 * @example 3819 * // Get size of primitive types 3820 * print(ffi.sizeof('int')); // => 4 3821 * print(ffi.sizeof('double')); // => 8 3822 * 3823 * @example 3824 * // Get size of struct 3825 * ffi.cdef('struct point { int x; int y; };'); 3826 * print(ffi.sizeof('struct point')); // => 8 3827 * 3828 * @example 3829 * // Get size of VLA with element count 3830 * ffi.cdef('int vla[];'); 3831 * print(ffi.sizeof('int[]', 10)); // => 40 (10 * sizeof(int)) 3832 */ 3833 static uc_value_t * 3834 uc_ffi_sizeof(uc_vm_t *vm, size_t nargs) 3835 { 3836 CTSize sz; 3837 3838 sz = uc_ctype_sizeof_common(ctype_cts(vm), uc_fn_arg(0), uc_fn_arg(1)); 3839 3840 return (sz != CTSIZE_INVALID) ? ucv_uint64_new(sz) : NULL; 3841 } 3842 3843 /** 3844 * Get the alignment requirement of a C type in bytes. 3845 * 3846 * The `alignof()` function returns the minimum alignment requirement in bytes 3847 * for a C type. This is useful for understanding structure padding and memory 3848 * layout. 3849 * 3850 * @function module:ffi#alignof 3851 * 3852 * @param {string} type 3853 * The C type declaration. 3854 * 3855 * @returns {number} 3856 * The alignment requirement in bytes (typically a power of 2). 3857 * 3858 * @throws {Error} 3859 * Throws an exception if the type is invalid. 3860 * 3861 * @example 3862 * // Get alignment of primitive types 3863 * print(ffi.alignof('int')); // => 4 3864 * print(ffi.alignof('double')); // => 8 3865 * 3866 * @example 3867 * // Get alignment of struct 3868 * ffi.cdef('struct foo { char a; int b; };'); 3869 * print(ffi.alignof('struct foo')); // => 4 (alignment of int member) 3870 */ 3871 static uc_value_t * 3872 uc_ffi_alignof(uc_vm_t *vm, size_t nargs) 3873 { 3874 CTState *cts = ctype_cts(vm); 3875 CTypeID id = ffi_checkctype(vm, nargs, 0, cts, NULL); 3876 3877 CTSize sz; 3878 CTInfo info = uc_ctype_info_raw(cts, id, &sz); 3879 3880 return ucv_uint64_new(1 << ctype_align(info)); 3881 } 3882 3883 /** 3884 * Get the offset of a struct field in bytes. 3885 * 3886 * The `offsetof()` function returns the byte offset of a field within a struct. 3887 * For bitfields, the bit position and bit size are returned in an array passed 3888 * as the third argument. 3889 * 3890 * @function module:ffi#offsetof 3891 * 3892 * @param {string} type 3893 * The struct type declaration. 3894 * 3895 * @param {string} field 3896 * The field name to get the offset of. 3897 * 3898 * @param {array} [bitpos] 3899 * Optional array to receive [bit_position, bit_size] for bitfield members. 3900 * 3901 * @returns {?number} 3902 * The byte offset of the field, or `null` if the field doesn't exist. 3903 * 3904 * @throws {Error} 3905 * Throws an exception if the type is not a struct or the field is invalid. 3906 * 3907 * @example 3908 * // Get field offset 3909 * ffi.cdef('struct point { int x; int y; };'); 3910 * print(ffi.offsetof('struct point', 'x')); // => 0 3911 * print(ffi.offsetof('struct point', 'y')); // => 4 3912 * 3913 * @example 3914 * // Get bitfield info 3915 * ffi.cdef('struct flags { unsigned int a:4; unsigned int b:4; };'); 3916 * let bitpos = []; 3917 * let offset = ffi.offsetof('struct flags', 'b', bitpos); 3918 * print(offset, bitpos[0], bitpos[1]); // => 0 4 4 3919 */ 3920 static uc_value_t * 3921 uc_ffi_offsetof(uc_vm_t *vm, size_t nargs) 3922 { 3923 CTState *cts = ctype_cts(vm); 3924 CTypeID id = ffi_checkctype(vm, nargs, 0, cts, NULL); 3925 uc_value_t *name = uc_fn_arg(1); 3926 uc_value_t *bitpos = uc_fn_arg(2); 3927 CType *ct = uc_ctype_rawref(cts, id); 3928 CTSize ofs; 3929 3930 if (!ctype_isstruct(ct->info) || ct->size == CTSIZE_INVALID) 3931 return NULL; 3932 3933 if (ucv_type(name) != UC_STRING) 3934 return NULL; 3935 3936 CType *fct = uc_ctype_getfield(cts, ct, name, &ofs); 3937 3938 if (ctype_isfield(fct->info)) 3939 return ucv_uint64_new(ofs); 3940 3941 if (ctype_isbitfield(fct->info)) { 3942 ucv_array_set(bitpos, 0, ucv_uint64_new(ctype_bitpos(fct->info))); 3943 ucv_array_set(bitpos, 1, ucv_uint64_new(ctype_bitbsz(fct->info))); 3944 3945 return ucv_uint64_new(ofs); 3946 } 3947 3948 return NULL; 3949 } 3950 3951 /** 3952 * Get or set the C `errno` value. 3953 * 3954 * The `errno()` function retrieves the current value of the C `errno` 3955 * variable, or sets it to a new value if an argument is provided. 3956 * 3957 * @function module:ffi#errno 3958 * 3959 * @param {number} [value] 3960 * Optional value to set errno to. 3961 * 3962 * @returns {number} 3963 * The current errno value (before any set operation). 3964 * 3965 * @example 3966 * // Get current errno 3967 * let err = ffi.errno(); 3968 * 3969 * @example 3970 * // Set errno 3971 * ffi.errno(0); // Clear errno 3972 */ 3973 static uc_value_t * 3974 uc_ffi_errno(uc_vm_t *vm, size_t nargs) 3975 { 3976 int err = errno; 3977 3978 if (nargs) 3979 errno = ucv_int64_get(uc_fn_arg(0)); 3980 3981 return ucv_int64_new(err); 3982 } 3983 3984 /** 3985 * Preloaded C types and variables. 3986 * 3987 * The FFI module automatically preloads certain C types and global variables 3988 * that are commonly needed. These are available without explicit `cdef()` declarations. 3989 * 3990 * ### Preloaded Global Variables 3991 * 3992 * The following global variables are automatically available through `ffi.C`: 3993 * 3994 * | Variable | Type | Description | 3995 * |----------|------|-------------| 3996 * | `errno` | `int *` | Thread-local error code pointer | 3997 * | `environ` | `char ***` | Process environment variables | 3998 * 3999 * Access these via `ffi.C.dlsym()`: 4000 * 4001 * ```javascript 4002 * // Get errno pointer 4003 * let errno_ptr = ffi.C.dlsym('errno'); 4004 * let err = errno_ptr.deref('int'); 4005 * 4006 * // Get environment variables 4007 * let env = ffi.C.dlsym('environ'); 4008 * for (let i = 0; i < 10; i++) { 4009 * let var = env.get(i); 4010 * if (!var) break; 4011 * print(ffi.string(var), "\n"); 4012 * } 4013 * ``` 4014 * 4015 * ### Builtin Type Definitions 4016 * 4017 * The following types are pre-declared and available without `cdef()`: 4018 * 4019 * | Type | Description | Typical Size | 4020 * |------|-------------|--------------| 4021 * | `size_t` | Unsigned pointer-sized integer | 4 or 8 bytes | 4022 * | `ssize_t` | Signed pointer-sized integer | 4 or 8 bytes | 4023 * | `intptr_t` | Signed integer with same size as pointer | 4 or 8 bytes | 4024 * | `uintptr_t` | Unsigned integer with same size as pointer | 4 or 8 bytes | 4025 * | `ptrdiff_t` | Signed difference type (pointer subtraction) | 4 or 8 bytes | 4026 * | `wchar_t` | Wide character type | 2 or 4 bytes | 4027 * | `va_list` | Variable argument list (for vararg functions) | Implementation-dependent | 4028 * 4029 * ### Fixed-Width Integer Types 4030 * 4031 * The following types from `<stdint.h>` are pre-declared: 4032 * 4033 * | Type | Description | Size | 4034 * |------|-------------|------| 4035 * | `int8_t` | Signed 8-bit integer | 1 byte | 4036 * | `int16_t` | Signed 16-bit integer | 2 bytes | 4037 * | `int32_t` | Signed 32-bit integer | 4 bytes | 4038 * | `int64_t` | Signed 64-bit integer | 8 bytes | 4039 * | `uint8_t` | Unsigned 8-bit integer | 1 byte | 4040 * | `uint16_t` | Unsigned 16-bit integer | 2 bytes | 4041 * | `uint32_t` | Unsigned 32-bit integer | 4 bytes | 4042 * | `uint64_t` | Unsigned 64-bit integer | 8 bytes | 4043 * 4044 * These types can be used directly without prior declaration: 4045 * 4046 * ```javascript 4047 * // Use builtin types directly 4048 * let sz = ffi.sizeof('size_t'); // => 8 (on 64-bit systems) 4049 * let ptr = ffi.ctype('uintptr_t', 0); 4050 * 4051 * // Use fixed-width types 4052 * let i32 = ffi.ctype('int32_t', 42); 4053 * let u64 = ffi.ctype('uint64_t', 0xFFFFFFFFFFFFFFFF); 4054 * 4055 * // Create arrays of builtin types 4056 * let buf = ffi.ctype('uint8_t[256]'); 4057 * let indices = ffi.ctype('size_t[10]'); 4058 * ``` 4059 * 4060 * Note: When wrapping functions that use these types, you still need to 4061 * declare the function prototype via `cdef()` or provide a full declaration 4062 * to `wrap()`: 4063 * 4064 * ```javascript 4065 * // Declare function using builtin types 4066 * ffi.cdef('size_t strlen(const char *);'); 4067 * let strlen = ffi.C.wrap('strlen'); 4068 * 4069 * // Or provide full declaration to wrap() 4070 * let strlen = ffi.C.wrap('size_t strlen(const char *)'); 4071 * ``` 4072 * 4073 * @section Preloaded Types 4074 */ 4075 4076 /** 4077 * Convert between ucode strings and C char arrays/pointers. 4078 * 4079 * The `string()` function has two modes: 4080 * 4081 * 1. **String to buffer**: Given a ucode string, creates a C char[] buffer 4082 * containing the string plus null terminator. Returns a cdata that can be 4083 * passed to C functions expecting `char*`. 4084 * 4085 * 2. **Pointer to string**: Given a char* cdata pointer, reads the C string 4086 * and returns a ucode string. An optional length parameter can be provided 4087 * to limit the maximum bytes read (reads up to `len` bytes or until null 4088 * terminator, whichever comes first). 4089 * 4090 * @function module:ffi#string 4091 * 4092 * @param {string|module:ffi.CData} arg 4093 * A ucode string to convert to char[], or a char* cdata pointer to read. 4094 * 4095 * @param {number} [len] 4096 * Optional maximum length for reading C strings (reads up to `len` bytes 4097 * or until null terminator). 4098 * 4099 * @returns {string|module:ffi.CData} 4100 * When given a char* pointer: returns a ucode string. 4101 * When given a ucode string: returns a char[] cdata buffer. 4102 * 4103 * @throws {Error} 4104 * Throws an exception if the argument type is invalid. 4105 * 4106 * @example 4107 * // Convert ucode string to char[] buffer 4108 * let buf = ffi.string("hello"); 4109 * // buf is now char[6] cdata (including null terminator) 4110 * // Can be passed to C functions expecting char* 4111 * 4112 * @example 4113 * // Read C string from char* pointer 4114 * ffi.cdef('char *getenv(char *);'); 4115 * let ptr = ffi.C.wrap('char *getenv(char *)')("PATH"); 4116 * let path = ffi.string(ptr); 4117 * print(path); // => "/usr/bin:..." 4118 * 4119 * @example 4120 * // Read fixed-length string (no null terminator) 4121 * ffi.cdef('char *strncpy(char *, const char *, size_t);'); 4122 * let src = ffi.string("hello world"); 4123 * let dst = ffi.ctype('char[5]'); 4124 * ffi.C.wrap('char *strncpy(char *, const char *, size_t)')(dst, src, 5); 4125 * let short_str = ffi.string(dst, 5); // => "hello" (no null terminator) 4126 */ 4127 static uc_value_t * 4128 uc_ffi_string(uc_vm_t *vm, size_t nargs) 4129 { 4130 uc_value_t *arg = uc_fn_arg(0); 4131 uc_value_t *len_arg = uc_fn_arg(1); 4132 CTState *cts = ctype_cts(vm); 4133 4134 /* If argument is ucode string, create C char[] buffer */ 4135 if (ucv_type(arg) == UC_STRING) { 4136 size_t len = ucv_string_length(arg) + 1; 4137 4138 /* Create char[N] array type directly without parser invocation */ 4139 CTypeID elem_type = CTID_CCHAR; /* char element type */ 4140 CTInfo array_info = CTINFO(CT_ARRAY, CTALIGN(0)) + elem_type; 4141 CTSize array_size = len; /* Total size in bytes */ 4142 4143 /* Intern the array type */ 4144 CTypeID array_typeid = uc_ctype_intern(cts, array_info, array_size); 4145 4146 /* Create cdata instance */ 4147 uc_value_t *arr = uc_cdata_new(vm, array_typeid, array_size); 4148 4149 /* Copy string including null terminator */ 4150 const char *src = ucv_string_get(arg); 4151 uint8_t *dst = (uint8_t *)cdataptr((GCcdata *)((uc_resource_t *)arr)->data); 4152 memcpy(dst, src, len); 4153 4154 return arr; 4155 } 4156 4157 /* Otherwise, argument is a C pointer - extract address and read as string */ 4158 void *p = NULL; 4159 size_t sz; 4160 4161 if (nargs > 1) { 4162 /* With explicit max-length: accept any pointer type */ 4163 uc_cconv_ct_tv(cts, ctype_get(cts, CTID_P_VOID), (uint8_t *)&p, arg, 4164 CCF_ARG(1), NULL); 4165 size_t max_len = ucv_uint64_get(len_arg); 4166 /* Read up to max_len bytes or until null terminator (like strncpy) */ 4167 sz = strnlen((const char *)p, max_len); 4168 } 4169 else { 4170 /* Without length: extract pointer address and treat as char* */ 4171 GCcdata *cd = ucv_resource_data(arg, "ffi.ctype"); 4172 if (!cd) { 4173 uc_vm_raise_exception(vm, EXCEPTION_TYPE, 4174 "string or cdata pointer expected, got %s", 4175 ucv_typename(arg)); 4176 4177 return NULL; 4178 } 4179 4180 /* Get pointer: arrays contain data directly, pointers contain address */ 4181 CType *cd_ct = ctype_get(cts, cd->ctypeid); 4182 if (ctype_isrefarray(cd_ct->info)) { 4183 /* Array: data is directly in cdata, use array size as limit */ 4184 p = cdataptr(cd); 4185 sz = strnlen((const char *)p, cd_ct->size); 4186 } 4187 else if (ctype_isptr(cd_ct->info)) { 4188 /* Pointer: dereference and read null-terminated string */ 4189 p = *(void **)cdataptr(cd); 4190 if (!p) 4191 return ucv_string_new(""); 4192 sz = strlen((const char *)p); 4193 } 4194 else { 4195 uc_vm_raise_exception(vm, EXCEPTION_TYPE, 4196 "string or cdata pointer expected, got %s", 4197 ucv_typename(arg)); 4198 4199 return NULL; 4200 } 4201 } 4202 4203 return ucv_string_new_length((const char *)p, sz); 4204 } 4205 4206 /** 4207 * Copy memory between pointers. 4208 * 4209 * The `copy()` function copies memory from a source pointer to a destination 4210 * pointer. If the source is a ucode string, it copies the string including 4211 * its null terminator. Otherwise, an explicit length must be provided. 4212 * 4213 * @function module:ffi#copy 4214 * 4215 * @param {module:ffi.CData} dest 4216 * Destination pointer. 4217 * 4218 * @param {string|module:ffi.CData} src 4219 * Source string or pointer. 4220 * 4221 * @param {number} [len] 4222 * Number of bytes to copy. Required if src is not a string. 4223 * 4224 * @returns {undefined} 4225 * Returns `undefined`. 4226 * 4227 * @example 4228 * // Copy string (includes null terminator) 4229 * let buf = ffi.ctype('char[10]'); 4230 * ffi.copy(buf, "hello"); 4231 * 4232 * @example 4233 * // Copy memory with explicit length 4234 * let src = ffi.ctype('char[5]', [1, 2, 3, 4, 5]); 4235 * let dst = ffi.ctype('char[5]'); 4236 * ffi.copy(dst, src, 5); 4237 */ 4238 static uc_value_t * 4239 uc_ffi_copy(uc_vm_t *vm, size_t nargs) 4240 { 4241 uc_value_t *dp_arg = uc_fn_arg(0); 4242 void *dp = ffi_checkptr(vm, nargs, 0, CTID_P_VOID); 4243 uc_value_t *sp_arg = uc_fn_arg(1); 4244 void *sp = NULL; 4245 size_t len, dp_size, sp_size = SIZE_MAX; 4246 CTState *cts = ctype_cts(vm); 4247 4248 /* Get destination buffer size for bounds checking */ 4249 dp_size = ffi_cdata_bufsize(cts, dp_arg); 4250 4251 /* Handle string source: copy directly from the string buffer */ 4252 if (ucv_type(sp_arg) == UC_STRING) { 4253 const char *src = ucv_string_get(sp_arg); 4254 size_t src_len = ucv_string_length(sp_arg); 4255 4256 /* Determine length: use explicit len if provided, else string + null */ 4257 if (nargs > 2) { 4258 len = ucv_uint64_get(uc_fn_arg(2)); 4259 } else { 4260 len = src_len + 1; 4261 } 4262 4263 sp = (void *)src; 4264 sp_size = src_len + 1; 4265 } else { 4266 sp = ffi_checkptr(vm, nargs, 1, CTID_P_CVOID); 4267 if (!sp) 4268 return NULL; 4269 4270 /* Get source buffer size for bounds checking */ 4271 sp_size = ffi_cdata_bufsize(cts, sp_arg); 4272 4273 if (nargs > 2) { 4274 len = ucv_uint64_get(uc_fn_arg(2)); 4275 } else { 4276 len = strnlen((const char *)sp, sp_size); 4277 } 4278 } 4279 4280 /* Cap length to destination buffer size */ 4281 if (len > dp_size) 4282 len = dp_size; 4283 4284 /* Cap length to source buffer size */ 4285 if (len > sp_size) 4286 len = sp_size; 4287 4288 memcpy(dp, sp, len); 4289 4290 return NULL; 4291 } 4292 4293 /** 4294 * Fill memory with a byte value. 4295 * 4296 * The `fill()` function sets `len` bytes at the destination pointer to 4297 * the specified fill value. The fill value can be a number, boolean, 4298 * or string (first character used). 4299 * 4300 * @function module:ffi#fill 4301 * 4302 * @param {module:ffi.CData} dest 4303 * Destination pointer. 4304 * 4305 * @param {number} len 4306 * Number of bytes to fill. 4307 * 4308 * @param {number|boolean|string} [value=0] 4309 * Fill value. Numbers/booleans use the value directly; strings use 4310 * the first character's ASCII code. 4311 * 4312 * @returns {undefined} 4313 * Returns `undefined`. 4314 * 4315 * @example 4316 * // Zero-fill a buffer 4317 * let buf = ffi.ctype('char[10]'); 4318 * ffi.fill(buf, 10, 0); 4319 * 4320 * // Fill with specific byte 4321 * ffi.fill(buf, 10, 0xFF); 4322 * 4323 * // Fill with character 4324 * ffi.fill(buf, 10, 'A'); // Fills with 65 (ASCII for 'A') 4325 */ 4326 static uc_value_t * 4327 uc_ffi_fill(uc_vm_t *vm, size_t nargs) 4328 { 4329 void *dp = ffi_checkptr(vm, nargs, 0, CTID_P_VOID); 4330 size_t len = ucv_int64_get(uc_fn_arg(1)); 4331 uc_value_t *fill = uc_fn_arg(2); 4332 int chr = 0; 4333 4334 switch (ucv_type(fill)) 4335 { 4336 case UC_INTEGER: 4337 case UC_DOUBLE: 4338 chr = ucv_int64_get(fill); 4339 break; 4340 4341 case UC_BOOLEAN: 4342 chr = ucv_boolean_get(fill) ? 1 : 0; 4343 break; 4344 4345 case UC_STRING: 4346 chr = ucv_string_get(fill)[0]; 4347 break; 4348 4349 default: 4350 chr = 0; 4351 break; 4352 } 4353 4354 memset(dp, chr, len); 4355 4356 return NULL; 4357 } 4358 4359 /** 4360 * Cast a value to a different C type. 4361 * 4362 * The `cast()` function converts a value to a specified C type. It supports 4363 * casts to numbers, enums, and pointers. The cast is performed without 4364 * intermediate ucode type conversions. 4365 * 4366 * @function module:ffi#cast 4367 * 4368 * @param {string} type 4369 * The target C type declaration. 4370 * 4371 * @param {*} value 4372 * The value to cast. Can be a ucode value or cdata. 4373 * 4374 * @returns {module:ffi.CData} 4375 * A cdata of the target type holding the cast value. 4376 * 4377 * @throws {Error} 4378 * Throws an exception if the cast is invalid (e.g., casting to a struct). 4379 * 4380 * @example 4381 * // Cast number to pointer 4382 * let ptr = ffi.cast('void *', 0x1000); 4383 * print(ptr.get()); // => 4096 4384 * 4385 * @example 4386 * // Cast between pointer types 4387 * ffi.cdef('int x;'); 4388 * let px = ffi.ctype('int *', ffi.ctype('int', 42).ptr()); 4389 * let pv = ffi.cast('void *', px); 4390 * 4391 * @example 4392 * // Cast pointer to integer 4393 * let str = ffi.string("hello"); 4394 * let addr = ffi.cast('uintptr_t', str.ptr()); 4395 * print(addr.get()); // => address as number 4396 * 4397 * @example 4398 * // Cast integer to enum 4399 * ffi.cdef('enum color { RED, GREEN, BLUE };'); 4400 * let c = ffi.cast('enum color', 2); // => BLUE 4401 */ 4402 static uc_value_t * 4403 uc_ffi_cast(uc_vm_t *vm, size_t nargs) 4404 { 4405 CTState *cts = ctype_cts(vm); 4406 CTypeID id = ffi_checkctype(vm, nargs, 0, cts, NULL); 4407 CType *d = ctype_raw(cts, id); 4408 uc_value_t *init = uc_fn_arg(1); 4409 GCcdata *cd = ucv_resource_data(init, "ffi.ctype"); 4410 4411 if (!ctype_isnum(d->info) && !ctype_isptr(d->info) && !ctype_isenum(d->info)) { 4412 uc_value_t *repr = uc_ctype_repr(vm, id, NULL); 4413 4414 uc_vm_raise_exception(vm, EXCEPTION_TYPE, 4415 "invalid cast to type '%s', only casts to " 4416 "numbers, enums or pointers are allowed", 4417 ucv_string_get(repr)); 4418 4419 ucv_put(repr); 4420 4421 return NULL; 4422 } 4423 4424 if (cd && cd->ctypeid == id) 4425 return ucv_get(init); 4426 4427 uc_value_t *res = uc_cdata_new(vm, id, d->size); 4428 uc_value_t *refs = NULL; 4429 4430 /* when we're casting to pointer, keep references to original memory */ 4431 if (cd && ctype_isptr(d->info)) { 4432 refs = ucv_array_new(vm); 4433 4434 /* keep reference to original value itself */ 4435 ucv_array_push(refs, ucv_get(init)); 4436 4437 /* merge original values references */ 4438 uc_value_t *src_refs = cd->refs; 4439 4440 for (size_t i = 0; i < ucv_array_length(src_refs); i++) 4441 ucv_array_push(refs, ucv_get(ucv_array_get(src_refs, i))); 4442 } 4443 4444 uc_cconv_ct_tv(cts, d, uc_cdata_dataptr(res), init, CCF_CAST, &refs); 4445 4446 cd = ucv_resource_data(res, "ffi.ctype"); 4447 cd->refs = refs; 4448 4449 return res; 4450 } 4451 4452 /** 4453 * Cast a cdata to a different C type. 4454 * 4455 * The `.cast()` method converts a cdata to a specified C type. This is 4456 * equivalent to calling `ffi.cast(type, cdata)`. It supports casts to 4457 * numbers, enums, and pointers. 4458 * 4459 * @function module:ffi.CData#cast 4460 * 4461 * @param {string} type 4462 * The target C type declaration. 4463 * 4464 * @returns {module:ffi.CData} 4465 * A cdata of the target type holding the cast value. 4466 * 4467 * @throws {Error} 4468 * Throws an exception if the cast is invalid. 4469 * 4470 * @example 4471 * // Cast pointer to void* 4472 * let px = ffi.ctype('int *', ffi.ctype('int', 42).ptr()); 4473 * let pv = px.cast('void *'); 4474 * 4475 * @example 4476 * // Cast pointer to integer 4477 * let str = ffi.string("hello"); 4478 * let addr = str.ptr().cast('uintptr_t'); 4479 * print(addr.get()); 4480 * 4481 * @see {@link module:ffi#cast|ffi.cast()} 4482 */ 4483 static uc_value_t * 4484 uc_ctype_cast(uc_vm_t *vm, size_t nargs) 4485 { 4486 uc_value_t *this_arg = _uc_fn_this_res(vm); 4487 GCcdata *cd = ucv_resource_data(this_arg, "ffi.ctype"); 4488 4489 if (!cd) 4490 return NULL; 4491 4492 CTState *cts = ctype_cts(vm); 4493 CTypeID id = ffi_checkctype(vm, nargs, 0, cts, NULL); 4494 CType *d = ctype_raw(cts, id); 4495 4496 if (!ctype_isnum(d->info) && !ctype_isptr(d->info) && !ctype_isenum(d->info)) { 4497 uc_value_t *repr = uc_ctype_repr(vm, id, NULL); 4498 4499 uc_vm_raise_exception(vm, EXCEPTION_TYPE, 4500 "invalid cast to type '%s', only casts to " 4501 "numbers, enums or pointers are allowed", 4502 ucv_string_get(repr)); 4503 4504 ucv_put(repr); 4505 4506 return NULL; 4507 } 4508 4509 if (cd->ctypeid == id) 4510 return ucv_get(this_arg); 4511 4512 uc_value_t *res = uc_cdata_new(vm, id, d->size); 4513 uc_value_t *refs = NULL; 4514 4515 /* when we're casting to pointer, keep references to original memory */ 4516 if (ctype_isptr(d->info)) { 4517 refs = ucv_array_new(vm); 4518 4519 /* keep reference to original value itself */ 4520 ucv_array_push(refs, ucv_get(this_arg)); 4521 4522 /* merge original values references */ 4523 uc_value_t *src_refs = cd->refs; 4524 4525 for (size_t i = 0; i < ucv_array_length(src_refs); i++) 4526 ucv_array_push(refs, ucv_get(ucv_array_get(src_refs, i))); 4527 } 4528 4529 uc_cconv_ct_tv(cts, d, uc_cdata_dataptr(res), this_arg, CCF_CAST, &refs); 4530 4531 GCcdata *res_cd = ucv_resource_data(res, "ffi.ctype"); 4532 res_cd->refs = refs; 4533 4534 return res; 4535 } 4536 4537 /** 4538 * Copy memory to a cdata from a source. 4539 * 4540 * The `.copy()` method copies memory from a source to this cdata. If the 4541 * source is a ucode string, it copies the string including its null 4542 * terminator. Otherwise, an explicit length can be provided. 4543 * 4544 * @function module:ffi.CData#copy 4545 * 4546 * @param {string|module:ffi.CData} src 4547 * Source string or pointer. 4548 * 4549 * @param {number} [len] 4550 * Number of bytes to copy. Required if src is not a string. 4551 * 4552 * @returns {undefined} 4553 * Returns `undefined`. 4554 * 4555 * @example 4556 * // Copy string into buffer 4557 * let buf = ffi.ctype('char[10]'); 4558 * buf.copy("hello"); 4559 * 4560 * @example 4561 * // Copy with explicit length 4562 * let src = ffi.ctype('char[5]', [1, 2, 3, 4, 5]); 4563 * let dst = ffi.ctype('char[5]'); 4564 * dst.copy(src, 5); 4565 * 4566 * @see {@link module:ffi#copy|ffi.copy()} 4567 */ 4568 static uc_value_t * 4569 uc_ctype_copy(uc_vm_t *vm, size_t nargs) 4570 { 4571 GCcdata *cd = uc_fn_thisval("ffi.ctype"); 4572 4573 if (!cd) 4574 return NULL; 4575 4576 uc_value_t *sp_arg = uc_fn_arg(0); 4577 uc_value_t *len_arg = uc_fn_arg(1); 4578 void *dp = cdataptr(cd); 4579 void *sp = NULL; 4580 size_t len, dp_size, sp_size = SIZE_MAX; 4581 CTState *cts = ctype_cts(vm); 4582 4583 /* Get destination buffer size for bounds checking */ 4584 dp_size = ffi_cdata_bufsize(cts, (uc_value_t *)cd); 4585 4586 /* Handle string source: copy directly from the string buffer */ 4587 if (ucv_type(sp_arg) == UC_STRING) { 4588 const char *src = ucv_string_get(sp_arg); 4589 size_t src_len = ucv_string_length(sp_arg); 4590 4591 /* Determine length: use explicit len if provided, else string + null */ 4592 if (nargs > 1) { 4593 len = ucv_uint64_get(len_arg); 4594 } else { 4595 len = src_len + 1; 4596 } 4597 4598 sp = (void *)src; 4599 sp_size = src_len + 1; 4600 } else { 4601 sp = ffi_checkptr(vm, nargs, 0, CTID_P_CVOID); 4602 if (!sp) 4603 return NULL; 4604 4605 /* Get source buffer size for bounds checking */ 4606 sp_size = ffi_cdata_bufsize(cts, sp_arg); 4607 4608 if (nargs > 1) { 4609 len = ucv_uint64_get(len_arg); 4610 } else { 4611 len = strnlen((const char *)sp, sp_size); 4612 } 4613 } 4614 4615 /* Cap length to destination buffer size */ 4616 if (len > dp_size) 4617 len = dp_size; 4618 4619 /* Cap length to source buffer size */ 4620 if (len > sp_size) 4621 len = sp_size; 4622 4623 memcpy(dp, sp, len); 4624 4625 return NULL; 4626 } 4627 4628 /** 4629 * Convert a cdata to a ucode string. 4630 * 4631 * The `.string()` method reads a C string from a char* pointer or char[] 4632 * array and returns a ucode string. For char* pointers, it reads until the 4633 * null terminator. For char[] arrays, it reads up to the array length. 4634 * An optional length parameter can limit the bytes read. 4635 * 4636 * @function module:ffi.CData#string 4637 * 4638 * @param {number} [len] 4639 * Optional maximum length for reading C strings (reads up to `len` bytes 4640 * or until null terminator). 4641 * 4642 * @returns {string} 4643 * The extracted ucode string. 4644 * 4645 * @throws {Error} 4646 * Throws an exception if the cdata is not a char* or char[] type. 4647 * 4648 * @example 4649 * // Read from char* pointer 4650 * ffi.cdef('char *getenv(char *);'); 4651 * let ptr = ffi.C.wrap('char *getenv(char *)')("PATH"); 4652 * let path = ptr.string(); 4653 * print(path); 4654 * 4655 * @example 4656 * // Read from char[] array 4657 * let buf = ffi.ctype('char[10]', "hello"); 4658 * print(buf.string()); // => "hello" 4659 * 4660 * @example 4661 * // Read fixed-length string 4662 * let buf = ffi.ctype('char[10]', "hello world"); 4663 * print(buf.string(5)); // => "hello" 4664 * 4665 * @see {@link module:ffi#string|ffi.string()} 4666 */ 4667 static uc_value_t * 4668 uc_ctype_string(uc_vm_t *vm, size_t nargs) 4669 { 4670 uc_value_t *this_arg = _uc_fn_this_res(vm); 4671 GCcdata *cd = ucv_resource_data(this_arg, "ffi.ctype"); 4672 4673 if (!cd) 4674 return NULL; 4675 4676 uc_value_t *len_arg = uc_fn_arg(0); 4677 CTState *cts = ctype_cts(vm); 4678 CType *cd_ct = ctype_get(cts, cd->ctypeid); 4679 void *p = NULL; 4680 size_t sz; 4681 4682 /* Get pointer: arrays contain data directly, pointers contain address */ 4683 if (ctype_isarray(cd_ct->info)) { 4684 /* Array: data is directly in cdata, use array size as limit */ 4685 p = cdataptr(cd); 4686 if (nargs > 0) { 4687 size_t max_len = ucv_uint64_get(len_arg); 4688 sz = strnlen((const char *)p, max_len); 4689 } else { 4690 sz = strnlen((const char *)p, cd_ct->size); 4691 } 4692 } else if (ctype_isptr(cd_ct->info)) { 4693 /* Pointer: dereference and read null-terminated string */ 4694 p = *(void **)cdataptr(cd); 4695 if (!p) 4696 return ucv_string_new(""); 4697 4698 if (nargs > 0) { 4699 size_t max_len = ucv_uint64_get(len_arg); 4700 sz = strnlen((const char *)p, max_len); 4701 } else { 4702 sz = strlen((const char *)p); 4703 } 4704 } else { 4705 uc_vm_raise_exception(vm, EXCEPTION_TYPE, 4706 "string() requires char* or char[] type, got %s", 4707 ucv_typename(this_arg)); 4708 4709 return NULL; 4710 } 4711 4712 return ucv_string_new_length((const char *)p, sz); 4713 } 4714 4715 #if UC_TARGET_CYGWIN 4716 #define CLIB_SOPREFIX "cyg" 4717 #else 4718 #define CLIB_SOPREFIX "lib" 4719 #endif 4720 4721 #if defined(__APPLE__) 4722 #define CLIB_SOEXT "%s.dylib" 4723 #elif UC_TARGET_CYGWIN 4724 #define CLIB_SOEXT "%s.dll" 4725 #else 4726 #define CLIB_SOEXT "%s.so" 4727 #endif 4728 4729 /** 4730 * Load a shared library. 4731 * 4732 * The `dlopen()` function loads a shared library into the process address 4733 * space and returns a CLib object that can be used to access symbols via 4734 * `dlsym()` or `wrap()`. 4735 * 4736 * ```javascript 4737 * // Load zlib compression library 4738 * let libz = ffi.dlopen('z'); 4739 * 4740 * // Load OpenSSL crypto library 4741 * let libcrypto = ffi.dlopen('crypto'); 4742 * 4743 * // Load absolute path 4744 * let custom = ffi.dlopen('/usr/local/lib/mylib.so'); 4745 * 4746 * // Use wrap() to get function pointers 4747 * let zlibVersion = libz.wrap('const char *zlibVersion(void)'); 4748 * print(zlibVersion().slice(), "\n"); // => "1.2.11" 4749 * ``` 4750 * 4751 * On Unix-like systems, the `.so` extension is automatically appended if 4752 * omitted. On macOS, `.dylib` is used. On Windows, `.dll` is used. 4753 * 4754 * When the optional third argument is provided, `dlopen()` will: 4755 * - Parse the C definitions to register types and function prototypes 4756 * - Resolve and wrap all declared functions 4757 * - Attach the wrapped functions as methods on the library object 4758 * 4759 * @function module:ffi#dlopen 4760 * 4761 * @param {string} name 4762 * The library name or path. 4763 * 4764 * @param {boolean} [global=false] 4765 * If `true`, make symbols available to subsequently loaded libraries. 4766 * 4767 * @param {string} [cdefs] 4768 * Optional C declaration string containing types and function prototypes. 4769 * Function declarations will be automatically wrapped and attached to the 4770 * library object as methods. 4771 * 4772 * @returns {?module:ffi.CLib} 4773 * A CLib object representing the loaded library, or `null` on error. 4774 * When `cdefs` is provided, the returned CLib will have wrapped functions 4775 * attached as methods. 4776 * 4777 * @throws {Error} 4778 * Throws an exception if the library cannot be loaded or if C definitions 4779 * cannot be parsed. 4780 * 4781 * @example 4782 * // Load zlib and call functions 4783 * let libz = ffi.dlopen('z'); 4784 * let zlibVersion = libz.wrap('const char *zlibVersion(void)'); 4785 * print(zlibVersion().slice()); 4786 * 4787 * @example 4788 * // Load OpenSSL crypto library 4789 * let libcrypto = ffi.dlopen('crypto'); 4790 * let OpenSSL_version = libcrypto.wrap('const char *OpenSSL_version(int)'); 4791 * print(OpenSSL_version(0).slice()); // => "OpenSSL 3.0.0..." 4792 * 4793 * @example 4794 * // Load library with automatic wrapping 4795 * let libssl = ffi.dlopen('ssl', false, ` 4796 * typedef void SSL_METHOD; 4797 * const SSL_METHOD *TLS_method(void); 4798 * `); 4799 * // TLS_method is now directly callable 4800 * let method = libssl.TLS_method(); 4801 * 4802 * @example 4803 * // Load zlib with pre-wrapped functions 4804 * let libz = ffi.dlopen('z', false, ` 4805 * const char *zlibVersion(void); 4806 * uLong compressBound(uLong sourceLen); 4807 * `); 4808 * print(libz.zlibVersion().slice()); 4809 * print(libz.compressBound(1024)); 4810 */ 4811 static uc_value_t * 4812 uc_ffi_dlopen(uc_vm_t *vm, size_t nargs) 4813 { 4814 uc_value_t *name = uc_fn_arg(0); 4815 uc_value_t *global = uc_fn_arg(1); 4816 uc_value_t *cdefs = uc_fn_arg(2); 4817 uc_value_t *clibs = uc_vm_registry_get(vm, "ffi.clibs"); 4818 4819 /* Handle dlopen(null) or dlopen("") case - returns global C library */ 4820 if (!name || (ucv_type(name) == UC_STRING && !ucv_string_length(name))) { 4821 uc_value_t *global_lib = ucv_object_get(clibs, "", NULL); 4822 4823 /* If cdefs provided, parse them and add wrapped functions to ffi.C prototype */ 4824 if (cdefs && ucv_type(cdefs) == UC_STRING && ucv_string_length(cdefs)) { 4825 uc_ffi_clib_t *lib = ucv_resource_data(global_lib, "ffi.clib"); 4826 uc_value_t *methods = ucv_prototype_get(global_lib); 4827 4828 if (lib) { 4829 CTState *cts = ctype_cts(vm); 4830 CPState cp = { 4831 .uv_vm = vm, 4832 .cts = cts, 4833 .srcname = ucv_string_get(cdefs), 4834 .p = ucv_string_get(cdefs), 4835 .uv_param = NULL, 4836 .mode = CPARSE_MODE_MULTI | CPARSE_MODE_DIRECT, 4837 .func_ids = &cp.func_ids_buf, 4838 .func_ids_buf = { .count = 0, .entries = NULL }, 4839 .error = NULL 4840 }; 4841 4842 if (!uc_cparse(&cp)) 4843 return NULL; 4844 4845 for (size_t i = 0; i < cp.func_ids_buf.count; i++) { 4846 CTypeID id = cp.func_ids_buf.entries[i]; 4847 CType *ct = ctype_get(cts, id); 4848 if (!ct || !ct->uv_name || ucv_type(ct->uv_name) != UC_STRING) 4849 continue; 4850 4851 const char *symname = ucv_string_get(ct->uv_name); 4852 void *fp = dlsym(RTLD_DEFAULT, symname); 4853 if (!fp) 4854 continue; 4855 4856 CTypeID cid = ctype_typeid(cts, ct); 4857 size_t namelen = strlen(symname); 4858 size_t off = ALIGN(sizeof(uc_cfunction_t) + namelen + 1); 4859 4860 uc_cfunction_t *cfn = xalloc(off + sizeof(cid) + sizeof(fp)); 4861 cfn->header.type = UC_CFUNCTION; 4862 cfn->cfn = clib_wrapped_call; 4863 snprintf(cfn->name, namelen + 1, "ffi.C.%s", symname); 4864 4865 memcpy((char *)cfn + off, &cid, sizeof(cid)); 4866 memcpy((char *)cfn + off + sizeof(cid), &fp, sizeof(fp)); 4867 4868 uc_value_t *wrapped = ucv_get(&cfn->header); 4869 ucv_object_add(methods, symname, wrapped); 4870 } 4871 uc_vector_clear(&cp.func_ids_buf); 4872 } 4873 } 4874 4875 return ucv_get(global_lib); 4876 } 4877 4878 if (ucv_type(name) != UC_STRING) 4879 return NULL; 4880 4881 char *path = ucv_string_get(name); 4882 char *s = path; 4883 4884 /* relative name provided */ 4885 if (!strchr(path, '/') && !strchr(path, '\\') && !strchr(path, '.')) 4886 xasprintf(&s, CLIB_SOPREFIX CLIB_SOEXT, path); 4887 4888 int mode = RTLD_LAZY | (ucv_is_truish(global) ? RTLD_GLOBAL : RTLD_LOCAL); 4889 void *dlh = dlopen(s, mode); 4890 4891 if (!dlh) { 4892 uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, 4893 "unable to load library '%s' (%s): %s", 4894 path, s, dlerror()); 4895 4896 if (s != path) 4897 free(s); 4898 4899 return NULL; 4900 } 4901 4902 /* Per-name instance caching: check if this library name already exists */ 4903 uc_value_t *cached = ucv_object_get(clibs, s, NULL); 4904 4905 if (cached) { 4906 dlclose(dlh); 4907 4908 if (s != path) 4909 free(s); 4910 4911 return ucv_get(cached); 4912 } 4913 4914 uc_ffi_clib_t *lib; 4915 4916 /* Create instance prototype - wrapped functions live here as instance methods */ 4917 uc_value_t *methods = ucv_object_new(vm); 4918 4919 size_t libnamesize = strlen(s) + 1; 4920 size_t datasize = ((sizeof(uc_ffi_clib_t) + libnamesize + 7) / 8) * 8; 4921 4922 uc_value_t *lib_obj = ucv_resource_new_with_proto( 4923 vm, ucv_resource_type_lookup(vm, "ffi.clib"), 4924 (void **)&lib, 0, datasize, methods); 4925 4926 lib->cache = ucv_object_new(vm); 4927 lib->dlh = dlh; 4928 4929 /* Copy library name into the allocated block right after the struct */ 4930 lib->name = memcpy((char *)lib + sizeof(uc_ffi_clib_t), s, libnamesize); 4931 4932 if (s != path) 4933 free(s); 4934 4935 /* If cdefs provided, parse them and wrap functions */ 4936 if (cdefs && ucv_type(cdefs) == UC_STRING && ucv_string_length(cdefs)) { 4937 CTState *cts = ctype_cts(vm); 4938 CPState cp = { 4939 .uv_vm = vm, 4940 .cts = cts, 4941 .srcname = ucv_string_get(cdefs), 4942 .p = ucv_string_get(cdefs), 4943 .uv_param = NULL, 4944 .mode = CPARSE_MODE_MULTI | CPARSE_MODE_DIRECT, 4945 .func_ids = &cp.func_ids_buf, 4946 .func_ids_buf = { .count = 0, .entries = NULL }, 4947 .error = NULL 4948 }; 4949 4950 if (!uc_cparse(&cp)) { 4951 if (lib->dlh != RTLD_DEFAULT) 4952 dlclose(lib->dlh); 4953 4954 ucv_put(lib_obj); 4955 4956 return NULL; 4957 } 4958 4959 for (size_t i = 0; i < cp.func_ids_buf.count; i++) { 4960 CTypeID id = cp.func_ids_buf.entries[i]; 4961 CType *ct = ctype_get(cts, id); 4962 if (!ct) 4963 continue; 4964 4965 if (!ct->uv_name || ucv_type(ct->uv_name) != UC_STRING) 4966 continue; 4967 4968 const char *symname = ucv_string_get(ct->uv_name); 4969 4970 void *fp = dlsym(dlh, symname); 4971 if (!fp) { 4972 uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, 4973 "unable to resolve symbol '%s' in library '%s'", 4974 symname, lib->name); 4975 4976 if (lib->dlh != RTLD_DEFAULT) 4977 dlclose(lib->dlh); 4978 4979 ucv_put(lib_obj); 4980 4981 return NULL; 4982 } 4983 4984 CTypeID cid = ctype_typeid(cts, ct); 4985 size_t fnamelen = strlen(symname); 4986 size_t off = ALIGN(sizeof(uc_cfunction_t) + fnamelen + 1); 4987 4988 uc_cfunction_t *cfn = xalloc(off + sizeof(cid) + sizeof(fp)); 4989 cfn->header.type = UC_CFUNCTION; 4990 cfn->cfn = clib_wrapped_call; 4991 snprintf(cfn->name, fnamelen + 1, "ffi.%s.%s", lib->name, symname); 4992 4993 memcpy((char *)cfn + off, &cid, sizeof(cid)); 4994 memcpy((char *)cfn + off + sizeof(cid), &fp, sizeof(fp)); 4995 4996 uc_value_t *wrapped = ucv_get(&cfn->header); 4997 ucv_object_add(methods, symname, wrapped); 4998 } 4999 5000 uc_vector_clear(&cp.func_ids_buf); 5001 } 5002 5003 ucv_object_add(clibs, lib->name, ucv_get(lib_obj)); 5004 5005 return lib_obj; 5006 } 5007 5008 /** 5009 * Import a C library with automatic function wrapping. 5010 * 5011 * This is a convenience function that combines library loading, type 5012 * declaration, and function wrapping into a single call. It loads the 5013 * specified library, parses the C definitions, and returns an object 5014 * with all functions pre-wrapped and ready to call. 5015 * 5016 * @function module:ffi#import 5017 * 5018 * @param {string} libname 5019 * The library name or path to load. Can be a bare name (e.g., 'z'), 5020 * a filename (e.g., 'libcrypto.so.3'), or an absolute path. 5021 * 5022 * @param {string} cdefs 5023 * A C declaration string containing function prototypes to import. 5024 * Only function declarations are wrapped; types, structs, and other 5025 * declarations are registered but not added to the result object. 5026 * 5027 * @returns {object|null} 5028 * An object containing wrapped functions keyed by their symbol names. 5029 * Returns null if the library cannot be loaded or if parsing fails. 5030 * 5031 * @throws {Error} 5032 * Throws an exception if: 5033 * - The library cannot be loaded 5034 * - The C declarations are syntactically invalid 5035 * - A declared function cannot be resolved in the library 5036 * 5037 * @example 5038 * // Import sqlite3 with all functions 5039 * let sqlite3 = ffi.import('sqlite3', ` 5040 * const char *sqlite3_libversion(void); 5041 * int sqlite3_libversion_number(void); 5042 * int sqlite3_open(const char *, void **); 5043 * int sqlite3_close(void *); 5044 * `); 5045 * 5046 * print("Version: ", sqlite3.sqlite3_libversion(), "\n"); 5047 * 5048 * @example 5049 * // Import zlib functions 5050 * let zlib = ffi.import('z', ` 5051 * const char *zlibVersion(void); 5052 * uLong compressBound(uLong sourceLen); 5053 * `); 5054 * 5055 * print(zlib.zlibVersion()); 5056 * print(zlib.compressBound(1024)); 5057 */ 5058 static uc_value_t * 5059 uc_ffi_import(uc_vm_t *vm, size_t nargs) 5060 { 5061 uc_value_t *libname = uc_fn_arg(0); 5062 uc_value_t *cdefs = uc_fn_arg(1); 5063 CTState *cts = ctype_cts(vm); 5064 5065 if (!libname || ucv_type(libname) != UC_STRING) 5066 return NULL; 5067 5068 if (!cdefs || ucv_type(cdefs) != UC_STRING) 5069 return NULL; 5070 5071 /* Load the library */ 5072 char *path = ucv_string_get(libname); 5073 void *dlh = dlopen(path, RTLD_LAZY | RTLD_LOCAL); 5074 5075 if (!dlh) { 5076 uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, 5077 "unable to load library '%s' (%s): %s", 5078 ucv_string_get(libname), path, dlerror()); 5079 5080 return NULL; 5081 } 5082 5083 /* Parse C definitions to register types */ 5084 CPState cp = { 5085 .uv_vm = vm, 5086 .cts = cts, 5087 .srcname = ucv_string_get(cdefs), 5088 .p = ucv_string_get(cdefs), 5089 .uv_param = NULL, 5090 .mode = CPARSE_MODE_MULTI | CPARSE_MODE_DIRECT, 5091 .func_ids = &cp.func_ids_buf 5092 }; 5093 5094 if (!uc_cparse(&cp)) { 5095 dlclose(dlh); 5096 return NULL; 5097 } 5098 5099 /* Create result object */ 5100 uc_value_t *result = ucv_object_new(vm); 5101 5102 /* Iterate over recorded function type IDs */ 5103 for (size_t i = 0; i < cp.func_ids_buf.count; i++) { 5104 CTypeID id = cp.func_ids_buf.entries[i]; 5105 CType *ct = ctype_get(cts, id); 5106 if (!ct) 5107 continue; 5108 5109 /* Get the function name */ 5110 if (!ct->uv_name || ucv_type(ct->uv_name) != UC_STRING) 5111 continue; 5112 5113 const char *symname = ucv_string_get(ct->uv_name); 5114 5115 /* Resolve the symbol from the library */ 5116 void *fp = dlsym(dlh, symname); 5117 if (!fp) { 5118 uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, 5119 "unable to resolve symbol '%s' in library", symname); 5120 5121 ucv_put(result); 5122 dlclose(dlh); 5123 return NULL; 5124 } 5125 5126 /* Wrap the function - create cfunction wrapper */ 5127 CTypeID cid = ctype_typeid(cts, ct); 5128 size_t namelen = strlen(symname); 5129 size_t off = ALIGN(sizeof(uc_cfunction_t) + namelen + 1); 5130 5131 uc_cfunction_t *cfn = xalloc(off + sizeof(cid) + sizeof(fp)); 5132 cfn->header.type = UC_CFUNCTION; 5133 cfn->cfn = clib_wrapped_call; 5134 snprintf(cfn->name, namelen + 1, "ffi.import.%s", symname); 5135 5136 /* Store cid and fp after the cfunction struct */ 5137 memcpy((char *)cfn + off, &cid, sizeof(cid)); 5138 memcpy((char *)cfn + off + sizeof(cid), &fp, sizeof(fp)); 5139 5140 uc_value_t *wrapped = ucv_get(&cfn->header); 5141 ucv_object_add(result, symname, wrapped); 5142 /* ucv_object_add already increments refcount, no need to put */ 5143 } 5144 5145 uc_vector_clear(&cp.func_ids_buf); 5146 dlclose(dlh); 5147 5148 return result; 5149 } 5150 5151 5152 static const uc_function_list_t clib_fns[] = { 5153 { "dlsym", uc_clib_dlsym }, 5154 { "resolve", uc_clib_resolve }, 5155 { "wrap", uc_clib_wrap }, 5156 }; 5157 5158 static const uc_function_list_t ctype_fns[] = { 5159 { "call", uc_ctype_call }, 5160 { "free", uc_ctype_free }, 5161 { "get", uc_ctype_get }, 5162 { "set", uc_ctype_set }, 5163 { "ptr", uc_ctype_ptr }, 5164 { "index", uc_ctype_index }, 5165 { "deref", uc_ctype_deref }, 5166 { "size", uc_ctype_sizeof }, 5167 { "length", uc_ctype_length }, 5168 { "itemsize", uc_ctype_itemsize }, 5169 { "slice", uc_ctype_slice }, 5170 { "tostring", uc_ctype_tostring }, 5171 { "cast", uc_ctype_cast }, 5172 { "copy", uc_ctype_copy }, 5173 { "string", uc_ctype_string }, 5174 }; 5175 5176 static const uc_function_list_t global_fns[] = { 5177 { "ctype", uc_ffi_ctype }, 5178 { "cdef", uc_ffi_cdef }, 5179 { "typeof", uc_ffi_typeof }, 5180 { "sizeof", uc_ffi_sizeof }, 5181 { "alignof", uc_ffi_alignof }, 5182 { "offsetof", uc_ffi_offsetof }, 5183 { "errno", uc_ffi_errno }, 5184 { "string", uc_ffi_string }, 5185 { "copy", uc_ffi_copy }, 5186 { "fill", uc_ffi_fill }, 5187 { "cast", uc_ffi_cast }, 5188 { "dlopen", uc_ffi_dlopen }, 5189 { "import", uc_ffi_import }, 5190 }; 5191 5192 5193 static void 5194 close_clib(void *ud) 5195 { 5196 uc_ffi_clib_t *clib = ud; 5197 5198 ucv_put(clib->cache); 5199 5200 if (clib->dlh != RTLD_DEFAULT) 5201 dlclose(clib->dlh); 5202 } 5203 5204 static void 5205 close_ctype(void *ud) 5206 { 5207 GCcdata *cd = ud; 5208 5209 /* ucode does not create libffi closure cdata objects; 5210 * closures are created transiently for callback arguments. */ 5211 5212 if (cd->refs) 5213 ucv_put(cd->refs); 5214 } 5215 5216 5217 extern char **environ; 5218 5219 static void 5220 preload_type(uc_vm_t *vm, uc_ffi_clib_t *lib, const char *cdef, void *val) 5221 { 5222 CTState *cts = ctype_cts(vm); 5223 uc_value_t *def = ucv_string_new(cdef); 5224 CTypeID cid = uv_to_ct(vm, CPARSE_MODE_DIRECT | CPARSE_MODE_NOIMPLICIT | CPARSE_MODE_MULTI, def, NULL); 5225 5226 ucv_put(def); 5227 5228 if (!cid) 5229 return; 5230 5231 CType *ct = ctype_get(cts, cid); 5232 5233 if (!ct || ucv_type(ct->uv_name) != UC_STRING) 5234 return; 5235 5236 uc_value_t *sym = uc_cdata_new(vm, cid, CTSIZE_PTR); 5237 5238 *(void **)uc_cdata_dataptr(sym) = val; 5239 5240 ucv_object_add(lib->cache, ucv_string_get(ct->uv_name), sym); 5241 } 5242 5243 void uc_module_init(uc_vm_t *vm, uc_value_t *scope) 5244 { 5245 uc_ctype_init(vm); 5246 5247 uc_type_declare(vm, "ffi.clib", clib_fns, close_clib); 5248 uc_type_declare(vm, "ffi.ctype", ctype_fns, close_ctype); 5249 5250 uc_function_list_register(scope, global_fns); 5251 5252 uc_value_t *clibs = ucv_object_new(vm); 5253 5254 uc_vm_registry_set(vm, "ffi.clibs", clibs); 5255 5256 uc_ffi_clib_t *C; 5257 uc_value_t *global_proto = ucv_object_new(vm); 5258 5259 uc_value_t *stdlib = ucv_resource_new_with_proto( 5260 vm, ucv_resource_type_lookup(vm, "ffi.clib"), 5261 (void **)&C, 0, sizeof(*C), global_proto); 5262 5263 C->dlh = RTLD_DEFAULT; 5264 C->name = NULL; 5265 C->cache = ucv_object_new(vm); 5266 5267 ucv_object_add(scope, "C", stdlib); 5268 ucv_object_add(clibs, "", ucv_get(stdlib)); 5269 5270 /* preload global variables */ 5271 preload_type(vm, C, "int *errno", &errno); 5272 preload_type(vm, C, "char **environ", environ); 5273 } 5274
This page was automatically generated by LXR 0.3.1. • OpenWrt