blob: cfc2bc846a4c526bd474517680dfc55fd049941e [file] [log] [blame]
Armin Ronacherba3757b2008-04-16 19:43:16 +02001# -*- coding: utf-8 -*-
2"""
3 jinja2.debug
4 ~~~~~~~~~~~~
5
Armin Ronacher187bde12008-05-01 18:19:16 +02006 Implements the debug interface for Jinja. This module does some pretty
7 ugly stuff with the Python traceback system in order to achieve tracebacks
8 with correct line numbers, locals and contents.
Armin Ronacherba3757b2008-04-16 19:43:16 +02009
10 :copyright: Copyright 2008 by Armin Ronacher.
11 :license: BSD.
12"""
Armin Ronacherba3757b2008-04-16 19:43:16 +020013import sys
Armin Ronacher32a910f2008-04-26 23:21:03 +020014from types import CodeType
Armin Ronacherb5365482008-05-11 00:18:35 +020015from jinja2.compiler import unmask_identifier
Armin Ronacherba3757b2008-04-16 19:43:16 +020016
17
Armin Ronacher8e8d0712008-04-16 23:10:49 +020018def translate_exception(exc_info):
19 """If passed an exc_info it will automatically rewrite the exceptions
20 all the way down to the correct line numbers and frames.
21 """
22 result_tb = prev_tb = None
Armin Ronacher6cc8dd02008-04-16 23:15:15 +020023 initial_tb = tb = exc_info[2].tb_next
Armin Ronacher8e8d0712008-04-16 23:10:49 +020024
25 while tb is not None:
26 template = tb.tb_frame.f_globals.get('__jinja_template__')
27 if template is not None:
28 lineno = template.get_corresponding_lineno(tb.tb_lineno)
29 tb = fake_exc_info(exc_info[:2] + (tb,), template.filename,
30 lineno, prev_tb)[2]
31 if result_tb is None:
32 result_tb = tb
33 prev_tb = tb
34 tb = tb.tb_next
35
36 return exc_info[:2] + (result_tb or initial_tb,)
Armin Ronacherba3757b2008-04-16 19:43:16 +020037
38
Armin Ronacheraaf010d2008-05-01 13:14:30 +020039def translate_syntax_error(error):
40 """When passed a syntax error it will generate a new traceback with
41 more debugging information.
42 """
43 filename = error.filename
44 if filename is None:
45 filename = '<template>'
46 elif isinstance(filename, unicode):
47 filename = filename.encode('utf-8')
48 code = compile('\n' * (error.lineno - 1) + 'raise __jinja_exception__',
49 filename, 'exec')
50 try:
51 exec code in {'__jinja_exception__': error}
52 except:
53 exc_info = sys.exc_info()
54 return exc_info[:2] + (exc_info[2].tb_next,)
55
56
Armin Ronacherba3757b2008-04-16 19:43:16 +020057def fake_exc_info(exc_info, filename, lineno, tb_back=None):
Armin Ronacher8e8d0712008-04-16 23:10:49 +020058 """Helper for `translate_exception`."""
Armin Ronacherba3757b2008-04-16 19:43:16 +020059 exc_type, exc_value, tb = exc_info
60
61 # figure the real context out
62 real_locals = tb.tb_frame.f_locals.copy()
Armin Ronacher203bfcb2008-04-24 21:54:44 +020063 ctx = real_locals.get('context')
64 if ctx:
65 locals = ctx.get_all()
66 else:
67 locals = {}
Armin Ronacherba3757b2008-04-16 19:43:16 +020068 for name, value in real_locals.iteritems():
69 if name.startswith('l_'):
Armin Ronacherb5365482008-05-11 00:18:35 +020070 try:
71 locals[str(unmask_identifier(name))] = value
72 except UnicodeError:
73 # bummer. someone actually used an unicode identifier.
74 # there is no way this can be added back into the python
75 # layer with python < 3. we have to ignore it...
76 pass
Armin Ronacherba3757b2008-04-16 19:43:16 +020077
Armin Ronacher18c6ca02008-04-17 10:03:29 +020078 # if there is a local called __jinja_exception__, we get
79 # rid of it to not break the debug functionality.
80 locals.pop('__jinja_exception__', None)
81
Armin Ronacherba3757b2008-04-16 19:43:16 +020082 # assamble fake globals we need
83 globals = {
84 '__name__': filename,
85 '__file__': filename,
86 '__jinja_exception__': exc_info[:2]
87 }
88
89 # and fake the exception
90 code = compile('\n' * (lineno - 1) + 'raise __jinja_exception__[0], ' +
91 '__jinja_exception__[1]', filename, 'exec')
Armin Ronacher32a910f2008-04-26 23:21:03 +020092
93 # if it's possible, change the name of the code. This won't work
94 # on some python environments such as google appengine
95 try:
96 function = tb.tb_frame.f_code.co_name
97 if function == 'root':
98 location = 'top-level template code'
99 elif function.startswith('block_'):
100 location = 'block "%s"' % function[6:]
101 else:
102 location = 'template'
103 code = CodeType(0, code.co_nlocals, code.co_stacksize,
104 code.co_flags, code.co_code, code.co_consts,
105 code.co_names, code.co_varnames, filename,
106 location, code.co_firstlineno,
107 code.co_lnotab, (), ())
108 except:
109 pass
110
111 # execute the code and catch the new traceback
Armin Ronacherba3757b2008-04-16 19:43:16 +0200112 try:
113 exec code in globals, locals
114 except:
115 exc_info = sys.exc_info()
Armin Ronacher32a910f2008-04-26 23:21:03 +0200116 new_tb = exc_info[2].tb_next
Armin Ronacherba3757b2008-04-16 19:43:16 +0200117
118 # now we can patch the exc info accordingly
119 if tb_set_next is not None:
120 if tb_back is not None:
Armin Ronacher32a910f2008-04-26 23:21:03 +0200121 tb_set_next(tb_back, new_tb)
Armin Ronacherba3757b2008-04-16 19:43:16 +0200122 if tb is not None:
Armin Ronacher32a910f2008-04-26 23:21:03 +0200123 tb_set_next(new_tb, tb.tb_next)
Armin Ronacher6cc8dd02008-04-16 23:15:15 +0200124
125 # return without this frame
Armin Ronacher32a910f2008-04-26 23:21:03 +0200126 return exc_info[:2] + (new_tb,)
Armin Ronacherba3757b2008-04-16 19:43:16 +0200127
128
Armin Ronacherba3757b2008-04-16 19:43:16 +0200129def _init_ugly_crap():
130 """This function implements a few ugly things so that we can patch the
131 traceback objects. The function returned allows resetting `tb_next` on
132 any python traceback object.
133 """
134 import ctypes
135 from types import TracebackType
136
137 # figure out side of _Py_ssize_t
138 if hasattr(ctypes.pythonapi, 'Py_InitModule4_64'):
139 _Py_ssize_t = ctypes.c_int64
140 else:
141 _Py_ssize_t = ctypes.c_int
142
143 # regular python
144 class _PyObject(ctypes.Structure):
145 pass
146 _PyObject._fields_ = [
147 ('ob_refcnt', _Py_ssize_t),
148 ('ob_type', ctypes.POINTER(_PyObject))
149 ]
150
151 # python with trace
152 if object.__basicsize__ != ctypes.sizeof(_PyObject):
153 class _PyObject(ctypes.Structure):
154 pass
155 _PyObject._fields_ = [
156 ('_ob_next', ctypes.POINTER(_PyObject)),
157 ('_ob_prev', ctypes.POINTER(_PyObject)),
158 ('ob_refcnt', _Py_ssize_t),
159 ('ob_type', ctypes.POINTER(_PyObject))
160 ]
161
162 class _Traceback(_PyObject):
163 pass
164 _Traceback._fields_ = [
165 ('tb_next', ctypes.POINTER(_Traceback)),
166 ('tb_frame', ctypes.POINTER(_PyObject)),
167 ('tb_lasti', ctypes.c_int),
168 ('tb_lineno', ctypes.c_int)
169 ]
170
171 def tb_set_next(tb, next):
Armin Ronacher8e8d0712008-04-16 23:10:49 +0200172 """Set the tb_next attribute of a traceback object."""
Armin Ronacherba3757b2008-04-16 19:43:16 +0200173 if not (isinstance(tb, TracebackType) and
Armin Ronacher8e8d0712008-04-16 23:10:49 +0200174 (next is None or isinstance(next, TracebackType))):
Armin Ronacherba3757b2008-04-16 19:43:16 +0200175 raise TypeError('tb_set_next arguments must be traceback objects')
176 obj = _Traceback.from_address(id(tb))
Armin Ronacher8e8d0712008-04-16 23:10:49 +0200177 if tb.tb_next is not None:
178 old = _Traceback.from_address(id(tb.tb_next))
179 old.ob_refcnt -= 1
180 if next is None:
181 obj.tb_next = ctypes.POINTER(_Traceback)()
182 else:
183 next = _Traceback.from_address(id(next))
184 next.ob_refcnt += 1
185 obj.tb_next = ctypes.pointer(next)
Armin Ronacherba3757b2008-04-16 19:43:16 +0200186
187 return tb_set_next
188
189
Armin Ronacherbd33f112008-04-18 09:17:32 +0200190# try to get a tb_set_next implementation
Armin Ronacherba3757b2008-04-16 19:43:16 +0200191try:
Armin Ronacherbd33f112008-04-18 09:17:32 +0200192 from jinja2._speedups import tb_set_next
193except ImportError:
194 try:
195 tb_set_next = _init_ugly_crap()
196 except:
197 tb_set_next = None
Armin Ronacherba3757b2008-04-16 19:43:16 +0200198del _init_ugly_crap