Skip to content

ffi: add new methods and error codes#62762

Open
ShogunPanda wants to merge 1 commit intonodejs:mainfrom
ShogunPanda:ffi-followup
Open

ffi: add new methods and error codes#62762
ShogunPanda wants to merge 1 commit intonodejs:mainfrom
ShogunPanda:ffi-followup

Conversation

@ShogunPanda
Copy link
Copy Markdown
Contributor

As promised to @justjake, also the following followups requested in #62072 (comment):

  • Added ffi.exportArrayBuffer(...) and ffi.exportArrayBufferView(...).
  • Added raw pointer helper ffi.getRawPointer(source) (Buffer | ArrayBuffer | ArrayBufferView), with strong “unsafe/dangerous” docs and tests.
  • Moved byte-export copying to native code (exportBytes) to avoid extra JS Buffer wrapper allocations.

@ShogunPanda ShogunPanda requested review from bengl and jasnell April 15, 2026 18:28
@nodejs-github-bot
Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/config
  • @nodejs/gyp
  • @nodejs/performance

@nodejs-github-bot nodejs-github-bot added lib / src Issues and PRs related to general changes in the lib or src directory. needs-ci PRs that need a full CI run. labels Apr 15, 2026
@justjake
Copy link
Copy Markdown

justjake commented Apr 15, 2026

Great! Thank you. It looks like I won't need my own unsafe-pointer once node:ffi gets stabilized 🎉 😄

@codecov
Copy link
Copy Markdown

codecov bot commented Apr 15, 2026

Codecov Report

❌ Patch coverage is 57.20165% with 104 lines in your changes missing coverage. Please review.
✅ Project coverage is 89.66%. Comparing base (d7ab027) to head (1fee1dd).
⚠️ Report is 7 commits behind head on main.

Files with missing lines Patch % Lines
src/ffi/data.cc 41.17% 45 Missing and 15 partials ⚠️
src/node_ffi.cc 46.80% 24 Missing and 1 partial ⚠️
src/ffi/types.cc 59.57% 19 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #62762      +/-   ##
==========================================
- Coverage   89.69%   89.66%   -0.03%     
==========================================
  Files         706      706              
  Lines      218143   218304     +161     
  Branches    41730    41772      +42     
==========================================
+ Hits       195655   195746      +91     
- Misses      14401    14475      +74     
+ Partials     8087     8083       -4     
Files with missing lines Coverage Δ
lib/ffi.js 96.56% <100.00%> (+0.71%) ⬆️
src/node_errors.h 86.48% <ø> (ø)
src/node_ffi.h 72.72% <ø> (ø)
src/ffi/types.cc 59.82% <59.57%> (+0.14%) ⬆️
src/node_ffi.cc 69.04% <46.80%> (-0.04%) ⬇️
src/ffi/data.cc 71.06% <41.17%> (-7.72%) ⬇️

... and 42 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@meixg
Copy link
Copy Markdown
Member

meixg commented Apr 16, 2026

LGTM
One small API thought: would it be better to expose this as something like getRawReference() instead of getRawPointer(), returning both the pointer and byteLength? For byte sources, the pointer is usually
meant to be used together with its length.

@ShogunPanda
Copy link
Copy Markdown
Contributor Author

@meixg I don't think it is acually necessary. When using the function you already own the source (getRawPointer(buffer)) so you can call the byteLength directly, isn't it?

@ShogunPanda ShogunPanda changed the title ffi: added new methods and error codes ffi: add new methods and error codes Apr 16, 2026
Signed-off-by: Paolo Insogna <paolo@cowtech.it>
Comment thread src/ffi/data.cc
Comment on lines +769 to +771
if (Buffer::HasInstance(args[0])) {
ptr = reinterpret_cast<uintptr_t>(Buffer::Data(args[0]));
} else if (args[0]->IsArrayBuffer()) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is already covered by the IsArrayBufferView branch below

Suggested change
if (Buffer::HasInstance(args[0])) {
ptr = reinterpret_cast<uintptr_t>(Buffer::Data(args[0]));
} else if (args[0]->IsArrayBuffer()) {
if (args[0]->IsArrayBuffer()) {

Comment thread src/ffi/types.cc
std::memchr(*value, '\0', value.length()) != nullptr) {
env->ThrowTypeError((label + " must not contain null bytes").c_str());
THROW_ERR_INVALID_ARG_VALUE(
env, (label + " must not contain null bytes").c_str());
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
env, (label + " must not contain null bytes").c_str());
env, "%s must not contain null bytes", label);

Ideally, this would be applied to all other formatting operations too

