@ 261105.16
current
'use strict';

const {
  ArrayBufferPrototypeGetDetached,
  DataViewPrototypeGetBuffer,
  FunctionPrototypeCall,
  ObjectDefineProperty,
  ObjectFreeze,
  ObjectGetOwnPropertyDescriptor,
  ObjectKeys,
  ReflectConstruct,
  SafeWeakMap,
  SafeWeakRef,
  SymbolDispose,
  TypedArrayPrototypeGetBuffer,
} = primordials;
const { Buffer } = require('buffer');
const { emitExperimentalWarning } = require('internal/util');
const {
  isArrayBuffer,
  isDataView,
  isArrayBufferView,
  isSharedArrayBuffer,
} = require('internal/util/types');
const {
  codes: {
    ERR_ACCESS_DENIED,
    ERR_INTERNAL_ASSERTION,
    ERR_INVALID_ARG_TYPE,
    ERR_OUT_OF_RANGE,
  },
} = require('internal/errors');
const permission = require('internal/process/permission');
const {
  validateInteger,
  validateString,
} = require('internal/validators');

emitExperimentalWarning('FFI');

const {
  DynamicLibrary: NativeDynamicLibrary,
  getInt8,
  getUint8,
  getInt16,
  getUint16,
  getInt32,
  getUint32,
  getInt64,
  getUint64,
  getFloat32,
  getFloat64,
  getCurrentEventLoop,
  exportBytes,
  getRawPointer,
  kFastArguments,
  kSbArguments,
  kSbReturn,
  setInt8,
  setUint8,
  setInt16,
  setUint16,
  setInt32,
  setUint32,
  setInt64,
  setUint64,
  setFloat32,
  setFloat64,
  toString,
  toBuffer,
  toArrayBuffer,
} = internalBinding('ffi');

const {
  wrapWithSharedBuffer,
} = require('internal/ffi-shared-buffer');

const {
  markFastLibraryClosed,
  wrapWithRawPointerConversions,
} = require('internal/ffi/fast-api');

function makeSignature(argumentTypes, returnType) {
  return {
    __proto__: null,
    arguments: argumentTypes,
    return: returnType,
  };
}

// The native layer hands out one raw function per resolved symbol, so the
// wrapper composed around it is reused too, otherwise every read of
// `library.functions` would return callables that are not identical to the
// previous read's. The entry holds a WeakRef because V8 can keep a raw function
// alive after user code drops the wrapper, and a strong value would then pin
// every wrapper for the lifetime of the library.
const wrappedFunctions = new SafeWeakMap();

function wrapFFIFunction(rawFn, owner) {
  if (rawFn === undefined || rawFn === null) {
    return rawFn;
  }
  const cached = wrappedFunctions.get(rawFn)?.deref();
  if (cached !== undefined) {
    return cached;
  }
  let returnType;
  const sbArguments = rawFn[kSbArguments];
  const argumentTypes = sbArguments ?? rawFn[kFastArguments];
  if (sbArguments !== undefined) {
    returnType = rawFn[kSbReturn];
  }
  let wrapped = wrapWithSharedBuffer(
    rawFn,
    argumentTypes === undefined ? undefined : makeSignature(argumentTypes, returnType));
  if (wrapped === rawFn) {
    wrapped = wrapWithRawPointerConversions(rawFn, argumentTypes, owner);
  }
  wrappedFunctions.set(rawFn, new SafeWeakRef(wrapped));
  return wrapped;
}

const { getVfsLibraryReader } = require('internal/ffi/vfs');

// A thin constructor in front of the native class so that a library inside
// a mounted virtual file system loads transparently: its bytes are read
// from the VFS and handed to the native constructor, which loads them from
// a private, self-cleaning image - the same way require() handles a native
// addon in a VFS. The reader is installed by the VFS while it is mounted
// (see internal/ffi/vfs), so no VFS code is ever loaded from here. The
// wrapper shares the native prototype, so instances and instanceof behave
// as if the native class were exposed directly.
function DynamicLibrary(path) {
  if (new.target === undefined) {
    // Let the native constructor produce its usual error.
    return FunctionPrototypeCall(NativeDynamicLibrary, this, path);
  }
  const readVirtualLibrary = getVfsLibraryReader();
  const binary =
    readVirtualLibrary === null || typeof path !== 'string' ?
      undefined : readVirtualLibrary(path);
  return ReflectConstruct(NativeDynamicLibrary,
                          binary === undefined ? [path] : [path, binary],
                          new.target);
}
DynamicLibrary.prototype = NativeDynamicLibrary.prototype;
ObjectDefineProperty(DynamicLibrary.prototype, 'constructor', {
  __proto__: null,
  configurable: true,
  value: DynamicLibrary,
  writable: true,
});

