Skip to content

Commit edd2a79

Browse files
committed
ffi: keep wrapped functions non-constructible
Use concise method functions for Fast API and shared-buffer wrappers so they preserve the native FFI functions' non-constructible behavior. Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com> Assisted-by: codex:gpt-5.6-sol
1 parent d512d2d commit edd2a79

3 files changed

Lines changed: 67 additions & 42 deletions

File tree

lib/internal/ffi-shared-buffer.js

Lines changed: 36 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,8 @@ function inheritMetadata(wrapper, rawFn, nargs) {
198198
// arguments out of it into invocation-local storage before `ffi_call` and
199199
// reads the return value back only after, so nested/reentrant calls into
200200
// the same function are safe.
201+
// Concise methods do not have [[Construct]], unlike function expressions, so
202+
// use them below to match the native FFI functions' non-constructible behavior.
201203
function wrapWithSharedBuffer(rawFn, signature) {
202204
if (rawFn == null) return rawFn;
203205
const buffer = rawFn[kSbSharedBuffer];
@@ -254,7 +256,7 @@ function wrapWithSharedBuffer(rawFn, signature) {
254256
// so arity specialization wouldn't buy much here.
255257
assert(slowInvoke !== undefined,
256258
'FFI: shared-buffer raw function with pointer arguments is missing kSbInvokeSlow');
257-
wrapper = function(...args) {
259+
wrapper = { invoke(...args) {
258260
if (args.length !== nargs) {
259261
throwFFIArgCountError(nargs, args.length);
260262
}
@@ -271,7 +273,7 @@ function wrapWithSharedBuffer(rawFn, signature) {
271273
}
272274
rawFn();
273275
return retGetter === null ? undefined : retGetter(view, 0, true);
274-
};
276+
} }.invoke;
275277
} else {
276278
// Arity specialization avoids the per-call `Array` allocation of
277279
// `...args`; the void/non-void split removes a per-call branch on
@@ -295,67 +297,67 @@ function buildNumericWrapper(
295297
/* c8 ignore start */
296298
if (nargs === 0) {
297299
if (retGetter === null) {
298-
return function() {
300+
return { invoke() {
299301
if (arguments.length !== 0) {
300302
throwFFIArgCountError(0, arguments.length);
301303
}
302304
rawFn();
303-
};
305+
} }.invoke;
304306
}
305-
return function() {
307+
return { invoke() {
306308
if (arguments.length !== 0) {
307309
throwFFIArgCountError(0, arguments.length);
308310
}
309311
rawFn();
310312
return retGetter(view, 0, true);
311-
};
313+
} }.invoke;
312314
}
313315
/* c8 ignore stop */
314316
if (nargs === 1) {
315317
const i0 = argInfos[0];
316318
const o0 = argOffsets[0];
317319
if (retGetter === null) {
318-
return function(a0) {
320+
return { invoke(a0) {
319321
if (arguments.length !== 1) {
320322
throwFFIArgCountError(1, arguments.length);
321323
}
322324
writeNumericArg(view, i0, o0, a0, 0);
323325
rawFn();
324-
};
326+
} }.invoke;
325327
}
326-
return function(a0) {
328+
return { invoke(a0) {
327329
if (arguments.length !== 1) {
328330
throwFFIArgCountError(1, arguments.length);
329331
}
330332
writeNumericArg(view, i0, o0, a0, 0);
331333
rawFn();
332334
return retGetter(view, 0, true);
333-
};
335+
} }.invoke;
334336
}
335337
if (nargs === 2) {
336338
const i0 = argInfos[0];
337339
const i1 = argInfos[1];
338340
const o0 = argOffsets[0];
339341
const o1 = argOffsets[1];
340342
if (retGetter === null) {
341-
return function(a0, a1) {
343+
return { invoke(a0, a1) {
342344
if (arguments.length !== 2) {
343345
throwFFIArgCountError(2, arguments.length);
344346
}
345347
writeNumericArg(view, i0, o0, a0, 0);
346348
writeNumericArg(view, i1, o1, a1, 1);
347349
rawFn();
348-
};
350+
} }.invoke;
349351
}
350-
return function(a0, a1) {
352+
return { invoke(a0, a1) {
351353
if (arguments.length !== 2) {
352354
throwFFIArgCountError(2, arguments.length);
353355
}
354356
writeNumericArg(view, i0, o0, a0, 0);
355357
writeNumericArg(view, i1, o1, a1, 1);
356358
rawFn();
357359
return retGetter(view, 0, true);
358-
};
360+
} }.invoke;
359361
}
360362
if (nargs === 3) {
361363
const i0 = argInfos[0];
@@ -365,17 +367,17 @@ function buildNumericWrapper(
365367
const o1 = argOffsets[1];
366368
const o2 = argOffsets[2];
367369
if (retGetter === null) {
368-
return function(a0, a1, a2) {
370+
return { invoke(a0, a1, a2) {
369371
if (arguments.length !== 3) {
370372
throwFFIArgCountError(3, arguments.length);
371373
}
372374
writeNumericArg(view, i0, o0, a0, 0);
373375
writeNumericArg(view, i1, o1, a1, 1);
374376
writeNumericArg(view, i2, o2, a2, 2);
375377
rawFn();
376-
};
378+
} }.invoke;
377379
}
378-
return function(a0, a1, a2) {
380+
return { invoke(a0, a1, a2) {
379381
if (arguments.length !== 3) {
380382
throwFFIArgCountError(3, arguments.length);
381383
}
@@ -384,7 +386,7 @@ function buildNumericWrapper(
384386
writeNumericArg(view, i2, o2, a2, 2);
385387
rawFn();
386388
return retGetter(view, 0, true);
387-
};
389+
} }.invoke;
388390
}
389391
if (nargs === 4) {
390392
const i0 = argInfos[0];
@@ -396,7 +398,7 @@ function buildNumericWrapper(
396398
const o2 = argOffsets[2];
397399
const o3 = argOffsets[3];
398400
if (retGetter === null) {
399-
return function(a0, a1, a2, a3) {
401+
return { invoke(a0, a1, a2, a3) {
400402
if (arguments.length !== 4) {
401403
throwFFIArgCountError(4, arguments.length);
402404
}
@@ -405,9 +407,9 @@ function buildNumericWrapper(
405407
writeNumericArg(view, i2, o2, a2, 2);
406408
writeNumericArg(view, i3, o3, a3, 3);
407409
rawFn();
408-
};
410+
} }.invoke;
409411
}
410-
return function(a0, a1, a2, a3) {
412+
return { invoke(a0, a1, a2, a3) {
411413
if (arguments.length !== 4) {
412414
throwFFIArgCountError(4, arguments.length);
413415
}
@@ -417,7 +419,7 @@ function buildNumericWrapper(
417419
writeNumericArg(view, i3, o3, a3, 3);
418420
rawFn();
419421
return retGetter(view, 0, true);
420-
};
422+
} }.invoke;
421423
}
422424
if (nargs === 5) {
423425
const i0 = argInfos[0];
@@ -431,7 +433,7 @@ function buildNumericWrapper(
431433
const o3 = argOffsets[3];
432434
const o4 = argOffsets[4];
433435
if (retGetter === null) {
434-
return function(a0, a1, a2, a3, a4) {
436+
return { invoke(a0, a1, a2, a3, a4) {
435437
if (arguments.length !== 5) {
436438
throwFFIArgCountError(5, arguments.length);
437439
}
@@ -441,9 +443,9 @@ function buildNumericWrapper(
441443
writeNumericArg(view, i3, o3, a3, 3);
442444
writeNumericArg(view, i4, o4, a4, 4);
443445
rawFn();
444-
};
446+
} }.invoke;
445447
}
446-
return function(a0, a1, a2, a3, a4) {
448+
return { invoke(a0, a1, a2, a3, a4) {
447449
if (arguments.length !== 5) {
448450
throwFFIArgCountError(5, arguments.length);
449451
}
@@ -454,7 +456,7 @@ function buildNumericWrapper(
454456
writeNumericArg(view, i4, o4, a4, 4);
455457
rawFn();
456458
return retGetter(view, 0, true);
457-
};
459+
} }.invoke;
458460
}
459461
if (nargs === 6) {
460462
const i0 = argInfos[0];
@@ -470,7 +472,7 @@ function buildNumericWrapper(
470472
const o4 = argOffsets[4];
471473
const o5 = argOffsets[5];
472474
if (retGetter === null) {
473-
return function(a0, a1, a2, a3, a4, a5) {
475+
return { invoke(a0, a1, a2, a3, a4, a5) {
474476
if (arguments.length !== 6) {
475477
throwFFIArgCountError(6, arguments.length);
476478
}
@@ -481,9 +483,9 @@ function buildNumericWrapper(
481483
writeNumericArg(view, i4, o4, a4, 4);
482484
writeNumericArg(view, i5, o5, a5, 5);
483485
rawFn();
484-
};
486+
} }.invoke;
485487
}
486-
return function(a0, a1, a2, a3, a4, a5) {
488+
return { invoke(a0, a1, a2, a3, a4, a5) {
487489
if (arguments.length !== 6) {
488490
throwFFIArgCountError(6, arguments.length);
489491
}
@@ -495,22 +497,22 @@ function buildNumericWrapper(
495497
writeNumericArg(view, i5, o5, a5, 5);
496498
rawFn();
497499
return retGetter(view, 0, true);
498-
};
500+
} }.invoke;
499501
}
500502
// 7+ args: further specialization is diminishing returns and bloats
501503
// this builder.
502504
if (retGetter === null) {
503-
return function(...args) {
505+
return { invoke(...args) {
504506
if (args.length !== nargs) {
505507
throwFFIArgCountError(nargs, args.length);
506508
}
507509
for (let i = 0; i < nargs; i++) {
508510
writeNumericArg(view, argInfos[i], argOffsets[i], args[i], i);
509511
}
510512
rawFn();
511-
};
513+
} }.invoke;
512514
}
513-
return function(...args) {
515+
return { invoke(...args) {
514516
if (args.length !== nargs) {
515517
throwFFIArgCountError(nargs, args.length);
516518
}
@@ -519,7 +521,7 @@ function buildNumericWrapper(
519521
}
520522
rawFn();
521523
return retGetter(view, 0, true);
522-
};
524+
} }.invoke;
523525
}
524526

525527
module.exports = {

lib/internal/ffi/fast-api.js

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,8 @@ function throwIfFastLibraryClosed(state) {
222222
}
223223
}
224224

225+
// Concise methods do not have [[Construct]], unlike function expressions.
226+
// Keep wrappers non-constructible to match the native FFI functions.
225227
function wrapWithRawPointerConversions(rawFn, argumentTypes, owner) {
226228
if (rawFn === undefined || rawFn === null) {
227229
return rawFn;
@@ -255,7 +257,7 @@ function wrapWithRawPointerConversions(rawFn, argumentTypes, owner) {
255257
const memory0 = needsRawPointerConversion(t0) || string0;
256258
const fastBufferInvoke = needsPointerLikeConversion(t0) ?
257259
rawFn[kFastBufferInvoke] : undefined;
258-
wrapper = function(a0) {
260+
wrapper = { invoke(a0) {
259261
throwIfFastLibraryClosed(state);
260262
if (arguments.length !== 1) {
261263
throwFFIArgCountError(1, arguments.length);
@@ -279,13 +281,13 @@ function wrapWithRawPointerConversions(rawFn, argumentTypes, owner) {
279281
arg = getRawPointer(arg);
280282
}
281283
return rawFn(arg);
282-
};
284+
} }.invoke;
283285
} else if (nargs === 2) {
284286
const c0 = ArrayPrototypeIncludes(indexes, 0);
285287
const c1 = ArrayPrototypeIncludes(indexes, 1);
286288
const t0 = argumentTypes[0];
287289
const t1 = argumentTypes[1];
288-
wrapper = function(a0, a1) {
290+
wrapper = { invoke(a0, a1) {
289291
throwIfFastLibraryClosed(state);
290292
if (arguments.length !== 2) {
291293
throwFFIArgCountError(2, arguments.length);
@@ -299,15 +301,15 @@ function wrapWithRawPointerConversions(rawFn, argumentTypes, owner) {
299301
} finally {
300302
if (stringCall) exitStringConversion(stringState);
301303
}
302-
};
304+
} }.invoke;
303305
} else if (nargs === 3) {
304306
const c0 = ArrayPrototypeIncludes(indexes, 0);
305307
const c1 = ArrayPrototypeIncludes(indexes, 1);
306308
const c2 = ArrayPrototypeIncludes(indexes, 2);
307309
const t0 = argumentTypes[0];
308310
const t1 = argumentTypes[1];
309311
const t2 = argumentTypes[2];
310-
wrapper = function(a0, a1, a2) {
312+
wrapper = { invoke(a0, a1, a2) {
311313
throwIfFastLibraryClosed(state);
312314
if (arguments.length !== 3) {
313315
throwFFIArgCountError(3, arguments.length);
@@ -323,9 +325,9 @@ function wrapWithRawPointerConversions(rawFn, argumentTypes, owner) {
323325
} finally {
324326
if (stringCall) exitStringConversion(stringState);
325327
}
326-
};
328+
} }.invoke;
327329
} else {
328-
wrapper = function(...args) {
330+
wrapper = { invoke(...args) {
329331
throwIfFastLibraryClosed(state);
330332
if (args.length !== nargs) {
331333
throwFFIArgCountError(nargs, args.length);
@@ -349,7 +351,7 @@ function wrapWithRawPointerConversions(rawFn, argumentTypes, owner) {
349351
} finally {
350352
if (stringCall) exitStringConversion(stringState);
351353
}
352-
};
354+
} }.invoke;
353355
}
354356

355357
return inheritMetadata(wrapper, rawFn, nargs);

test/ffi/test-ffi-dynamic-library.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,27 @@ test('dlopen resolves functions from definitions', () => {
6767
}
6868
});
6969

70+
test('FFI functions are not constructible', () => {
71+
const { lib, functions } = ffi.dlopen(libraryPath, {
72+
add_i32: fixtureSymbols.add_i32,
73+
multiply_f64: fixtureSymbols.multiply_f64,
74+
});
75+
76+
try {
77+
assert.strictEqual(Object.hasOwn(functions.add_i32, 'prototype'), false);
78+
assert.strictEqual(
79+
Object.hasOwn(functions.multiply_f64, 'prototype'), false);
80+
assert.throws(
81+
() => Reflect.construct(functions.add_i32, [20, 22]),
82+
TypeError);
83+
assert.throws(
84+
() => Reflect.construct(functions.multiply_f64, [6, 7]),
85+
TypeError);
86+
} finally {
87+
lib.close();
88+
}
89+
});
90+
7091
test('DynamicLibrary exposes functions and symbols', () => {
7192
const lib = new ffi.DynamicLibrary(libraryPath);
7293

0 commit comments

Comments
 (0)