minijinja_stack_ref/
lib.rs

1//! An extension package to MiniJinja that allows stack borrows.
2//!
3//! **This is an experimental package. There might be soundness issues and there
4//! might be problems with the API. Please give feedback.**
5//!
6//! # Intro
7//!
8//! When implementing dynamic objects for MiniJinja lifetimes a common hurdle can
9//! be lifetimes.  That's because MiniJinja requires that all values passed to the
10//! template are owned by the runtime engine.  Thus it becomes impossible to carry
11//! non static lifetimes into the template.
12//!
13//! This crate provides a solution to this issue by moving lifetime checks to the
14//! runtime for MiniJinja objects.  One first needs to create a [`Scope`] with
15//! the [`scope`] function.  It invokes a callback to which a scope is passed
16//! which in turn then provides functionality to create [`Value`]s to those
17//! borrowed values such as the [`object_ref`](crate::Scope::object_ref) method.
18//!
19//! # Example
20//!
21//! This example demonstrates how to pass borrowed information into a template:
22//!
23//! ```
24//! use minijinja::value::{StructObject, Value};
25//! use minijinja::{context, Environment};
26//! use minijinja_stack_ref::scope;
27//!
28//! struct State {
29//!     version: &'static str,
30//! }
31//!
32//! impl StructObject for State {
33//!     fn get_field(&self, field: &str) -> Option<Value> {
34//!         match field {
35//!             "version" => Some(Value::from(self.version)),
36//!             _ => None,
37//!         }
38//!     }
39//! }
40//!
41//! let mut env = Environment::new();
42//! env.add_template(
43//!     "info",
44//!     "app version: {{ state.version }}\nitems: {{ items }}"
45//! )
46//! .unwrap();
47//!
48//! let state = State {
49//!     version: env!("CARGO_PKG_VERSION"),
50//! };
51//! let items = [1u32, 2, 3, 4];
52//!
53//! let rv = scope(|scope| {
54//!     let tmpl = env.get_template("info").unwrap();
55//!     tmpl.render(context! {
56//!         state => scope.struct_object_ref(&state),
57//!         items => scope.seq_object_ref(&items[..]),
58//!     }).unwrap()
59//! });
60//! println!("{}", rv);
61//! ```
62//!
63//! # Reborrowing
64//!
65//! If an object holds other complex values it can be interesting to again
66//! return a reference to a member rather.  In that case it becomes necessary
67//! again to get access to the [`Scope`].  This can be accomplished with the
68//! [`reborrow`] functionality which.  It lets you return references to `&self`
69//! from within an referenced object:
70//!
71//! ```
72//! use minijinja::value::{StructObject, Value};
73//! use minijinja::{context, Environment};
74//! use minijinja_stack_ref::{reborrow, scope};
75//!
76//! struct Config {
77//!     version: &'static str,
78//! }
79//!
80//! struct State {
81//!     config: Config,
82//! }
83//!
84//! impl StructObject for Config {
85//!     fn get_field(&self, field: &str) -> Option<Value> {
86//!         match field {
87//!             "version" => Some(Value::from(self.version)),
88//!             _ => None,
89//!         }
90//!     }
91//! }
92//!
93//! impl StructObject for State {
94//!     fn get_field(&self, field: &str) -> Option<Value> {
95//!         match field {
96//!             // return a reference to the inner config through reborrowing
97//!             "config" => Some(reborrow(self, |slf, scope| {
98//!                 scope.struct_object_ref(&slf.config)
99//!             })),
100//!             _ => None,
101//!         }
102//!     }
103//! }
104//!
105//! let mut env = Environment::new();
106//! env.add_template(
107//!     "info",
108//!     "app version: {{ state.config.version }}"
109//! )
110//! .unwrap();
111//!
112//! let state = State {
113//!     config: Config {
114//!         version: env!("CARGO_PKG_VERSION"),
115//!     }
116//! };
117//!
118//! let rv = scope(|scope| {
119//!     let tmpl = env.get_template("info").unwrap();
120//!     tmpl.render(context! {
121//!         state => scope.struct_object_ref(&state),
122//!     }).unwrap()
123//! });
124//! println!("{}", rv);
125//! ```
126use std::cell::RefCell;
127use std::collections::HashSet;
128use std::ffi::c_void;
129use std::fmt;
130use std::marker::PhantomData;
131use std::mem::transmute;
132use std::sync::atomic::{AtomicPtr, AtomicU64, Ordering};
133use std::sync::Arc;
134
135use minijinja::value::{Object, ObjectKind, SeqObject, StructObject, Value};
136use minijinja::{Error, State};
137
138static STACK_SCOPE_COUNTER: AtomicU64 = AtomicU64::new(0);
139
140thread_local! {
141    static STACK_SCOPE_IS_VALID: RefCell<HashSet<u64>> = RefCell::default();
142    static CURRENT_HANDLE: AtomicPtr<c_void> = const { AtomicPtr::new(std::ptr::null_mut()) };
143}
144
145/// A handle to an enclosed value.
146///
147/// For as long as the [`Scope`] is still valid access to the
148/// reference can be temporarily fetched via the [`with`](Self::with)
149/// method.  Doing so after the scope is gone, this will panic on all
150/// operations.
151///
152/// To check if a handle is still valid [`is_valid`](Self::is_valid)
153/// can be used.
154///
155/// A stack handle implements the underlying object protocols from
156/// MiniJinja.
157pub struct StackHandle<T: ?Sized> {
158    ptr: *const T,
159    id: u64,
160}
161
162unsafe impl<T: Send + ?Sized> Send for StackHandle<T> {}
163unsafe impl<T: Sync + ?Sized> Sync for StackHandle<T> {}
164
165struct ResetHandleOnDrop(*mut c_void);
166
167impl Drop for ResetHandleOnDrop {
168    fn drop(&mut self) {
169        CURRENT_HANDLE.with(|handle| handle.store(self.0, Ordering::SeqCst));
170    }
171}
172
173/// Reborrows a reference to a dynamic object with the scope's lifetime.
174///
175/// Within the trait methods of [`Object`], [`StructObject`] or [`SeqObject`] of a
176/// value that is currently referenced by a [`StackHandle`], this utility can be
177/// used to reborrow `&self` with the lifetime of the scope.
178///
179/// This lets code return a [`Value`] that borrows into a field of `&self`.
180///
181/// ```
182/// use minijinja::value::{Value, StructObject};
183/// use minijinja_stack_ref::{reborrow, scope};
184///
185/// struct MyObject {
186///     values: Vec<u32>,
187/// }
188///
189/// impl StructObject for MyObject {
190///     fn get_field(&self, field: &str) -> Option<Value> {
191///         match field {
192///             "values" => Some(reborrow(self, |slf, scope| {
193///                 scope.seq_object_ref(&slf.values[..])
194///             })),
195///             _ => None
196///         }
197///     }
198/// }
199///
200/// let obj = MyObject { values: (0..100).collect() };
201/// scope(|scope| {
202///     let value = scope.struct_object_ref(&obj);
203///     // do something with value
204/// #   let _ = value;
205/// })
206/// ```
207///
208/// # Panics
209///
210/// This function panics if the passed object is not currently interacted with
211/// or not created via the [`Scope`].  In other words this function can only be
212/// used within object methods of [`Object`], [`SeqObject`] or [`StructObject`]
213/// of an object that has been put into a [`Value`] via a [`Scope`].
214///
215/// To check if reborrowing is possible, [`can_reborrow`] can be used instead.
216pub fn reborrow<T: ?Sized, R>(obj: &T, f: for<'a> fn(&'a T, &'a Scope) -> R) -> R {
217    CURRENT_HANDLE.with(|handle_ptr| {
218        let handle = match unsafe {
219            (handle_ptr.load(Ordering::SeqCst) as *const StackHandle<T>).as_ref()
220        } {
221            Some(handle) => handle,
222            None => {
223                panic!(
224                    "cannot reborrow &{} because there is no handle on the stack",
225                    std::any::type_name::<T>()
226                );
227            }
228        };
229
230        if !std::ptr::eq(handle.ptr, obj as *const T) {
231            panic!(
232                "cannot reborrow &{} as it's not held in an active stack handle ({:?} != {:?})",
233                std::any::type_name::<T>(),
234                handle.ptr,
235                obj as *const T,
236            );
237        }
238
239        assert!(
240            StackHandle::is_valid(handle),
241            "cannot reborrow &{} because stack is gone",
242            std::any::type_name::<T>()
243        );
244
245        let scope = Scope {
246            id: handle.id,
247            unset: false,
248            _marker: PhantomData,
249        };
250        f(unsafe { &*handle.ptr as &T }, &scope)
251    })
252}
253
254/// Returns `true` if reborrowing is possible.
255///
256/// This can be used to make an object conditionally reborrow.  If this method returns
257/// `true`, then [`reborrow`] will not panic.
258///
259/// ```
260/// use minijinja::value::{Value, StructObject};
261/// use minijinja_stack_ref::{reborrow, can_reborrow, scope};
262///
263/// struct MyObject {
264///     values: Vec<u32>,
265/// }
266///
267/// impl StructObject for MyObject {
268///     fn get_field(&self, field: &str) -> Option<Value> {
269///         match field {
270///             "values" => if can_reborrow(self) {
271///                 Some(reborrow(self, |slf, scope| {
272///                     scope.seq_object_ref(&slf.values[..])
273///                 }))
274///             } else {
275///                 Some(Value::from_serialize(&self.values))
276///             },
277///             _ => None
278///         }
279///     }
280/// }
281/// ```
282pub fn can_reborrow<T: ?Sized>(obj: &T) -> bool {
283    CURRENT_HANDLE.with(|handle_ptr| {
284        let handle = match unsafe {
285            (handle_ptr.load(Ordering::SeqCst) as *const StackHandle<T>).as_ref()
286        } {
287            Some(handle) => handle,
288            None => return false,
289        };
290
291        if !std::ptr::eq(handle.ptr, obj as *const T) {
292            return false;
293        }
294
295        StackHandle::is_valid(handle)
296    })
297}
298
299impl<T: ?Sized> StackHandle<T> {
300    /// Checks if the handle is still valid.
301    #[inline]
302    pub fn is_valid(handle: &StackHandle<T>) -> bool {
303        STACK_SCOPE_IS_VALID.with(|valid_ids| valid_ids.borrow().contains(&handle.id))
304    }
305
306    /// Invokes a function with the resolved reference.
307    ///
308    /// # Panics
309    ///
310    /// This method panics if the handle is not valid.
311    pub fn with<F: FnOnce(&T) -> R, R>(&self, f: F) -> R {
312        assert!(StackHandle::is_valid(self), "stack is gone");
313        let _reset = ResetHandleOnDrop(
314            CURRENT_HANDLE
315                .with(|handle| handle.swap(self as *const _ as *mut c_void, Ordering::SeqCst)),
316        );
317        f(unsafe { &*self.ptr as &T })
318    }
319}
320
321impl<T: SeqObject + Send + Sync + 'static + ?Sized> SeqObject for StackHandle<T> {
322    fn get_item(&self, idx: usize) -> Option<Value> {
323        self.with(|val| val.get_item(idx))
324    }
325
326    fn item_count(&self) -> usize {
327        self.with(|val| val.item_count())
328    }
329}
330
331impl<T: StructObject + Send + Sync + 'static + ?Sized> StructObject for StackHandle<T> {
332    fn get_field(&self, idx: &str) -> Option<Value> {
333        self.with(|val| val.get_field(idx))
334    }
335
336    fn static_fields(&self) -> Option<&'static [&'static str]> {
337        self.with(|val| val.static_fields())
338    }
339
340    fn fields(&self) -> Vec<Arc<str>> {
341        self.with(|val| val.fields())
342    }
343
344    fn field_count(&self) -> usize {
345        self.with(|val| val.field_count())
346    }
347}
348
349impl<T: Object + ?Sized> fmt::Debug for StackHandle<T> {
350    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
351        self.with(|val| fmt::Debug::fmt(val, f))
352    }
353}
354
355impl<T: Object + ?Sized> fmt::Display for StackHandle<T> {
356    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
357        self.with(|val| fmt::Display::fmt(val, f))
358    }
359}
360
361impl<T: Object + ?Sized> Object for StackHandle<T> {
362    fn kind(&self) -> ObjectKind<'_> {
363        self.with(|val| match val.kind() {
364            ObjectKind::Plain => ObjectKind::Plain,
365            ObjectKind::Seq(_) => {
366                ObjectKind::Seq(unsafe { transmute::<_, &StackHandleProxy<T>>(self) })
367            }
368            ObjectKind::Struct(_) => {
369                ObjectKind::Struct(unsafe { transmute::<_, &StackHandleProxy<T>>(self) })
370            }
371            _ => unimplemented!(),
372        })
373    }
374
375    fn call_method(&self, state: &State, name: &str, args: &[Value]) -> Result<Value, Error> {
376        self.with(|val| val.call_method(state, name, args))
377    }
378
379    fn call(&self, state: &State, args: &[Value]) -> Result<Value, Error> {
380        self.with(|val| val.call(state, args))
381    }
382}
383
384#[repr(transparent)]
385struct StackHandleProxy<T: Object + ?Sized>(StackHandle<T>);
386
387macro_rules! unwrap_kind {
388    ($val:expr, $pat:path) => {
389        if let $pat(rv) = $val.kind() {
390            rv
391        } else {
392            unreachable!("object changed shape")
393        }
394    };
395}
396
397impl<T: Object + ?Sized> SeqObject for StackHandleProxy<T> {
398    fn get_item(&self, idx: usize) -> Option<Value> {
399        self.0
400            .with(|val| unwrap_kind!(val, ObjectKind::Seq).get_item(idx))
401    }
402
403    fn item_count(&self) -> usize {
404        self.0
405            .with(|val| unwrap_kind!(val, ObjectKind::Seq).item_count())
406    }
407}
408
409impl<T: Object + ?Sized> StructObject for StackHandleProxy<T> {
410    fn get_field(&self, name: &str) -> Option<Value> {
411        self.0
412            .with(|val| unwrap_kind!(val, ObjectKind::Struct).get_field(name))
413    }
414
415    fn fields(&self) -> Vec<Arc<str>> {
416        self.0
417            .with(|val| unwrap_kind!(val, ObjectKind::Struct).fields())
418    }
419
420    fn field_count(&self) -> usize {
421        self.0
422            .with(|val| unwrap_kind!(val, ObjectKind::Struct).field_count())
423    }
424}
425
426/// Captures the calling scope.
427///
428/// To create a new scope, [`scope`] can be used.  To get the current active scope the
429/// [`reborrow`] functionality is available.
430pub struct Scope {
431    id: u64,
432    unset: bool,
433    _marker: PhantomData<*const ()>,
434}
435
436impl fmt::Debug for Scope {
437    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
438        f.debug_struct("Scope").field("id", &self.id).finish()
439    }
440}
441
442impl Scope {
443    fn new() -> Scope {
444        let id = STACK_SCOPE_COUNTER.fetch_add(1, Ordering::SeqCst);
445        let unset = STACK_SCOPE_IS_VALID.with(|valid_ids| valid_ids.borrow_mut().insert(id));
446        Scope {
447            id,
448            unset,
449            _marker: PhantomData,
450        }
451    }
452
453    /// Creates a [`StackHandle`] to a value with at least the scope's lifetime.
454    pub fn handle<'env, T: 'env + ?Sized>(&'env self, value: &'env T) -> StackHandle<T> {
455        StackHandle {
456            ptr: value as *const T,
457            id: self.id,
458        }
459    }
460
461    /// Creates a [`Value`] from a borrowed [`Object`].
462    ///
463    /// This is equivalent to `Value::from_object(self.handle(value))`.
464    pub fn object_ref<'env, T: Object + ?Sized>(&'env self, value: &'env T) -> Value {
465        Value::from_object(self.handle(value))
466    }
467
468    /// Creates a [`Value`] from a borrowed [`SeqObject`].
469    ///
470    /// This is equivalent to `Value::from_seq_object(self.handle(value))`.
471    pub fn seq_object_ref<'env, T: SeqObject + 'static + ?Sized>(
472        &'env self,
473        value: &'env T,
474    ) -> Value {
475        Value::from_seq_object(self.handle(value))
476    }
477
478    /// Creates a [`Value`] from a borrowed [`StructObject`].
479    ///
480    /// This is equivalent to `Value::from_struct_object(self.handle(value))`.
481    pub fn struct_object_ref<'env, T: StructObject + 'static + ?Sized>(
482        &'env self,
483        value: &'env T,
484    ) -> Value {
485        Value::from_struct_object(self.handle(value))
486    }
487}
488
489impl Drop for Scope {
490    fn drop(&mut self) {
491        if self.unset {
492            STACK_SCOPE_IS_VALID.with(|valid_ids| valid_ids.borrow_mut().remove(&self.id));
493        }
494    }
495}
496
497/// Invokes a function with a reference to the stack scope so values can be borrowed.
498///
499/// ```
500/// # use minijinja_stack_ref::scope;
501/// use minijinja::render;
502///
503/// let items = [1u32, 2, 3, 4];
504/// let rv = scope(|scope| {
505///     render!("items: {{ items }}", items => scope.seq_object_ref(&items[..]))
506/// });
507/// assert_eq!(rv, "items: [1, 2, 3, 4]");
508/// ```
509pub fn scope<R, F: FnOnce(&Scope) -> R>(f: F) -> R {
510    f(&Scope::new())
511}
512
513#[test]
514fn test_stack_handle() {
515    let value = vec![1, 2, 3];
516
517    let leaked_handle = {
518        scope(|scope| {
519            let value_handle: StackHandle<Vec<i32>> = scope.handle(&value);
520            assert_eq!(value_handle.with(|x| x.len()), 3);
521            value_handle
522        })
523    };
524
525    assert_eq!(value.len(), 3);
526    assert!(!StackHandle::is_valid(&leaked_handle));
527}
528
529#[test]
530#[should_panic = "stack is gone"]
531fn test_stack_handle_panic() {
532    let value = vec![1, 2, 3];
533    let leaked_handle = {
534        scope(|scope| {
535            let value_handle: StackHandle<Vec<i32>> = scope.handle(&value);
536            assert_eq!(value_handle.with(|x| x.len()), 3);
537            value_handle
538        })
539    };
540
541    assert_eq!(leaked_handle.with(|x| x.len()), 3);
542}