koffi 2.14.0-beta.2 → 2.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/build/koffi/darwin_arm64/koffi.node +0 -0
- package/build/koffi/darwin_x64/koffi.node +0 -0
- package/build/koffi/freebsd_arm64/koffi.node +0 -0
- package/build/koffi/freebsd_ia32/koffi.node +0 -0
- package/build/koffi/freebsd_x64/koffi.node +0 -0
- package/build/koffi/linux_arm64/koffi.node +0 -0
- package/build/koffi/linux_armhf/koffi.node +0 -0
- package/build/koffi/linux_ia32/koffi.node +0 -0
- package/build/koffi/linux_loong64/koffi.node +0 -0
- package/build/koffi/linux_riscv64d/koffi.node +0 -0
- package/build/koffi/linux_x64/koffi.node +0 -0
- package/build/koffi/musl_arm64/koffi.node +0 -0
- package/build/koffi/musl_x64/koffi.node +0 -0
- package/build/koffi/openbsd_ia32/koffi.node +0 -0
- package/build/koffi/openbsd_x64/koffi.node +0 -0
- package/build/koffi/win32_arm64/koffi.node +0 -0
- package/build/koffi/win32_ia32/koffi.node +0 -0
- package/build/koffi/win32_x64/koffi.node +0 -0
- package/doc/pages/benchmarks.md +11 -11
- package/doc/pages/input.md +66 -0
- package/doc/static/perf_linux.png +0 -0
- package/doc/static/perf_windows.png +0 -0
- package/index.d.ts +2 -2
- package/index.js +1 -1
- package/indirect.js +1 -1
- package/package.json +1 -1
- package/src/core/base/base.cc +25 -0
- package/src/core/base/base.hh +1 -0
- package/src/koffi/src/call.cc +30 -27
- package/src/koffi/src/call.hh +4 -4
- package/src/koffi/src/ffi.cc +38 -34
- package/src/koffi/src/util.cc +7 -4
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,17 @@
|
|
|
5
5
|
|
|
6
6
|
## Koffi 2
|
|
7
7
|
|
|
8
|
+
### Koffi 2.14
|
|
9
|
+
|
|
10
|
+
#### Koffi 2.14.0
|
|
11
|
+
|
|
12
|
+
*Released on 2025-08-17*
|
|
13
|
+
|
|
14
|
+
- Improve support for structs with [flexible array member](input#flexible-arrays)
|
|
15
|
+
- Automatically encode/decode dynamic arrays pointers when length is known though struct member
|
|
16
|
+
- Fix parser crash when direction qualifier is followed by unknown type
|
|
17
|
+
- Add missing TypeScript types and arguments
|
|
18
|
+
|
|
8
19
|
### Koffi 2.13
|
|
9
20
|
|
|
10
21
|
#### Koffi 2.13.0
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/doc/pages/benchmarks.md
CHANGED
|
@@ -15,7 +15,7 @@ These results are detailed and explained below, and compared to node-ffi/node-ff
|
|
|
15
15
|
|
|
16
16
|
# Linux x86_64
|
|
17
17
|
|
|
18
|
-
The results presented below were measured on my x86_64 Linux machine (
|
|
18
|
+
The results presented below were measured on my x86_64 Linux machine (AMD Ryzen™ 5 2600).
|
|
19
19
|
|
|
20
20
|
## rand results
|
|
21
21
|
|
|
@@ -27,9 +27,9 @@ This test is based around repeated calls to a simple standard C function `rand`,
|
|
|
27
27
|
|
|
28
28
|
Benchmark | Iteration time | Relative performance | Overhead
|
|
29
29
|
------------- | -------------- | -------------------- | --------
|
|
30
|
-
rand_napi |
|
|
31
|
-
rand_koffi |
|
|
32
|
-
rand_node_ffi |
|
|
30
|
+
rand_napi | 569 ns | x1.00 | (ref)
|
|
31
|
+
rand_koffi | 855 ns | x0.67 | +50%
|
|
32
|
+
rand_node_ffi | 58730 ns | x0.010 | +10228%
|
|
33
33
|
|
|
34
34
|
Because rand is a pretty small function, the FFI overhead is clearly visible.
|
|
35
35
|
|
|
@@ -39,9 +39,9 @@ This test is similar to the rand one, but it is based on `atoi`, which takes a s
|
|
|
39
39
|
|
|
40
40
|
Benchmark | Iteration time | Relative performance | Overhead
|
|
41
41
|
------------- | -------------- | -------------------- | --------
|
|
42
|
-
atoi_napi |
|
|
43
|
-
atoi_koffi |
|
|
44
|
-
atoi_node_ffi |
|
|
42
|
+
atoi_napi | 1039 ns | x1.00 | (ref)
|
|
43
|
+
atoi_koffi | 1642 ns | x0.63 | +58%
|
|
44
|
+
atoi_node_ffi | 164790 ns | x0.006 | +15767%
|
|
45
45
|
|
|
46
46
|
Because atoi is a pretty small function, the FFI overhead is clearly visible.
|
|
47
47
|
|
|
@@ -54,10 +54,10 @@ This benchmark uses the CPU-based image drawing functions in Raylib. The calls a
|
|
|
54
54
|
|
|
55
55
|
Benchmark | Iteration time | Relative performance | Overhead
|
|
56
56
|
------------------ | -------------- | -------------------- | --------
|
|
57
|
-
raylib_cc |
|
|
58
|
-
raylib_node_raylib |
|
|
59
|
-
raylib_koffi | 28.
|
|
60
|
-
raylib_node_ffi |
|
|
57
|
+
raylib_cc | 17.5 µs | x1.34 | -25%
|
|
58
|
+
raylib_node_raylib | 23.4 µs | x1.00 | (ref)
|
|
59
|
+
raylib_koffi | 28.8 µs | x0.81 | +23%
|
|
60
|
+
raylib_node_ffi | 103.9 µs | x0.23 | +344%
|
|
61
61
|
|
|
62
62
|
# Windows x86_64
|
|
63
63
|
|
package/doc/pages/input.md
CHANGED
|
@@ -396,6 +396,72 @@ Koffi can also convert JS strings to fixed-sized arrays in the following cases:
|
|
|
396
396
|
|
|
397
397
|
The reverse case is also true, Koffi can convert a C fixed-size buffer to a JS string. This happens by default for char, char16_t and char32_t arrays, but you can also explicitly ask for this with the `String` array hint (e.g. `koffi.array('char', 8, 'String')`).
|
|
398
398
|
|
|
399
|
+
## Flexible arrays
|
|
400
|
+
|
|
401
|
+
*Added in Koffi 2.14.0*
|
|
402
|
+
|
|
403
|
+
C structs ending with a flexible array member are often used for variable-sized structs, and are generally paired with dynamic memory allocation. In many cases, the number of elements is described by another struct member.
|
|
404
|
+
|
|
405
|
+
Use `koffi.array(type, countedBy, maxLen)` to make a flexible array type, for which the array length is determined by the struct member indicated by the `countedBy` parameter. Flexible array types can only be used as the last member of a struct, as shown below:
|
|
406
|
+
|
|
407
|
+
```js
|
|
408
|
+
const FlexibleArray = koffi.struct('FlexibleArray', {
|
|
409
|
+
count: 'size_t',
|
|
410
|
+
numbers: koffi.array('int', 'count', 128)
|
|
411
|
+
});
|
|
412
|
+
````
|
|
413
|
+
|
|
414
|
+
For various reasons, Koffi requires you to specify an upper bound (`maxLen`) for the number of elements in the flexible array member.
|
|
415
|
+
|
|
416
|
+
> [!WARNING]
|
|
417
|
+
> Also, unlike C flexible arrays, the struct size will expand to accomodate the maximum size of the flexible array.
|
|
418
|
+
|
|
419
|
+
The following example illustrates how to use a flexible array API in C from Koffi.
|
|
420
|
+
|
|
421
|
+
```c
|
|
422
|
+
// Build with: clang -fPIC -o flexible.so -shared flexible.c -Wall -O2
|
|
423
|
+
|
|
424
|
+
#include <stddef.h>
|
|
425
|
+
|
|
426
|
+
struct FlexibleArray {
|
|
427
|
+
size_t count;
|
|
428
|
+
int numbers[];
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
void AppendValues(struct FlexibleArray *arr, size_t count, int start, int step)
|
|
432
|
+
{
|
|
433
|
+
for (size_t i = 0; i < count; i++) {
|
|
434
|
+
arr->numbers[arr->count + i] = start + i * step;
|
|
435
|
+
}
|
|
436
|
+
arr->count += count;
|
|
437
|
+
}
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
```js
|
|
441
|
+
// ES6 syntax: import koffi from 'koffi';
|
|
442
|
+
const koffi = require('koffi');
|
|
443
|
+
|
|
444
|
+
const lib = koffi.load('./flexible.so');
|
|
445
|
+
|
|
446
|
+
const FlexibleArray = koffi.struct('FlexibleArray', {
|
|
447
|
+
count: 'size_t',
|
|
448
|
+
numbers: koffi.array('int', 'count', 256, 'Array')
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
const AppendValues = lib.func('void AppendValues(_Inout_ FlexibleArray *arr, int count, int start, int step)');
|
|
452
|
+
|
|
453
|
+
let array = { count: 0, numbers: [] };
|
|
454
|
+
|
|
455
|
+
AppendValues(array, 5, 1, 1);
|
|
456
|
+
console.log(array); // Prints { count: 5, numbers: [1, 2, 3, 4, 5] }
|
|
457
|
+
|
|
458
|
+
AppendValues(array, 3, 10, 2);
|
|
459
|
+
console.log(array); // Prints { count: 8, numbers: [1, 2, 3, 4, 5, 10, 12, 14] }
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
> [!NOTE]
|
|
463
|
+
> This is frequently used in the Win32 API, an exemple of this is [AllocateAndInitializeSid()](https://learn.microsoft.com/windows/win32/api/securitybaseapi/nf-securitybaseapi-allocateandinitializesid).
|
|
464
|
+
|
|
399
465
|
## Dynamic arrays (pointers)
|
|
400
466
|
|
|
401
467
|
In C, dynamically-sized arrays are usually passed around as pointers. Read more about [array pointers](pointers#dynamic-arrays) in the relevant section.
|
|
Binary file
|
|
Binary file
|
package/index.d.ts
CHANGED
|
@@ -105,14 +105,14 @@ export class Union {
|
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
export function array(ref: TypeSpec, len: number, hint?: ArrayHint | null): IKoffiCType;
|
|
108
|
-
export function array(ref: TypeSpec, countedBy: string,
|
|
108
|
+
export function array(ref: TypeSpec, countedBy: string, maxLen: number, hint?: ArrayHint | null): IKoffiCType;
|
|
109
109
|
|
|
110
110
|
export function opaque(name: string | null | undefined): IKoffiCType;
|
|
111
111
|
export function opaque(): IKoffiCType;
|
|
112
112
|
/** @deprecated */ export function handle(name: string | null | undefined): IKoffiCType;
|
|
113
113
|
/** @deprecated */ export function handle(): IKoffiCType;
|
|
114
114
|
|
|
115
|
-
export function pointer(ref: TypeSpec
|
|
115
|
+
export function pointer(ref: TypeSpec): IKoffiCType;
|
|
116
116
|
export function pointer(ref: TypeSpec, count: number): IKoffiCType;
|
|
117
117
|
export function pointer(name: string | null | undefined, ref: TypeSpec, countedBy?: string | null): IKoffiCType;
|
|
118
118
|
export function pointer(name: string | null | undefined, ref: TypeSpec, count: number): IKoffiCType;
|
package/index.js
CHANGED
|
@@ -402,7 +402,7 @@ var require_package = __commonJS({
|
|
|
402
402
|
"../../../bin/Koffi/package/src/koffi/package.json"(exports2, module2) {
|
|
403
403
|
module2.exports = {
|
|
404
404
|
name: "koffi",
|
|
405
|
-
version: "2.14.0
|
|
405
|
+
version: "2.14.0",
|
|
406
406
|
description: "Fast and simple C FFI (foreign function interface) for Node.js",
|
|
407
407
|
keywords: [
|
|
408
408
|
"foreign",
|
package/indirect.js
CHANGED
|
@@ -402,7 +402,7 @@ var require_package = __commonJS({
|
|
|
402
402
|
"../../../bin/Koffi/package/src/koffi/package.json"(exports2, module2) {
|
|
403
403
|
module2.exports = {
|
|
404
404
|
name: "koffi",
|
|
405
|
-
version: "2.14.0
|
|
405
|
+
version: "2.14.0",
|
|
406
406
|
description: "Fast and simple C FFI (foreign function interface) for Node.js",
|
|
407
407
|
keywords: [
|
|
408
408
|
"foreign",
|
package/package.json
CHANGED
package/src/core/base/base.cc
CHANGED
|
@@ -854,6 +854,31 @@ TimeSpec DecomposeTimeLocal(int64_t time)
|
|
|
854
854
|
return spec;
|
|
855
855
|
}
|
|
856
856
|
|
|
857
|
+
int64_t ComposeTimeUTC(const TimeSpec &spec)
|
|
858
|
+
{
|
|
859
|
+
RG_ASSERT(!spec.offset);
|
|
860
|
+
|
|
861
|
+
struct tm ti = {};
|
|
862
|
+
|
|
863
|
+
ti.tm_year = spec.year - 1900;
|
|
864
|
+
ti.tm_mon = spec.month - 1;
|
|
865
|
+
ti.tm_mday = spec.day;
|
|
866
|
+
ti.tm_hour = spec.hour;
|
|
867
|
+
ti.tm_min = spec.min;
|
|
868
|
+
ti.tm_sec = spec.sec;
|
|
869
|
+
|
|
870
|
+
#if defined(_WIN32)
|
|
871
|
+
int64_t time = (int64_t)_mkgmtime64(&ti);
|
|
872
|
+
#else
|
|
873
|
+
int64_t time = (int64_t)timegm(&ti);
|
|
874
|
+
#endif
|
|
875
|
+
|
|
876
|
+
time *= 1000;
|
|
877
|
+
time += spec.msec;
|
|
878
|
+
|
|
879
|
+
return time;
|
|
880
|
+
}
|
|
881
|
+
|
|
857
882
|
// ------------------------------------------------------------------------
|
|
858
883
|
// Strings
|
|
859
884
|
// ------------------------------------------------------------------------
|
package/src/core/base/base.hh
CHANGED
|
@@ -3669,6 +3669,7 @@ struct TimeSpec {
|
|
|
3669
3669
|
|
|
3670
3670
|
TimeSpec DecomposeTimeUTC(int64_t time);
|
|
3671
3671
|
TimeSpec DecomposeTimeLocal(int64_t time);
|
|
3672
|
+
int64_t ComposeTimeUTC(const TimeSpec &spec);
|
|
3672
3673
|
|
|
3673
3674
|
// ------------------------------------------------------------------------
|
|
3674
3675
|
// Format
|
package/src/koffi/src/call.cc
CHANGED
|
@@ -517,7 +517,7 @@ bool CallData::PushObject(Napi::Object obj, const TypeInfo *type, uint8_t *origi
|
|
|
517
517
|
RG_UNREACHABLE();
|
|
518
518
|
}
|
|
519
519
|
|
|
520
|
-
|
|
520
|
+
MemSet(origin, 0, type->size);
|
|
521
521
|
|
|
522
522
|
for (Size i = 0; i < members.len; i++) {
|
|
523
523
|
const RecordMember &member = members[i];
|
|
@@ -715,13 +715,11 @@ bool CallData::PushObject(Napi::Object obj, const TypeInfo *type, uint8_t *origi
|
|
|
715
715
|
case PrimitiveKind::Array: {
|
|
716
716
|
if (value.IsArray()) {
|
|
717
717
|
Napi::Array array = value.As<Napi::Array>();
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
if (!PushNormalArray(array, len, member.type, dest))
|
|
718
|
+
if (!PushNormalArray(array, member.type, member.type->size, dest))
|
|
721
719
|
return false;
|
|
722
720
|
} else if (IsRawBuffer(value)) {
|
|
723
721
|
Span<const uint8_t> buffer = GetRawBuffer(value);
|
|
724
|
-
PushBuffer(buffer, member.type
|
|
722
|
+
PushBuffer(buffer, member.type, dest);
|
|
725
723
|
} else if (value.IsString()) {
|
|
726
724
|
if (!PushStringArray(value, member.type, dest))
|
|
727
725
|
return false;
|
|
@@ -763,15 +761,18 @@ bool CallData::PushObject(Napi::Object obj, const TypeInfo *type, uint8_t *origi
|
|
|
763
761
|
return true;
|
|
764
762
|
}
|
|
765
763
|
|
|
766
|
-
bool CallData::PushNormalArray(Napi::Array array,
|
|
764
|
+
bool CallData::PushNormalArray(Napi::Array array, const TypeInfo *type, Size size, uint8_t *origin)
|
|
767
765
|
{
|
|
768
766
|
RG_ASSERT(array.IsArray());
|
|
769
767
|
|
|
770
768
|
const TypeInfo *ref = type->ref.type;
|
|
769
|
+
Size len = (Size)array.Length();
|
|
770
|
+
Size available = len * ref->size;
|
|
771
771
|
|
|
772
|
-
if (
|
|
773
|
-
|
|
774
|
-
|
|
772
|
+
if (available > size) {
|
|
773
|
+
len = size / ref->size;
|
|
774
|
+
} else {
|
|
775
|
+
MemSet(origin + available, 0, size - available);
|
|
775
776
|
}
|
|
776
777
|
|
|
777
778
|
Size offset = 0;
|
|
@@ -945,13 +946,11 @@ bool CallData::PushNormalArray(Napi::Array array, Size len, const TypeInfo *type
|
|
|
945
946
|
|
|
946
947
|
if (value.IsArray()) {
|
|
947
948
|
Napi::Array array2 = value.As<Napi::Array>();
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
if (!PushNormalArray(array2, len2, ref, dest))
|
|
949
|
+
if (!PushNormalArray(array2, ref, (Size)ref->size, dest))
|
|
951
950
|
return false;
|
|
952
951
|
} else if (IsRawBuffer(value)) {
|
|
953
952
|
Span<const uint8_t> buffer = GetRawBuffer(value);
|
|
954
|
-
PushBuffer(buffer, ref
|
|
953
|
+
PushBuffer(buffer, ref, dest);
|
|
955
954
|
} else if (value.IsString()) {
|
|
956
955
|
if (!PushStringArray(value, ref, dest))
|
|
957
956
|
return false;
|
|
@@ -1001,13 +1000,13 @@ bool CallData::PushNormalArray(Napi::Array array, Size len, const TypeInfo *type
|
|
|
1001
1000
|
return true;
|
|
1002
1001
|
}
|
|
1003
1002
|
|
|
1004
|
-
void CallData::PushBuffer(Span<const uint8_t> buffer,
|
|
1003
|
+
void CallData::PushBuffer(Span<const uint8_t> buffer, const TypeInfo *type, uint8_t *origin)
|
|
1005
1004
|
{
|
|
1006
|
-
buffer.len = std::min(buffer.len, size);
|
|
1005
|
+
buffer.len = std::min(buffer.len, (Size)type->size);
|
|
1007
1006
|
|
|
1008
1007
|
// Go fast brrrrrrr :)
|
|
1009
|
-
MemCpy(origin, buffer.ptr,
|
|
1010
|
-
MemSet(origin + buffer.len, 0, (
|
|
1008
|
+
MemCpy(origin, buffer.ptr, buffer.len);
|
|
1009
|
+
MemSet(origin + buffer.len, 0, (Size)type->size - buffer.len);
|
|
1011
1010
|
|
|
1012
1011
|
#define SWAP(CType) \
|
|
1013
1012
|
do { \
|
|
@@ -1131,7 +1130,7 @@ bool CallData::PushPointer(Napi::Value value, const TypeInfo *type, int directio
|
|
|
1131
1130
|
ptr = AllocHeap(size, 16);
|
|
1132
1131
|
|
|
1133
1132
|
if (directions & 1) {
|
|
1134
|
-
if (!PushNormalArray(array,
|
|
1133
|
+
if (!PushNormalArray(array, type, size, ptr))
|
|
1135
1134
|
return false;
|
|
1136
1135
|
} else {
|
|
1137
1136
|
MemSet(ptr, 0, size);
|
|
@@ -1369,9 +1368,9 @@ void CallData::DumpForward(const FunctionInfo *func) const
|
|
|
1369
1368
|
bool CallData::CheckDynamicLength(Napi::Object obj, Size element, const char *countedby, Napi::Value value)
|
|
1370
1369
|
{
|
|
1371
1370
|
int64_t expected = -1;
|
|
1372
|
-
int64_t
|
|
1371
|
+
int64_t size = -1;
|
|
1373
1372
|
|
|
1374
|
-
// Get expected
|
|
1373
|
+
// Get expected size
|
|
1375
1374
|
{
|
|
1376
1375
|
Napi::Value by = obj.Get(countedby);
|
|
1377
1376
|
|
|
@@ -1380,24 +1379,28 @@ bool CallData::CheckDynamicLength(Napi::Object obj, Size element, const char *co
|
|
|
1380
1379
|
return false;
|
|
1381
1380
|
}
|
|
1382
1381
|
|
|
1383
|
-
|
|
1382
|
+
// If we get anywhere near overflow there are other problems to worry about.
|
|
1383
|
+
// So let's not worry about that.
|
|
1384
|
+
expected = GetNumber<int64_t>(by) * element;
|
|
1384
1385
|
}
|
|
1385
1386
|
|
|
1386
|
-
// Get actual
|
|
1387
|
+
// Get actual size
|
|
1387
1388
|
if (value.IsArray()) {
|
|
1388
1389
|
Napi::Array array = value.As<Napi::Array>();
|
|
1389
|
-
|
|
1390
|
+
size = array.Length() * element;
|
|
1390
1391
|
} else if (value.IsTypedArray()) {
|
|
1391
1392
|
Napi::TypedArray typed = value.As<Napi::TypedArray>();
|
|
1392
|
-
|
|
1393
|
+
size = typed.ByteLength();
|
|
1393
1394
|
} else if (value.IsArrayBuffer()) {
|
|
1394
1395
|
Napi::ArrayBuffer buffer = value.As<Napi::ArrayBuffer>();
|
|
1395
|
-
|
|
1396
|
+
size = buffer.ByteLength();
|
|
1397
|
+
} else if (!IsNullOrUndefined(value)) {
|
|
1398
|
+
size = element;
|
|
1396
1399
|
} else {
|
|
1397
|
-
|
|
1400
|
+
size = 0;
|
|
1398
1401
|
}
|
|
1399
1402
|
|
|
1400
|
-
if (
|
|
1403
|
+
if (size != expected) {
|
|
1401
1404
|
ThrowError<Napi::Error>(env, "Mismatched dynamic length between '%1' and actual array", countedby);
|
|
1402
1405
|
return false;
|
|
1403
1406
|
}
|
package/src/koffi/src/call.hh
CHANGED
|
@@ -121,8 +121,8 @@ public:
|
|
|
121
121
|
bool PushString32(Napi::Value value, int directions, const char32_t **out_str32);
|
|
122
122
|
Size PushString32Value(Napi::Value value, const char32_t **out_str32);
|
|
123
123
|
bool PushObject(Napi::Object obj, const TypeInfo *type, uint8_t *origin);
|
|
124
|
-
bool PushNormalArray(Napi::Array array,
|
|
125
|
-
void PushBuffer(Span<const uint8_t> buffer,
|
|
124
|
+
bool PushNormalArray(Napi::Array array, const TypeInfo *type, Size size, uint8_t *origin);
|
|
125
|
+
void PushBuffer(Span<const uint8_t> buffer, const TypeInfo *type, uint8_t *origin);
|
|
126
126
|
bool PushStringArray(Napi::Value value, const TypeInfo *type, uint8_t *origin);
|
|
127
127
|
bool PushPointer(Napi::Value value, const TypeInfo *type, int directions, void **out_ptr);
|
|
128
128
|
bool PushCallback(Napi::Value value, const TypeInfo *type, void **out_ptr);
|
|
@@ -156,7 +156,7 @@ inline bool CallData::AllocStack(Size size, Size align, T **out_ptr)
|
|
|
156
156
|
}
|
|
157
157
|
|
|
158
158
|
#if defined(RG_DEBUG)
|
|
159
|
-
|
|
159
|
+
MemSet(ptr, 0, delta);
|
|
160
160
|
#endif
|
|
161
161
|
|
|
162
162
|
mem->stack.len -= delta;
|
|
@@ -173,7 +173,7 @@ inline T *CallData::AllocHeap(Size size, Size align)
|
|
|
173
173
|
|
|
174
174
|
if (size < 4096 && delta <= mem->heap.len) [[likely]] {
|
|
175
175
|
#if defined(RG_DEBUG)
|
|
176
|
-
|
|
176
|
+
MemSet(mem->heap.ptr, 0, delta);
|
|
177
177
|
#endif
|
|
178
178
|
|
|
179
179
|
mem->heap.ptr += delta;
|
package/src/koffi/src/ffi.cc
CHANGED
|
@@ -348,6 +348,8 @@ static Napi::Value CreateStructType(const Napi::CallbackInfo &info, bool pad)
|
|
|
348
348
|
size = member.offset + member.type->size;
|
|
349
349
|
type->align = std::max(type->align, align);
|
|
350
350
|
|
|
351
|
+
member.countedby = -1;
|
|
352
|
+
|
|
351
353
|
if (size > instance->config.max_type_size) {
|
|
352
354
|
ThrowError<Napi::Error>(env, "Struct '%1' size is too high (max = %2)", type->name, FmtMemSize(size));
|
|
353
355
|
return env.Null();
|
|
@@ -372,8 +374,9 @@ static Napi::Value CreateStructType(const Napi::CallbackInfo &info, bool pad)
|
|
|
372
374
|
type->members.Append(member);
|
|
373
375
|
}
|
|
374
376
|
|
|
375
|
-
for (
|
|
376
|
-
|
|
377
|
+
for (Size i = 0; i < type->members.len; i++) {
|
|
378
|
+
RecordMember *member = &type->members[i];
|
|
379
|
+
const char *countedby = member->type->countedby;
|
|
377
380
|
|
|
378
381
|
if (countedby) {
|
|
379
382
|
const RecordMember *by = std::find_if(type->members.begin(), type->members.end(),
|
|
@@ -387,10 +390,12 @@ static Napi::Value CreateStructType(const Napi::CallbackInfo &info, bool pad)
|
|
|
387
390
|
ThrowError<Napi::Error>(env, "Dynamic length member %1 is not an integer", countedby);
|
|
388
391
|
return env.Null();
|
|
389
392
|
}
|
|
393
|
+
if (member->type->primitive == PrimitiveKind::Array && i < type->members.len - 1) {
|
|
394
|
+
ThrowError<Napi::Error>(env, "Flexible array '%1' is not the last member of struct", member->name);
|
|
395
|
+
return env.Null();
|
|
396
|
+
}
|
|
390
397
|
|
|
391
|
-
member
|
|
392
|
-
} else {
|
|
393
|
-
member.countedby = -1;
|
|
398
|
+
member->countedby = by - type->members.ptr;
|
|
394
399
|
}
|
|
395
400
|
}
|
|
396
401
|
|
|
@@ -966,7 +971,7 @@ static Napi::Value CreateArrayType(const Napi::CallbackInfo &info)
|
|
|
966
971
|
InstanceData *instance = env.GetInstanceData<InstanceData>();
|
|
967
972
|
|
|
968
973
|
if (info.Length() < 2) {
|
|
969
|
-
ThrowError<Napi::TypeError>(env, "Expected 2 to
|
|
974
|
+
ThrowError<Napi::TypeError>(env, "Expected 2 to 4 arguments, got %1", info.Length());
|
|
970
975
|
return env.Null();
|
|
971
976
|
}
|
|
972
977
|
|
|
@@ -974,27 +979,26 @@ static Napi::Value CreateArrayType(const Napi::CallbackInfo &info)
|
|
|
974
979
|
if (!ref)
|
|
975
980
|
return env.Null();
|
|
976
981
|
|
|
977
|
-
|
|
978
|
-
Napi::Value countedby;
|
|
979
|
-
|
|
980
|
-
if (info[1].IsNumber()) {
|
|
981
|
-
len = info[1].As<Napi::Number>().Int64Value();
|
|
982
|
-
} else if (info[1].IsString()) {
|
|
983
|
-
countedby = info[1];
|
|
982
|
+
bool dynamic = (info.Length() >= 3) && info[1].IsString();
|
|
984
983
|
|
|
985
|
-
|
|
986
|
-
|
|
984
|
+
if (dynamic && !info[1].IsString()) {
|
|
985
|
+
ThrowError<Napi::TypeError>(env, "Unexpected %1 value for countedBy, expected string", GetValueType(instance, info[1]));
|
|
986
|
+
return env.Null();
|
|
987
|
+
}
|
|
988
|
+
if (!info[1 + dynamic].IsNumber()) {
|
|
989
|
+
if (info.Length() == 2 && info[1].IsString()) {
|
|
990
|
+
ThrowError<Napi::TypeError>(env, "Missing maxLen argument");
|
|
991
|
+
} else {
|
|
992
|
+
ThrowError<Napi::TypeError>(env, "Unexpected %1 value for %2, expected integer", GetValueType(instance, info[1]), dynamic ? "maxLen" : "len");
|
|
987
993
|
}
|
|
988
|
-
|
|
989
|
-
ThrowError<Napi::TypeError>(env, "Unexpected %1 value for length, expected integer or string", GetValueType(instance, info[1]));
|
|
994
|
+
return env.Null();
|
|
990
995
|
}
|
|
991
996
|
|
|
992
|
-
|
|
997
|
+
int64_t len = info[1 + dynamic].As<Napi::Number>().Int64Value();
|
|
998
|
+
|
|
999
|
+
if (len <= 0) {
|
|
993
1000
|
ThrowError<Napi::TypeError>(env, "Array length must be positive and non-zero");
|
|
994
1001
|
return env.Null();
|
|
995
|
-
} else if (len < 0) {
|
|
996
|
-
ThrowError<Napi::TypeError>(env, "Array length must be positive");
|
|
997
|
-
return env.Null();
|
|
998
1002
|
}
|
|
999
1003
|
if (len > instance->config.max_type_size / ref->size) {
|
|
1000
1004
|
ThrowError<Napi::TypeError>(env, "Array length is too high (max = %1)", instance->config.max_type_size / ref->size);
|
|
@@ -1003,25 +1007,25 @@ static Napi::Value CreateArrayType(const Napi::CallbackInfo &info)
|
|
|
1003
1007
|
|
|
1004
1008
|
TypeInfo *type = nullptr;
|
|
1005
1009
|
|
|
1006
|
-
if (info.Length() >=
|
|
1007
|
-
if (!info[2].IsString()) {
|
|
1010
|
+
if (info.Length() >= 3u + dynamic && !IsNullOrUndefined(info[2 + dynamic])) {
|
|
1011
|
+
if (!info[2 + dynamic].IsString()) {
|
|
1008
1012
|
ThrowError<Napi::TypeError>(env, "Unexpected %1 value for hint, expected string", GetValueType(instance, info[2]));
|
|
1009
1013
|
return env.Null();
|
|
1010
1014
|
}
|
|
1011
1015
|
|
|
1012
|
-
std::string
|
|
1016
|
+
std::string str = info[2 + dynamic].As<Napi::String>();
|
|
1013
1017
|
ArrayHint hint = {};
|
|
1014
1018
|
|
|
1015
|
-
if (
|
|
1019
|
+
if (str == "Typed" || str == "typed") {
|
|
1016
1020
|
if (!(ref->flags & (int)TypeFlag::HasTypedArray)) {
|
|
1017
1021
|
ThrowError<Napi::Error>(env, "Array hint 'Typed' cannot be used with type %1", ref->name);
|
|
1018
1022
|
return env.Null();
|
|
1019
1023
|
}
|
|
1020
1024
|
|
|
1021
1025
|
hint = ArrayHint::Typed;
|
|
1022
|
-
} else if (
|
|
1026
|
+
} else if (str == "Array" || str == "array") {
|
|
1023
1027
|
hint = ArrayHint::Array;
|
|
1024
|
-
} else if (
|
|
1028
|
+
} else if (str == "String" || str == "string") {
|
|
1025
1029
|
if (ref->primitive != PrimitiveKind::Int8 && ref->primitive != PrimitiveKind::Int16) {
|
|
1026
1030
|
ThrowError<Napi::Error>(env, "Array hint 'String' can only be used with 8 and 16-bit signed integer types");
|
|
1027
1031
|
return env.Null();
|
|
@@ -1038,8 +1042,8 @@ static Napi::Value CreateArrayType(const Napi::CallbackInfo &info)
|
|
|
1038
1042
|
type = MakeArrayType(instance, ref, len);
|
|
1039
1043
|
}
|
|
1040
1044
|
|
|
1041
|
-
if (
|
|
1042
|
-
Napi::String str =
|
|
1045
|
+
if (dynamic) {
|
|
1046
|
+
Napi::String str = info[1].As<Napi::String>();
|
|
1043
1047
|
type->countedby = DuplicateString(str.Utf8Value().c_str(), &instance->str_alloc).ptr;
|
|
1044
1048
|
}
|
|
1045
1049
|
|
|
@@ -2087,7 +2091,7 @@ static Napi::Value DecodeValue(const Napi::CallbackInfo &info)
|
|
|
2087
2091
|
Napi::Env env = info.Env();
|
|
2088
2092
|
|
|
2089
2093
|
bool has_offset = (info.Length() >= 2 && info[1].IsNumber());
|
|
2090
|
-
bool has_len = (info.Length() >= 3u + has_offset && info[
|
|
2094
|
+
bool has_len = (info.Length() >= 3u + has_offset && info[2 + has_offset].IsNumber());
|
|
2091
2095
|
|
|
2092
2096
|
if (info.Length() < 2u + has_offset) [[unlikely]] {
|
|
2093
2097
|
ThrowError<Napi::TypeError>(env, "Expected %1 to 4 arguments, got %2", 2 + has_offset, info.Length());
|
|
@@ -2102,7 +2106,7 @@ static Napi::Value DecodeValue(const Napi::CallbackInfo &info)
|
|
|
2102
2106
|
int64_t offset = has_offset ? info[1].As<Napi::Number>().Int64Value() : 0;
|
|
2103
2107
|
|
|
2104
2108
|
if (has_len) {
|
|
2105
|
-
Size len = info[
|
|
2109
|
+
Size len = info[2 + has_offset].As<Napi::Number>();
|
|
2106
2110
|
|
|
2107
2111
|
Napi::Value ret = Decode(value, offset, type, &len);
|
|
2108
2112
|
return ret;
|
|
@@ -2164,7 +2168,7 @@ static Napi::Value EncodeValue(const Napi::CallbackInfo &info)
|
|
|
2164
2168
|
Napi::Env env = info.Env();
|
|
2165
2169
|
|
|
2166
2170
|
bool has_offset = (info.Length() >= 2 && info[1].IsNumber());
|
|
2167
|
-
bool has_len = (info.Length() >= 4u + has_offset && info[
|
|
2171
|
+
bool has_len = (info.Length() >= 4u + has_offset && info[3 + has_offset].IsNumber());
|
|
2168
2172
|
|
|
2169
2173
|
if (info.Length() < 3u + has_offset) [[unlikely]] {
|
|
2170
2174
|
ThrowError<Napi::TypeError>(env, "Expected %1 to 5 arguments, got %2", 3 + has_offset, info.Length());
|
|
@@ -2177,10 +2181,10 @@ static Napi::Value EncodeValue(const Napi::CallbackInfo &info)
|
|
|
2177
2181
|
|
|
2178
2182
|
Napi::Value ref = info[0];
|
|
2179
2183
|
int64_t offset = has_offset ? info[1].As<Napi::Number>().Int64Value() : 0;
|
|
2180
|
-
Napi::Value value = info[
|
|
2184
|
+
Napi::Value value = info[2 + has_offset];
|
|
2181
2185
|
|
|
2182
2186
|
if (has_len) {
|
|
2183
|
-
Size len = info[
|
|
2187
|
+
Size len = info[3 + has_offset].As<Napi::Number>();
|
|
2184
2188
|
|
|
2185
2189
|
if (!Encode(ref, offset, value, type, &len))
|
|
2186
2190
|
return env.Null();
|
package/src/koffi/src/util.cc
CHANGED
|
@@ -906,7 +906,12 @@ void DecodeObject(Napi::Object obj, const uint8_t *origin, const TypeInfo *type)
|
|
|
906
906
|
case PrimitiveKind::Array: {
|
|
907
907
|
if (member.countedby >= 0) {
|
|
908
908
|
const RecordMember &by = type->members[member.countedby];
|
|
909
|
+
|
|
909
910
|
uint32_t len = DecodeDynamicLength(origin, by);
|
|
911
|
+
uint32_t max = member.type->size / member.type->ref.type->size;
|
|
912
|
+
|
|
913
|
+
// Silently truncate result
|
|
914
|
+
len = std::min(len, max);
|
|
910
915
|
|
|
911
916
|
Napi::Value value = DecodeArray(env, src, member.type, len);
|
|
912
917
|
obj.Set(member.name, value);
|
|
@@ -1634,13 +1639,11 @@ bool Encode(Napi::Env env, uint8_t *origin, Napi::Value value, const TypeInfo *t
|
|
|
1634
1639
|
case PrimitiveKind::Array: {
|
|
1635
1640
|
if (value.IsArray()) {
|
|
1636
1641
|
Napi::Array array = value.As<Napi::Array>();
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
if (!call.PushNormalArray(array, len, type, origin))
|
|
1642
|
+
if (!call.PushNormalArray(array, type, type->size, origin))
|
|
1640
1643
|
return false;
|
|
1641
1644
|
} else if (IsRawBuffer(value)) {
|
|
1642
1645
|
Span<const uint8_t> buffer = GetRawBuffer(value);
|
|
1643
|
-
call.PushBuffer(buffer, type
|
|
1646
|
+
call.PushBuffer(buffer, type, origin);
|
|
1644
1647
|
} else if (value.IsString()) {
|
|
1645
1648
|
if (!call.PushStringArray(value, type, origin))
|
|
1646
1649
|
return false;
|