Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/7094-tls-alpn-surface.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added Node-compatible TLS ALPN conversion and modern `TLSSocket` prototype metadata.
8 changes: 8 additions & 0 deletions crates/perry-api-manifest/src/entries/part_1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -797,6 +797,14 @@ pub(crate) const API_MANIFEST_PART_1: &[ApiEntry] = &[
&[p_any("hostname"), p_any("cert")],
TypeSpec::Any,
),
method_sig(
"tls",
"convertALPNProtocols",
false,
None,
&[p_any("protocols"), p_any("out")],
TypeSpec::Any,
),
method_sig(
"tls",
"createSecureContext",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,15 @@ pub(crate) const NODE_CORE_MODULE_SEA_TLS_TEST_ROWS: &[NativeModSig] = &[
args: &[NA_F64, NA_F64],
ret: NR_F64,
},
NativeModSig {
module: "tls",
has_receiver: false,
method: "convertALPNProtocols",
class_filter: None,
runtime: "js_tls_convert_alpn_protocols",
args: &[NA_F64, NA_F64],
ret: NR_F64,
},
NativeModSig {
module: "tls",
has_receiver: false,
Expand Down
16 changes: 12 additions & 4 deletions crates/perry-runtime/src/object/instanceof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -296,11 +296,15 @@ pub extern "C" fn js_instanceof_dynamic(value: f64, type_ref: f64) -> f64 {
method.as_str(),
"Readable" | "Writable" | "Duplex" | "Transform" | "PassThrough" | "Stream"
)
&& crate::node_stream::is_classic_stream_instance_of(value, method.as_str())
&& (crate::node_stream::is_classic_stream_instance_of(value, method.as_str())
|| super::tls_constructor_prototype_is_instance_of(value, method.as_str()))
{
return f64::from_bits(crate::value::TAG_TRUE);
}
if module == "events" && method == "EventEmitter" && is_event_emitter_instance_value(value)
if module == "events"
&& method == "EventEmitter"
&& (is_event_emitter_instance_value(value)
|| super::tls_constructor_prototype_is_instance_of(value, method.as_str()))
{
return f64::from_bits(crate::value::TAG_TRUE);
}
Expand Down Expand Up @@ -1007,14 +1011,18 @@ pub extern "C" fn js_instanceof(value: f64, class_id: u32) -> f64 {
_ => None,
};
if let Some(name) = classic_stream_name {
return if crate::node_stream::is_classic_stream_instance_of(value, name) {
return if crate::node_stream::is_classic_stream_instance_of(value, name)
|| super::tls_constructor_prototype_is_instance_of(value, name)
{
true_val
} else {
false_val
};
}
if class_id == CLASS_ID_EVENT_EMITTER {
return if is_event_emitter_instance_value(value) {
return if is_event_emitter_instance_value(value)
|| super::tls_constructor_prototype_is_instance_of(value, "EventEmitter")
{
true_val
} else {
false_val
Expand Down
8 changes: 5 additions & 3 deletions crates/perry-runtime/src/object/native_module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,10 @@ pub(crate) use callable_exports::{
fs_namespace_descriptor_getter_value, fs_namespace_descriptor_setter_value,
is_buffer_constructor_value, is_cluster_emitter_method, module_cjs_cache_value,
module_cjs_extensions_value, module_cjs_global_paths_value, module_cjs_path_cache_value,
native_string_value, set_bound_native_closure_name, set_builtin_closure_length,
set_builtin_closure_non_constructable, sqlite_session_constructor_value,
sqlite_statement_sync_constructor_value, timers_promises_parent_namespace,
native_string_value, scan_tls_derived_prototype_roots_mut, set_bound_native_closure_name,
set_builtin_closure_length, set_builtin_closure_non_constructable,
sqlite_session_constructor_value, sqlite_statement_sync_constructor_value,
timers_promises_parent_namespace, tls_constructor_prototype_is_instance_of,
util_inspect_default_options_value, zlib_codes_object,
};
pub(crate) use constants::get_native_module_constant;
Expand Down Expand Up @@ -239,6 +240,7 @@ pub fn scan_native_callable_export_roots_mut(visitor: &mut crate::gc::RuntimeRoo
#[cfg(feature = "mod-http2-constants")]
crate::node_http2_constants::scan_roots_mut(visitor);
scan_stream_event_emitter_prototype_roots_mut(visitor);
scan_tls_derived_prototype_roots_mut(visitor);
}

/// Special class ID for native module namespace objects
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@ pub(crate) fn is_native_module_callable_export_reference(module: &str, prop: &st
| ("net", "_normalizeArgs")
| ("net", "_createServerHandle")
| ("tls", "connect")
| ("tls", "convertALPNProtocols")
| ("tls", "createServer")
| ("tls", "Server")
| ("tls", "TLSSocket")
Expand Down Expand Up @@ -1834,6 +1835,7 @@ static CALLABLE_EXPORT_TABLE: &[(&str, &[&str])] = &[
"TLSSocket",
"checkServerIdentity",
"connect",
"convertALPNProtocols",
"createSecureContext",
"createServer",
"getCACertificates",
Expand Down
225 changes: 225 additions & 0 deletions crates/perry-runtime/src/object/native_module/callable_exports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@ fn native_callable_export_arity_reference(module: &str, prop: &str) -> Option<u3
Some(1)
}
("tls", "checkServerIdentity") => Some(2),
("tls", "convertALPNProtocols") => Some(2),
("tls", "SecureContext") => Some(1),
// #3726: `crypto.Cipheriv` / `crypto.Decipheriv` constructor exports —
// `(cipher, key, iv, options)` arity matches Node's length 4.
Expand Down Expand Up @@ -1396,6 +1397,181 @@ fn attach_tls_secure_context_prototype(constructor_value: f64) {
crate::tls::attach_secure_context_constructor_prototype(constructor_value);
}

const TLS_SOCKET_PROTOTYPE_METHODS: &[(&str, u32)] = &[
("setKeyCert", 1),
("getSharedSigalgs", 0),
("getX509Certificate", 0),
("getPeerX509Certificate", 0),
];
Comment thread
coderabbitai[bot] marked this conversation as resolved.

thread_local! {
static TLS_DERIVED_PROTOTYPES: RefCell<Vec<(u64, u8)>> = const { RefCell::new(Vec::new()) };
}

const TLS_PARENT_EVENT_EMITTER: u8 = 1;
const TLS_PARENT_DUPLEX: u8 = 2;

pub(crate) fn scan_tls_derived_prototype_roots_mut(
visitor: &mut crate::gc::RuntimeRootVisitor<'_>,
) {
TLS_DERIVED_PROTOTYPES.with(|prototypes| {
for (bits, _) in prototypes.borrow_mut().iter_mut() {
visitor.visit_nanbox_u64_slot(bits);
}
});
}

extern "C" fn tls_prototype_method_thunk(
closure: *const crate::closure::ClosureHeader,
rest: f64,
) -> f64 {
unsafe {
let name_ptr = crate::closure::js_closure_get_capture_ptr(closure, 0) as *const i8;
let name_len = crate::closure::js_closure_get_capture_ptr(closure, 1) as usize;
let receiver = crate::object::js_implicit_this_get();
let args_array = crate::value::js_nanbox_get_pointer(rest);
crate::object::js_native_call_method_apply(receiver, name_ptr, name_len, args_array)
}
}

fn attach_tls_constructor_prototype(constructor_value: f64, constructor_name: &str) -> f64 {
let methods = if constructor_name == "TLSSocket" {
TLS_SOCKET_PROTOTYPE_METHODS
} else {
&[]
};
let constructor_js = JSValue::from_bits(constructor_value.to_bits());
if !constructor_js.is_pointer() {
return constructor_value;
}
let constructor = constructor_js.as_pointer::<crate::closure::ClosureHeader>() as usize;
if constructor == 0 {
return constructor_value;
}

// Every allocator below can move objects. Hold only updateable handles
// across allocations and reload the current address at each use.
let scope = crate::gc::RuntimeHandleScope::new();
let constructor_handle =
scope.root_raw_mut_ptr(constructor as *mut crate::closure::ClosureHeader);
let prototype = js_object_alloc(0, 0);
if prototype.is_null() {
return crate::value::js_nanbox_pointer(
constructor_handle.get_raw_mut_ptr::<crate::closure::ClosureHeader>() as i64,
);
}
let prototype_handle = scope.root_raw_mut_ptr(prototype);
let constructor_key =
crate::string::js_string_from_bytes(b"constructor".as_ptr(), "constructor".len() as u32);
let constructor_key_handle = scope.root_string_ptr(constructor_key);
js_object_set_field_by_name(
prototype_handle.get_raw_mut_ptr(),
constructor_key_handle.get_raw_mut_ptr(),
crate::value::js_nanbox_pointer(
constructor_handle.get_raw_mut_ptr::<crate::closure::ClosureHeader>() as i64,
),
);
super::super::set_builtin_property_attrs(
prototype_handle.get_raw_mut_ptr::<ObjectHeader>() as usize,
"constructor".to_string(),
super::super::PropertyAttrs::new(true, false, true),
);

let thunk = tls_prototype_method_thunk as *const u8;
crate::closure::js_register_closure_rest(thunk, 0);
for &(name, length) in methods {
let method = crate::closure::js_closure_alloc(thunk, 2);
if method.is_null() {
continue;
}
let method_handle = scope.root_raw_mut_ptr(method);
crate::closure::js_closure_set_capture_ptr(
method_handle.get_raw_mut_ptr(),
0,
name.as_ptr() as i64,
);
crate::closure::js_closure_set_capture_ptr(
method_handle.get_raw_mut_ptr(),
1,
name.len() as i64,
);
let name_string = crate::string::js_string_from_bytes(name.as_ptr(), name.len() as u32);
let name_handle = scope.root_string_ptr(name_string);
crate::closure::closure_set_dynamic_prop(
method_handle.get_raw_mut_ptr::<crate::closure::ClosureHeader>() as usize,
"name",
f64::from_bits(JSValue::string_ptr(name_handle.get_raw_mut_ptr()).bits()),
);
super::super::set_builtin_property_attrs(
method_handle.get_raw_mut_ptr::<crate::closure::ClosureHeader>() as usize,
"name".to_string(),
super::super::PropertyAttrs::new(false, false, true),
);
set_builtin_closure_length(
method_handle.get_raw_mut_ptr::<crate::closure::ClosureHeader>() as usize,
length,
);
js_object_set_field_by_name(
prototype_handle.get_raw_mut_ptr(),
name_handle.get_raw_mut_ptr(),
crate::value::js_nanbox_pointer(
method_handle.get_raw_mut_ptr::<crate::closure::ClosureHeader>() as i64,
),
);
super::super::set_builtin_property_attrs(
prototype_handle.get_raw_mut_ptr::<ObjectHeader>() as usize,
name.to_string(),
super::super::PropertyAttrs::new(true, false, true),
);
}

crate::closure::closure_set_dynamic_prop(
constructor_handle.get_raw_mut_ptr::<crate::closure::ClosureHeader>() as usize,
"prototype",
crate::value::js_nanbox_pointer(prototype_handle.get_raw_mut_ptr::<ObjectHeader>() as i64),
);
let parent_kind = match constructor_name {
"Server" => TLS_PARENT_EVENT_EMITTER,
"TLSSocket" => TLS_PARENT_DUPLEX,
_ => 0,
};
if parent_kind != 0 {
let bits = crate::value::js_nanbox_pointer(
prototype_handle.get_raw_mut_ptr::<ObjectHeader>() as i64,
)
.to_bits();
crate::gc::runtime_write_barrier_root_nanbox(bits);
TLS_DERIVED_PROTOTYPES.with(|prototypes| {
let mut prototypes = prototypes.borrow_mut();
if !prototypes.iter().any(|(existing, _)| *existing == bits) {
prototypes.push((bits, parent_kind));
}
});
}
super::super::set_builtin_property_attrs(
constructor_handle.get_raw_mut_ptr::<crate::closure::ClosureHeader>() as usize,
"prototype".to_string(),
super::super::PropertyAttrs::new(true, false, false),
);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
crate::value::js_nanbox_pointer(
constructor_handle.get_raw_mut_ptr::<crate::closure::ClosureHeader>() as i64,
)
}

pub(crate) fn tls_constructor_prototype_is_instance_of(value: f64, parent_name: &str) -> bool {
let parent_kind = match parent_name {
"EventEmitter" => TLS_PARENT_EVENT_EMITTER,
"Duplex" => TLS_PARENT_DUPLEX,
_ => return false,
};
TLS_DERIVED_PROTOTYPES.with(|prototypes| {
prototypes
.borrow()
.iter()
.any(|(bits, kind)| *bits == value.to_bits() && *kind == parent_kind)
})
}

pub(crate) unsafe fn bound_native_callable_module_and_method(
value: f64,
) -> Option<(String, String)> {
Expand Down Expand Up @@ -1574,6 +1750,8 @@ pub(crate) unsafe fn nm_attach_tls(
) -> f64 {
if property_name == "SecureContext" {
attach_tls_secure_context_prototype(value);
} else if matches!(property_name, "Server" | "TLSSocket") {
value = attach_tls_constructor_prototype(value, property_name);
}
value
}
Expand Down Expand Up @@ -2103,6 +2281,7 @@ static CALLABLE_EXPORT_ARITY_TABLE: &[(&str, &[(&str, u32)])] = &[
("TLSSocket", 2),
("checkServerIdentity", 2),
("connect", 4),
("convertALPNProtocols", 2),
("createSecureContext", 1),
("createServer", 2),
("getCACertificates", 1),
Expand Down Expand Up @@ -2220,4 +2399,50 @@ mod callable_export_arity_table_tests {
}
}
}

#[test]
fn tls_constructor_prototypes_match_node_parent_classes() {
let server = bound_native_callable_export_value("tls", "Server");
let server_addr = (server.to_bits() & crate::value::POINTER_MASK) as usize;
let server_proto = crate::closure::closure_get_dynamic_prop(server_addr, "prototype");
assert!(tls_constructor_prototype_is_instance_of(
server_proto,
"EventEmitter"
));
assert_eq!(
crate::object::js_instanceof(server_proto, 0xFFFF_0076).to_bits(),
crate::value::TAG_TRUE
);
let event_emitter = bound_native_callable_export_value("events", "EventEmitter");
assert_eq!(
crate::object::js_instanceof_dynamic(server_proto, event_emitter).to_bits(),
crate::value::TAG_TRUE
);

let socket = bound_native_callable_export_value("tls", "TLSSocket");
let socket_addr = (socket.to_bits() & crate::value::POINTER_MASK) as usize;
let socket_proto = crate::closure::closure_get_dynamic_prop(socket_addr, "prototype");
assert!(tls_constructor_prototype_is_instance_of(
socket_proto,
"Duplex"
));
let socket_proto_obj =
JSValue::from_bits(socket_proto.to_bits()).as_pointer::<ObjectHeader>();
for &(name, length) in TLS_SOCKET_PROTOTYPE_METHODS {
let key = crate::string::js_string_from_bytes(name.as_ptr(), name.len() as u32);
let method = crate::object::js_object_get_field_by_name(socket_proto_obj, key);
let method_addr = method.as_pointer::<crate::closure::ClosureHeader>() as usize;
assert!(crate::closure::is_closure_ptr(method_addr), "{name}");
assert_eq!(builtin_closure_length(method_addr), Some(length), "{name}");
}
assert_eq!(
crate::object::js_instanceof(socket_proto, 0xFFFF_0073).to_bits(),
crate::value::TAG_TRUE
);
let duplex = bound_native_callable_export_value("stream", "Duplex");
assert_eq!(
crate::object::js_instanceof_dynamic(socket_proto, duplex).to_bits(),
crate::value::TAG_TRUE
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1827,6 +1827,7 @@ pub(crate) fn native_module_enumerable_keys(module_name: &str) -> Option<&'stati
"tls" => Some(&[
b"checkServerIdentity",
b"connect",
b"convertALPNProtocols",
b"createServer",
b"createSecureContext",
b"getCACertificates",
Expand Down
Loading
Loading