koffi 2.7.0 → 2.7.1

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,11 @@
4
4
 
5
5
  ### Koffi 2.7
6
6
 
7
+ #### Koffi 2.7.1 (2024-01-02)
8
+
9
+ - Support C-like `int[3]` syntax for [fixed array types](input.md#fixed-size-c-arrays)
10
+ - Refuse type specifiers with invalid tokens at the end (previously ignored)
11
+
7
12
  #### Koffi 2.7.0 (2023-12-21)
8
13
 
9
14
  - Support alternative [callback calling convention](callbacks.md#callback-types) in classic syntax
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/functions.md CHANGED
@@ -9,7 +9,7 @@ To declare functions, start by loading the shared library with `koffi.load(filen
9
9
  const koffi = require('koffi');
10
10
 
11
11
  const lib = koffi.load('/path/to/shared/library'); // File extension depends on platforms: .so, .dll, .dylib, etc.
12
- ````
12
+ ```
13
13
 
14
14
  This library will be automatically unloaded once all references to it are gone (including all the functions that use it, as described below).
15
15
 
package/doc/input.md CHANGED
@@ -336,6 +336,8 @@ try {
336
336
 
337
337
  ### Fixed-size C arrays
338
338
 
339
+ *Changed in Koffi 2.7.1*
340
+
339
341
  Fixed-size arrays are declared with `koffi.array(type, length)`. Just like in C, they cannot be passed as functions parameters (they degenerate to pointers), or returned by value. You can however embed them in struct types.
340
342
 
341
343
  Koffi applies the following conversion rules when passing arrays to/from C:
@@ -350,13 +352,13 @@ See the example below:
350
352
  const koffi = require('koffi');
351
353
 
352
354
  // Those two structs are exactly the same, only the array conversion hint is different
353
- const Foo1 = koffi.struct('Foo', {
355
+ const Foo1 = koffi.struct('Foo1', {
354
356
  i: 'int',
355
- a16: koffi.array('int16_t', 8)
357
+ a16: koffi.array('int16_t', 2)
356
358
  });
357
- const Foo2 = koffi.struct('Foo', {
359
+ const Foo2 = koffi.struct('Foo2', {
358
360
  i: 'int',
359
- a16: koffi.array('int16_t', 8, 'Array')
361
+ a16: koffi.array('int16_t', 2, 'Array')
360
362
  });
361
363
 
362
364
  // Uses an hypothetical C function that just returns the struct passed as a parameter
@@ -367,6 +369,19 @@ console.log(ReturnFoo1({ i: 5, a16: [6, 8] })) // Prints { i: 5, a16: Int16Array
367
369
  console.log(ReturnFoo2({ i: 5, a16: [6, 8] })) // Prints { i: 5, a16: [6, 8] }
368
370
  ```
369
371
 
372
+ You can also declare arrays with the C-like short syntax in type declarations, as shown below:
373
+
374
+ ```js
375
+ const StructType = koffi.struct('StructType', {
376
+ f8: 'float [8]',
377
+ self4: 'StructType *[4]'
378
+ });
379
+ ```
380
+
381
+ ```{note}
382
+ The short C-like syntax was introduced in Koffi 2.7.1, use `koffi.array()` for older versions.
383
+ ```
384
+
370
385
  ### Fixed-size string buffers
371
386
 
372
387
  Koffi can also convert JS strings to fixed-sized arrays in the following cases:
package/index.d.ts CHANGED
@@ -104,8 +104,8 @@ declare module 'koffi' {
104
104
  /** @deprecated */ export function handle(): IKoffiCType;
105
105
 
106
106
  export function pointer(ref: TypeSpec): IKoffiCType;
107
- export function pointer(ref: TypeSpec, asteriskCount: number): IKoffiCType;
108
- export function pointer(name: string, ref: TypeSpec, asteriskCount: number): IKoffiCType;
107
+ export function pointer(ref: TypeSpec, asteriskCount?: number): IKoffiCType;
108
+ export function pointer(name: string, ref: TypeSpec, asteriskCount?: number): IKoffiCType;
109
109
 
110
110
  export function out(type: TypeSpec): IKoffiCType;
111
111
  export function inout(type: TypeSpec): IKoffiCType;
@@ -131,6 +131,7 @@ declare module 'koffi' {
131
131
  export function decode(value: any, offset: number, type: TypeSpec): any;
132
132
  export function decode(value: any, offset: number, type: TypeSpec, len: number): any;
133
133
  export function address(value: any): bigint;
134
+ export function call(value: any, type: TypeSpec, ...args: any[]): any;
134
135
  export function encode(ref: any, type: TypeSpec): void;
135
136
  export function encode(ref: any, type: TypeSpec, value: any): void;
136
137
  export function encode(ref: any, type: TypeSpec, value: any, len: number): void;
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.7.0",
382
- stable: "2.7.0",
381
+ version: "2.7.1",
382
+ stable: "2.7.1",
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.7.0",
382
- stable: "2.7.0",
381
+ version: "2.7.1",
382
+ stable: "2.7.1",
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.7.0",
4
- "stable": "2.7.0",
3
+ "version": "2.7.1",
4
+ "stable": "2.7.1",
5
5
  "description": "Fast and simple C FFI (foreign function interface) for Node.js",
6
6
  "keywords": [
7
7
  "foreign",
@@ -3881,8 +3881,11 @@ bool CreateOverlappedPipe(bool overlap0, bool overlap1, PipeMode mode, HANDLE ou
3881
3881
 
3882
3882
  void CloseHandleSafe(HANDLE *handle_ptr)
3883
3883
  {
3884
- if (*handle_ptr && *handle_ptr != INVALID_HANDLE_VALUE) {
3885
- CloseHandle(*handle_ptr);
3884
+ HANDLE h = *handle_ptr;
3885
+
3886
+ if (h && h != INVALID_HANDLE_VALUE) {
3887
+ CancelIo(h);
3888
+ CloseHandle(h);
3886
3889
  }
3887
3890
 
3888
3891
  *handle_ptr = nullptr;
@@ -4021,6 +4024,7 @@ bool ExecuteCommandLine(const char *cmd_line, const ExecuteInfo &info,
4021
4024
  CloseHandleSafe(&si.hStdOutput);
4022
4025
  CloseHandleSafe(&si.hStdError);
4023
4026
  };
4027
+
4024
4028
  if (in_func.IsValid() || out_func.IsValid()) {
4025
4029
  if (!DuplicateHandle(GetCurrentProcess(), in_func.IsValid() ? in_pipe[0] : GetStdHandle(STD_INPUT_HANDLE),
4026
4030
  GetCurrentProcess(), &si.hStdInput, 0, TRUE, DUPLICATE_SAME_ACCESS)) {
@@ -4095,6 +4099,7 @@ bool ExecuteCommandLine(const char *cmd_line, const ExecuteInfo &info,
4095
4099
  if (proc_in.err && proc_in.err != ERROR_BROKEN_PIPE && proc_in.err != ERROR_NO_DATA) {
4096
4100
  LogError("Failed to write to process: %1", GetWin32ErrorString(proc_in.err));
4097
4101
  }
4102
+
4098
4103
  proc_in.pending = true;
4099
4104
  }
4100
4105
 
@@ -4111,30 +4116,23 @@ bool ExecuteCommandLine(const char *cmd_line, const ExecuteInfo &info,
4111
4116
  }
4112
4117
  }
4113
4118
 
4114
- if (proc_out.err && proc_out.err != ERROR_BROKEN_PIPE && proc_out.err != ERROR_NO_DATA) {
4115
- LogError("Failed to read process output: %1", GetWin32ErrorString(proc_out.err));
4119
+ if (proc_out.err) {
4120
+ if (proc_out.err != ERROR_BROKEN_PIPE && proc_out.err != ERROR_NO_DATA) {
4121
+ LogError("Failed to read process output: %1", GetWin32ErrorString(proc_out.err));
4122
+ }
4123
+ break;
4116
4124
  }
4125
+
4117
4126
  proc_out.pending = true;
4118
4127
  }
4119
4128
 
4120
- HANDLE events[2] = {
4121
- process_handle,
4122
- console_ctrl_event
4123
- };
4124
-
4125
- running = (WaitForMultipleObjectsEx(RG_LEN(events), events, FALSE, INFINITE, TRUE) > WAIT_OBJECT_0 + 1);
4129
+ running = (WaitForSingleObjectEx(console_ctrl_event, INFINITE, TRUE) != WAIT_OBJECT_0);
4126
4130
  }
4127
4131
  }
4128
4132
 
4129
4133
  // Terminate any remaining I/O
4130
- if (in_pipe[1]) {
4131
- CancelIo(in_pipe[1]);
4132
- CloseHandleSafe(&in_pipe[1]);
4133
- }
4134
- if (out_pipe[0]) {
4135
- CancelIo(out_pipe[0]);
4136
- CloseHandleSafe(&out_pipe[0]);
4137
- }
4134
+ CloseHandleSafe(&in_pipe[1]);
4135
+ CloseHandleSafe(&out_pipe[0]);
4138
4136
 
4139
4137
  // Wait for process exit
4140
4138
  {
@@ -7518,6 +7516,13 @@ void OptionParser::LogUnknownError() const
7518
7516
  }
7519
7517
  }
7520
7518
 
7519
+ void OptionParser::LogUnusedArguments() const
7520
+ {
7521
+ if (pos < args.len) {
7522
+ LogWarning("Unused command-line arguments");
7523
+ }
7524
+ }
7525
+
7521
7526
  // ------------------------------------------------------------------------
7522
7527
  // Console prompter (simplified readline)
7523
7528
  // ------------------------------------------------------------------------
@@ -1220,11 +1220,22 @@ public:
1220
1220
  {
1221
1221
  RG_ASSERT(len <= N - count);
1222
1222
 
1223
- T *it = data + len;
1224
- *it = {};
1225
- len += count;
1223
+ T *first = data + len;
1224
+ #if __cplusplus >= 201703L
1225
+ if constexpr(!std::is_trivial<T>::value) {
1226
+ #else
1227
+ if (true) {
1228
+ #endif
1229
+ for (Size i = 0; i < count; i++) {
1230
+ new (data + len) T();
1231
+ len++;
1232
+ }
1233
+ } else {
1234
+ memset_safe(first, 0, count * RG_SIZE(T));
1235
+ len += count;
1236
+ }
1226
1237
 
1227
- return it;
1238
+ return first;
1228
1239
  }
1229
1240
 
1230
1241
  T *Append(const T &value)
@@ -1434,6 +1445,7 @@ public:
1434
1445
  memset_safe(first, 0, count * RG_SIZE(T));
1435
1446
  len += count;
1436
1447
  }
1448
+
1437
1449
  return first;
1438
1450
  }
1439
1451
 
@@ -4887,7 +4899,9 @@ public:
4887
4899
  { return Test(test1, nullptr, type); }
4888
4900
 
4889
4901
  bool TestHasFailed() const { return test_failed; }
4902
+
4890
4903
  void LogUnknownError() const;
4904
+ void LogUnusedArguments() const;
4891
4905
  };
4892
4906
 
4893
4907
  template <typename T>
@@ -144,6 +144,10 @@ const TypeInfo *PrototypeParser::ParseType(int *out_directions)
144
144
  while (++offset < tokens.len && (tokens[offset] == '*' ||
145
145
  tokens[offset] == '!' ||
146
146
  tokens[offset] == "const"));
147
+ if (offset < tokens.len && tokens[offset] == "[") [[unlikely]] {
148
+ MarkError("Array types decay to pointers in prototypes (C standard), use pointers");
149
+ return instance->void_type;
150
+ }
147
151
  offset--;
148
152
 
149
153
  while (offset >= start) {
@@ -192,7 +192,8 @@ const TypeInfo *ResolveType(Napi::Env env, Span<const char> str, int *out_direct
192
192
  {
193
193
  InstanceData *instance = env.GetInstanceData<InstanceData>();
194
194
 
195
- int indirect = 0;
195
+ // Each item can be > 0 for array or 0 for a pointer
196
+ LocalArray<Size, 8> arrays;
196
197
  uint8_t disposables = 0;
197
198
 
198
199
  // Consume parameter direction qualifier
@@ -223,18 +224,18 @@ const TypeInfo *ResolveType(Napi::Env env, Span<const char> str, int *out_direct
223
224
  Span<const char> remain = str;
224
225
 
225
226
  // Skip initial const qualifiers
226
- remain = TrimStr(remain);
227
+ remain = TrimStrLeft(remain);
227
228
  while (SplitIdentifier(remain) == "const") {
228
229
  remain = remain.Take(6, remain.len - 6);
229
- remain = TrimStr(remain);
230
+ remain = TrimStrLeft(remain);
230
231
  }
231
- remain = TrimStr(remain);
232
+ remain = TrimStrLeft(remain);
232
233
 
233
234
  after = remain;
234
235
 
235
236
  // Consume one or more identifiers (e.g. unsigned int)
236
237
  for (;;) {
237
- after = TrimStr(after);
238
+ after = TrimStrLeft(after);
238
239
 
239
240
  Span<const char> token = SplitIdentifier(after);
240
241
  if (!token.len)
@@ -245,26 +246,57 @@ const TypeInfo *ResolveType(Napi::Env env, Span<const char> str, int *out_direct
245
246
  name = TrimStr(MakeSpan(remain.ptr, after.ptr - remain.ptr));
246
247
  }
247
248
 
248
- // Consume pointer indirections
249
+ // Consume type indirections (pointer, array, etc.)
249
250
  while (after.len) {
250
251
  if (after[0] == '*') {
251
252
  after = after.Take(1, after.len - 1);
252
- indirect++;
253
253
 
254
- if (indirect >= RG_SIZE(disposables) * 8) [[unlikely]] {
255
- ThrowError<Napi::Error>(env, "Too many pointer indirections");
254
+ if (!arrays.Available()) [[unlikely]] {
255
+ ThrowError<Napi::Error>(env, "Too many type indirections");
256
256
  return nullptr;
257
257
  }
258
+
259
+ arrays.Append(0);
258
260
  } else if (after[0] == '!') {
259
261
  after = after.Take(1, after.len - 1);
260
- disposables |= (1u << indirect);
262
+ disposables |= (1u << arrays.len);
263
+ } else if (after[0] == '[') {
264
+ after = after.Take(1, after.len - 1);
265
+
266
+ Size len = 0;
267
+
268
+ after = TrimStrLeft(after);
269
+ if (!ParseInt(after, &len, 0, &after) || len < 0) [[unlikely]] {
270
+ ThrowError<Napi::Error>(env, "Invalid array length");
271
+ return nullptr;
272
+ }
273
+ after = TrimStrLeft(after);
274
+ if (!after.len || after[0] != ']') [[unlikely]] {
275
+ ThrowError<Napi::Error>(env, "Expected ']' after array length");
276
+ return nullptr;
277
+ }
278
+ after = after.Take(1, after.len - 1);
279
+
280
+ if (!arrays.Available()) [[unlikely]] {
281
+ ThrowError<Napi::Error>(env, "Too many type indirections");
282
+ return nullptr;
283
+ }
284
+
285
+ arrays.Append(len);
261
286
  } else if (SplitIdentifier(after) == "const") {
262
287
  after = after.Take(6, after.len - 6);
263
288
  } else {
289
+ after = TrimStrRight(after);
290
+
291
+ if (after.len) [[unlikely]] {
292
+ ThrowError<Napi::Error>(env, "Unexpected character '%1' in type specifier", after[0]);
293
+ return nullptr;
294
+ }
295
+
264
296
  break;
265
297
  }
266
298
 
267
- after = TrimStr(after);
299
+ after = TrimStrLeft(after);
268
300
  }
269
301
 
270
302
  const TypeInfo *type = instance->types_map.FindValue(name, nullptr);
@@ -317,11 +349,29 @@ const TypeInfo *ResolveType(Napi::Env env, Span<const char> str, int *out_direct
317
349
  type = copy;
318
350
  }
319
351
 
320
- if (i >= indirect)
352
+ if (i >= arrays.len)
321
353
  break;
354
+ Size len = arrays[i];
322
355
 
323
- type = MakePointerType(instance, type);
324
- RG_ASSERT(type);
356
+ if (len > 0) {
357
+ if (type->flags & (int)TypeFlag::IsIncomplete) [[unlikely]] {
358
+ ThrowError<Napi::TypeError>(env, "Cannot make array of incomplete type");
359
+ return nullptr;
360
+ }
361
+
362
+ if (len > instance->config.max_type_size / type->size) {
363
+ ThrowError<Napi::TypeError>(env, "Array length is too high (max = %1)", instance->config.max_type_size / type->size);
364
+ return nullptr;
365
+ }
366
+
367
+ type = MakeArrayType(instance, type, len);
368
+ RG_ASSERT(type);
369
+ } else {
370
+ RG_ASSERT(!len);
371
+
372
+ type = MakePointerType(instance, type);
373
+ RG_ASSERT(type);
374
+ }
325
375
  }
326
376
 
327
377
  if (type->flags & (int)TypeFlag::IsIncomplete) [[unlikely]] {
@@ -22,15 +22,12 @@
22
22
  #pragma once
23
23
 
24
24
  #include "src/core/libcc/libcc.hh"
25
+ #include "ffi.hh"
25
26
 
26
27
  #include <napi.h>
27
28
 
28
29
  namespace RG {
29
30
 
30
- struct InstanceData;
31
- struct TypeInfo;
32
- struct FunctionInfo;
33
-
34
31
  extern const int TypeInfoMarker;
35
32
  extern const int CastMarker;
36
33
  extern const int MagicUnionMarker;
@@ -21,6 +21,7 @@
21
21
 
22
22
  #ifdef _WIN32
23
23
 
24
+ #include "util.hh"
24
25
  #include "win32.hh"
25
26
 
26
27
  #ifndef NOMINMAX
@@ -24,6 +24,7 @@
24
24
  #include "src/core/libcc/libcc.hh"
25
25
 
26
26
  #include <intrin.h>
27
+ #include <napi.h>
27
28
 
28
29
  namespace RG {
29
30