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