Skip to content

Conversation

@littledivy
Copy link
Member

Closes #31433

@coderabbitai
Copy link

coderabbitai bot commented Nov 28, 2025

Walkthrough

This PR introduces a new N-API function napi_create_object_with_properties that creates JavaScript objects with specified properties and prototypes. The function is added to the Rust N-API bindings and exported in symbol lists for Linux, macOS, and Windows. The implementation validates arguments, converts property names to v8::Name objects, and constructs objects with both a custom prototype and properties. Comprehensive tests are included covering standard property creation, empty objects, and objects with custom prototypes.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding a new NAPI API function for creating objects with properties.
Description check ✅ Passed The description references the closed issue #31433, which relates to implementing the napi_create_object_with_properties API.
Linked Issues check ✅ Passed The PR implements napi_create_object_with_properties across all platforms (Linux, macOS, Windows) with supporting tests, meeting the objective to support faster object creation in Deno's Node API compatibility layer.
Out of Scope Changes check ✅ Passed All changes are in-scope: new API implementation, platform-specific symbol exports, and comprehensive tests for the new functionality. No unrelated modifications detected.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ef3192d and 440eead.

📒 Files selected for processing (7)
  • ext/napi/generated_symbol_exports_list_linux.def (1 hunks)
  • ext/napi/generated_symbol_exports_list_macos.def (1 hunks)
  • ext/napi/generated_symbol_exports_list_windows.def (1 hunks)
  • ext/napi/js_native_api.rs (1 hunks)
  • ext/napi/sym/symbol_exports.json (1 hunks)
  • tests/napi/object_test.js (1 hunks)
  • tests/napi/src/object.rs (3 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.rs

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.rs: For debugging Rust code, set breakpoints in IDE debuggers (VS Code with rust-analyzer, IntelliJ IDEA) or use lldb directly
Use eprintln!() or dbg!() macros for debug prints in Rust code

Files:

  • ext/napi/js_native_api.rs
  • tests/napi/src/object.rs
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js}: For JavaScript runtime debugging, enable V8 inspector with --inspect-brk flag and connect Chrome DevTools to chrome://inspect
Use console.log() for debug prints in JavaScript runtime code

Files:

  • tests/napi/object_test.js
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: test debug linux-x86_64
  • GitHub Check: test debug macos-aarch64
  • GitHub Check: test release linux-x86_64
  • GitHub Check: test debug windows-x86_64
  • GitHub Check: test debug linux-aarch64
  • GitHub Check: test debug macos-x86_64
  • GitHub Check: lint debug macos-x86_64
  • GitHub Check: build libs
  • GitHub Check: lint debug windows-x86_64
  • GitHub Check: lint debug linux-x86_64
🔇 Additional comments (8)
ext/napi/generated_symbol_exports_list_windows.def (1)

96-98: Windows export entry looks correct

Symbol name and ordering (between napi_create_object and napi_create_promise) are consistent with the other export lists and JSON manifest.

ext/napi/generated_symbol_exports_list_linux.def (1)

1-1: Linux symbol list updated consistently

"napi_create_object_with_properties" is added in the expected position alongside the other object-related symbols.

ext/napi/generated_symbol_exports_list_macos.def (1)

94-96: macOS export looks aligned

The _napi_create_object_with_properties symbol is correctly exported and ordered after _napi_create_object.

ext/napi/sym/symbol_exports.json (1)

96-98: Symbol manifest entry is consistent

The JSON export list now includes "napi_create_object_with_properties" in the same relative position as the platform-specific .def files. No issues.

tests/napi/object_test.js (1)

40-65: New N-API object tests look solid

The three tests exercise non-empty, empty, and custom-prototype cases and validate both shape and prototype behavior (via the test method). This provides good coverage for the new API.

tests/napi/src/object.rs (3)

11-21: Extern declaration matches the intended N-API shape

The experimental napi_create_object_with_properties declaration lines up with the expected N-API signature and is fine as a temporary bridge until napi-sys grows this API.


81-205: Rust tests nicely cover the new API (and highlight the 0-property edge case)

The three helpers exercise:

  • non-empty properties with a null prototype,
  • the property_count == 0 case with null pointers for the property arrays,
  • and a custom prototype carrying a method plus an own data property.

Once the implementation in napi_create_object_with_properties is adjusted to avoid from_raw_parts on null pointers when property_count == 0, these tests give good coverage of the new behavior.


216-230: Init wiring for new test exports looks correct

The new entries in the properties array correctly expose all three test helpers under the expected names; this integrates cleanly with the existing init pattern.