Comment thread src/ffi/types.cc
" must have either 'returns', 'return' or 'result' "
"property";
env->ThrowTypeError(msg.c_str());
THROW_ERR_INVALID_ARG_VALUE(env, msg.c_str());
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
THROW_ERR_INVALID_ARG_VALUE(env, msg.c_str());
THROW_ERR_INVALID_ARG_VALUE(env, msg);

Comment thread src/ffi/types.cc
" must have either 'parameters' or 'arguments' "
"property";
env->ThrowTypeError(msg.c_str());
THROW_ERR_INVALID_ARG_VALUE(env, msg.c_str());
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
THROW_ERR_INVALID_ARG_VALUE(env, msg.c_str());
THROW_ERR_INVALID_ARG_VALUE(env, msg);

Comment thread src/ffi/types.cc
std::string msg =
"Return value type of function " + name + " must be a string";
env->ThrowTypeError(msg.c_str());
THROW_ERR_INVALID_ARG_VALUE(env, msg.c_str());
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
THROW_ERR_INVALID_ARG_VALUE(env, msg.c_str());
THROW_ERR_INVALID_ARG_VALUE(env, msg);

Comment thread src/ffi/types.cc
Comment on lines 469 to 471
("Argument " + std::to_string(index) +
" must be a buffer, an ArrayBuffer, a string, or a bigint")
.c_str());
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
("Argument " + std::to_string(index) +
" must be a buffer, an ArrayBuffer, a string, or a bigint")
.c_str());
"Argument %s"
" must be a buffer, an ArrayBuffer, a string, or a bigint", index);

Comment thread src/ffi/types.cc
Comment on lines +459 to +461
("Argument " + std::to_string(index) +
" must be a non-negative pointer bigint")
.c_str());
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
("Argument " + std::to_string(index) +
" must be a non-negative pointer bigint")
.c_str());
"Argument %s"
" must be a non-negative pointer bigint", index);

Comment thread src/ffi/types.cc
Comment on lines +446 to +448
("Invalid ArrayBuffer backing store for argument " +
std::to_string(index))
.c_str());
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
("Invalid ArrayBuffer backing store for argument " +
std::to_string(index))
.c_str());
"Invalid ArrayBuffer backing store for argument %s",
index);

Comment thread src/ffi/types.cc
Comment on lines 424 to 426
("Invalid ArrayBufferView backing store for argument " +
std::to_string(index))
.c_str());
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
("Invalid ArrayBufferView backing store for argument " +
std::to_string(index))
.c_str());
"Invalid ArrayBufferView backing store for argument %s", index);

Comment thread src/ffi/data.cc
Comment on lines +690 to +693
if (Buffer::HasInstance(args[0])) {
source_data = reinterpret_cast<uint8_t*>(Buffer::Data(args[0]));
source_len = Buffer::Length(args[0]);
} else if (args[0]->IsArrayBuffer()) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto here

Suggested change
if (Buffer::HasInstance(args[0])) {
source_data = reinterpret_cast<uint8_t*>(Buffer::Data(args[0]));
source_len = Buffer::Length(args[0]);
} else if (args[0]->IsArrayBuffer()) {
if (args[0]->IsArrayBuffer()) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And more broadly than that, you can probably just implement this a lot more easily by using ArrayBufferViewContents

Comment thread src/node_ffi.cc
return;
}
library_path = *filename;
lib->path_ = std::string(*filename);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
lib->path_ = std::string(*filename);
lib->path_ = filename.ToString();

Comment thread src/node_ffi.cc
if (ThrowIfContainsNullBytes(env, filename, "Library path")) {
return;
}
library_path = *filename;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a use-after-free bug – *filename goes out of scope outside of this conditional

Comment thread src/ffi/data.cc
env->ThrowRangeError(
(std::string("The ") + label + " is too large").c_str());
THROW_ERR_OUT_OF_RANGE(
env, (std::string("The ") + label + " is too large").c_str());
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
env, (std::string("The ") + label + " is too large").c_str());
"The %s is too large", label);

Comment thread src/ffi/data.cc
Comment on lines 87 to 88
(std::string("The ") + label + " exceeds the platform pointer range")
.c_str());
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
(std::string("The ") + label + " exceeds the platform pointer range")
.c_str());
"The %s exceeds the platform pointer range", label);

Comment thread src/ffi/data.cc
Comment on lines +690 to +693
if (Buffer::HasInstance(args[0])) {
source_data = reinterpret_cast<uint8_t*>(Buffer::Data(args[0]));
source_len = Buffer::Length(args[0]);
} else if (args[0]->IsArrayBuffer()) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And more broadly than that, you can probably just implement this a lot more easily by using ArrayBufferViewContents

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lib / src Issues and PRs related to general changes in the lib or src directory. needs-ci PRs that need a full CI run.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants