koffi 2.14.0-beta.1 → 2.14.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
@@ -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 (Intel® Corei5-4460).
18
+ The results presented below were measured on my x86_64 Linux machine (AMD Ryzen5 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 | 700 ns | x1.00 | (ref)
31
- rand_koffi | 1152 ns | x0.61 | +64%
32
- rand_node_ffi | 32750 ns | x0.02 | +4576%
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 | 1028 ns | x1.00 | (ref)
43
- atoi_koffi | 1730 ns | x0.59 | +68%
44
- atoi_node_ffi | 121670 ns | x0.008 | +11738%
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 | 18.5 µs | x1.42 | -30%
58
- raylib_node_raylib | 26.3 µs | x1.00 | (ref)
59
- raylib_koffi | 28.0 µs | x0.94 | +6%
60
- raylib_node_ffi | 87.0 µs | x0.30 | +230%
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
 
Binary file
Binary file
package/index.d.ts CHANGED
@@ -105,14 +105,15 @@ 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, hint?: ArrayHint | null, len?: number | null): IKoffiCType;
108
+ export function array(ref: TypeSpec, countedBy: string, hint?: ArrayHint | null): IKoffiCType;
109
+ export function array(ref: TypeSpec, countedBy: string, maxLen: number, hint?: ArrayHint | null): IKoffiCType;
109
110
 
110
111
  export function opaque(name: string | null | undefined): IKoffiCType;
111
112
  export function opaque(): IKoffiCType;
112
113
  /** @deprecated */ export function handle(name: string | null | undefined): IKoffiCType;
113
114
  /** @deprecated */ export function handle(): IKoffiCType;
114
115
 
115
- export function pointer(ref: TypeSpec, countedBy?: string | null): IKoffiCType;
116
+ export function pointer(ref: TypeSpec): IKoffiCType;
116
117
  export function pointer(ref: TypeSpec, count: number): IKoffiCType;
117
118
  export function pointer(name: string | null | undefined, ref: TypeSpec, countedBy?: string | null): IKoffiCType;
118
119
  export function pointer(name: string | null | undefined, ref: TypeSpec, count: number): IKoffiCType;
package/index.js CHANGED
@@ -4,9 +4,9 @@ var __commonJS = (cb, mod3) => function __require() {
4
4
  return mod3 || (0, cb[__getOwnPropNames(cb)[0]])((mod3 = { exports: {} }).exports, mod3), mod3.exports;
5
5
  };
6
6
 
