use crate::ported::utils::{zerr, zwarn, zwarnnam};
use crate::ported::zsh_h::{OPT_ISSET, PM_DECLARED, PM_HIDE, PM_REMOVABLE, PM_RO_BY_DESIGN, PM_SPECIAL, PM_UNSET, module, param, PM_NORESTORE, options, hashtable, HashTable, PM_RESTRICTED, PM_READONLY, eprog, funcwrap, PM_AUTOLOAD, PM_NAMEREF, isset, features, hashnode,MAX_OPS};
use std::sync::atomic::Ordering;
use std::sync::{Mutex, OnceLock};
use crate::ported::builtin::bin_typeset;
use crate::ported::mem::{queue_signals, unqueue_signals};
use crate::ported::options::optlookup;
use crate::ported::params::{deleteparamtable, endparamscope, locallevel, newparamtable, printparamnode, startparamscope};
#[derive(Debug, Clone, Copy)]
#[allow(non_camel_case_types)]
pub struct gsu_closure {
pub kind: u8, pub g: usize, }
#[allow(unused_variables)]
pub fn makeprivate(hn: *mut param, flags: i32) {
if hn.is_null() {
return;
}
let pm_level = unsafe { (*hn).level };
let cur_local = locallevel.load(Ordering::Relaxed);
if pm_level != cur_local {
return;
}
let pm_flags = unsafe { (*hn).node.flags };
let pm_ename = unsafe { (*hn).ename.is_some() };
let has_old = unsafe { (*hn).old.is_some() };
let pm_special = (pm_flags & PM_SPECIAL as i32) != 0;
let pm_removable = (pm_flags & PM_REMOVABLE as i32) != 0;
let pm_norestore = (pm_flags & PM_NORESTORE as i32) != 0;
let outer_cond = pm_ename
|| pm_norestore
|| (has_old
&& (
true
));
if outer_cond {
if pm_special && !pm_removable {
zerr(
&format!("can't change scope of existing param: {}", unsafe {
(*hn).node.nam.clone()
}), );
}
MAKEPRIVATE_ERROR.store(1, Ordering::Relaxed); return; }
let name = unsafe { (*hn).node.nam.clone() };
if let Ok(mut p) = PRIVATE_PARAMS.lock() {
p.insert(name);
}
unsafe {
(*hn).node.flags |= (PM_HIDE
| PM_SPECIAL
| PM_REMOVABLE
| PM_RO_BY_DESIGN) as i32;
}
unsafe {
(*hn).level -= 1;
}
}
pub fn is_private(pm: *const param) -> i32 {
if pm.is_null() {
return 0;
}
let name = unsafe { (*pm).node.nam.clone() };
if PRIVATE_PARAMS
.lock()
.map(|p| p.contains(&name))
.unwrap_or(false)
{
1 } else {
0 }
}
pub fn bin_private(
nam: &str,
args: &[String], ops_in: &options,
func: i32,
) -> i32 {
let mut ops_local = ops_in.clone();
let ops = &mut ops_local;
let mut assigns: Vec<(String, String)> = Vec::new();
let assigns = &mut assigns;
let mut from_typeset: i32 = 1; let ofake = FAKELEVEL.load(Ordering::Relaxed); let hasargs = !assigns.is_empty(); MAKEPRIVATE_ERROR.store(0, Ordering::Relaxed);
if !OPT_ISSET(ops, b'P') {
FAKELEVEL.store(0, Ordering::Relaxed); from_typeset = bin_typeset(nam, args, ops, func); FAKELEVEL.store(ofake, Ordering::Relaxed); return from_typeset; }
if OPT_ISSET(ops, b'T') {
zwarn("bad option: -T"); return 1; }
let locallevel2 = locallevel.load(Ordering::Relaxed);
if locallevel2 == 0 {
let warn =
isset(optlookup("warncreateglobal"));
if warn {
zwarnnam(nam, "invalid local scope, using globals"); }
return bin_typeset("private", args, ops, func); }
if !(OPT_ISSET(ops, b'm') || OPT_ISSET(ops, b'+')) {
ops.ind[b'g' as usize] = 2; }
if OPT_ISSET(ops, b'p') || OPT_ISSET(ops, b'm') || (!hasargs && OPT_ISSET(ops, b'+'))
{
return bin_typeset("private", args, ops, func); }
queue_signals(); FAKELEVEL.store(locallevel2, Ordering::Relaxed); let mut paramscope_buf = newparamtable(17, "private_scope")
.unwrap_or_else(|| {
Box::new(hashtable {
hsize: 0,
ct: 0,
nodes: Vec::new(),
tmpdata: 0,
hash: None,
emptytable: None,
filltable: None,
cmpnodes: None,
addnode: None,
getnode: None,
getnode2: None,
removenode: None,
disablenode: None,
enablenode: None,
freenode: None,
printnode: None,
scantab: None,
})
});
startparamscope(&mut paramscope_buf); from_typeset = bin_typeset("private", args, ops, func); endparamscope(); FAKELEVEL.store(ofake, Ordering::Relaxed); unqueue_signals();
let mpe = MAKEPRIVATE_ERROR.load(Ordering::Relaxed);
mpe | from_typeset }
pub fn setfn_error(pm: *mut param) {
if pm.is_null() {
return;
}
unsafe {
(*pm).node.flags |= PM_UNSET as i32;
} let name = unsafe { (*pm).node.nam.clone() };
zerr(&format!(
"{}: attempt to assign private in nested scope",
name
)); }
pub fn pps_getfn(pm: *mut param) -> String {
if pm.is_null() {
return String::new();
}
let pm_level = unsafe { (*pm).level };
if locallevel.load(Ordering::Relaxed) >= pm_level {
unsafe { (*pm).u_str.clone().unwrap_or_default() }
} else {
String::new() }
}
pub fn pps_setfn(pm: *mut param, x: &str) {
if pm.is_null() {
return;
}
let pm_level = unsafe { (*pm).level };
if locallevel.load(Ordering::Relaxed) == pm_level
|| locallevel.load(Ordering::Relaxed)
> private_wraplevel.load(Ordering::Relaxed)
{
unsafe {
(*pm).u_str = Some(x.to_string());
} } else {
setfn_error(pm); }
}
pub fn pps_unsetfn(pm: *mut param, explicit: i32) {
if pm.is_null() {
return;
}
let pm_level = unsafe { (*pm).level };
if locallevel.load(Ordering::Relaxed) <= pm_level {
unsafe {
(*pm).u_str = None;
}
}
if explicit != 0 {
unsafe {
(*pm).node.flags |= PM_DECLARED as i32;
} } else {
if let Ok(mut p) = PRIVATE_PARAMS.lock() {
unsafe {
p.remove(&(*pm).node.nam);
}
}
}
}
pub fn ppi_getfn(pm: *mut param) -> i64 {
if pm.is_null() {
return 0;
}
let pm_level = unsafe { (*pm).level };
if locallevel.load(Ordering::Relaxed) >= pm_level {
unsafe { (*pm).u_val } } else {
0 }
}
pub fn ppi_setfn(pm: *mut param, x: i64) {
if pm.is_null() {
return;
}
let pm_level = unsafe { (*pm).level };
if locallevel.load(Ordering::Relaxed) == pm_level
|| locallevel.load(Ordering::Relaxed)
> private_wraplevel.load(Ordering::Relaxed)
{
unsafe {
(*pm).u_val = x;
} } else {
setfn_error(pm); }
}
pub fn ppi_unsetfn(pm: *mut param, explicit: i32) {
if pm.is_null() {
return;
}
let pm_level = unsafe { (*pm).level };
if locallevel.load(Ordering::Relaxed) <= pm_level {
unsafe {
(*pm).u_val = 0;
} }
if explicit != 0 {
unsafe {
(*pm).node.flags |= PM_DECLARED as i32;
} } else {
if let Ok(mut p) = PRIVATE_PARAMS.lock() {
unsafe {
p.remove(&(*pm).node.nam);
}
}
}
}
pub fn ppf_getfn(pm: *mut param) -> f64 {
if pm.is_null() {
return 0.0;
}
let pm_level = unsafe { (*pm).level };
if locallevel.load(Ordering::Relaxed) >= pm_level {
unsafe { (*pm).u_dval } } else {
0.0 }
}
pub fn ppf_setfn(pm: *mut param, x: f64) {
if pm.is_null() {
return;
}
let pm_level = unsafe { (*pm).level };
if locallevel.load(Ordering::Relaxed) == pm_level
|| locallevel.load(Ordering::Relaxed)
> private_wraplevel.load(Ordering::Relaxed)
{
unsafe {
(*pm).u_dval = x;
} } else {
setfn_error(pm); }
}
pub fn ppf_unsetfn(pm: *mut param, explicit: i32) {
if pm.is_null() {
return;
}
let pm_level = unsafe { (*pm).level };
if locallevel.load(Ordering::Relaxed) <= pm_level {
unsafe {
(*pm).u_dval = 0.0;
} }
if explicit != 0 {
unsafe {
(*pm).node.flags |= PM_DECLARED as i32;
}
} else {
if let Ok(mut p) = PRIVATE_PARAMS.lock() {
unsafe {
p.remove(&(*pm).node.nam);
}
}
}
}
pub fn ppa_getfn(pm: *mut param) -> Vec<String> {
if pm.is_null() {
return Vec::new();
}
let pm_level = unsafe { (*pm).level };
if locallevel.load(Ordering::Relaxed) >= pm_level {
unsafe { (*pm).u_arr.clone().unwrap_or_default() } } else {
Vec::new() }
}
pub fn ppa_setfn(pm: *mut param, x: Vec<String>) {
if pm.is_null() {
return;
}
let pm_level = unsafe { (*pm).level };
if locallevel.load(Ordering::Relaxed) == pm_level
|| locallevel.load(Ordering::Relaxed)
> private_wraplevel.load(Ordering::Relaxed)
{
unsafe {
(*pm).u_arr = Some(x);
} } else {
setfn_error(pm); }
}
pub fn ppa_unsetfn(pm: *mut param, explicit: i32) {
if pm.is_null() {
return;
}
let pm_level = unsafe { (*pm).level };
if locallevel.load(Ordering::Relaxed) <= pm_level {
unsafe {
(*pm).u_arr = None;
} }
if explicit != 0 {
unsafe {
(*pm).node.flags |= PM_DECLARED as i32;
}
} else {
if let Ok(mut p) = PRIVATE_PARAMS.lock() {
unsafe {
p.remove(&(*pm).node.nam);
}
}
}
}
#[allow(non_upper_case_globals)]
pub static emptytable: Mutex<Option<HashTable>> =
Mutex::new(None);
pub fn pph_getfn(pm: *mut param) -> Option<()> {
if pm.is_null() {
return None;
}
let pm_level = unsafe { (*pm).level };
if locallevel.load(Ordering::Relaxed) >= pm_level {
unsafe { (*pm).u_hash.as_ref().map(|_| ()) } } else {
None }
}
pub fn pph_setfn(
pm: *mut param, x: Option<HashTable>,
) {
if pm.is_null() {
return;
}
let pm_level = unsafe { (*pm).level };
if locallevel.load(Ordering::Relaxed) == pm_level
|| locallevel.load(Ordering::Relaxed)
> private_wraplevel.load(Ordering::Relaxed)
{
unsafe {
(*pm).u_hash = x;
} } else {
setfn_error(pm); }
}
pub fn pph_unsetfn(pm: *mut param, explicit: i32) {
if pm.is_null() {
return;
}
let pm_level = unsafe { (*pm).level };
if locallevel.load(Ordering::Relaxed) <= pm_level {
unsafe {
(*pm).u_hash = None;
} }
if explicit != 0 {
unsafe {
(*pm).node.flags |= PM_DECLARED as i32;
}
} else {
if let Ok(mut p) = PRIVATE_PARAMS.lock() {
unsafe {
p.remove(&(*pm).node.nam);
}
}
}
}
pub const PM_WAS_UNSET: u32 = PM_NORESTORE; pub const PM_WAS_RONLY: u32 = PM_RESTRICTED;
pub fn scopeprivate(hn: *mut param, onoff: i32) {
if hn.is_null() {
return;
}
let pm_level = unsafe { (*hn).level };
let local = locallevel.load(Ordering::Relaxed);
if pm_level != local {
return;
} if is_private(hn) == 0 {
return;
} unsafe {
let f = (*hn).node.flags;
if onoff == PM_UNSET as i32 {
if (f & PM_UNSET as i32) != 0 {
(*hn).node.flags |= PM_WAS_UNSET as i32;
} else {
(*hn).node.flags |= PM_UNSET as i32;
}
if (f & PM_READONLY as i32) != 0 {
(*hn).node.flags |= PM_WAS_RONLY as i32;
} else {
(*hn).node.flags |= PM_READONLY as i32;
}
} else {
if (f & PM_WAS_UNSET as i32) != 0 {
(*hn).node.flags |= PM_UNSET as i32;
} else {
(*hn).node.flags &= !(PM_UNSET as i32);
}
if (f & PM_WAS_RONLY as i32) != 0 {
(*hn).node.flags |= PM_READONLY as i32;
} else {
(*hn).node.flags &= !(PM_READONLY as i32);
}
(*hn).node.flags &= !((PM_WAS_UNSET | PM_WAS_RONLY) as i32);
}
}
}
pub static private_wraplevel: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub fn wrap_private(
_prog: *const eprog, _w: *const funcwrap,
_name: *mut libc::c_char,
) -> i32 {
let local = locallevel.load(Ordering::Relaxed);
let pwl = private_wraplevel.load(Ordering::Relaxed);
if pwl < local {
let owl = pwl; private_wraplevel.store(local, Ordering::Relaxed); private_wraplevel.store(owl, Ordering::Relaxed); return 0; }
1 }
pub fn getprivatenode(pm: *mut param) -> *mut param {
let mut cur = pm;
if cur.is_null() {
return cur;
}
let pm_flags = unsafe { (*cur).node.flags };
if (pm_flags & PM_AUTOLOAD as i32) != 0 {
} else {}
while !cur.is_null() {
let cur_level = unsafe { (*cur).level };
let fakelvl = FAKELEVEL.load(Ordering::Relaxed);
let local = locallevel.load(Ordering::Relaxed);
let pwl = private_wraplevel.load(Ordering::Relaxed);
if !(fakelvl == 0 && local > cur_level && is_private(cur) != 0) {
break;
}
if cur_level == pwl + 1 {
break;
} cur = unsafe {
(*cur)
.old
.as_mut()
.map(|b| &mut **b as *mut _)
.unwrap_or(std::ptr::null_mut())
};
}
if !cur.is_null() {
let f = unsafe { (*cur).node.flags };
if (f & PM_NAMEREF as i32) != 0 {
}
}
cur }
pub fn getprivatenode2(pm: *mut param) -> *mut param {
let mut cur = pm;
while !cur.is_null() {
let cur_level = unsafe { (*cur).level };
let fakelvl = FAKELEVEL.load(Ordering::Relaxed);
let local = locallevel.load(Ordering::Relaxed);
if !(fakelvl == 0 && local > cur_level && is_private(cur) != 0) {
break;
}
cur = unsafe {
(*cur)
.old
.as_mut()
.map(|b| &mut **b as *mut _)
.unwrap_or(std::ptr::null_mut())
};
}
cur }
#[allow(unused_variables)]
pub fn printprivatenode(hn: *mut param, printflags: i32) {
let mut cur = hn;
while !cur.is_null() {
let pm_level = unsafe { (*cur).level };
let pm_flags = unsafe { (*cur).node.flags };
let fakelvl = FAKELEVEL.load(Ordering::Relaxed);
let unset_in_fake = fakelvl != 0
&& fakelvl > pm_level
&& (pm_flags & PM_UNSET as i32) != 0;
let cond = (fakelvl == 0 || unset_in_fake)
&& locallevel.load(Ordering::Relaxed)
> pm_level
&& is_private(cur) != 0;
if !cond {
break;
}
cur = unsafe {
(*cur)
.old
.as_ref()
.map(|b| b.as_ref() as *const _ as *mut _)
.unwrap_or(std::ptr::null_mut())
};
}
if !cur.is_null() {
let hn: &mut param = unsafe { &mut *cur };
printparamnode(hn, printflags); }
}
#[allow(unused_variables)]
pub fn setup_(m: *const module) -> i32 {
0
}
pub fn features_(m: *const module, features: &mut Vec<String>) -> i32 {
*features = featuresarray(m, module_features());
0
}
pub fn enables_(m: *const module, enables: &mut Option<Vec<i32>>) -> i32 {
handlefeatures(m, module_features(), enables)
}
#[allow(unused_variables)]
pub fn boot_(m: *const module) -> i32 {
if let Some(t) = newparamtable(1, "private") {
if let Ok(mut e) = emptytable.lock() {
*e = Some(t);
}
}
0 }
pub fn cleanup_(m: *const module) -> i32 {
setfeatureenables(m, module_features(), None)
}
#[allow(unused_variables)]
pub fn finish_(m: *const module) -> i32 {
if let Ok(mut e) = emptytable.lock() {
if let Some(t) = e.take() {
deleteparamtable(Some(t));
}
}
0 }
pub static MAKEPRIVATE_ERROR: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static PRIVATE_PARAMS: std::sync::LazyLock<
Mutex<std::collections::HashSet<String>>,
> = std::sync::LazyLock::new(|| Mutex::new(std::collections::HashSet::new()));
pub static FAKELEVEL: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
static MODULE_FEATURES: OnceLock<Mutex<features>> = OnceLock::new();
fn featuresarray(_m: *const module, _f: &Mutex<features>) -> Vec<String> {
vec!["b:private".to_string()]
}
fn handlefeatures(
_m: *const module,
_f: &Mutex<features>,
enables: &mut Option<Vec<i32>>,
) -> i32 {
if enables.is_none() {
*enables = Some(vec![1; 1]);
}
0
}
fn setfeatureenables(_m: *const module, _f: &Mutex<features>, _e: Option<&[i32]>) -> i32 {
0
}
fn module_features() -> &'static Mutex<features> {
MODULE_FEATURES.get_or_init(|| {
Mutex::new(features {
bn_list: None,
bn_size: 1,
cd_list: None,
cd_size: 0,
mf_list: None,
mf_size: 0,
pd_list: None,
pd_size: 0,
n_abstract: 0,
})
})
}
#[cfg(test)]
mod tests {
use super::*;
fn empty_ops_pp() -> options {
options {
ind: [0u8; MAX_OPS],
args: Vec::new(),
argscount: 0,
argsalloc: 0,
}
}
#[test]
fn bin_private_no_args_returns_zero() {
let _g = crate::test_util::global_state_lock();
let mut ops = empty_ops_pp();
let mut assigns: Vec<(String, String)> = Vec::new();
assert_eq!(bin_private("private", &[], &ops, 0), 0);
}
#[test]
fn bin_private_scalar_assign() {
let _g = crate::test_util::global_state_lock();
let mut ops = empty_ops_pp();
ops.ind[b'P' as usize] = 1;
let r = bin_private(
"private",
&["foo=bar".to_string()],
&ops,
0,
);
assert_eq!(r, 0);
}
#[test]
fn bin_private_minus_p_minus_t_refused() {
let _g = crate::test_util::global_state_lock();
let mut ops = empty_ops_pp();
ops.ind[b'P' as usize] = 1;
ops.ind[b'T' as usize] = 1;
let mut assigns: Vec<(String, String)> = Vec::new();
assert_eq!(bin_private("private", &[], &ops, 0), 1);
}
#[test]
fn module_loaders_return_zero() {
let _g = crate::test_util::global_state_lock();
let m: *const module = std::ptr::null();
let mut features: Vec<String> = Vec::new();
let mut enables: Option<Vec<i32>> = None;
assert_eq!(setup_(m), 0);
assert_eq!(features_(m, &mut features), 0);
assert_eq!(enables_(m, &mut enables), 0);
assert_eq!(boot_(m), 0);
assert_eq!(cleanup_(m), 0);
assert_eq!(finish_(m), 0);
}
#[test]
fn is_private_on_null_returns_zero() {
let _g = crate::test_util::global_state_lock();
assert_eq!(is_private(std::ptr::null()), 0);
}
#[test]
fn is_private_for_unregistered_param_returns_zero() {
let _g = crate::test_util::global_state_lock();
let pm = param {
node: hashnode {
next: None,
nam: "__not_private__".to_string(),
flags: 0,
},
u_data: 0,
u_arr: None,
u_str: None,
u_val: 0,
u_dval: 0.0,
u_hash: None,
gsu_s: None,
gsu_i: None,
gsu_f: None,
gsu_a: None,
gsu_h: None,
base: 0,
width: 0,
env: None,
ename: None,
old: None,
level: 0,
};
assert_eq!(is_private(&pm as *const _), 0);
}
#[test]
fn bin_private_minus_p_minus_t_currently_passes_through() {
let _g = crate::test_util::global_state_lock();
let mut ops = empty_ops_pp();
ops.ind[b'P' as usize] = 1;
ops.ind[b't' as usize] = 1;
let r = bin_private(
"private",
&["foo=bar".to_string()],
&ops,
0,
);
assert!(r == 0 || r == 1, "got unexpected exit code {}", r);
}
#[test]
fn makeprivate_on_null_is_safe() {
let _g = crate::test_util::global_state_lock();
makeprivate(std::ptr::null_mut(), 0);
}
#[test]
fn setfn_error_on_null_is_safe() {
let _g = crate::test_util::global_state_lock();
setfn_error(std::ptr::null_mut());
}
#[test]
fn pps_getfn_on_null_returns_empty() {
let _g = crate::test_util::global_state_lock();
let r = pps_getfn(std::ptr::null_mut());
assert_eq!(r, "", "pps_getfn(NULL) must return empty");
}
#[test]
fn ppi_getfn_on_null_returns_zero() {
let _g = crate::test_util::global_state_lock();
let r = ppi_getfn(std::ptr::null_mut());
assert_eq!(r, 0, "ppi_getfn(NULL) must return 0");
}
#[test]
fn ppf_getfn_on_null_returns_zero_float() {
let _g = crate::test_util::global_state_lock();
let r = ppf_getfn(std::ptr::null_mut());
assert_eq!(r, 0.0, "ppf_getfn(NULL) must return 0.0");
}
#[test]
fn ppa_getfn_on_null_returns_empty_vec() {
let _g = crate::test_util::global_state_lock();
let r = ppa_getfn(std::ptr::null_mut());
assert!(r.is_empty(), "ppa_getfn(NULL) must yield empty Vec");
}
#[test]
fn all_set_callbacks_accept_null_safely() {
let _g = crate::test_util::global_state_lock();
pps_setfn(std::ptr::null_mut(), "value");
ppi_setfn(std::ptr::null_mut(), 42);
ppf_setfn(std::ptr::null_mut(), 3.14);
ppa_setfn(std::ptr::null_mut(), vec!["a".to_string()]);
}
#[test]
fn all_unset_callbacks_accept_null_safely() {
let _g = crate::test_util::global_state_lock();
pps_unsetfn(std::ptr::null_mut(), 0);
ppi_unsetfn(std::ptr::null_mut(), 0);
ppf_unsetfn(std::ptr::null_mut(), 0);
ppa_unsetfn(std::ptr::null_mut(), 0);
pph_unsetfn(std::ptr::null_mut(), 0);
}
#[test]
fn module_lifecycle_shims_all_return_zero() {
let _g = crate::test_util::global_state_lock();
let m: *const module = std::ptr::null();
assert_eq!(setup_(m), 0);
assert_eq!(boot_(m), 0);
assert_eq!(cleanup_(m), 0);
assert_eq!(finish_(m), 0);
}
#[test]
fn param_private_corpus_is_private_null_returns_zero() {
let _g = crate::test_util::global_state_lock();
assert_eq!(is_private(std::ptr::null()), 0);
}
#[test]
fn param_private_corpus_setfn_error_null_no_panic() {
let _g = crate::test_util::global_state_lock();
setfn_error(std::ptr::null_mut());
}
#[test]
fn param_private_corpus_pps_getfn_null_returns_empty() {
let _g = crate::test_util::global_state_lock();
let s = pps_getfn(std::ptr::null_mut());
assert!(s.is_empty(), "null pm → empty string, got {s:?}");
}
#[test]
fn param_private_corpus_ppi_getfn_null_returns_zero() {
let _g = crate::test_util::global_state_lock();
assert_eq!(ppi_getfn(std::ptr::null_mut()), 0);
}
}