const rawGetFunction = DynamicLibrary.prototype.getFunction;
const rawToFunction = DynamicLibrary.prototype.toFunction;
const rawGetFunctions = DynamicLibrary.prototype.getFunctions;
const rawClose = DynamicLibrary.prototype.close;

function close() {
  const result = FunctionPrototypeCall(rawClose, this);
  markFastLibraryClosed(this);
  return result;
}

ObjectDefineProperty(DynamicLibrary.prototype, 'close', {
  __proto__: null,
  configurable: true,
  value: close,
  writable: true,
});

ObjectDefineProperty(DynamicLibrary.prototype, SymbolDispose, {
  __proto__: null,
  configurable: true,
  value: close,
  writable: true,
});

DynamicLibrary.prototype.getFunction = function getFunction(name, signature) {
  const raw = FunctionPrototypeCall(rawGetFunction, this, name, signature);
  return wrapFFIFunction(raw, this);
};

DynamicLibrary.prototype.toFunction = function toFunction(pointer, signature) {
  const raw = FunctionPrototypeCall(rawToFunction, this, pointer, signature);
  return wrapFFIFunction(raw, this);
};

DynamicLibrary.prototype.getFunctions = function getFunctions(definitions) {
  const raw = definitions === undefined ?
    FunctionPrototypeCall(rawGetFunctions, this) :
    FunctionPrototypeCall(rawGetFunctions, this, definitions);
  if (raw === undefined || raw === null) return raw;
  const keys = ObjectKeys(raw);
  const out = { __proto__: null };
  for (let i = 0; i < keys.length; i++) {
    const name = keys[i];
    out[name] = wrapFFIFunction(raw[name], this);
  }
  return out;
};

{
  const functionsDescriptor =
    ObjectGetOwnPropertyDescriptor(DynamicLibrary.prototype, 'functions');
  if (functionsDescriptor === undefined || functionsDescriptor.get === undefined) {
    throw new ERR_INTERNAL_ASSERTION(
      'FFI: DynamicLibrary.prototype.functions accessor not found or has no getter');
  }
  const origGetter = functionsDescriptor.get;
  ObjectDefineProperty(DynamicLibrary.prototype, 'functions', {
    __proto__: null,
    configurable: true,
    enumerable: functionsDescriptor.enumerable,
    get() {
      const raw = FunctionPrototypeCall(origGetter, this);
      if (raw === undefined || raw === null) return raw;
      const wrapped = { __proto__: null };
      const keys = ObjectKeys(raw);
      for (let i = 0; i < keys.length; i++) {
        const name = keys[i];
        wrapped[name] = wrapFFIFunction(raw[name], this);
      }
      return wrapped;
    },
  });
}

function checkFFIPermission() {
  if (!permission.isEnabled()) {
    return;
  }

  if (permission.has('ffi') || permission.isAuditMode()) {
    return;
  }

  throw new ERR_ACCESS_DENIED(
    'Access to this API has been restricted. Use --allow-ffi to manage permissions.',
    'FFI');
}

function dlopen(path, definitions) {
  checkFFIPermission();

  const lib = new DynamicLibrary(path);
  try {
    const functions = definitions === undefined ? ObjectFreeze({ __proto__: null }) : lib.getFunctions(definitions);
    return {
      lib,
      functions,
      [SymbolDispose]() { lib.close(); },
    };
  } catch (error) {
    lib.close();
    throw error;
  }
}

function dlclose(handle) {
  handle.close();
}

function dlsym(handle, symbol) {
  return handle.getSymbol(symbol);
}

