1e31aef6aSopenharmony_ciimport sys
2e31aef6aSopenharmony_ciimport typing as t
3e31aef6aSopenharmony_cifrom types import CodeType
4e31aef6aSopenharmony_cifrom types import TracebackType
5e31aef6aSopenharmony_ci
6e31aef6aSopenharmony_cifrom .exceptions import TemplateSyntaxError
7e31aef6aSopenharmony_cifrom .utils import internal_code
8e31aef6aSopenharmony_cifrom .utils import missing
9e31aef6aSopenharmony_ci
10e31aef6aSopenharmony_ciif t.TYPE_CHECKING:
11e31aef6aSopenharmony_ci    from .runtime import Context
12e31aef6aSopenharmony_ci
13e31aef6aSopenharmony_ci
14e31aef6aSopenharmony_cidef rewrite_traceback_stack(source: t.Optional[str] = None) -> BaseException:
15e31aef6aSopenharmony_ci    """Rewrite the current exception to replace any tracebacks from
16e31aef6aSopenharmony_ci    within compiled template code with tracebacks that look like they
17e31aef6aSopenharmony_ci    came from the template source.
18e31aef6aSopenharmony_ci
19e31aef6aSopenharmony_ci    This must be called within an ``except`` block.
20e31aef6aSopenharmony_ci
21e31aef6aSopenharmony_ci    :param source: For ``TemplateSyntaxError``, the original source if
22e31aef6aSopenharmony_ci        known.
23e31aef6aSopenharmony_ci    :return: The original exception with the rewritten traceback.
24e31aef6aSopenharmony_ci    """
25e31aef6aSopenharmony_ci    _, exc_value, tb = sys.exc_info()
26e31aef6aSopenharmony_ci    exc_value = t.cast(BaseException, exc_value)
27e31aef6aSopenharmony_ci    tb = t.cast(TracebackType, tb)
28e31aef6aSopenharmony_ci
29e31aef6aSopenharmony_ci    if isinstance(exc_value, TemplateSyntaxError) and not exc_value.translated:
30e31aef6aSopenharmony_ci        exc_value.translated = True
31e31aef6aSopenharmony_ci        exc_value.source = source
32e31aef6aSopenharmony_ci        # Remove the old traceback, otherwise the frames from the
33e31aef6aSopenharmony_ci        # compiler still show up.
34e31aef6aSopenharmony_ci        exc_value.with_traceback(None)
35e31aef6aSopenharmony_ci        # Outside of runtime, so the frame isn't executing template
36e31aef6aSopenharmony_ci        # code, but it still needs to point at the template.
37e31aef6aSopenharmony_ci        tb = fake_traceback(
38e31aef6aSopenharmony_ci            exc_value, None, exc_value.filename or "<unknown>", exc_value.lineno
39e31aef6aSopenharmony_ci        )
40e31aef6aSopenharmony_ci    else:
41e31aef6aSopenharmony_ci        # Skip the frame for the render function.
42e31aef6aSopenharmony_ci        tb = tb.tb_next
43e31aef6aSopenharmony_ci
44e31aef6aSopenharmony_ci    stack = []
45e31aef6aSopenharmony_ci
46e31aef6aSopenharmony_ci    # Build the stack of traceback object, replacing any in template
47e31aef6aSopenharmony_ci    # code with the source file and line information.
48e31aef6aSopenharmony_ci    while tb is not None:
49e31aef6aSopenharmony_ci        # Skip frames decorated with @internalcode. These are internal
50e31aef6aSopenharmony_ci        # calls that aren't useful in template debugging output.
51e31aef6aSopenharmony_ci        if tb.tb_frame.f_code in internal_code:
52e31aef6aSopenharmony_ci            tb = tb.tb_next
53e31aef6aSopenharmony_ci            continue
54e31aef6aSopenharmony_ci
55e31aef6aSopenharmony_ci        template = tb.tb_frame.f_globals.get("__jinja_template__")
56e31aef6aSopenharmony_ci
57e31aef6aSopenharmony_ci        if template is not None:
58e31aef6aSopenharmony_ci            lineno = template.get_corresponding_lineno(tb.tb_lineno)
59e31aef6aSopenharmony_ci            fake_tb = fake_traceback(exc_value, tb, template.filename, lineno)
60e31aef6aSopenharmony_ci            stack.append(fake_tb)
61e31aef6aSopenharmony_ci        else:
62e31aef6aSopenharmony_ci            stack.append(tb)
63e31aef6aSopenharmony_ci
64e31aef6aSopenharmony_ci        tb = tb.tb_next
65e31aef6aSopenharmony_ci
66e31aef6aSopenharmony_ci    tb_next = None
67e31aef6aSopenharmony_ci
68e31aef6aSopenharmony_ci    # Assign tb_next in reverse to avoid circular references.
69e31aef6aSopenharmony_ci    for tb in reversed(stack):
70e31aef6aSopenharmony_ci        tb.tb_next = tb_next
71e31aef6aSopenharmony_ci        tb_next = tb
72e31aef6aSopenharmony_ci
73e31aef6aSopenharmony_ci    return exc_value.with_traceback(tb_next)
74e31aef6aSopenharmony_ci
75e31aef6aSopenharmony_ci
76e31aef6aSopenharmony_cidef fake_traceback(  # type: ignore
77e31aef6aSopenharmony_ci    exc_value: BaseException, tb: t.Optional[TracebackType], filename: str, lineno: int
78e31aef6aSopenharmony_ci) -> TracebackType:
79e31aef6aSopenharmony_ci    """Produce a new traceback object that looks like it came from the
80e31aef6aSopenharmony_ci    template source instead of the compiled code. The filename, line
81e31aef6aSopenharmony_ci    number, and location name will point to the template, and the local
82e31aef6aSopenharmony_ci    variables will be the current template context.
83e31aef6aSopenharmony_ci
84e31aef6aSopenharmony_ci    :param exc_value: The original exception to be re-raised to create
85e31aef6aSopenharmony_ci        the new traceback.
86e31aef6aSopenharmony_ci    :param tb: The original traceback to get the local variables and
87e31aef6aSopenharmony_ci        code info from.
88e31aef6aSopenharmony_ci    :param filename: The template filename.
89e31aef6aSopenharmony_ci    :param lineno: The line number in the template source.
90e31aef6aSopenharmony_ci    """
91e31aef6aSopenharmony_ci    if tb is not None:
92e31aef6aSopenharmony_ci        # Replace the real locals with the context that would be
93e31aef6aSopenharmony_ci        # available at that point in the template.
94e31aef6aSopenharmony_ci        locals = get_template_locals(tb.tb_frame.f_locals)
95e31aef6aSopenharmony_ci        locals.pop("__jinja_exception__", None)
96e31aef6aSopenharmony_ci    else:
97e31aef6aSopenharmony_ci        locals = {}
98e31aef6aSopenharmony_ci
99e31aef6aSopenharmony_ci    globals = {
100e31aef6aSopenharmony_ci        "__name__": filename,
101e31aef6aSopenharmony_ci        "__file__": filename,
102e31aef6aSopenharmony_ci        "__jinja_exception__": exc_value,
103e31aef6aSopenharmony_ci    }
104e31aef6aSopenharmony_ci    # Raise an exception at the correct line number.
105e31aef6aSopenharmony_ci    code: CodeType = compile(
106e31aef6aSopenharmony_ci        "\n" * (lineno - 1) + "raise __jinja_exception__", filename, "exec"
107e31aef6aSopenharmony_ci    )
108e31aef6aSopenharmony_ci
109e31aef6aSopenharmony_ci    # Build a new code object that points to the template file and
110e31aef6aSopenharmony_ci    # replaces the location with a block name.
111e31aef6aSopenharmony_ci    location = "template"
112e31aef6aSopenharmony_ci
113e31aef6aSopenharmony_ci    if tb is not None:
114e31aef6aSopenharmony_ci        function = tb.tb_frame.f_code.co_name
115e31aef6aSopenharmony_ci
116e31aef6aSopenharmony_ci        if function == "root":
117e31aef6aSopenharmony_ci            location = "top-level template code"
118e31aef6aSopenharmony_ci        elif function.startswith("block_"):
119e31aef6aSopenharmony_ci            location = f"block {function[6:]!r}"
120e31aef6aSopenharmony_ci
121e31aef6aSopenharmony_ci    if sys.version_info >= (3, 8):
122e31aef6aSopenharmony_ci        code = code.replace(co_name=location)
123e31aef6aSopenharmony_ci    else:
124e31aef6aSopenharmony_ci        code = CodeType(
125e31aef6aSopenharmony_ci            code.co_argcount,
126e31aef6aSopenharmony_ci            code.co_kwonlyargcount,
127e31aef6aSopenharmony_ci            code.co_nlocals,
128e31aef6aSopenharmony_ci            code.co_stacksize,
129e31aef6aSopenharmony_ci            code.co_flags,
130e31aef6aSopenharmony_ci            code.co_code,
131e31aef6aSopenharmony_ci            code.co_consts,
132e31aef6aSopenharmony_ci            code.co_names,
133e31aef6aSopenharmony_ci            code.co_varnames,
134e31aef6aSopenharmony_ci            code.co_filename,
135e31aef6aSopenharmony_ci            location,
136e31aef6aSopenharmony_ci            code.co_firstlineno,
137e31aef6aSopenharmony_ci            code.co_lnotab,
138e31aef6aSopenharmony_ci            code.co_freevars,
139e31aef6aSopenharmony_ci            code.co_cellvars,
140e31aef6aSopenharmony_ci        )
141e31aef6aSopenharmony_ci
142e31aef6aSopenharmony_ci    # Execute the new code, which is guaranteed to raise, and return
143e31aef6aSopenharmony_ci    # the new traceback without this frame.
144e31aef6aSopenharmony_ci    try:
145e31aef6aSopenharmony_ci        exec(code, globals, locals)
146e31aef6aSopenharmony_ci    except BaseException:
147e31aef6aSopenharmony_ci        return sys.exc_info()[2].tb_next  # type: ignore
148e31aef6aSopenharmony_ci
149e31aef6aSopenharmony_ci
150e31aef6aSopenharmony_cidef get_template_locals(real_locals: t.Mapping[str, t.Any]) -> t.Dict[str, t.Any]:
151e31aef6aSopenharmony_ci    """Based on the runtime locals, get the context that would be
152e31aef6aSopenharmony_ci    available at that point in the template.
153e31aef6aSopenharmony_ci    """
154e31aef6aSopenharmony_ci    # Start with the current template context.
155e31aef6aSopenharmony_ci    ctx: "t.Optional[Context]" = real_locals.get("context")
156e31aef6aSopenharmony_ci
157e31aef6aSopenharmony_ci    if ctx is not None:
158e31aef6aSopenharmony_ci        data: t.Dict[str, t.Any] = ctx.get_all().copy()
159e31aef6aSopenharmony_ci    else:
160e31aef6aSopenharmony_ci        data = {}
161e31aef6aSopenharmony_ci
162e31aef6aSopenharmony_ci    # Might be in a derived context that only sets local variables
163e31aef6aSopenharmony_ci    # rather than pushing a context. Local variables follow the scheme
164e31aef6aSopenharmony_ci    # l_depth_name. Find the highest-depth local that has a value for
165e31aef6aSopenharmony_ci    # each name.
166e31aef6aSopenharmony_ci    local_overrides: t.Dict[str, t.Tuple[int, t.Any]] = {}
167e31aef6aSopenharmony_ci
168e31aef6aSopenharmony_ci    for name, value in real_locals.items():
169e31aef6aSopenharmony_ci        if not name.startswith("l_") or value is missing:
170e31aef6aSopenharmony_ci            # Not a template variable, or no longer relevant.
171e31aef6aSopenharmony_ci            continue
172e31aef6aSopenharmony_ci
173e31aef6aSopenharmony_ci        try:
174e31aef6aSopenharmony_ci            _, depth_str, name = name.split("_", 2)
175e31aef6aSopenharmony_ci            depth = int(depth_str)
176e31aef6aSopenharmony_ci        except ValueError:
177e31aef6aSopenharmony_ci            continue
178e31aef6aSopenharmony_ci
179e31aef6aSopenharmony_ci        cur_depth = local_overrides.get(name, (-1,))[0]
180e31aef6aSopenharmony_ci
181e31aef6aSopenharmony_ci        if cur_depth < depth:
182e31aef6aSopenharmony_ci            local_overrides[name] = (depth, value)
183e31aef6aSopenharmony_ci
184e31aef6aSopenharmony_ci    # Modify the context with any derived context.
185e31aef6aSopenharmony_ci    for name, (_, value) in local_overrides.items():
186e31aef6aSopenharmony_ci        if value is missing:
187e31aef6aSopenharmony_ci            data.pop(name, None)
188e31aef6aSopenharmony_ci        else:
189e31aef6aSopenharmony_ci            data[name] = value
190e31aef6aSopenharmony_ci
191e31aef6aSopenharmony_ci    return data
192