Skip to main content

cljrs_runtime/env/
policy.rs

1//! Dynamic execution policy for restricted evaluator profiles.
2
3use std::cell::Cell;
4
5use crate::env::error::{EvalError, EvalResult};
6
7thread_local! {
8    static TRANSACTION_DEPTH: Cell<usize> = const { Cell::new(0) };
9    static TRANSACTION_GENSYM: Cell<u64> = const { Cell::new(0) };
10}
11
12/// Installs the side-effect-free transaction policy for its dynamic extent.
13#[must_use = "dropping the guard removes the transaction execution policy"]
14pub struct TransactionPolicyGuard;
15
16impl TransactionPolicyGuard {
17    pub fn install() -> Self {
18        TRANSACTION_DEPTH.with(|depth| {
19            if depth.get() == 0 {
20                TRANSACTION_GENSYM.with(|counter| counter.set(0));
21            }
22            depth.set(depth.get() + 1);
23        });
24        Self
25    }
26}
27
28impl Drop for TransactionPolicyGuard {
29    fn drop(&mut self) {
30        TRANSACTION_DEPTH.with(|depth| depth.set(depth.get().saturating_sub(1)));
31    }
32}
33
34pub fn transaction_policy_active() -> bool {
35    TRANSACTION_DEPTH.with(|depth| depth.get() != 0)
36}
37
38/// Return an invocation-local deterministic gensym sequence number when the
39/// transaction policy is active.
40pub fn next_transaction_gensym() -> Option<u64> {
41    if !transaction_policy_active() {
42        return None;
43    }
44    Some(TRANSACTION_GENSYM.with(|counter| {
45        let current = counter.get();
46        counter.set(current.wrapping_add(1));
47        current
48    }))
49}
50
51fn forbidden(operation: &str) -> EvalError {
52    EvalError::ForbiddenEffect(operation.to_string())
53}
54
55/// Check a native builtin at its final call boundary.
56///
57/// The transaction environment registers only clojurust's builtins, so this
58/// denylist is the capability surface: filesystem, output, clocks, randomness,
59/// process-global state, blocking/concurrency, and Rust object construction.
60pub fn check_native(name: &str) -> EvalResult<()> {
61    if !transaction_policy_active() {
62        return Ok(());
63    }
64    const DENIED: &[&str] = &[
65        "print",
66        "println",
67        "pr",
68        "prn",
69        "printf",
70        "newline",
71        "flush",
72        "spit",
73        "slurp",
74        "close",
75        "nanotime",
76        "sleep",
77        "rand",
78        "rand-int",
79        "random-sample",
80        "shuffle",
81        "random-uuid",
82        "gensym",
83        "add-tap",
84        "remove-tap",
85        "tap>",
86        "shared-atom",
87        "promise",
88        "deliver",
89        "send",
90        "send-off",
91        "new",
92        "Exception.",
93        "push-precision!",
94        "pop-precision!",
95    ];
96    if DENIED.contains(&name) {
97        Err(forbidden(name))
98    } else {
99        Ok(())
100    }
101}
102
103/// Check special forms that can reach outside the invocation environment.
104pub fn check_special(name: &str) -> EvalResult<()> {
105    if !transaction_policy_active() {
106        return Ok(());
107    }
108    const DENIED: &[&str] = &[
109        ".",
110        "ns",
111        "require",
112        "in-ns",
113        "alias",
114        "load-file",
115        "with-out-str",
116        "await",
117    ];
118    if DENIED.contains(&name) {
119        Err(forbidden(name))
120    } else {
121        Ok(())
122    }
123}
124
125pub fn check_versioned_lookup() -> EvalResult<()> {
126    if transaction_policy_active() {
127        Err(forbidden("versioned namespace lookup"))
128    } else {
129        Ok(())
130    }
131}