function exportString(str, data, len, encoding = 'utf8') {
  checkFFIPermission();
  validateString(str, 'string');
  validateString(encoding, 'encoding');
  validateInteger(len, 'len', 0);

  let terminatorSize;

  switch (encoding.toLowerCase()) {
    case 'ucs2':
    case 'ucs-2':
    case 'utf16le':
    case 'utf-16le':
      terminatorSize = 2;
      break;
    default:
      terminatorSize = 1;
      break;
  }

  const sourceBuffer = Buffer.from(str, encoding);
  const requiredLength = sourceBuffer.length + terminatorSize;

  if (len < requiredLength) {
    throw new ERR_OUT_OF_RANGE('len', `>= ${requiredLength}`, len);
  }

  const terminated = Buffer.allocUnsafe(requiredLength);
  sourceBuffer.copy(terminated);
  terminated.fill(0, sourceBuffer.length);
  exportBytes(terminated, data, len);
}

function exportBuffer(source, data, len) {
  checkFFIPermission();

  if (!Buffer.isBuffer(source)) {
    throw new ERR_INVALID_ARG_TYPE('buffer', 'Buffer', source);
  }

  validateInteger(len, 'len', 0);

  if (len < source.length) {
    throw new ERR_OUT_OF_RANGE('len', `>= ${source.length}`, len);
  }

  exportBytes(source, data, len);
}

function exportArrayBuffer(source, data, len) {
  checkFFIPermission();

  if (!isArrayBuffer(source)) {
    throw new ERR_INVALID_ARG_TYPE('arrayBuffer', 'ArrayBuffer', source);
  }

  validateInteger(len, 'len', 0);

  if (len < source.byteLength) {
    throw new ERR_OUT_OF_RANGE('len', `>= ${source.byteLength}`, len);
  }

  exportBytes(source, data, len);
}

function exportArrayBufferView(source, data, len) {
  checkFFIPermission();

  if (!isArrayBufferView(source)) {
    throw new ERR_INVALID_ARG_TYPE('arrayBufferView', 'ArrayBufferView', source);
  }

  validateInteger(len, 'len', 0);

  // Reading byteLength throws for a detached DataView. Let the native binding
  // reject detached views consistently with detached ArrayBuffers.
  const buffer = isDataView(source) ?
    DataViewPrototypeGetBuffer(source) : TypedArrayPrototypeGetBuffer(source);
  if ((isSharedArrayBuffer(buffer) ||
       !ArrayBufferPrototypeGetDetached(buffer)) &&
      len < source.byteLength) {
    throw new ERR_OUT_OF_RANGE('len', `>= ${source.byteLength}`, len);
  }

  exportBytes(source, data, len);
}

const suffix = process.platform === 'win32' ? 'dll' : process.platform === 'darwin' ? 'dylib' : 'so';

const types = ObjectFreeze({
  __proto__: null,
  VOID: 'void',
  POINTER: 'pointer',
  BUFFER: 'buffer',
  ARRAY_BUFFER: 'arraybuffer',
  FUNCTION: 'function',
  BOOL: 'bool',
  CHAR: 'char',
  STRING: 'string',
  FLOAT: 'float',
  DOUBLE: 'double',
  INT_8: 'int8',
  UINT_8: 'uint8',
  INT_16: 'int16',
  UINT_16: 'uint16',
  INT_32: 'int32',
  UINT_32: 'uint32',
  INT_64: 'int64',
  UINT_64: 'uint64',
  FLOAT_32: 'float32',
  FLOAT_64: 'float64',
});


module.exports = {
  DynamicLibrary,
  dlopen,
  dlclose,
  dlsym,
  exportArrayBuffer,
  exportArrayBufferView,
  exportString,
  exportBuffer,
  getInt8,
  getUint8,
  getInt16,
  getUint16,
  getInt32,
  getUint32,
  getInt64,
  getUint64,
  getFloat32,
  getFloat64,
  getCurrentEventLoop,
  getRawPointer,
  setInt8,
  setUint8,
  setInt16,
  setUint16,
  setInt32,
  setUint32,
  setInt64,
  setUint64,
  setFloat32,
  setFloat64,
  suffix,
  toString,
  toArrayBuffer,
  toBuffer,
  types,
};