koffi 2.6.9 → 2.6.11

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 CHANGED
@@ -4,6 +4,15 @@
4
4
 
5
5
  ### Koffi 2.6
6
6
 
7
+ #### Koffi 2.6.11 (2023-12-05)
8
+
9
+ - Speed up resolving simple and often used type names
10
+ - Fix use of optional length argument with [koffi.encode()](variables.md#encode-to-c-memory)
11
+
12
+ #### Koffi 2.6.10 (2023-11-29)
13
+
14
+ - Protect GetLastError() value from Node.js and V8 on Windows
15
+
7
16
  #### Koffi 2.6.9 (2023-11-25)
8
17
 
9
18
  - Search in DLL directory for additional dependencies on Windows
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/variables.md CHANGED
@@ -54,7 +54,7 @@ There is also an optional ending `length` argument that you can use in two cases
54
54
  - Use it to give the number of bytes to decode in non-NUL terminated strings: `koffi.decode(value, 'char *', 5)`
55
55
  - Decode consecutive values into an array. For example, here is how you can decode an array with 3 float values: `koffi.decode(value, 'float', 3)`. This is equivalent to `koffi.decode(value, koffi.array('float', 3))`.
56
56
 
57
- Thge example below will decode the symbol `my_string` defined above but only the first three bytes.
57
+ The example below will decode the symbol `my_string` defined above but only the first three bytes.
58
58
 
59
59
  ```js
60
60
  // Only decode 3 bytes from the C string my_string
@@ -69,7 +69,7 @@ In Koffi 2.2 and earlier versions, the length argument is only used to decode st
69
69
 
70
70
  *New in Koffi 2.6*
71
71
 
72
- Use `koffi.encode()` to encode C pointers, wrapped as external objects or as simple numbers.
72
+ Use `koffi.encode()` to encode JS values into C symbols or pointers, wrapped as external objects or as simple numbers.
73
73
 
74
74
  Some arguments are optional and this function can be called in several ways:
75
75
 
@@ -98,3 +98,9 @@ console.log(koffi.decode(my_string, 'const char *')) // Prints "Hello World!"
98
98
  ```
99
99
 
100
100
  When encoding strings (either directly or embedded in arrays or structs), the memory will be bound to the raw pointer value and managed by Koffi. You can assign to the same string again and again without any leak or risk of use-after-free.
101
+
102
+ There is also an optional ending `length` argument that you can use to encode an array. For example, here is how you can encode an array with 3 float values: `koffi.encode(symbol, 'float', [1, 2, 3], 3)`. This is equivalent to `koffi.encode(symbol, koffi.array('float', 3), [1, 2, 3])`.
103
+
104
+ ```{note}
105
+ The length argument did not work correctly before Koffi 2.6.11.
106
+ ```
package/index.js CHANGED
@@ -378,8 +378,8 @@ var require_package = __commonJS({
378
378
  "build/dist/src/koffi/package.json"(exports2, module2) {
379
379
  module2.exports = {
380
380
  name: "koffi",
381
- version: "2.6.9",
382
- stable: "2.6.9",
381
+ version: "2.6.11",
382
+ stable: "2.6.11",
383
383
  description: "Fast and simple C FFI (foreign function interface) for Node.js",
384
384
  keywords: [
385
385
  "foreign",
package/indirect.js CHANGED
@@ -378,8 +378,8 @@ var require_package = __commonJS({
378
378
  "build/dist/src/koffi/package.json"(exports2, module2) {
379
379
  module2.exports = {
380
380
  name: "koffi",
381
- version: "2.6.9",
382
- stable: "2.6.9",
381
+ version: "2.6.11",
382
+ stable: "2.6.11",
383
383
  description: "Fast and simple C FFI (foreign function interface) for Node.js",
384
384
  keywords: [
385
385
  "foreign",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koffi",
3
- "version": "2.6.9",
4
- "stable": "2.6.9",
3
+ "version": "2.6.11",
4
+ "stable": "2.6.11",
5
5
  "description": "Fast and simple C FFI (foreign function interface) for Node.js",
6
6
  "keywords": [
7
7
  "foreign",
@@ -931,6 +931,35 @@ static Span<char> FormatUnsignedToDecimal(uint64_t value, char out_buf[32])
931
931
  return MakeSpan(out_buf + offset, 32 - offset);
932
932
  }
933
933
 
934
+ static Span<char> FormatUnsignedToBinary(uint64_t value, char out_buf[64])
935
+ {
936
+ Size msb = 64 - (Size)CountLeadingZeros(value);
937
+ if (!msb) {
938
+ msb = 1;
939
+ }
940
+
941
+ for (Size i = 0; i < msb; i++) {
942
+ bool bit = (value >> (msb - i - 1)) & 0x1;
943
+ out_buf[i] = bit ? '1' : '0';
944
+ }
945
+
946
+ return MakeSpan(out_buf, msb);
947
+ }
948
+
949
+ static Span<char> FormatUnsignedToOctal(uint64_t value, char out_buf[64])
950
+ {
951
+ static const char literals[] = "012345678";
952
+
953
+ Size offset = 64;
954
+ do {
955
+ uint64_t digit = value & 0x7;
956
+ value >>= 3;
957
+ out_buf[--offset] = literals[digit];
958
+ } while (value);
959
+
960
+ return MakeSpan(out_buf + offset, 64 - offset);
961
+ }
962
+
934
963
  static Span<char> FormatUnsignedToBigHex(uint64_t value, char out_buf[32])
935
964
  {
936
965
  static const char literals[] = "0123456789ABCDEF";
@@ -959,21 +988,6 @@ static Span<char> FormatUnsignedToSmallHex(uint64_t value, char out_buf[32])
959
988
  return MakeSpan(out_buf + offset, 32 - offset);
960
989
  }
961
990
 
962
- static Span<char> FormatUnsignedToBinary(uint64_t value, char out_buf[64])
963
- {
964
- Size msb = 64 - (Size)CountLeadingZeros(value);
965
- if (!msb) {
966
- msb = 1;
967
- }
968
-
969
- for (Size i = 0; i < msb; i++) {
970
- bool bit = (value >> (msb - i - 1)) & 0x1;
971
- out_buf[i] = bit ? '1' : '0';
972
- }
973
-
974
- return MakeSpan(out_buf, msb);
975
- }
976
-
977
991
  #ifdef JKJ_HEADER_DRAGONBOX
978
992
  static Size FakeFloatPrecision(Span<char> buf, int K, int min_prec, int max_prec, int *out_K)
979
993
  {
@@ -1250,6 +1264,9 @@ static inline void ProcessArg(const FmtArg &arg, AppendFunc append)
1250
1264
  case FmtType::Binary: {
1251
1265
  out = FormatUnsignedToBinary(arg.u.u, num_buf);
1252
1266
  } break;
1267
+ case FmtType::Octal: {
1268
+ out = FormatUnsignedToOctal(arg.u.u, num_buf);
1269
+ } break;
1253
1270
  case FmtType::BigHex: {
1254
1271
  out = FormatUnsignedToBigHex(arg.u.u, num_buf);
1255
1272
  } break;
@@ -1480,6 +1497,7 @@ static inline void ProcessArg(const FmtArg &arg, AppendFunc append)
1480
1497
  case FmtType::Integer:
1481
1498
  case FmtType::Unsigned:
1482
1499
  case FmtType::Binary:
1500
+ case FmtType::Octal:
1483
1501
  case FmtType::BigHex:
1484
1502
  case FmtType::SmallHex: {
1485
1503
  switch (arg.u.span.type_len) {
@@ -2155,7 +2173,7 @@ fail:
2155
2173
  return str_buf;
2156
2174
  }
2157
2175
 
2158
- static FileType FileAttributesToType(uint32_t attr)
2176
+ static inline FileType FileAttributesToType(uint32_t attr)
2159
2177
  {
2160
2178
  if (attr & FILE_ATTRIBUTE_DIRECTORY) {
2161
2179
  return FileType::Directory;
@@ -2862,6 +2862,7 @@ enum class FmtType {
2862
2862
  Float,
2863
2863
  Double,
2864
2864
  Binary,
2865
+ Octal,
2865
2866
  BigHex,
2866
2867
  SmallHex,
2867
2868
  MemorySize,
@@ -2960,6 +2961,13 @@ static inline FmtArg FmtBin(uint64_t u)
2960
2961
  arg.u.u = u;
2961
2962
  return arg;
2962
2963
  }
2964
+ static inline FmtArg FmtOctal(uint64_t u)
2965
+ {
2966
+ FmtArg arg;
2967
+ arg.type = FmtType::Octal;
2968
+ arg.u.u = u;
2969
+ return arg;
2970
+ }
2963
2971
  static inline FmtArg FmtHex(uint64_t u, FmtType type = FmtType::BigHex)
2964
2972
  {
2965
2973
  RG_ASSERT(type == FmtType::BigHex || type == FmtType::SmallHex);
@@ -577,6 +577,8 @@ void CallData::Execute(const FunctionInfo *func, void *native)
577
577
  teb->StackLimit = limit;
578
578
  teb->DeallocationStack = dealloc;
579
579
  teb->GuaranteedStackBytes = guaranteed;
580
+
581
+ instance->last_error = teb->LastErrorValue;
580
582
  };
581
583
 
582
584
  // Adjust stack limits so SEH works correctly
@@ -585,6 +587,8 @@ void CallData::Execute(const FunctionInfo *func, void *native)
585
587
  teb->StackLimit = mem->stack0.ptr;
586
588
  teb->DeallocationStack = mem->stack0.ptr;
587
589
  teb->GuaranteedStackBytes = 0;
590
+
591
+ teb->LastErrorValue = instance->last_error;
588
592
  #endif
589
593
 
590
594
  #define PERFORM_CALL(Suffix) \
@@ -239,6 +239,8 @@ void CallData::Execute(const FunctionInfo *func, void *native)
239
239
  teb->StackLimit = limit;
240
240
  teb->DeallocationStack = dealloc;
241
241
  teb->GuaranteedStackBytes = guaranteed;
242
+
243
+ instance->last_error = teb->LastErrorValue;
242
244
  };
243
245
 
244
246
  // Adjust stack limits so SEH works correctly
@@ -248,6 +250,8 @@ void CallData::Execute(const FunctionInfo *func, void *native)
248
250
  teb->DeallocationStack = mem->stack0.ptr;
249
251
  teb->GuaranteedStackBytes = 0;
250
252
 
253
+ teb->LastErrorValue = instance->last_error;
254
+
251
255
  #define PERFORM_CALL(Suffix) \
252
256
  ([&]() { \
253
257
  auto ret = (func->forward_fp ? ForwardCallX ## Suffix(native, new_sp, &old_sp) \
@@ -336,6 +336,8 @@ void CallData::Execute(const FunctionInfo *func, void *native)
336
336
  teb->StackLimit = limit;
337
337
  teb->DeallocationStack = dealloc;
338
338
  teb->GuaranteedStackBytes = guaranteed;
339
+
340
+ instance->last_error = teb->LastErrorValue;
339
341
  };
340
342
 
341
343
  // Adjust stack limits so SEH works correctly
@@ -344,6 +346,8 @@ void CallData::Execute(const FunctionInfo *func, void *native)
344
346
  teb->StackLimit = mem->stack0.ptr;
345
347
  teb->DeallocationStack = mem->stack0.ptr;
346
348
  teb->GuaranteedStackBytes = 0;
349
+
350
+ teb->LastErrorValue = instance->last_error;
347
351
  #endif
348
352
 
349
353
  #define PERFORM_CALL(Suffix) \
@@ -1639,51 +1639,6 @@ static Napi::Value UnloadLibrary(const Napi::CallbackInfo &info)
1639
1639
  return env.Undefined();
1640
1640
  }
1641
1641
 
1642
- #ifdef _WIN32
1643
- static HANDLE LoadWindowsLibrary(Napi::Env env, Span<const char> path)
1644
- {
1645
- BlockAllocator temp_alloc;
1646
-
1647
- Span<wchar_t> filename_w = AllocateSpan<wchar_t>(&temp_alloc, path.len + 1);
1648
-
1649
- if (ConvertUtf8ToWin32Wide(path, filename_w) < 0)
1650
- return nullptr;
1651
-
1652
- HMODULE module = LoadLibraryW(filename_w.ptr);
1653
-
1654
- if (!module) {
1655
- DWORD flags = LOAD_LIBRARY_SEARCH_DEFAULT_DIRS | LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR;
1656
-
1657
- Span<const char> filename = NormalizePath(path, GetWorkingDirectory(), &temp_alloc);
1658
- Span<wchar_t> filename_w = AllocateSpan<wchar_t>(&temp_alloc, filename.len + 1);
1659
-
1660
- if (ConvertUtf8ToWin32Wide(filename, filename_w) < 0)
1661
- return nullptr;
1662
-
1663
- module = LoadLibraryExW(filename_w.ptr, nullptr, flags);
1664
- }
1665
-
1666
- if (!module) {
1667
- if (GetLastError() == ERROR_BAD_EXE_FORMAT) {
1668
- int process = GetSelfMachine();
1669
- int dll = GetDllMachine(filename_w.ptr);
1670
-
1671
- if (process >= 0 && dll >= 0 && dll != process) {
1672
- ThrowError<Napi::Error>(env, "Cannot load '%1' DLL in '%2' process",
1673
- WindowsMachineNames.FindValue(dll, "Unknown"),
1674
- WindowsMachineNames.FindValue(process, "Unknown"));
1675
- return nullptr;
1676
- }
1677
- }
1678
-
1679
- ThrowError<Napi::Error>(env, "Failed to load shared library: %1", GetWin32ErrorString());
1680
- return nullptr;
1681
- }
1682
-
1683
- return module;
1684
- }
1685
- #endif
1686
-
1687
1642
  static Napi::Value LoadSharedLibrary(const Napi::CallbackInfo &info)
1688
1643
  {
1689
1644
  Napi::Env env = info.Env();
@@ -2011,7 +1966,7 @@ static Napi::Value EncodeValue(const Napi::CallbackInfo &info)
2011
1966
  Napi::Env env = info.Env();
2012
1967
 
2013
1968
  bool has_offset = (info.Length() >= 2 && info[1].IsNumber());
2014
- bool has_len = (info.Length() >= 4u + has_offset && info[2u + has_offset].IsNumber());
1969
+ bool has_len = (info.Length() >= 4u + has_offset && info[3u + has_offset].IsNumber());
2015
1970
 
2016
1971
  if (info.Length() < 3u + has_offset) [[unlikely]] {
2017
1972
  ThrowError<Napi::TypeError>(env, "Expected %1 to 5 arguments, got %2", 3 + has_offset, info.Length());
@@ -2024,10 +1979,10 @@ static Napi::Value EncodeValue(const Napi::CallbackInfo &info)
2024
1979
 
2025
1980
  Napi::Value ref = info[0];
2026
1981
  int64_t offset = has_offset ? info[1].As<Napi::Number>().Int64Value() : 0;
2027
- Napi::Value value = info[2u + has_offset + has_len];
1982
+ Napi::Value value = info[2u + has_offset];
2028
1983
 
2029
1984
  if (has_len) {
2030
- Size len = info[2u + has_offset].As<Napi::Number>();
1985
+ Size len = info[3u + has_offset].As<Napi::Number>();
2031
1986
 
2032
1987
  if (!Encode(ref, offset, value, type, &len))
2033
1988
  return env.Null();
@@ -286,6 +286,8 @@ struct InstanceData {
286
286
  #ifdef _WIN32
287
287
  void *main_stack_max;
288
288
  void *main_stack_min;
289
+
290
+ uint32_t last_error = 0;
289
291
  #endif
290
292
 
291
293
  BucketArray<BlockAllocator> encode_allocators;
@@ -117,13 +117,28 @@ const TypeInfo *ResolveType(Napi::Value value, int *out_directions)
117
117
 
118
118
  if (value.IsString()) {
119
119
  std::string str = value.As<Napi::String>();
120
- const TypeInfo *type = ResolveType(env, str.c_str(), out_directions);
121
120
 
122
- if (!type) {
123
- if (!env.IsExceptionPending()) {
124
- ThrowError<Napi::TypeError>(env, "Unknown or invalid type name '%1'", str.c_str());
121
+ // Quick path for known types (int, float *, etc.)
122
+ const TypeInfo *type = instance->types_map.FindValue(str.c_str(), nullptr);
123
+
124
+ if (!type || (type->flags & (int)TypeFlag::IsIncomplete)) {
125
+ type = ResolveType(env, str.c_str(), out_directions);
126
+
127
+ if (!type) {
128
+ if (!env.IsExceptionPending()) {
129
+ ThrowError<Napi::TypeError>(env, "Unknown or invalid type name '%1'", str.c_str());
130
+ }
131
+ return nullptr;
132
+ }
133
+
134
+ // Cache for quick future access
135
+ bool inserted;
136
+ auto bucket = instance->types_map.TrySetDefault(str.c_str(), &inserted);
137
+
138
+ if (inserted) {
139
+ bucket->key = DuplicateString(str.c_str(), &instance->str_alloc).ptr;
140
+ bucket->value = type;
125
141
  }
126
- return nullptr;
127
142
  }
128
143
 
129
144
  return type;
@@ -200,48 +215,54 @@ const TypeInfo *ResolveType(Napi::Env env, Span<const char> str, int *out_direct
200
215
  }
201
216
  }
202
217
 
203
- // Skip initial const qualifiers
204
- str = TrimStr(str);
205
- while (SplitIdentifier(str) == "const") {
206
- str = str.Take(6, str.len - 6);
207
- str = TrimStr(str);
208
- }
209
- str = TrimStr(str);
210
-
211
- Span<const char> remain = str;
218
+ Span<const char> name;
219
+ Span<const char> after;
220
+ {
221
+ Span<const char> remain = str;
212
222
 
213
- // Consume one or more identifiers (e.g. unsigned int)
214
- for (;;) {
223
+ // Skip initial const qualifiers
224
+ remain = TrimStr(remain);
225
+ while (SplitIdentifier(remain) == "const") {
226
+ remain = remain.Take(6, remain.len - 6);
227
+ remain = TrimStr(remain);
228
+ }
215
229
  remain = TrimStr(remain);
216
230
 
217
- Span<const char> token = SplitIdentifier(remain);
218
- if (!token.len)
219
- break;
220
- remain = remain.Take(token.len, remain.len - token.len);
221
- }
231
+ after = remain;
222
232
 
223
- Span<const char> name = TrimStr(MakeSpan(str.ptr, remain.ptr - str.ptr));
233
+ // Consume one or more identifiers (e.g. unsigned int)
234
+ for (;;) {
235
+ after = TrimStr(after);
236
+
237
+ Span<const char> token = SplitIdentifier(after);
238
+ if (!token.len)
239
+ break;
240
+ after = after.Take(token.len, after.len - token.len);
241
+ }
242
+
243
+ name = TrimStr(MakeSpan(remain.ptr, after.ptr - remain.ptr));
244
+ }
224
245
 
225
246
  // Consume pointer indirections
226
- while (remain.len) {
227
- if (remain[0] == '*') {
228
- remain = remain.Take(1, remain.len - 1);
247
+ while (after.len) {
248
+ if (after[0] == '*') {
249
+ after = after.Take(1, after.len - 1);
229
250
  indirect++;
230
251
 
231
252
  if (indirect >= RG_SIZE(disposables) * 8) [[unlikely]] {
232
253
  ThrowError<Napi::Error>(env, "Too many pointer indirections");
233
254
  return nullptr;
234
255
  }
235
- } else if (remain[0] == '!') {
236
- remain = remain.Take(1, remain.len - 1);
256
+ } else if (after[0] == '!') {
257
+ after = after.Take(1, after.len - 1);
237
258
  disposables |= (1u << indirect);
238
- } else if (SplitIdentifier(remain) == "const") {
239
- remain = remain.Take(6, remain.len - 6);
259
+ } else if (SplitIdentifier(after) == "const") {
260
+ after = after.Take(6, after.len - 6);
240
261
  } else {
241
262
  break;
242
263
  }
243
264
 
244
- remain = TrimStr(remain);
265
+ after = TrimStr(after);
245
266
  }
246
267
 
247
268
  const TypeInfo *type = instance->types_map.FindValue(name, nullptr);
@@ -67,6 +67,49 @@ const HashMap<int, const char *> WindowsMachineNames = {
67
67
  { 0x169, "MIPS little-endian WCE v2" }
68
68
  };
69
69
 
70
+ HANDLE LoadWindowsLibrary(Napi::Env env, Span<const char> path)
71
+ {
72
+ BlockAllocator temp_alloc;
73
+
74
+ Span<wchar_t> filename_w = AllocateSpan<wchar_t>(&temp_alloc, path.len + 1);
75
+
76
+ if (ConvertUtf8ToWin32Wide(path, filename_w) < 0)
77
+ return nullptr;
78
+
79
+ HMODULE module = LoadLibraryW(filename_w.ptr);
80
+
81
+ if (!module) {
82
+ DWORD flags = LOAD_LIBRARY_SEARCH_DEFAULT_DIRS | LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR;
83
+
84
+ Span<const char> filename = NormalizePath(path, GetWorkingDirectory(), &temp_alloc);
85
+ Span<wchar_t> filename_w = AllocateSpan<wchar_t>(&temp_alloc, filename.len + 1);
86
+
87
+ if (ConvertUtf8ToWin32Wide(filename, filename_w) < 0)
88
+ return nullptr;
89
+
90
+ module = LoadLibraryExW(filename_w.ptr, nullptr, flags);
91
+ }
92
+
93
+ if (!module) {
94
+ if (GetLastError() == ERROR_BAD_EXE_FORMAT) {
95
+ int process = GetSelfMachine();
96
+ int dll = GetDllMachine(filename_w.ptr);
97
+
98
+ if (process >= 0 && dll >= 0 && dll != process) {
99
+ ThrowError<Napi::Error>(env, "Cannot load '%1' DLL in '%2' process",
100
+ WindowsMachineNames.FindValue(dll, "Unknown"),
101
+ WindowsMachineNames.FindValue(process, "Unknown"));
102
+ return nullptr;
103
+ }
104
+ }
105
+
106
+ ThrowError<Napi::Error>(env, "Failed to load shared library: %1", GetWin32ErrorString());
107
+ return nullptr;
108
+ }
109
+
110
+ return module;
111
+ }
112
+
70
113
  // Fails silently on purpose
71
114
  static bool ReadAt(HANDLE h, int32_t offset, void *buf, int len)
72
115
  {
@@ -72,9 +72,11 @@ struct TEB {
72
72
  void *ExceptionList;
73
73
  void *StackBase;
74
74
  void *StackLimit;
75
- char _pad1[5216];
75
+ char _pad1[80];
76
+ unsigned long LastErrorValue;
77
+ char _pad2[5132];
76
78
  void *DeallocationStack;
77
- char _pad2[712];
79
+ char _pad3[712];
78
80
  uint32_t GuaranteedStackBytes;
79
81
  };
80
82
  static_assert(RG_OFFSET_OF(TEB, DeallocationStack) == 0x1478);
@@ -86,9 +88,11 @@ struct TEB {
86
88
  void *ExceptionList;
87
89
  void *StackBase;
88
90
  void *StackLimit;
89
- char _pad1[3584];
91
+ char _pad1[40];
92
+ unsigned long LastErrorValue;
93
+ char _pad2[3540];
90
94
  void *DeallocationStack;
91
- char _pad2[360];
95
+ char _pad3[360];
92
96
  uint32_t GuaranteedStackBytes;
93
97
  };
94
98
  static_assert(RG_OFFSET_OF(TEB, DeallocationStack) == 0xE0C);
@@ -111,6 +115,8 @@ static inline TEB *GetTEB()
111
115
 
112
116
  extern const HashMap<int, const char *> WindowsMachineNames;
113
117
 
118
+ void *LoadWindowsLibrary(Napi::Env env, Span<const char> path); // Returns HANDLE
119
+
114
120
  int GetSelfMachine();
115
121
  int GetDllMachine(const wchar_t *filename);
116
122