Comment on lines +1000 to +1058
#[napi_sym]
fn napi_create_object_with_properties<'s>(
env_ptr: *mut Env,
prototype_or_null: napi_value<'s>,
property_names: *const napi_value<'s>,
property_values: *const napi_value<'s>,
property_count: usize,
result: *mut napi_value<'s>,
) -> napi_status {
let env = check_env!(env_ptr);
check_arg!(env, result);

if property_count > 0 {
check_arg!(env, property_names);
check_arg!(env, property_values);
}

unsafe {
v8::callback_scope!(unsafe scope, env.context());

let prototype = if let Some(proto) = *prototype_or_null {
proto
} else {
v8::null(scope).into()
};

let names = std::slice::from_raw_parts(property_names, property_count);
let values = std::slice::from_raw_parts(property_values, property_count);

for name in names {
if let Some(name_val) = **name {
if !name_val.is_name() {
return napi_name_expected;
}
} else {
return napi_invalid_arg;
}
}

let mut v8_names = Vec::with_capacity(property_count);
let mut v8_values = Vec::with_capacity(property_count);

for i in 0..property_count {
if let Some(name) = *names[i] {
v8_names.push(name.try_cast::<v8::Name>().unwrap());
}
if let Some(value) = *values[i] {
v8_values.push(value);
}
}

*result = v8::Object::with_prototype_and_properties(
scope, prototype, &v8_names, &v8_values,
)
.into();
}

return napi_clear_last_error(env_ptr);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find the file under review
find . -name "js_native_api.rs" -type f

Repository: denoland/deno

Length of output: 170


🏁 Script executed:

#!/bin/bash
# Look for test files related to napi_create_object_with_properties
find . -type f -name "*.rs" | head -20 && \
rg -l "napi_create_object_with_properties" --type rust

Repository: denoland/deno

Length of output: 170


🏁 Script executed:

#!/bin/bash
# Search for tests that call this function with property_count == 0
rg -A5 -B5 "property_count.*0\|napi_create_object_with_properties" --type rust -C3

Repository: denoland/deno

Length of output: 170


🌐 Web query:

Rust std::slice::from_raw_parts null pointer UB undefined behavior zero length

💡 Result:

Short answer: It's UB. std::slice::from_raw_parts / from_raw_parts_mut require the pointer to be non-null and properly aligned even when len == 0 — passing a null pointer to produce a reference is undefined behavior. Use NonNull::dangling() (or a valid non-null pointer) for zero-length slices instead. [1][2][3]

References

  • std::slice::from_raw_parts / from_raw_parts_mut safety section (non-null required even for zero-length slices). [1]
  • core::ptr::slice_from_raw_parts docs note a raw slice with a null pointer is dangerous and that a slice reference must never have a null pointer. [2]
  • from_raw_parts_mut explicit wording about non-null for zero-length slices and NonNull::dangling(). [3]

[1] https://doc.rust-lang.org/std/slice/fn.from_raw_parts.html
[2] https://doc.rust-lang.org/std/ptr/fn.slice_from_raw_parts.html
[3] https://doc.rust-lang.org/std/slice/fn.from_raw_parts_mut.html


🌐 Web query:

Node.js NAPI napi_create_object_with_properties prototype documentation

💡 Result:

There is no napi_create_object_with_properties in upstream Node.js N-API. The official Node.js N-API uses napi_create_object and napi_define_properties (or napi_create_object_with_named_properties in some forks). Example Node.js N-API prototypes from the official docs:

  • napi_create_object:
    napi_status napi_create_object(napi_env env, napi_value* result). [1]

  • napi_define_properties:
    napi_status napi_define_properties(napi_env env,
    napi_value object,
    size_t property_count,
    const napi_property_descriptor* properties); [1]

The symbol napi_create_object_with_properties appears in other runtimes/forks (e.g., Harmony/Ark/other Node-API variants) with this prototype:
napi_status napi_create_object_with_properties(napi_env env,
napi_value* result,
size_t property_count,
const napi_property_descriptor* properties);
(Used in Harmony/Ark Node-API documentation.) [2][3]

References

  • Official Node.js N-API docs (shows napi_create_object and napi_define_properties). [1]
  • Harmony/Ark Node-API docs (shows napi_create_object_with_properties / napi_create_object_with_named_properties). [2][3]

Sources:
[1] Node.js N-API documentation (napi_create_object / napi_define_properties).
[2] Harmony/Ark Node-API — napi_create_object_with_properties.
[3] Harmony/Ark Node-API — napi_create_object_with_named_properties.


Based on my verification through web searches:

  1. Rust from_raw_parts UB with null pointers: ✅ CONFIRMED

    • The official Rust documentation explicitly states that std::slice::from_raw_parts requires a non-null and properly aligned pointer even when len == 0. Passing a null pointer to produce a slice reference is undefined behavior, regardless of slice length.
  2. The review's suggested fix is correct: The approach of using empty slices (&[]) when property_count == 0 and avoiding the from_raw_parts call with null pointers is the recommended Rust pattern.

  3. Prototype semantics concern: The function signature with prototype_or_null appears to be non-standard (official Node.js N-API doesn't have this parameter in napi_create_object_with_properties). The review's request to verify against upstream is reasonable, though this appears to be a Deno-specific extension.

The review comment is accurate and the critical issue is justified.


Undefined behavior when calling with property_count == 0 and null pointers

The code unconditionally constructs slices via std::slice::from_raw_parts(property_names, property_count) and std::slice::from_raw_parts(property_values, property_count), even when property_count == 0. Passing a null pointer to from_raw_parts is undefined behavior in Rust, even for zero-length slices—the pointer must always be non-null and properly aligned.

The existing guard if property_count > 0 only validates pointers via check_arg! but does not prevent the null pointers from reaching from_raw_parts. This creates UB when tests (or callers) invoke this function with property_count == 0 and null pointer arguments.

Fix: Only construct slices when property_count > 0; use empty slices otherwise:

-    let names = std::slice::from_raw_parts(property_names, property_count);
-    let values = std::slice::from_raw_parts(property_values, property_count);
+    let (names, values): (&[napi_value<'s>], &[napi_value<'s>]) =
+      if property_count == 0 {
+        (&[], &[])
+      } else {
+        (
+          std::slice::from_raw_parts(property_names, property_count),
+          std::slice::from_raw_parts(property_values, property_count),
+        )
+      };

Also verify the intended semantics for prototype_or_null (whether nullptr prototype maps to JS null or the default Object.prototype) against the upstream NAPI specification or Node.js PR you're aligning with.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support node_api_create_object_with_properties

1 participant