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
8 changes: 8 additions & 0 deletions crates/perry-codegen-js/src/emit/exprs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,14 @@ impl JsEmitter {
}
self.output.push_str(") : [0, 0])");
}
Expr::ProcessTitle => {
self.output.push_str("(typeof process !== 'undefined' ? process.title : '')");
}
Expr::ProcessSetTitle(value) => {
self.output.push_str("(typeof process !== 'undefined' ? (process.title = ");
self.emit_expr(value);
self.output.push_str(") : undefined)");
}
Expr::ProcessNextTick { callback, args } => {
self.output.push_str("(typeof process !== 'undefined' ? process.nextTick(");
self.emit_expr(callback);
Expand Down
4 changes: 3 additions & 1 deletion crates/perry-codegen-wasm/src/emit/expr/url_process_path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ impl<'a> FuncEmitCtx<'a> {
| Expr::ProcessVersions
| Expr::ProcessHrtimeBigint
| Expr::ProcessHrtime(_)
| Expr::ProcessTitle
| Expr::ProcessStdin
| Expr::ProcessStdout
| Expr::ProcessStderr
Expand All @@ -129,7 +130,8 @@ impl<'a> FuncEmitCtx<'a> {
| Expr::ProcessAbort
| Expr::ProcessUmask(_)
| Expr::ProcessEmitWarning(_)
| Expr::ProcessCpuUsage(_) => {
| Expr::ProcessCpuUsage(_)
| Expr::ProcessSetTitle(_) => {
func.instruction(&Instruction::I64Const(TAG_UNDEFINED as i64));
}
Expr::EnvGet(_) | Expr::EnvGetDynamic(_) => {
Expand Down
1 change: 1 addition & 0 deletions crates/perry-codegen/src/collectors/escape_objects.rs
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,7 @@ pub fn check_object_literal_escapes_in_expr(
| Expr::ProcessPid | Expr::ProcessPpid
| Expr::ProcessVersion | Expr::ProcessVersions | Expr::ProcessHrtimeBigint
| Expr::ProcessHrtime(_)
| Expr::ProcessTitle
| Expr::ProcessStdin | Expr::ProcessStdout | Expr::ProcessStderr
| Expr::ProcessEnv
| Expr::GlobalThisExpr
Expand Down
9 changes: 9 additions & 0 deletions crates/perry-codegen/src/expr/misc_methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,15 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result<String> {
.call(DOUBLE, "js_process_hrtime", &[(DOUBLE, &prior_val)]))
}

// -------- process.title getter/setter (#1401) --------
Expr::ProcessTitle => Ok(ctx.block().call(DOUBLE, "js_process_title", &[])),
Expr::ProcessSetTitle(value) => {
let v = lower_expr(ctx, value)?;
ctx.block()
.call_void("js_process_set_title", &[(DOUBLE, &v)]);
Ok(v)
}

// -------- RegExpExecIndex — reads thread-local from the last exec() call --------
Expr::RegExpExecIndex => Ok(ctx.block().call(DOUBLE, "js_regexp_exec_get_index", &[])),

Expand Down
2 changes: 2 additions & 0 deletions crates/perry-codegen/src/expr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1044,6 +1044,8 @@ pub(crate) fn lower_expr(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result<String> {
| Expr::PathIsAbsolute(..)
| Expr::ProcessHrtimeBigint
| Expr::ProcessHrtime(..)
| Expr::ProcessTitle
| Expr::ProcessSetTitle(..)
| Expr::RegExpExecIndex
| Expr::CryptoRandomUUID
| Expr::CryptoRandomBytes(..)
Expand Down
2 changes: 2 additions & 0 deletions crates/perry-codegen/src/runtime_decls/strings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,8 @@ pub fn declare_phase_b_strings(module: &mut LlModule) {
module.declare_function("js_process_env", DOUBLE, &[]);
module.declare_function("js_process_hrtime_bigint", DOUBLE, &[]);
module.declare_function("js_process_hrtime", DOUBLE, &[DOUBLE]);
module.declare_function("js_process_title", DOUBLE, &[]);
module.declare_function("js_process_set_title", VOID, &[DOUBLE]);
module.declare_function("js_process_chdir", VOID, &[I64]);
module.declare_function("js_process_kill", VOID, &[DOUBLE, DOUBLE]);
module.declare_function("js_process_exit", VOID, &[DOUBLE]);
Expand Down
3 changes: 3 additions & 0 deletions crates/perry-codegen/src/type_analysis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ pub(crate) fn refine_type_from_init(ctx: &FnCtx<'_>, init: &Expr) -> Option<HirT
// hit the string method fast path.
| Expr::ProcessVersion
| Expr::ProcessCwd
| Expr::ProcessTitle
| Expr::OsArch
| Expr::OsType
| Expr::OsPlatform
Expand Down Expand Up @@ -852,6 +853,7 @@ pub(crate) fn is_definitely_string_expr(ctx: &FnCtx<'_>, e: &Expr) -> bool {
}
| Expr::ProcessVersion
| Expr::ProcessCwd
| Expr::ProcessTitle
| Expr::OsArch
| Expr::OsType
| Expr::OsPlatform
Expand Down Expand Up @@ -1004,6 +1006,7 @@ pub(crate) fn is_string_expr(ctx: &FnCtx<'_>, e: &Expr) -> bool {
// through to the generic native method dispatch and returns undefined.
Expr::ProcessVersion
| Expr::ProcessCwd
| Expr::ProcessTitle
| Expr::OsArch
| Expr::OsType
| Expr::OsPlatform
Expand Down
2 changes: 2 additions & 0 deletions crates/perry-hir/src/audit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,8 @@ fn specialized_stdlib_call(expr: &Expr) -> Option<(&'static str, &'static str)>
Expr::ProcessResourceUsage => ("process", "resourceUsage"),
Expr::ProcessActiveResourcesInfo => ("process", "getActiveResourcesInfo"),
Expr::ProcessHrtime(_) => ("process", "hrtime"),
Expr::ProcessTitle => ("process", "title"),
Expr::ProcessSetTitle(_) => ("process", "title"),
Expr::ProcessStdinIsTTY => ("process", "stdin.isTTY"),
Expr::ProcessStdoutIsTTY => ("process", "stdout.isTTY"),
Expr::ProcessStderrIsTTY => ("process", "stderr.isTTY"),
Expand Down
8 changes: 4 additions & 4 deletions crates/perry-hir/src/ir/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -469,10 +469,10 @@ pub enum Expr {
ProcessCpuUsage(Option<Box<Expr>>), // process.cpuUsage(prior?) -> { user, system } µs (diff if prior given)
ProcessResourceUsage, // process.resourceUsage() -> {userCPUTime, maxRSS, ...} (#1376)
ProcessActiveResourcesInfo, // process.getActiveResourcesInfo() -> string[] (#1376)
// process.stdin -> stub object { write: fn }
ProcessStdin,
// process.stdout -> stub object { write: fn }
ProcessStdout,
ProcessTitle, // process.title getter (#1401)
ProcessSetTitle(Box<Expr>), // process.title = X setter (#1401)
ProcessStdin, // process.stdin -> stub object { write: fn }
ProcessStdout, // process.stdout -> stub object { write: fn }
// process.stderr -> stub object { write: fn }
ProcessStderr,
// process.stdin.setRawMode(enabled) -> stdin (#347 Phase 2)
Expand Down
12 changes: 12 additions & 0 deletions crates/perry-hir/src/lower/expr_assign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,18 @@ pub(super) fn lower_assign(ctx: &mut LoweringContext, assign: &ast::AssignExpr)
proto: value,
});
}
// #1401: process.title = X — route through a runtime
// cell so subsequent reads see the new value. Without
// this, the assignment lands on the GlobalGet sentinel
// that the title getter never consults, so reads still
// return argv[0].
if property == "title" {
if let ast::Expr::Ident(obj_ident) = member.obj.as_ref() {
if obj_ident.sym.as_ref() == "process" {
return Ok(Expr::ProcessSetTitle(value));
}
}
}
Ok(Expr::PropertySet {
object,
property,
Expand Down
10 changes: 8 additions & 2 deletions crates/perry-hir/src/lower/expr_member.rs
Original file line number Diff line number Diff line change
Expand Up @@ -234,12 +234,17 @@ pub(super) fn lower_member(ctx: &mut LoweringContext, member: &ast::MemberExpr)
// to argv[0] too until something assigns `.title`).
// Settable `process.title` is tracked separately
// (#1401); the shape-only read is what closes #1346.
"argv0" | "execPath" | "title" => {
"argv0" | "execPath" => {
return Ok(Expr::IndexGet {
object: Box::new(Expr::ProcessArgv),
index: Box::new(Expr::Number(0.0)),
});
}
"title" => {
// #1401: title is settable; route through a
// runtime cell that falls back to argv[0].
return Ok(Expr::ProcessTitle);
}
_ => {}
}
}
Expand Down Expand Up @@ -326,12 +331,13 @@ pub(super) fn lower_member(ctx: &mut LoweringContext, member: &ast::MemberExpr)
}
"allowedNodeEnvironmentFlags" => return Ok(Expr::SetNew),
"report" => return Ok(process_report_literal()),
"argv0" | "execPath" | "title" => {
"argv0" | "execPath" => {
return Ok(Expr::IndexGet {
object: Box::new(Expr::ProcessArgv),
index: Box::new(Expr::Number(0.0)),
});
}
"title" => return Ok(Expr::ProcessTitle),
_ => {}
}
}
Expand Down
4 changes: 4 additions & 0 deletions crates/perry-hir/src/monomorph/substitute_expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,10 @@ pub(crate) fn substitute_expr(expr: &Expr, substitutions: &HashMap<String, Type>
.as_ref()
.map(|e| Box::new(substitute_expr(e, substitutions))),
),
Expr::ProcessTitle => Expr::ProcessTitle,
Expr::ProcessSetTitle(v) => {
Expr::ProcessSetTitle(Box::new(substitute_expr(v, substitutions)))
}

// File system
Expr::FsReadFileSync(path) => {
Expand Down
2 changes: 2 additions & 0 deletions crates/perry-hir/src/stable_hash/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ impl SH for Expr {
Expr::ProcessResourceUsage => tag(h, 11232),
Expr::ProcessActiveResourcesInfo => tag(h, 11233),
Expr::ProcessHrtime(e) => { tag(h, 11234); e.hash(h); }
Expr::ProcessTitle => tag(h, 11235),
Expr::ProcessSetTitle(e) => { tag(h, 11236); e.as_ref().hash(h); }
Expr::ProcessStdin => tag(h, 70),
Expr::ProcessStdout => tag(h, 71),
Expr::ProcessStderr => tag(h, 72),
Expand Down
2 changes: 2 additions & 0 deletions crates/perry-hir/src/walker/expr_mut.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ where
| Expr::ProcessPosixCredential(_)
| Expr::ProcessResourceUsage
| Expr::ProcessActiveResourcesInfo
| Expr::ProcessTitle
| Expr::ProcessStdinIsTTY
| Expr::ProcessStdoutIsTTY
| Expr::ProcessStderrIsTTY
Expand Down Expand Up @@ -1068,6 +1069,7 @@ where
f(v);
}
}
Expr::ProcessSetTitle(v) => f(v),

// ─── Child process ───────────────────────────────────────────────
Expr::ChildProcessExecSync { command, options } => {
Expand Down
2 changes: 2 additions & 0 deletions crates/perry-hir/src/walker/expr_ref.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ where
| Expr::ProcessPosixCredential(_)
| Expr::ProcessResourceUsage
| Expr::ProcessActiveResourcesInfo
| Expr::ProcessTitle
| Expr::ProcessStdinIsTTY
| Expr::ProcessStdoutIsTTY
| Expr::ProcessStderrIsTTY
Expand Down Expand Up @@ -1049,6 +1050,7 @@ where
f(v);
}
}
Expr::ProcessSetTitle(v) => f(v),
Expr::ChildProcessExecSync { command, options } => {
f(command);
if let Some(o) = options {
Expand Down
53 changes: 53 additions & 0 deletions crates/perry-runtime/src/process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,59 @@ pub extern "C" fn js_process_abort() {
std::process::abort();
}

/// Thread-local cell holding the process title set via `process.title = X`
/// (#1401). `None` means "not assigned yet, fall back to argv[0]". The
/// setter records the value here; on Linux it also calls `prctl(PR_SET_NAME)`
/// so `/proc/<pid>/comm` reflects the new value. macOS has no per-process
/// analog — the assignment is still observable via subsequent `process.title`
/// reads, matching Node's best-effort semantics.
thread_local! {
static PROCESS_TITLE: std::cell::RefCell<Option<String>> = const {
std::cell::RefCell::new(None)
};
}

/// process.title -> string. Returns the value set via the setter, or
/// falls back to argv[0].
#[no_mangle]
pub extern "C" fn js_process_title() -> f64 {
use crate::value::JSValue;
let stored: Option<String> = PROCESS_TITLE.with(|c| c.borrow().clone());
let s = stored.unwrap_or_else(|| std::env::args().next().unwrap_or_default());
let bytes = s.as_bytes();
let ptr = js_string_from_bytes(bytes.as_ptr(), bytes.len() as u32);
f64::from_bits(JSValue::string_ptr(ptr).bits())
}

/// process.title = value — coerces to string and stores in the cell.
#[no_mangle]
pub extern "C" fn js_process_set_title(value: f64) {
let ptr = crate::value::js_jsvalue_to_string(value);
let s = if ptr.is_null() {
String::new()
} else {
unsafe {
let header = &*ptr;
let len = header.byte_len as usize;
let data = (ptr as *const u8).add(std::mem::size_of::<StringHeader>());
String::from_utf8_lossy(std::slice::from_raw_parts(data, len)).into_owned()
}
};
#[cfg(target_os = "linux")]
{
let mut buf = [0i8; 16];
let src = s.as_bytes();
let copy_len = std::cmp::min(src.len(), 15);
for i in 0..copy_len {
buf[i] = src[i] as i8;
}
unsafe {
libc::prctl(libc::PR_SET_NAME, buf.as_ptr() as libc::c_ulong, 0, 0, 0);
}
}
PROCESS_TITLE.with(|c| *c.borrow_mut() = Some(s));
}

/// process.umask() -> number. Returns the current file-mode creation mask
/// without modifying it. POSIX's `umask` syscall has no read-only form, so
/// we set the mask to 0, capture the previous value, then restore it.
Expand Down