koffi 2.14.0-beta.2 → 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.2",
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.2",
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.2",
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",
@@ -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
- Size len = (Size)member.type->size / member.type->ref.type->size;
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->size, member.type, dest);
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, Size len, const TypeInfo *type, uint8_t *origin)
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 (array.Length() != (size_t)len) [[unlikely]] {
773
- ThrowError<Napi::Error>(env, "Expected array of length %1, got %2", len, array.Length());
774
- return false;
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
- Size len2 = (Size)ref->size / ref->ref.type->size;
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->size, ref, dest);
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, Size size, const TypeInfo *type, uint8_t *origin)
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, (size_t)buffer.len);
1010
- 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);
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, len, type, ptr))
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 len = -1;
1371
+ int64_t size = -1;
1373
1372
 
1374
- // Get expected length
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
- expected = GetNumber<int64_t>(by);
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 length
1387
+ // Get actual size
1387
1388
  if (value.IsArray()) {
1388
1389
  Napi::Array array = value.As<Napi::Array>();
1389
- len = array.Length();
1390
+ size = array.Length() * element;
1390
1391
  } else if (value.IsTypedArray()) {
1391
1392
  Napi::TypedArray typed = value.As<Napi::TypedArray>();
1392
- len = typed.ByteLength() / element;
1393
+ size = typed.ByteLength();
1393
1394
  } else if (value.IsArrayBuffer()) {
1394
1395
  Napi::ArrayBuffer buffer = value.As<Napi::ArrayBuffer>();
1395
- len = buffer.ByteLength() / element;
1396
+ size = buffer.ByteLength();
1397
+ } else if (!IsNullOrUndefined(value)) {
1398
+ size = element;
1396
1399
  } else {
1397
- len = !IsNullOrUndefined(value);
1400
+ size = 0;
1398
1401
  }
1399
1402
 
1400
- if (len != expected) {
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
  }
@@ -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);
@@ -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 (RecordMember &member: type->members) {
376
- const char *countedby = member.type->countedby;
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.countedby = by - type->members.ptr;
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 3 arguments, got %1", info.Length());
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,22 @@ static Napi::Value CreateArrayType(const Napi::CallbackInfo &info)
974
979
  if (!ref)
975
980
  return env.Null();
976
981
 
977
- int64_t len = 0;
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
- if (info.Length() >= 4 && !IsNullOrUndefined(info[3])) {
986
- len = info[3].As<Napi::Number>().Int64Value();
987
- }
988
- } else {
989
- 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();
990
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();
991
+ }
992
+
993
+ int64_t len = info[1 + dynamic].As<Napi::Number>().Int64Value();
991
994
 
992
- if (countedby.IsEmpty() && len <= 0) {
995
+ if (len <= 0) {
993
996
  ThrowError<Napi::TypeError>(env, "Array length must be positive and non-zero");
994
997
  return env.Null();
995
- } else if (len < 0) {
996
- ThrowError<Napi::TypeError>(env, "Array length must be positive");
997
- return env.Null();
998
998
  }
999
999
  if (len > instance->config.max_type_size / ref->size) {
1000
1000
  ThrowError<Napi::TypeError>(env, "Array length is too high (max = %1)", instance->config.max_type_size / ref->size);
@@ -1003,25 +1003,25 @@ static Napi::Value CreateArrayType(const Napi::CallbackInfo &info)
1003
1003
 
1004
1004
  TypeInfo *type = nullptr;
1005
1005
 
1006
- if (info.Length() >= 3 && !IsNullOrUndefined(info[2])) {
1007
- if (!info[2].IsString()) {
1006
+ if (info.Length() >= 3 + dynamic && !IsNullOrUndefined(info[2 + dynamic])) {
1007
+ if (!info[2 + dynamic].IsString()) {
1008
1008
  ThrowError<Napi::TypeError>(env, "Unexpected %1 value for hint, expected string", GetValueType(instance, info[2]));
1009
1009
  return env.Null();
1010
1010
  }
1011
1011
 
1012
- std::string to = info[2].As<Napi::String>();
1012
+ std::string str = info[2 + dynamic].As<Napi::String>();
1013
1013
  ArrayHint hint = {};
1014
1014
 
1015
- if (to == "Typed" || to == "typed") {
1015
+ if (str == "Typed" || str == "typed") {
1016
1016
  if (!(ref->flags & (int)TypeFlag::HasTypedArray)) {
1017
1017
  ThrowError<Napi::Error>(env, "Array hint 'Typed' cannot be used with type %1", ref->name);
1018
1018
  return env.Null();
1019
1019
  }
1020
1020
 
1021
1021
  hint = ArrayHint::Typed;
1022
- } else if (to == "Array" || to == "array") {
1022
+ } else if (str == "Array" || str == "array") {
1023
1023
  hint = ArrayHint::Array;
1024
- } else if (to == "String" || to == "string") {
1024
+ } else if (str == "String" || str == "string") {
1025
1025
  if (ref->primitive != PrimitiveKind::Int8 && ref->primitive != PrimitiveKind::Int16) {
1026
1026
  ThrowError<Napi::Error>(env, "Array hint 'String' can only be used with 8 and 16-bit signed integer types");
1027
1027
  return env.Null();
@@ -1038,8 +1038,8 @@ static Napi::Value CreateArrayType(const Napi::CallbackInfo &info)
1038
1038
  type = MakeArrayType(instance, ref, len);
1039
1039
  }
1040
1040
 
1041
- if (!countedby.IsEmpty()) {
1042
- Napi::String str = countedby.As<Napi::String>();
1041
+ if (dynamic) {
1042
+ Napi::String str = info[1].As<Napi::String>();
1043
1043
  type->countedby = DuplicateString(str.Utf8Value().c_str(), &instance->str_alloc).ptr;
1044
1044
  }
1045
1045
 
@@ -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
- Size len = (Size)type->size / type->ref.type->size;
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->size, type, origin);
1646
+ call.PushBuffer(buffer, type, origin);
1644
1647
  } else if (value.IsString()) {
1645
1648
  if (!call.PushStringArray(value, type, origin))
1646
1649
  return false;