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}