7
- // ../../bin/Koffi/package/src/cnoke/src/tools.js
7
+ // bin/Koffi/package/src/cnoke/src/tools.js
8
8
  var require_tools = __commonJS({
9
- "../../bin/Koffi/package/src/cnoke/src/tools.js"(exports2, module2) {
9
+ "bin/Koffi/package/src/cnoke/src/tools.js"(exports2, module2) {
10
10
  "use strict";
11
11
  var crypto = require("crypto");
12
12
  var fs2 = require("fs");
@@ -397,12 +397,12 @@ var require_tools = __commonJS({
397
397
  }
398
398
  });
399
399
 
400
- // ../../bin/Koffi/package/src/koffi/package.json
400
+ // bin/Koffi/package/src/koffi/package.json
401
401
  var require_package = __commonJS({
402
- "../../bin/Koffi/package/src/koffi/package.json"(exports2, module2) {
402
+ "bin/Koffi/package/src/koffi/package.json"(exports2, module2) {
403
403
  module2.exports = {
404
404
  name: "koffi",
405
- version: "2.14.0-beta.1",
405
+ version: "2.14.0-beta.3",
406
406
  description: "Fast and simple C FFI (foreign function interface) for Node.js",
407
407
  keywords: [
408
408
  "foreign",
@@ -443,9 +443,9 @@ var require_package = __commonJS({
443
443
  }
444
444
  });
445
445
 
446
- // ../../bin/Koffi/package/src/koffi/src/init.js
446
+ // bin/Koffi/package/src/koffi/src/init.js
447
447
  var require_init = __commonJS({
448
- "../../bin/Koffi/package/src/koffi/src/init.js"(exports, module) {
448
+ "bin/Koffi/package/src/koffi/src/init.js"(exports, module) {
449
449
  var fs = require("fs");
450
450
  var path = require("path");
451
451
  var util = require("util");
@@ -528,7 +528,7 @@ var require_init = __commonJS({
528
528
  }
529
529
  });
530
530
 
531
- // ../../bin/Koffi/package/src/koffi/index.js
531
+ // bin/Koffi/package/src/koffi/index.js
532
532
  var { detect: detect2, init: init2 } = require_init();
533
533
  var triplet2 = detect2();
534
534
  var native2 = null;
package/indirect.js CHANGED
@@ -4,9 +4,9 @@ var __commonJS = (cb, mod3) => function __require() {
4
4
  return mod3 || (0, cb[__getOwnPropNames(cb)[0]])((mod3 = { exports: {} }).exports, mod3), mod3.exports;
5
5
  };
6
6
 
7
- // ../../bin/Koffi/package/src/cnoke/src/tools.js
7
+ // bin/Koffi/package/src/cnoke/src/tools.js
8
8
  var require_tools = __commonJS({
9
- "../../bin/Koffi/package/src/cnoke/src/tools.js"(exports2, module2) {
9
+ "bin/Koffi/package/src/cnoke/src/tools.js"(exports2, module2) {
10
10
  "use strict";
11
11
  var crypto = require("crypto");
12
12
  var fs2 = require("fs");
@@ -397,12 +397,12 @@ var require_tools = __commonJS({
397
397
  }
398
398
  });
399
399
 
400
- // ../../bin/Koffi/package/src/koffi/package.json
400
+ // bin/Koffi/package/src/koffi/package.json
401
401
  var require_package = __commonJS({
402
- "../../bin/Koffi/package/src/koffi/package.json"(exports2, module2) {
402
+ "bin/Koffi/package/src/koffi/package.json"(exports2, module2) {
403
403
  module2.exports = {
404
404
  name: "koffi",
405
- version: "2.14.0-beta.1",
405
+ version: "2.14.0-beta.3",
406
406
  description: "Fast and simple C FFI (foreign function interface) for Node.js",
407
407
  keywords: [
408
408
  "foreign",
@@ -443,9 +443,9 @@ var require_package = __commonJS({
443
443
  }
444
444
  });
445
445
 
446
- // ../../bin/Koffi/package/src/koffi/src/init.js
446
+ // bin/Koffi/package/src/koffi/src/init.js
447
447
  var require_init = __commonJS({
448
- "../../bin/Koffi/package/src/koffi/src/init.js"(exports, module) {
448
+ "bin/Koffi/package/src/koffi/src/init.js"(exports, module) {
449
449
  var fs = require("fs");
450
450
  var path = require("path");
451
451
  var util = require("util");
@@ -528,7 +528,7 @@ var require_init = __commonJS({
528
528
  }
529
529
  });
530
530
 
531
- // ../../bin/Koffi/package/src/koffi/indirect.js
531
+ // bin/Koffi/package/src/koffi/indirect.js
532
532
  var { detect: detect2, init: init2 } = require_init();
533
533
  var triplet2 = detect2();
534
534
  var mod2 = init2(triplet2, null);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "koffi",
3
- "version": "2.14.0-beta.1",
3
+ "version": "2.14.0-beta.3",
4
4
  "description": "Fast and simple C FFI (foreign function interface) for Node.js",
5
5
  "keywords": [
6
6
  "foreign",
@@ -523,6 +523,13 @@ bool CallData::PushObject(Napi::Object obj, const TypeInfo *type, uint8_t *origi
523
523
  const RecordMember &member = members[i];
524
524
  Napi::Value value = obj.Get(member.name);
525
525
 
526
+ if (member.countedby >= 0) {
527
+ const char *countedby = members[member.countedby].name;
528
+
529
+ if (!CheckDynamicLength(obj, member.type->ref.type->size, countedby, value)) [[unlikely]]
530
+ return false;
531
+ }
532
+
526
533
  if (value.IsUndefined())
527
534
  continue;
528
535
 
@@ -708,13 +715,11 @@ bool CallData::PushObject(Napi::Object obj, const TypeInfo *type, uint8_t *origi
708
715
  case PrimitiveKind::Array: {
709
716
  if (value.IsArray()) {
710
717
  Napi::Array array = value.As<Napi::Array>();
711
- Size len = (Size)member.type->size / member.type->ref.type->size;
712
-
713
- if (!PushNormalArray(array, len, member.type, dest))
718
+ if (!PushNormalArray(array, member.type, member.type->size, dest))
714
719
  return false;
715
720
  } else if (IsRawBuffer(value)) {
716
721
  Span<const uint8_t> buffer = GetRawBuffer(value);
717
- PushBuffer(buffer, member.type->size, member.type, dest);
722
+ PushBuffer(buffer, member.type, dest);
718
723
  } else if (value.IsString()) {
719
724
  if (!PushStringArray(value, member.type, dest))
720
725
  return false;
@@ -756,15 +761,18 @@ bool CallData::PushObject(Napi::Object obj, const TypeInfo *type, uint8_t *origi
756
761
  return true;
757
762
  }
758
763
 
759
- bool CallData::PushNormalArray(Napi::Array array, Size len, const TypeInfo *type, uint8_t *origin)
764
+ bool CallData::PushNormalArray(Napi::Array array, const TypeInfo *type, Size size, uint8_t *origin)
760
765
  {
761
766
  RG_ASSERT(array.IsArray());
762
767
 
763
768
  const TypeInfo *ref = type->ref.type;
769
+ Size len = (Size)array.Length();
770
+ Size available = len * ref->size;
764
771
 
765
- if (array.Length() != (size_t)len) [[unlikely]] {
766
- ThrowError<Napi::Error>(env, "Expected array of length %1, got %2", len, array.Length());
767
- return false;
772
+ if (available > size) {
773
+ len = size / ref->size;
774
+ } else {
775
+ MemSet(origin + available, 0, size - available);
768
776
  }
769
777
 
770
778
  Size offset = 0;
@@ -938,13 +946,11 @@ bool CallData::PushNormalArray(Napi::Array array, Size len, const TypeInfo *type
938
946
 
939
947
  if (value.IsArray()) {
940
948
  Napi::Array array2 = value.As<Napi::Array>();
941
- Size len2 = (Size)ref->size / ref->ref.type->size;
942
-
943
- if (!PushNormalArray(array2, len2, ref, dest))
949
+ if (!PushNormalArray(array2, ref, (Size)ref->size, dest))
944
950
  return false;
945
951
  } else if (IsRawBuffer(value)) {
946
952
  Span<const uint8_t> buffer = GetRawBuffer(value);
947
- PushBuffer(buffer, ref->size, ref, dest);
953
+ PushBuffer(buffer, ref, dest);
948
954
  } else if (value.IsString()) {
949
955
  if (!PushStringArray(value, ref, dest))
950
956
  return false;
@@ -994,13 +1000,13 @@ bool CallData::PushNormalArray(Napi::Array array, Size len, const TypeInfo *type
994
1000
  return true;
995
1001
  }
996
1002
 
997
- void CallData::PushBuffer(Span<const uint8_t> buffer, Size size, const TypeInfo *type, uint8_t *origin)
1003
+ void CallData::PushBuffer(Span<const uint8_t> buffer, const TypeInfo *type, uint8_t *origin)
998
1004
  {
999
- buffer.len = std::min(buffer.len, size);
1005
+ buffer.len = std::min(buffer.len, (Size)type->size);
1000
1006
 
1001
1007
  // Go fast brrrrrrr :)
1002
- MemCpy(origin, buffer.ptr, (size_t)buffer.len);
1003
- MemSet(origin + buffer.len, 0, (size_t)(size - buffer.len));
1008
+ MemCpy(origin, buffer.ptr, buffer.len);
1009
+ MemSet(origin + buffer.len, 0, (Size)type->size - buffer.len);
1004
1010
 
1005
1011
  #define SWAP(CType) \
1006
1012
  do { \
@@ -1124,7 +1130,7 @@ bool CallData::PushPointer(Napi::Value value, const TypeInfo *type, int directio
1124
1130
  ptr = AllocHeap(size, 16);
1125
1131
 
1126
1132
  if (directions & 1) {
1127
- if (!PushNormalArray(array, len, type, ptr))
1133
+ if (!PushNormalArray(array, type, size, ptr))
1128
1134
  return false;
1129
1135
  } else {
1130
1136
  MemSet(ptr, 0, size);
@@ -1359,6 +1365,49 @@ void CallData::DumpForward(const FunctionInfo *func) const
1359
1365
  DumpMemory("Heap", heap);
1360
1366
  }
1361
1367
 
1368
+ bool CallData::CheckDynamicLength(Napi::Object obj, Size element, const char *countedby, Napi::Value value)
1369
+ {
1370
+ int64_t expected = -1;
1371
+ int64_t size = -1;
1372
+
1373
+ // Get expected size
1374
+ {
1375
+ Napi::Value by = obj.Get(countedby);
1376
+
1377
+ if (!by.IsNumber() && !by.IsBigInt()) [[unlikely]] {
1378
+ ThrowError<Napi::Error>(env, "Unexpected %1 value for dynamic length, expected number", GetValueType(instance, by));
1379
+ return false;
1380
+ }
1381
+
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;
1385
+ }
1386
+
1387
+ // Get actual size
1388
+ if (value.IsArray()) {
1389
+ Napi::Array array = value.As<Napi::Array>();
1390
+ size = array.Length() * element;
1391
+ } else if (value.IsTypedArray()) {
1392
+ Napi::TypedArray typed = value.As<Napi::TypedArray>();
1393
+ size = typed.ByteLength();
1394
+ } else if (value.IsArrayBuffer()) {
1395
+ Napi::ArrayBuffer buffer = value.As<Napi::ArrayBuffer>();
1396
+ size = buffer.ByteLength();
1397
+ } else if (!IsNullOrUndefined(value)) {
1398
+ size = element;
1399
+ } else {
1400
+ size = 0;
1401
+ }
1402
+
1403
+ if (size != expected) {
1404
+ ThrowError<Napi::Error>(env, "Mismatched dynamic length between '%1' and actual array", countedby);
1405
+ return false;
1406
+ }
1407
+
1408
+ return true;
1409
+ }
1410
+
1362
1411
  static inline Napi::Value GetReferenceValue(Napi::Env env, napi_ref ref)
1363
1412
  {
1364
1413
  napi_value value;
@@ -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, Size len, const TypeInfo *type, uint8_t *origin);
125
- void PushBuffer(Span<const uint8_t> buffer, Size size, const TypeInfo *type, uint8_t *origin);
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);
@@ -138,6 +138,8 @@ private:
138
138
  template <typename T = uint8_t>
139
139
  T *AllocHeap(Size size, Size align);
140
140
 
141
+ bool CheckDynamicLength(Napi::Object obj, Size element, const char *countedby, Napi::Value value);
142
+
141
143
  void PopOutArguments();
142
144
  };
143
145
 
@@ -232,33 +232,6 @@ static bool MapType(Napi::Env env, InstanceData *instance, const TypeInfo *type,
232
232
  return true;
233
233
  }
234
234
 
235
- static bool CheckDynamicMembers(Napi::Env env, TypeInfo *type)
236
- {
237
- for (RecordMember &member: type->members) {
238
- const char *countedby = member.type->countedby;
239
-
240
- if (countedby) {
241
- const RecordMember *by = std::find_if(type->members.begin(), type->members.end(),
242
- [&](const RecordMember &member) { return TestStr(member.name, countedby); });
243
-
244
- if (by == member.type->members.end()) {
245
- ThrowError<Napi::Error>(env, "Record type %1 does not have member '%2'", type->name, countedby);
246
- return false;
247
- }
248
- if (!IsInteger(by->type)) {
249
- ThrowError<Napi::Error>(env, "Dynamic length member %1 is not an integer", countedby);
250
- return false;
251
- }
252
-
253
- member.countedby = by - type->members.ptr;
254
- } else {
255
- member.countedby = -1;
256
- }
257
- }
258
-
259
- return true;
260
- }
261
-
262
235
  static Napi::Value CreateStructType(const Napi::CallbackInfo &info, bool pad)
263
236
  {
264
237
  Napi::Env env = info.Env();
@@ -375,6 +348,8 @@ static Napi::Value CreateStructType(const Napi::CallbackInfo &info, bool pad)
375
348
  size = member.offset + member.type->size;
376
349
  type->align = std::max(type->align, align);
377
350
 
351
+ member.countedby = -1;
352
+
378
353
  if (size > instance->config.max_type_size) {
379
354
  ThrowError<Napi::Error>(env, "Struct '%1' size is too high (max = %2)", type->name, FmtMemSize(size));
380
355
  return env.Null();
@@ -399,8 +374,30 @@ static Napi::Value CreateStructType(const Napi::CallbackInfo &info, bool pad)
399
374
  type->members.Append(member);
400
375
  }
401
376
 
402
- if (!CheckDynamicMembers(env, type))
403
- return env.Null();
377
+ for (Size i = 0; i < type->members.len; i++) {
378
+ RecordMember *member = &type->members[i];
379
+ const char *countedby = member->type->countedby;
380
+
381
+ if (countedby) {
382
+ const RecordMember *by = std::find_if(type->members.begin(), type->members.end(),
383
+ [&](const RecordMember &member) { return TestStr(member.name, countedby); });
384
+
385
+ if (by == type->members.end()) {
386
+ ThrowError<Napi::Error>(env, "Record type %1 does not have member '%2'", type->name, countedby);
387
+ return env.Null();
388
+ }
389
+ if (!IsInteger(by->type)) {
390
+ ThrowError<Napi::Error>(env, "Dynamic length member %1 is not an integer", countedby);
391
+ return env.Null();
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
+ }
397
+
398
+ member->countedby = by - type->members.ptr;
399
+ }
400
+ }
404
401
 
405
402
  size = (int32_t)AlignLen(size, type->align);
406
403
  if (!size) {
@@ -537,11 +534,17 @@ static Napi::Value CreateUnionType(const Napi::CallbackInfo &info)
537
534
  ThrowError<Napi::TypeError>(env, "Type %1 cannot be used as a member (maybe try %1 *)", member.type->name);
538
535
  return env.Null();
539
536
  }
537
+ if (member.type->countedby) {
538
+ ThrowError<Napi::TypeError>(env, "Cannot use dynamic-length array or pointer inside of union");
539
+ return env.Null();
540
+ }
540
541
 
541
542
  align = align ? align : member.type->align;
542
543
  size = std::max(size, member.type->size);
543
544
  type->align = std::max(type->align, align);
544
545
 
546
+ member.countedby = -1;
547
+
545
548
  if (TestStr(member.name, "_"))
546
549
  continue;
547
550
 
@@ -561,9 +564,6 @@ static Napi::Value CreateUnionType(const Napi::CallbackInfo &info)
561
564
  type->members.Append(member);
562
565
  }
563
566
 
564
- if (!CheckDynamicMembers(env, type))
565
- return env.Null();
566
-
567
567
  size = (int32_t)AlignLen(size, type->align);
568
568
  if (!size) {
569
569
  ThrowError<Napi::Error>(env, "Empty union '%1' is not allowed in C", type->name);
@@ -971,7 +971,7 @@ static Napi::Value CreateArrayType(const Napi::CallbackInfo &info)
971
971
  InstanceData *instance = env.GetInstanceData<InstanceData>();
972
972
 
973
973
  if (info.Length() < 2) {
974
- ThrowError<Napi::TypeError>(env, "Expected 2 to 3 arguments, got %1", info.Length());
974
+ ThrowError<Napi::TypeError>(env, "Expected 2 to 4 arguments, got %1", info.Length());
975
975
  return env.Null();
976
976
  }
977
977
 
@@ -979,27 +979,22 @@ static Napi::Value CreateArrayType(const Napi::CallbackInfo &info)
979
979
  if (!ref)
980
980
  return env.Null();
981
981
 
982
- int64_t len = 0;
983
- Napi::Value countedby;
984
-
985
- if (info[1].IsNumber()) {
986
- len = info[1].As<Napi::Number>().Int64Value();
987
- } else if (info[1].IsString()) {
988
- countedby = info[1];
982
+ bool dynamic = (info.Length() >= 3) && info[1].IsString();
989
983
 
990
- if (info.Length() >= 4 && !IsNullOrUndefined(info[3])) {
991
- len = info[3].As<Napi::Number>().Int64Value();
992
- }
993
- } else {
994
- ThrowError<Napi::TypeError>(env, "Unexpected %1 value for length, expected integer or string", GetValueType(instance, info[1]));
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
+ ThrowError<Napi::TypeError>(env, "Unexpected %1 value for %2, expected integer", GetValueType(instance, info[1]), dynamic ? "maxLen" : "len");
990
+ return env.Null();
995
991
  }
996
992
 
997
- if (countedby.IsEmpty() && len <= 0) {
993
+ int64_t len = info[1 + dynamic].As<Napi::Number>().Int64Value();
994
+
995
+ if (len <= 0) {
998
996
  ThrowError<Napi::TypeError>(env, "Array length must be positive and non-zero");
999
997
  return env.Null();
1000
- } else if (len < 0) {
1001
- ThrowError<Napi::TypeError>(env, "Array length must be positive");
1002
- return env.Null();
1003
998
  }
1004
999
  if (len > instance->config.max_type_size / ref->size) {
1005
1000
  ThrowError<Napi::TypeError>(env, "Array length is too high (max = %1)", instance->config.max_type_size / ref->size);
@@ -1008,25 +1003,25 @@ static Napi::Value CreateArrayType(const Napi::CallbackInfo &info)
1008
1003
 
1009
1004
  TypeInfo *type = nullptr;
1010
1005
 
1011
- if (info.Length() >= 3 && !IsNullOrUndefined(info[2])) {
1012
- if (!info[2].IsString()) {
1006
+ if (info.Length() >= 3 + dynamic && !IsNullOrUndefined(info[2 + dynamic])) {
1007
+ if (!info[2 + dynamic].IsString()) {
1013
1008
  ThrowError<Napi::TypeError>(env, "Unexpected %1 value for hint, expected string", GetValueType(instance, info[2]));
1014
1009
  return env.Null();
1015
1010
  }
1016
1011
 
1017
- std::string to = info[2].As<Napi::String>();
1012
+ std::string str = info[2 + dynamic].As<Napi::String>();
1018
1013
  ArrayHint hint = {};
1019
1014
 
1020
- if (to == "Typed" || to == "typed") {
1015
+ if (str == "Typed" || str == "typed") {
1021
1016
  if (!(ref->flags & (int)TypeFlag::HasTypedArray)) {
1022
1017
  ThrowError<Napi::Error>(env, "Array hint 'Typed' cannot be used with type %1", ref->name);
1023
1018
  return env.Null();
1024
1019
  }
1025
1020
 
1026
1021
  hint = ArrayHint::Typed;
1027
- } else if (to == "Array" || to == "array") {
1022
+ } else if (str == "Array" || str == "array") {
1028
1023
  hint = ArrayHint::Array;
1029
- } else if (to == "String" || to == "string") {
1024
+ } else if (str == "String" || str == "string") {
1030
1025
  if (ref->primitive != PrimitiveKind::Int8 && ref->primitive != PrimitiveKind::Int16) {
1031
1026
  ThrowError<Napi::Error>(env, "Array hint 'String' can only be used with 8 and 16-bit signed integer types");
1032
1027
  return env.Null();
@@ -1043,8 +1038,8 @@ static Napi::Value CreateArrayType(const Napi::CallbackInfo &info)
1043
1038
  type = MakeArrayType(instance, ref, len);
1044
1039
  }
1045
1040
 
1046
- if (!countedby.IsEmpty()) {
1047
- Napi::String str = countedby.As<Napi::String>();
1041
+ if (dynamic) {
1042
+ Napi::String str = info[1].As<Napi::String>();
1048
1043
  type->countedby = DuplicateString(str.Utf8Value().c_str(), &instance->str_alloc).ptr;
1049
1044
  }
1050
1045
 
@@ -483,6 +483,9 @@ Napi::External<TypeInfo> WrapType(Napi::Env env, InstanceData *instance, const T
483
483
 
484
484
  bool CanPassType(const TypeInfo *type, int directions)
485
485
  {
486
+ if (type->countedby)
487
+ return false;
488
+
486
489
  if (directions & 2) {
487
490
  if (type->primitive == PrimitiveKind::Pointer)
488
491
  return true;
@@ -510,6 +513,9 @@ bool CanPassType(const TypeInfo *type, int directions)
510
513
 
511
514
  bool CanReturnType(const TypeInfo *type)
512
515
  {
516
+ if (type->countedby)
517
+ return false;
518
+
513
519
  if (type->primitive == PrimitiveKind::Void && !TestStr(type->name, "void"))
514
520
  return false;
515
521
  if (type->primitive == PrimitiveKind::Array)
@@ -900,7 +906,12 @@ void DecodeObject(Napi::Object obj, const uint8_t *origin, const TypeInfo *type)
900
906
  case PrimitiveKind::Array: {
901
907
  if (member.countedby >= 0) {
902
908
  const RecordMember &by = type->members[member.countedby];
909
+
903
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);
904
915
 
905
916
  Napi::Value value = DecodeArray(env, src, member.type, len);
906
917
  obj.Set(member.name, value);
@@ -1628,13 +1639,11 @@ bool Encode(Napi::Env env, uint8_t *origin, Napi::Value value, const TypeInfo *t
1628
1639
  case PrimitiveKind::Array: {
1629
1640
  if (value.IsArray()) {
1630
1641
  Napi::Array array = value.As<Napi::Array>();
1631
- Size len = (Size)type->size / type->ref.type->size;
1632
-
1633
- if (!call.PushNormalArray(array, len, type, origin))
1642
+ if (!call.PushNormalArray(array, type, type->size, origin))
1634
1643
  return false;
1635
1644
  } else if (IsRawBuffer(value)) {
1636
1645
  Span<const uint8_t> buffer = GetRawBuffer(value);
1637
- call.PushBuffer(buffer, type->size, type, origin);
1646
+ call.PushBuffer(buffer, type, origin);
1638
1647
  } else if (value.IsString()) {
1639
1648
  if (!call.PushStringArray(value, type, origin))
1640
1649
  return false;