minijinja/vm/
mod.rs

1use std::collections::BTreeMap;
2use std::mem;
3
4#[cfg(feature = "macros")]
5use std::sync::Arc;
6
7use crate::compiler::instructions::{
8    Instruction, Instructions, LOOP_FLAG_RECURSIVE, LOOP_FLAG_WITH_LOOP_VAR, MAX_LOCALS,
9};
10use crate::environment::Environment;
11use crate::error::{Error, ErrorKind};
12use crate::output::{CaptureMode, Output};
13use crate::utils::{untrusted_size_hint, AutoEscape, UndefinedBehavior};
14use crate::value::namespace_object::Namespace;
15use crate::value::{ops, value_map_with_capacity, Kwargs, ObjectRepr, Value, ValueMap};
16use crate::vm::context::{Frame, Stack};
17use crate::vm::loop_object::{Loop, LoopState};
18use crate::vm::state::BlockStack;
19
20#[cfg(feature = "macros")]
21use crate::vm::closure_object::Closure;
22
23pub(crate) use crate::vm::context::Context;
24pub use crate::vm::state::State;
25
26#[cfg(feature = "macros")]
27mod closure_object;
28mod context;
29#[cfg(feature = "fuel")]
30mod fuel;
31mod loop_object;
32#[cfg(feature = "macros")]
33mod macro_object;
34#[cfg(feature = "multi_template")]
35mod module_object;
36mod state;
37
38// the cost of a single include against the stack limit.
39#[cfg(feature = "multi_template")]
40const INCLUDE_RECURSION_COST: usize = 10;
41
42// the cost of a single macro call against the stack limit.
43#[cfg(feature = "macros")]
44const MACRO_RECURSION_COST: usize = 4;
45
46/// Helps to evaluate something.
47#[cfg_attr(feature = "internal_debug", derive(Debug))]
48pub struct Vm<'env> {
49    env: &'env Environment<'env>,
50}
51
52pub(crate) fn prepare_blocks<'env, 'template>(
53    blocks: &'template BTreeMap<&'env str, Instructions<'env>>,
54) -> BTreeMap<&'env str, BlockStack<'template, 'env>> {
55    blocks
56        .iter()
57        .map(|(name, instr)| (*name, BlockStack::new(instr)))
58        .collect()
59}
60
61fn get_or_lookup_local<'a, F>(vec: &mut [Option<&'a Value>], idx: u8, f: F) -> Option<&'a Value>
62where
63    F: FnOnce() -> Option<&'a Value>,
64{
65    if idx == !0 {
66        f()
67    } else if let Some(Some(rv)) = vec.get(idx as usize) {
68        Some(*rv)
69    } else {
70        let val = some!(f());
71        vec[idx as usize] = Some(val);
72        Some(val)
73    }
74}
75
76impl<'env> Vm<'env> {
77    /// Creates a new VM.
78    pub fn new(env: &'env Environment<'env>) -> Vm<'env> {
79        Vm { env }
80    }
81
82    /// Evaluates the given inputs.
83    ///
84    /// It returns both the last value left on the stack as well as the state
85    /// at the end of the evaluation.
86    pub fn eval<'template>(
87        &self,
88        instructions: &'template Instructions<'env>,
89        root: Value,
90        blocks: &'template BTreeMap<&'env str, Instructions<'env>>,
91        out: &mut Output,
92        auto_escape: AutoEscape,
93    ) -> Result<(Option<Value>, State<'template, 'env>), Error> {
94        let mut state = State::new(
95            Context::new_with_frame(self.env, ok!(Frame::new_checked(root))),
96            auto_escape,
97            instructions,
98            prepare_blocks(blocks),
99        );
100        self.eval_state(&mut state, out).map(|x| (x, state))
101    }
102
103    /// Evaluate a macro in a state.
104    #[cfg(feature = "macros")]
105    pub fn eval_macro(
106        &self,
107        state: &State,
108        macro_id: usize,
109        out: &mut Output,
110        closure: Value,
111        caller: Option<Value>,
112        args: Vec<Value>,
113    ) -> Result<Option<Value>, Error> {
114        let (instructions, pc) = &state.macros[macro_id];
115        let context_base = state.ctx.clone_base();
116        let mut ctx = Context::new_with_frame(self.env, Frame::new(context_base));
117        ok!(ctx.push_frame(Frame::new(closure)));
118        if let Some(caller) = caller {
119            ctx.store("caller", caller);
120        }
121        ok!(ctx.incr_depth(state.ctx.depth() + MACRO_RECURSION_COST));
122        self.do_eval(
123            &mut State {
124                ctx,
125                current_block: None,
126                auto_escape: state.auto_escape(),
127                instructions,
128                blocks: BTreeMap::default(),
129                temps: state.temps.clone(),
130                loaded_templates: Default::default(),
131                #[cfg(feature = "macros")]
132                id: state.id,
133                #[cfg(feature = "macros")]
134                macros: state.macros.clone(),
135                #[cfg(feature = "macros")]
136                closure_tracker: state.closure_tracker.clone(),
137                #[cfg(feature = "fuel")]
138                fuel_tracker: state.fuel_tracker.clone(),
139            },
140            out,
141            Stack::from(args),
142            *pc,
143        )
144    }
145
146    /// This is the actual evaluation loop that works with a specific context.
147    #[inline(always)]
148    fn eval_state(
149        &self,
150        state: &mut State<'_, 'env>,
151        out: &mut Output,
152    ) -> Result<Option<Value>, Error> {
153        self.do_eval(state, out, Stack::default(), 0)
154    }
155
156    /// Performs the actual evaluation, optionally with stack growth functionality.
157    fn do_eval(
158        &self,
159        state: &mut State<'_, 'env>,
160        out: &mut Output,
161        stack: Stack,
162        pc: u32,
163    ) -> Result<Option<Value>, Error> {
164        #[cfg(feature = "stacker")]
165        {
166            stacker::maybe_grow(32 * 1024, 1024 * 1024, || {
167                self.eval_impl(state, out, stack, pc)
168            })
169        }
170        #[cfg(not(feature = "stacker"))]
171        {
172            self.eval_impl(state, out, stack, pc)
173        }
174    }
175
176    #[inline]
177    fn eval_impl(
178        &self,
179        state: &mut State<'_, 'env>,
180        out: &mut Output,
181        mut stack: Stack,
182        mut pc: u32,
183    ) -> Result<Option<Value>, Error> {
184        let initial_auto_escape = state.auto_escape;
185        let undefined_behavior = state.undefined_behavior();
186        let mut auto_escape_stack = vec![];
187        let mut next_loop_recursion_jump = None;
188        let mut loaded_filters = [None; MAX_LOCALS];
189        let mut loaded_tests = [None; MAX_LOCALS];
190
191        // If we are extending we are holding the instructions of the target parent
192        // template here.  This is used to detect multiple extends and the evaluation
193        // uses these instructions when it makes it to the end of the instructions.
194        #[cfg(feature = "multi_template")]
195        let mut parent_instructions = None;
196
197        macro_rules! recurse_loop {
198            ($capture:expr, $loop_object:expr) => {{
199                let Some(jump_target) = $loop_object.recurse_jump_target else {
200                    bail!(Error::new(
201                        ErrorKind::InvalidOperation,
202                        "cannot recurse outside of recursive loop",
203                    ))
204                };
205                // the way this works is that we remember the next instruction
206                // as loop exit jump target.  Whenever a loop is pushed, it
207                // memorizes the value in `next_loop_iteration_jump` to jump
208                // to.
209                next_loop_recursion_jump = Some((pc + 1, $capture));
210                if $capture {
211                    out.begin_capture(CaptureMode::Capture);
212                }
213                pc = jump_target;
214                continue;
215            }};
216        }
217
218        // looks nicer this way
219        #[allow(clippy::while_let_loop)]
220        loop {
221            let instr = match state.instructions.get(pc) {
222                Some(instr) => instr,
223                #[cfg(not(feature = "multi_template"))]
224                None => break,
225                #[cfg(feature = "multi_template")]
226                None => {
227                    // when an extends statement appears in a template, when we hit the
228                    // last instruction we need to check if parent instructions were
229                    // stashed away (which means we found an extends tag which invoked
230                    // `LoadBlocks`).  If we do find instructions, we reset back to 0
231                    // from the new instructions.
232                    state.instructions = match parent_instructions.take() {
233                        Some(instr) => instr,
234                        None => break,
235                    };
236                    out.end_capture(AutoEscape::None);
237                    pc = 0;
238                    // because we swap out the instructions we also need to unload all
239                    // the filters and tests to ensure that we are not accidentally
240                    // reusing the local_ids for completely different filters.
241                    loaded_filters = [None; MAX_LOCALS];
242                    loaded_tests = [None; MAX_LOCALS];
243                    continue;
244                }
245            };
246
247            // if we only have two arguments that we pull from the stack, we
248            // can assign them to a and b.  This slightly reduces the amount of
249            // code bloat generated here.  Same with the error.
250            let a;
251            let b;
252            let mut err;
253
254            macro_rules! func_binop {
255                ($method:ident) => {{
256                    b = stack.pop();
257                    a = stack.pop();
258                    stack.push(ctx_ok!(ops::$method(&a, &b)));
259                }};
260            }
261
262            macro_rules! op_binop {
263                ($op:tt) => {{
264                    b = stack.pop();
265                    a = stack.pop();
266                    stack.push(Value::from(a $op b));
267                }};
268            }
269
270            macro_rules! bail {
271                ($err:expr) => {{
272                    err = $err;
273                    process_err(&mut err, pc, state);
274                    return Err(err);
275                }};
276            }
277
278            macro_rules! ctx_ok {
279                ($expr:expr) => {
280                    match $expr {
281                        Ok(rv) => rv,
282                        Err(err) => bail!(err),
283                    }
284                };
285            }
286
287            macro_rules! assert_valid {
288                ($expr:expr) => {{
289                    let val = $expr;
290                    match val.validate() {
291                        Ok(val) => val,
292                        Err(err) => bail!(err),
293                    }
294                }};
295            }
296
297            // if the fuel consumption feature is enabled, track the fuel
298            // consumption here.
299            #[cfg(feature = "fuel")]
300            if let Some(ref tracker) = state.fuel_tracker {
301                ctx_ok!(tracker.track(instr));
302            }
303
304            match instr {
305                Instruction::Swap => {
306                    a = stack.pop();
307                    b = stack.pop();
308                    stack.push(a);
309                    stack.push(b);
310                }
311                Instruction::EmitRaw(val) => {
312                    // this only produces a format error, no need to attach
313                    // location information.
314                    ok!(out.write_str(val).map_err(Error::from));
315                }
316                Instruction::Emit => {
317                    ctx_ok!(self.env.format(&stack.pop(), state, out));
318                }
319                Instruction::StoreLocal(name) => {
320                    state.ctx.store(name, stack.pop());
321                }
322                Instruction::Lookup(name) => {
323                    stack.push(assert_valid!(state
324                        .lookup(name)
325                        .unwrap_or(Value::UNDEFINED)));
326                }
327                Instruction::GetAttr(name) => {
328                    a = stack.pop();
329                    // This is a common enough operation that it's interesting to consider a fast
330                    // path here.  This is slightly faster than the regular attr lookup because we
331                    // do not need to pass down the error object for the more common success case.
332                    // Only when we cannot look up something, we start to consider the undefined
333                    // special case.
334                    stack.push(match a.get_attr_fast(name) {
335                        Some(value) => assert_valid!(value),
336                        None => ctx_ok!(undefined_behavior.handle_undefined(a.is_undefined())),
337                    });
338                }
339                Instruction::SetAttr(name) => {
340                    b = stack.pop();
341                    a = stack.pop();
342                    if let Some(ns) = b.downcast_object_ref::<Namespace>() {
343                        ns.set_value(name, a);
344                    } else {
345                        bail!(Error::new(
346                            ErrorKind::InvalidOperation,
347                            format!("can only assign to namespaces, not {}", b.kind())
348                        ));
349                    }
350                }
351                Instruction::GetItem => {
352                    a = stack.pop();
353                    b = stack.pop();
354                    stack.push(match b.get_item_opt(&a) {
355                        Some(value) => assert_valid!(value),
356                        None => ctx_ok!(undefined_behavior.handle_undefined(b.is_undefined())),
357                    });
358                }
359                Instruction::Slice => {
360                    let step = stack.pop();
361                    let stop = stack.pop();
362                    b = stack.pop();
363                    a = stack.pop();
364                    if a.is_undefined() && matches!(undefined_behavior, UndefinedBehavior::Strict) {
365                        bail!(Error::from(ErrorKind::UndefinedError));
366                    }
367                    stack.push(ctx_ok!(ops::slice(a, b, stop, step)));
368                }
369                Instruction::LoadConst(value) => {
370                    stack.push(value.clone());
371                }
372                Instruction::BuildMap(pair_count) => {
373                    let mut map = value_map_with_capacity(*pair_count);
374                    stack.reverse_top(*pair_count * 2);
375                    for _ in 0..*pair_count {
376                        let key = stack.pop();
377                        let value = stack.pop();
378                        map.insert(key, value);
379                    }
380                    stack.push(Value::from_object(map))
381                }
382                Instruction::BuildKwargs(pair_count) => {
383                    let mut map = value_map_with_capacity(*pair_count);
384                    stack.reverse_top(*pair_count * 2);
385                    for _ in 0..*pair_count {
386                        let key = stack.pop();
387                        let value = stack.pop();
388                        map.insert(key, value);
389                    }
390                    stack.push(Kwargs::wrap(map))
391                }
392                Instruction::MergeKwargs(count) => {
393                    let mut kwargs_sources = Vec::from_iter((0..*count).map(|_| stack.pop()));
394                    kwargs_sources.reverse();
395                    stack.push(ctx_ok!(self.merge_kwargs(kwargs_sources)));
396                }
397                Instruction::BuildList(n) => {
398                    let count = n.unwrap_or_else(|| stack.pop().try_into().unwrap());
399                    let mut v = Vec::with_capacity(untrusted_size_hint(count));
400                    for _ in 0..count {
401                        v.push(stack.pop());
402                    }
403                    v.reverse();
404                    stack.push(Value::from_object(v))
405                }
406                Instruction::UnpackList(count) => {
407                    ctx_ok!(self.unpack_list(&mut stack, *count));
408                }
409                Instruction::UnpackLists(count) => {
410                    let lists = Vec::from_iter((0..*count).map(|_| stack.pop()));
411                    let mut len = 0;
412                    for list in lists.into_iter().rev() {
413                        for item in ctx_ok!(list.try_iter()) {
414                            stack.push(item);
415                            len += 1;
416                        }
417                    }
418                    stack.push(Value::from(len));
419                }
420                Instruction::Add => func_binop!(add),
421                Instruction::Sub => func_binop!(sub),
422                Instruction::Mul => func_binop!(mul),
423                Instruction::Div => func_binop!(div),
424                Instruction::IntDiv => func_binop!(int_div),
425                Instruction::Rem => func_binop!(rem),
426                Instruction::Pow => func_binop!(pow),
427                Instruction::Eq => op_binop!(==),
428                Instruction::Ne => op_binop!(!=),
429                Instruction::Gt => op_binop!(>),
430                Instruction::Gte => op_binop!(>=),
431                Instruction::Lt => op_binop!(<),
432                Instruction::Lte => op_binop!(<=),
433                Instruction::Not => {
434                    a = stack.pop();
435                    stack.push(Value::from(!a.is_true()));
436                }
437                Instruction::StringConcat => {
438                    a = stack.pop();
439                    b = stack.pop();
440                    stack.push(ops::string_concat(b, &a));
441                }
442                Instruction::In => {
443                    a = stack.pop();
444                    b = stack.pop();
445                    // the in-operator can fail if the value is undefined and
446                    // we are in strict mode.
447                    ctx_ok!(state.undefined_behavior().assert_iterable(&a));
448                    stack.push(ctx_ok!(ops::contains(&a, &b)));
449                }
450                Instruction::Neg => {
451                    a = stack.pop();
452                    stack.push(ctx_ok!(ops::neg(&a)));
453                }
454                Instruction::PushWith => {
455                    ctx_ok!(state.ctx.push_frame(Frame::default()));
456                }
457                Instruction::PopFrame => {
458                    state.ctx.pop_frame();
459                }
460                Instruction::PopLoopFrame => {
461                    let mut l = state.ctx.pop_frame().current_loop.unwrap();
462                    if let Some((target, end_capture)) = l.current_recursion_jump.take() {
463                        pc = target;
464                        if end_capture {
465                            stack.push(out.end_capture(state.auto_escape));
466                        }
467                        continue;
468                    }
469                }
470                #[cfg(feature = "macros")]
471                Instruction::IsUndefined => {
472                    a = stack.pop();
473                    stack.push(Value::from(a.is_undefined()));
474                }
475                Instruction::PushLoop(flags) => {
476                    a = stack.pop();
477                    ctx_ok!(self.push_loop(state, a, *flags, pc, next_loop_recursion_jump.take()));
478                }
479                Instruction::Iterate(jump_target) => {
480                    match state.ctx.current_loop().unwrap().next() {
481                        Some(item) => stack.push(assert_valid!(item)),
482                        None => {
483                            pc = *jump_target;
484                            continue;
485                        }
486                    };
487                }
488                Instruction::PushDidNotIterate => {
489                    stack.push(Value::from(
490                        state.ctx.current_loop().unwrap().did_not_iterate(),
491                    ));
492                }
493                Instruction::Jump(jump_target) => {
494                    pc = *jump_target;
495                    continue;
496                }
497                Instruction::JumpIfFalse(jump_target) => {
498                    a = stack.pop();
499                    if !ctx_ok!(undefined_behavior.is_true(&a)) {
500                        pc = *jump_target;
501                        continue;
502                    }
503                }
504                Instruction::JumpIfFalseOrPop(jump_target) => {
505                    if !ctx_ok!(undefined_behavior.is_true(stack.peek())) {
506                        pc = *jump_target;
507                        continue;
508                    } else {
509                        stack.pop();
510                    }
511                }
512                Instruction::JumpIfTrueOrPop(jump_target) => {
513                    if ctx_ok!(undefined_behavior.is_true(stack.peek())) {
514                        pc = *jump_target;
515                        continue;
516                    } else {
517                        stack.pop();
518                    }
519                }
520                Instruction::PushAutoEscape => {
521                    a = stack.pop();
522                    auto_escape_stack.push(state.auto_escape);
523                    state.auto_escape = ctx_ok!(self.derive_auto_escape(a, initial_auto_escape));
524                }
525                Instruction::PopAutoEscape => {
526                    state.auto_escape = auto_escape_stack.pop().unwrap();
527                }
528                Instruction::BeginCapture(mode) => {
529                    out.begin_capture(*mode);
530                }
531                Instruction::EndCapture => {
532                    stack.push(out.end_capture(state.auto_escape));
533                }
534                Instruction::ApplyFilter(name, arg_count, local_id) => {
535                    let filter =
536                        ctx_ok!(get_or_lookup_local(&mut loaded_filters, *local_id, || {
537                            state.env().get_filter(name)
538                        })
539                        .ok_or_else(|| {
540                            Error::new(
541                                ErrorKind::UnknownFilter,
542                                format!("filter {name} is unknown"),
543                            )
544                        }));
545                    let args = stack.get_call_args(*arg_count);
546                    let arg_count = args.len();
547                    a = ctx_ok!(filter.call(state, args));
548                    stack.drop_top(arg_count);
549                    stack.push(a);
550                }
551                Instruction::PerformTest(name, arg_count, local_id) => {
552                    let test = ctx_ok!(get_or_lookup_local(&mut loaded_tests, *local_id, || {
553                        state.env().get_test(name)
554                    })
555                    .ok_or_else(|| {
556                        Error::new(ErrorKind::UnknownTest, format!("test {name} is unknown"))
557                    }));
558                    let args = stack.get_call_args(*arg_count);
559                    let arg_count = args.len();
560                    a = ctx_ok!(test.call(state, args));
561                    stack.drop_top(arg_count);
562                    stack.push(Value::from(a.is_true()));
563                }
564                Instruction::CallFunction(name, arg_count) => {
565                    let args = stack.get_call_args(*arg_count);
566                    // super is a special function reserved for super-ing into blocks.
567                    let rv = if *name == "super" {
568                        if !args.is_empty() {
569                            bail!(Error::new(
570                                ErrorKind::InvalidOperation,
571                                "super() takes no arguments",
572                            ));
573                        }
574                        ctx_ok!(self.perform_super(state, out, true))
575                    } else if let Some(func) = state.lookup(name) {
576                        // calling loops is a special operation that starts the recursion process.
577                        // this bypasses the actual `call` implementation which would just fail
578                        // with an error.
579                        if let Some(loop_object) = func.downcast_object_ref::<Loop>() {
580                            if args.len() != 1 {
581                                bail!(Error::new(
582                                    ErrorKind::InvalidOperation,
583                                    "loop() takes one argument"
584                                ));
585                            }
586                            recurse_loop!(true, loop_object);
587                        } else {
588                            ctx_ok!(func.call(state, args))
589                        }
590                    } else {
591                        bail!(Error::new(
592                            ErrorKind::UnknownFunction,
593                            format!("{name} is unknown"),
594                        ));
595                    };
596                    let arg_count = args.len();
597                    stack.drop_top(arg_count);
598                    stack.push(rv);
599                }
600                Instruction::CallMethod(name, arg_count) => {
601                    let args = stack.get_call_args(*arg_count);
602                    let arg_count = args.len();
603                    a = ctx_ok!(args[0].call_method(state, name, &args[1..]));
604                    stack.drop_top(arg_count);
605                    stack.push(a);
606                }
607                Instruction::CallObject(arg_count) => {
608                    let args = stack.get_call_args(*arg_count);
609                    let arg_count = args.len();
610                    a = ctx_ok!(args[0].call(state, &args[1..]));
611                    stack.drop_top(arg_count);
612                    stack.push(a);
613                }
614                Instruction::DupTop => {
615                    stack.push(stack.peek().clone());
616                }
617                Instruction::DiscardTop => {
618                    stack.pop();
619                }
620                Instruction::FastSuper => {
621                    ctx_ok!(self.perform_super(state, out, false));
622                }
623                Instruction::FastRecurse => match state.ctx.current_loop() {
624                    Some(l) => recurse_loop!(false, &l.object),
625                    None => bail!(Error::new(ErrorKind::UnknownFunction, "loop is unknown")),
626                },
627                // Explanation on the behavior of `LoadBlocks` and rendering of
628                // inherited templates:
629                //
630                // MiniJinja inherits the behavior from Jinja2 where extending
631                // loads the blocks (`LoadBlocks`) and the rest of the template
632                // keeps executing but with output disabled, only at the end the
633                // parent template is then invoked.  This has the effect that
634                // you can still set variables or declare macros and that they
635                // become visible in the blocks.
636                //
637                // This behavior has a few downsides.  First of all what happens
638                // in the parent template overrides what happens in the child.
639                // For instance if you declare a macro named `foo` after `{%
640                // extends %}` and then a variable with that named is also set
641                // in the parent template, then you won't be able to call that
642                // macro in the body.
643                //
644                // The reason for this is that blocks unlike macros do not have
645                // closures in Jinja2/MiniJinja.
646                //
647                // However for the common case this is convenient because it
648                // lets you put some imports there and for as long as you do not
649                // create name clashes this works fine.
650                #[cfg(feature = "multi_template")]
651                Instruction::LoadBlocks => {
652                    a = stack.pop();
653                    if parent_instructions.is_some() {
654                        bail!(Error::new(
655                            ErrorKind::InvalidOperation,
656                            "tried to extend a second time in a template"
657                        ));
658                    }
659                    parent_instructions = Some(ctx_ok!(self.load_blocks(a, state)));
660                    out.begin_capture(CaptureMode::Discard);
661                }
662                #[cfg(feature = "multi_template")]
663                Instruction::Include(ignore_missing) => {
664                    a = stack.pop();
665                    ctx_ok!(self.perform_include(a, state, out, *ignore_missing));
666                }
667                #[cfg(feature = "multi_template")]
668                Instruction::ExportLocals => {
669                    let captured = stack.pop();
670                    let locals = state.ctx.current_locals_mut();
671                    let mut values = value_map_with_capacity(locals.len());
672                    for (key, value) in locals.iter() {
673                        values.insert(Value::from(*key), value.clone());
674                    }
675                    stack.push(Value::from_object(crate::vm::module_object::Module::new(
676                        values, captured,
677                    )));
678                }
679                #[cfg(feature = "multi_template")]
680                Instruction::CallBlock(name) => {
681                    if parent_instructions.is_none() && !out.is_discarding() {
682                        self.call_block(name, state, out)?;
683                    }
684                }
685                #[cfg(feature = "macros")]
686                Instruction::BuildMacro(name, offset, flags) => {
687                    self.build_macro(&mut stack, state, *offset, name, *flags);
688                }
689                #[cfg(feature = "macros")]
690                Instruction::Return => break,
691                #[cfg(feature = "macros")]
692                Instruction::Enclose(name) => {
693                    // the first time we enclose a value, we need to create a closure
694                    // and store it on the context, and add it to the closure tracker
695                    // for cycle breaking.
696                    if state.ctx.closure().is_none() {
697                        let closure = Arc::new(Closure::default());
698                        state.closure_tracker.track_closure(closure.clone());
699                        state.ctx.reset_closure(Some(closure));
700                    }
701                    state.ctx.enclose(name);
702                }
703                #[cfg(feature = "macros")]
704                Instruction::GetClosure => {
705                    stack.push(
706                        state
707                            .ctx
708                            .closure()
709                            .map_or(Value::UNDEFINED, |x| Value::from_dyn_object(x.clone())),
710                    );
711                }
712            }
713            pc += 1;
714        }
715
716        Ok(stack.try_pop())
717    }
718
719    fn merge_kwargs(&self, values: Vec<Value>) -> Result<Value, Error> {
720        let mut rv = ValueMap::new();
721        for value in values {
722            ok!(self.env.undefined_behavior().assert_iterable(&value));
723            let iter = ok!(value
724                .as_object()
725                .filter(|x| x.repr() == ObjectRepr::Map)
726                .and_then(|x| x.try_iter_pairs())
727                .ok_or_else(|| {
728                    Error::new(
729                        ErrorKind::InvalidOperation,
730                        format!(
731                            "attempted to apply keyword arguments from non map (got {})",
732                            value.kind()
733                        ),
734                    )
735                }));
736            for (key, value) in iter {
737                rv.insert(key, value);
738            }
739        }
740        Ok(Kwargs::wrap(rv))
741    }
742
743    #[cfg(feature = "multi_template")]
744    fn perform_include(
745        &self,
746        name: Value,
747        state: &mut State<'_, 'env>,
748        out: &mut Output,
749        ignore_missing: bool,
750    ) -> Result<(), Error> {
751        let obj = name.as_object();
752        let choices = obj
753            .as_ref()
754            .and_then(|d| d.try_iter())
755            .into_iter()
756            .flatten()
757            .chain(obj.is_none().then(|| name.clone()));
758
759        let mut templates_tried = vec![];
760        for choice in choices {
761            let name = ok!(choice.as_str().ok_or_else(|| {
762                Error::new(
763                    ErrorKind::InvalidOperation,
764                    "template name was not a string",
765                )
766            }));
767            let tmpl = match state.get_template(name) {
768                Ok(tmpl) => tmpl,
769                Err(err) => {
770                    if err.kind() == ErrorKind::TemplateNotFound {
771                        templates_tried.push(choice);
772                    } else {
773                        return Err(err);
774                    }
775                    continue;
776                }
777            };
778
779            let (new_instructions, new_blocks) = ok!(tmpl.instructions_and_blocks());
780            let old_escape = mem::replace(&mut state.auto_escape, tmpl.initial_auto_escape());
781            let old_instructions = mem::replace(&mut state.instructions, new_instructions);
782            let old_blocks = mem::replace(&mut state.blocks, prepare_blocks(new_blocks));
783            // we need to make a copy of the loaded templates here as we want
784            // to forget about the templates that an include triggered by the
785            // time the include finishes.
786            let old_loaded_templates = state.loaded_templates.clone();
787            ok!(state.ctx.incr_depth(INCLUDE_RECURSION_COST));
788            let rv;
789            #[cfg(feature = "macros")]
790            {
791                let old_closure = state.ctx.take_closure();
792                rv = self.eval_state(state, out);
793                state.ctx.reset_closure(old_closure);
794            }
795            #[cfg(not(feature = "macros"))]
796            {
797                rv = self.eval_state(state, out);
798            }
799            state.ctx.decr_depth(INCLUDE_RECURSION_COST);
800            state.loaded_templates = old_loaded_templates;
801            state.auto_escape = old_escape;
802            state.instructions = old_instructions;
803            state.blocks = old_blocks;
804            ok!(rv.map_err(|err| {
805                Error::new(
806                    ErrorKind::BadInclude,
807                    format!("error in \"{}\"", tmpl.name()),
808                )
809                .with_source(err)
810            }));
811            return Ok(());
812        }
813        if !templates_tried.is_empty() && !ignore_missing {
814            Err(Error::new(
815                ErrorKind::TemplateNotFound,
816                if templates_tried.len() == 1 {
817                    format!(
818                        "tried to include non-existing template {:?}",
819                        templates_tried[0]
820                    )
821                } else {
822                    format!(
823                        "tried to include one of multiple templates, none of which existed {}",
824                        Value::from(templates_tried)
825                    )
826                },
827            ))
828        } else {
829            Ok(())
830        }
831    }
832
833    fn perform_super(
834        &self,
835        state: &mut State<'_, 'env>,
836        out: &mut Output,
837        capture: bool,
838    ) -> Result<Value, Error> {
839        let name = ok!(state.current_block.ok_or_else(|| {
840            Error::new(ErrorKind::InvalidOperation, "cannot super outside of block")
841        }));
842
843        let block_stack = state.blocks.get_mut(name).unwrap();
844        if !block_stack.push() {
845            return Err(Error::new(
846                ErrorKind::InvalidOperation,
847                "no parent block exists",
848            ));
849        }
850
851        if capture {
852            out.begin_capture(CaptureMode::Capture);
853        }
854
855        let old_instructions = mem::replace(&mut state.instructions, block_stack.instructions());
856        ok!(state.ctx.push_frame(Frame::default()));
857        let rv = self.eval_state(state, out);
858        state.ctx.pop_frame();
859        state.instructions = old_instructions;
860        state.blocks.get_mut(name).unwrap().pop();
861
862        ok!(rv.map_err(|err| {
863            Error::new(ErrorKind::EvalBlock, "error in super block").with_source(err)
864        }));
865        if capture {
866            Ok(out.end_capture(state.auto_escape))
867        } else {
868            Ok(Value::UNDEFINED)
869        }
870    }
871
872    #[cfg(feature = "multi_template")]
873    fn load_blocks(
874        &self,
875        name: Value,
876        state: &mut State<'_, 'env>,
877    ) -> Result<&'env Instructions<'env>, Error> {
878        let Some(name) = name.as_str() else {
879            return Err(Error::new(
880                ErrorKind::InvalidOperation,
881                "template name was not a string",
882            ));
883        };
884        if state.loaded_templates.contains(&name) {
885            return Err(Error::new(
886                ErrorKind::InvalidOperation,
887                format!("cycle in template inheritance. {name:?} was referenced more than once"),
888            ));
889        }
890        let tmpl = ok!(state.get_template(name));
891        let (new_instructions, new_blocks) = ok!(tmpl.instructions_and_blocks());
892        state.loaded_templates.insert(new_instructions.name());
893        for (name, instr) in new_blocks.iter() {
894            state
895                .blocks
896                .entry(name)
897                .or_default()
898                .append_instructions(instr);
899        }
900        Ok(new_instructions)
901    }
902
903    #[cfg(feature = "multi_template")]
904    pub(crate) fn call_block(
905        &self,
906        name: &str,
907        state: &mut State<'_, 'env>,
908        out: &mut Output,
909    ) -> Result<Option<Value>, Error> {
910        if let Some((name, block_stack)) = state.blocks.get_key_value(name) {
911            let old_block = mem::replace(&mut state.current_block, Some(name));
912            let old_instructions =
913                mem::replace(&mut state.instructions, block_stack.instructions());
914            state.ctx.push_frame(Frame::default())?;
915            let rv = self.eval_state(state, out);
916            state.ctx.pop_frame();
917            state.instructions = old_instructions;
918            state.current_block = old_block;
919            rv
920        } else {
921            Err(Error::new(
922                ErrorKind::UnknownBlock,
923                format!("block '{}' not found", name),
924            ))
925        }
926    }
927
928    fn derive_auto_escape(
929        &self,
930        value: Value,
931        initial_auto_escape: AutoEscape,
932    ) -> Result<AutoEscape, Error> {
933        match (value.as_str(), value == Value::from(true)) {
934            (Some("html"), _) => Ok(AutoEscape::Html),
935            #[cfg(feature = "json")]
936            (Some("json"), _) => Ok(AutoEscape::Json),
937            (Some("none"), _) | (None, false) => Ok(AutoEscape::None),
938            (None, true) => Ok(if matches!(initial_auto_escape, AutoEscape::None) {
939                AutoEscape::Html
940            } else {
941                initial_auto_escape
942            }),
943            _ => Err(Error::new(
944                ErrorKind::InvalidOperation,
945                "invalid value to autoescape tag",
946            )),
947        }
948    }
949
950    fn push_loop(
951        &self,
952        state: &mut State<'_, 'env>,
953        iterable: Value,
954        flags: u8,
955        pc: u32,
956        current_recursion_jump: Option<(u32, bool)>,
957    ) -> Result<(), Error> {
958        let iter = ok!(state
959            .undefined_behavior()
960            .try_iter(iterable)
961            .map_err(|mut err| {
962                if let Some((jump_instr, _)) = current_recursion_jump {
963                    // When a recursion error happens, we need to process the error at both the
964                    // recursion call site and the loop definition.  This is because the error
965                    // happens at the recursion call site, but the loop definition is where the
966                    // recursion is defined.  This helps provide a more helpful error message.
967                    // For better reporting we basically turn this around.  jump_pc - 1 is the
968                    // location of FastRecurse or CallFunction.
969                    process_err(&mut err, pc, state);
970                    let mut call_err = Error::new(
971                        ErrorKind::InvalidOperation,
972                        "cannot recurse because of non-iterable value",
973                    );
974                    process_err(&mut call_err, jump_instr - 1, state);
975                    call_err.with_source(err)
976                } else {
977                    err
978                }
979            }));
980        let depth = state
981            .ctx
982            .current_loop()
983            .filter(|x| x.object.recurse_jump_target.is_some())
984            .map_or(0, |x| x.object.depth + 1);
985        state.ctx.push_frame(Frame {
986            current_loop: Some(LoopState::new(
987                iter,
988                depth,
989                flags & LOOP_FLAG_WITH_LOOP_VAR != 0,
990                (flags & LOOP_FLAG_RECURSIVE != 0).then_some(pc),
991                current_recursion_jump,
992            )),
993            ..Frame::default()
994        })
995    }
996
997    fn unpack_list(&self, stack: &mut Stack, count: usize) -> Result<(), Error> {
998        let top = stack.pop();
999        let iter = ok!(top
1000            .as_object()
1001            .and_then(|x| x.try_iter())
1002            .ok_or_else(|| Error::new(ErrorKind::CannotUnpack, "value is not iterable")));
1003
1004        let mut n = 0;
1005        for item in iter {
1006            stack.push(item);
1007            n += 1;
1008        }
1009
1010        if n == count {
1011            stack.reverse_top(n);
1012            Ok(())
1013        } else {
1014            Err(Error::new(
1015                ErrorKind::CannotUnpack,
1016                format!("sequence of wrong length (expected {}, got {})", count, n,),
1017            ))
1018        }
1019    }
1020
1021    #[cfg(feature = "macros")]
1022    fn build_macro(
1023        &self,
1024        stack: &mut Stack,
1025        state: &mut State,
1026        offset: u32,
1027        name: &str,
1028        flags: u8,
1029    ) {
1030        use crate::{compiler::instructions::MACRO_CALLER, vm::macro_object::Macro};
1031
1032        let arg_spec = stack.pop().try_iter().unwrap().collect();
1033        let closure = stack.pop();
1034        let macro_ref_id = state.macros.len();
1035        Arc::make_mut(&mut state.macros).push((state.instructions, offset));
1036        stack.push(Value::from_object(Macro {
1037            name: Value::from(name),
1038            arg_spec,
1039            macro_ref_id,
1040            state_id: state.id,
1041            closure,
1042            caller_reference: (flags & MACRO_CALLER) != 0,
1043        }));
1044    }
1045}
1046
1047#[inline(never)]
1048#[cold]
1049fn process_err(err: &mut Error, pc: u32, state: &State) {
1050    // only attach line information if the error does not have line info yet.
1051    if err.line().is_none() {
1052        if let Some(span) = state.instructions.get_span(pc) {
1053            err.set_filename_and_span(state.instructions.name(), span);
1054        } else if let Some(lineno) = state.instructions.get_line(pc) {
1055            err.set_filename_and_line(state.instructions.name(), lineno);
1056        }
1057    }
1058    // only attach debug info if we don't have one yet and we are in debug mode.
1059    #[cfg(feature = "debug")]
1060    {
1061        if state.env().debug() && err.debug_info().is_none() {
1062            err.attach_debug_info(state.make_debug_info(pc, state.instructions));
1063        }
1064    }
1065}