use crate::pathctx::{self, Binding};
pub(super) const UNCERTAIN_VALUE: &str = "/__SAFE_CHAINS_CMDSUB__";
pub(super) const MAX_UNCERTAIN_BINDINGS: u32 = 256;
thread_local! {
static SCOPE_BINDINGS: std::cell::Cell<u32> = const { std::cell::Cell::new(0) };
static SCOPE_SPENT: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
static SCOPE_WALKS: std::cell::Cell<u32> = const { std::cell::Cell::new(0) };
}
pub(super) struct ScopeWalk;
impl ScopeWalk {
pub(super) fn enter() -> Self {
let top = SCOPE_WALKS.with(|d| {
let n = d.get();
d.set(n + 1);
n == 0
});
if top {
SCOPE_BINDINGS.with(|c| c.set(0));
SCOPE_SPENT.with(|s| s.set(false));
}
ScopeWalk
}
}
impl Drop for ScopeWalk {
fn drop(&mut self) {
SCOPE_WALKS.with(|d| d.set(d.get().saturating_sub(1)));
}
}
fn already_uncertain(name: &str) -> bool {
matches!(pathctx::binding(name), Binding::Value(v) if v == UNCERTAIN_VALUE)
}
fn enter_uncertain_binding() -> bool {
let over = SCOPE_BINDINGS.with(|c| {
let n = c.get().saturating_add(1);
c.set(n);
n > MAX_UNCERTAIN_BINDINGS
});
if over {
SCOPE_SPENT.with(|s| s.set(true));
}
!over
}
pub(super) fn rebind_uncertain(names: impl IntoIterator<Item = String>, vars: &mut Vec<pathctx::VarGuard>) {
for name in names {
if already_uncertain(&name) {
continue;
}
if !enter_uncertain_binding() {
break;
}
vars.push(pathctx::enter_var(name, UNCERTAIN_VALUE.to_string()));
}
}
pub(super) fn scope_budget_spent() -> bool {
SCOPE_SPENT.with(std::cell::Cell::get)
}
#[cfg(test)]
mod tests {
use super::*;
fn entered() -> u32 {
SCOPE_BINDINGS.with(std::cell::Cell::get)
}
proptest::proptest! {
#[test]
fn sources_never_multiply_the_binding_set(
names in proptest::collection::vec(proptest::string::string_regex("[A-Za-z_][A-Za-z0-9_]{0,5}").unwrap(), 0..8),
sources in 0..24usize,
) {
let mut cmd = String::new();
for n in &names {
cmd.push_str(&format!("{n}=./a; "));
}
for _ in 0..sources {
cmd.push_str(". ./e; ");
}
cmd.push_str("ls");
let allowed = crate::is_safe_command(&cmd);
proptest::prop_assert!(
entered() <= names.len() as u32 + 8,
"{sources} sources over {} names entered {} bindings",
names.len(),
entered()
);
proptest::prop_assert_eq!(
allowed,
crate::cst::explain(&cmd).is_allowed(),
"the explanation describes a different verdict than the one enforced: {:?}",
cmd
);
}
}
}