use std::cell::Cell;
use crate::env::error::{EvalError, EvalResult};
thread_local! {
static TRANSACTION_DEPTH: Cell<usize> = const { Cell::new(0) };
static TRANSACTION_GENSYM: Cell<u64> = const { Cell::new(0) };
}
#[must_use = "dropping the guard removes the transaction execution policy"]
pub struct TransactionPolicyGuard;
impl TransactionPolicyGuard {
pub fn install() -> Self {
TRANSACTION_DEPTH.with(|depth| {
if depth.get() == 0 {
TRANSACTION_GENSYM.with(|counter| counter.set(0));
}
depth.set(depth.get() + 1);
});
Self
}
}
impl Drop for TransactionPolicyGuard {
fn drop(&mut self) {
TRANSACTION_DEPTH.with(|depth| depth.set(depth.get().saturating_sub(1)));
}
}
pub fn transaction_policy_active() -> bool {
TRANSACTION_DEPTH.with(|depth| depth.get() != 0)
}
pub fn next_transaction_gensym() -> Option<u64> {
if !transaction_policy_active() {
return None;
}
Some(TRANSACTION_GENSYM.with(|counter| {
let current = counter.get();
counter.set(current.wrapping_add(1));
current
}))
}
fn forbidden(operation: &str) -> EvalError {
EvalError::ForbiddenEffect(operation.to_string())
}
pub fn check_native(name: &str) -> EvalResult<()> {
if !transaction_policy_active() {
return Ok(());
}
const DENIED: &[&str] = &[
"print",
"println",
"pr",
"prn",
"printf",
"newline",
"flush",
"spit",
"slurp",
"close",
"nanotime",
"sleep",
"rand",
"rand-int",
"random-sample",
"shuffle",
"random-uuid",
"gensym",
"add-tap",
"remove-tap",
"tap>",
"shared-atom",
"promise",
"deliver",
"send",
"send-off",
"new",
"Exception.",
"push-precision!",
"pop-precision!",
];
if DENIED.contains(&name) {
Err(forbidden(name))
} else {
Ok(())
}
}
pub fn check_special(name: &str) -> EvalResult<()> {
if !transaction_policy_active() {
return Ok(());
}
const DENIED: &[&str] = &[
".",
"ns",
"require",
"in-ns",
"alias",
"load-file",
"with-out-str",
"await",
];
if DENIED.contains(&name) {
Err(forbidden(name))
} else {
Ok(())
}
}
pub fn check_versioned_lookup() -> EvalResult<()> {
if transaction_policy_active() {
Err(forbidden("versioned namespace lookup"))
} else {
Ok(())
}
}