blob: 08e828c4c759836d07b0f95adc63ec96bbb969c2 [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
Armin Ronacher55494e42010-01-22 09:41:48 +010010 :copyright: (c) 2010 by the Jinja Team.
Armin Ronachera18872d2009-03-05 23:47:00 +010011 :license: BSD, see LICENSE for more details.
Armin Ronacherba3757b2008-04-16 19:43:16 +020012"""
Armin Ronacherba3757b2008-04-16 19:43:16 +020013import sys
Armin Ronachera18872d2009-03-05 23:47:00 +010014import traceback
Armin Ronacherd416a972009-02-24 22:58:00 +010015from jinja2.utils import CodeType, missing, internal_code
Armin Ronachera18872d2009-03-05 23:47:00 +010016from jinja2.exceptions import TemplateSyntaxError
17
18
Armin Ronacherbd357722009-08-05 20:25:06 +020019# how does the raise helper look like?
20try:
21 exec "raise TypeError, 'foo'"
22except SyntaxError:
23 raise_helper = 'raise __jinja_exception__[1]'
24except TypeError:
25 raise_helper = 'raise __jinja_exception__[0], __jinja_exception__[1]'
26
27
Armin Ronachera18872d2009-03-05 23:47:00 +010028class TracebackFrameProxy(object):
29 """Proxies a traceback frame."""
30
31 def __init__(self, tb):
32 self.tb = tb
33
34 def _set_tb_next(self, next):
35 if tb_set_next is not None:
36 tb_set_next(self.tb, next and next.tb or None)
37 self._tb_next = next
38
39 def _get_tb_next(self):
40 return self._tb_next
41
42 tb_next = property(_get_tb_next, _set_tb_next)
43 del _get_tb_next, _set_tb_next
44
45 @property
46 def is_jinja_frame(self):
47 return '__jinja_template__' in self.tb.tb_frame.f_globals
48
49 def __getattr__(self, name):
50 return getattr(self.tb, name)
51
52
53class ProcessedTraceback(object):
54 """Holds a Jinja preprocessed traceback for priting or reraising."""
55
56 def __init__(self, exc_type, exc_value, frames):
57 assert frames, 'no frames for this traceback?'
58 self.exc_type = exc_type
59 self.exc_value = exc_value
60 self.frames = frames
61
62 def chain_frames(self):
63 """Chains the frames. Requires ctypes or the speedups extension."""
64 prev_tb = None
65 for tb in self.frames:
66 if prev_tb is not None:
67 prev_tb.tb_next = tb
68 prev_tb = tb
69 prev_tb.tb_next = None
70
71 def render_as_text(self, limit=None):
72 """Return a string with the traceback."""
73 lines = traceback.format_exception(self.exc_type, self.exc_value,
74 self.frames[0], limit=limit)
75 return ''.join(lines).rstrip()
76
Armin Ronacher32ed6c92009-04-02 14:04:41 +020077 def render_as_html(self, full=False):
78 """Return a unicode string with the traceback as rendered HTML."""
79 from jinja2.debugrenderer import render_traceback
80 return u'%s\n\n<!--\n%s\n-->' % (
81 render_traceback(self, full=full),
82 self.render_as_text().decode('utf-8', 'replace')
83 )
84
Armin Ronachera18872d2009-03-05 23:47:00 +010085 @property
86 def is_template_syntax_error(self):
87 """`True` if this is a template syntax error."""
88 return isinstance(self.exc_value, TemplateSyntaxError)
89
90 @property
91 def exc_info(self):
92 """Exception info tuple with a proxy around the frame objects."""
93 return self.exc_type, self.exc_value, self.frames[0]
94
95 @property
96 def standard_exc_info(self):
97 """Standard python exc_info for re-raising"""
98 return self.exc_type, self.exc_value, self.frames[0].tb
99
100
101def make_traceback(exc_info, source_hint=None):
102 """Creates a processed traceback object from the exc_info."""
103 exc_type, exc_value, tb = exc_info
104 if isinstance(exc_value, TemplateSyntaxError):
105 exc_info = translate_syntax_error(exc_value, source_hint)
Armin Ronacher2a791922009-04-16 23:15:22 +0200106 initial_skip = 0
107 else:
108 initial_skip = 1
109 return translate_exception(exc_info, initial_skip)
Armin Ronacherd416a972009-02-24 22:58:00 +0100110
111
112def translate_syntax_error(error, source=None):
113 """Rewrites a syntax error to please traceback systems."""
114 error.source = source
115 error.translated = True
116 exc_info = (type(error), error, None)
117 filename = error.filename
118 if filename is None:
119 filename = '<unknown>'
120 return fake_exc_info(exc_info, filename, error.lineno)
Armin Ronacherba3757b2008-04-16 19:43:16 +0200121
122
Armin Ronacher2a791922009-04-16 23:15:22 +0200123def translate_exception(exc_info, initial_skip=0):
Armin Ronacher8e8d0712008-04-16 23:10:49 +0200124 """If passed an exc_info it will automatically rewrite the exceptions
125 all the way down to the correct line numbers and frames.
126 """
Armin Ronacher2a791922009-04-16 23:15:22 +0200127 tb = exc_info[2]
Armin Ronachera18872d2009-03-05 23:47:00 +0100128 frames = []
Armin Ronacher8e8d0712008-04-16 23:10:49 +0200129
Armin Ronacher2a791922009-04-16 23:15:22 +0200130 # skip some internal frames if wanted
131 for x in xrange(initial_skip):
132 if tb is not None:
133 tb = tb.tb_next
134 initial_tb = tb
135
Armin Ronacher8e8d0712008-04-16 23:10:49 +0200136 while tb is not None:
Armin Ronacherd416a972009-02-24 22:58:00 +0100137 # skip frames decorated with @internalcode. These are internal
138 # calls we can't avoid and that are useless in template debugging
139 # output.
Armin Ronachera18872d2009-03-05 23:47:00 +0100140 if tb.tb_frame.f_code in internal_code:
Armin Ronacherd416a972009-02-24 22:58:00 +0100141 tb = tb.tb_next
142 continue
143
Armin Ronachera18872d2009-03-05 23:47:00 +0100144 # save a reference to the next frame if we override the current
145 # one with a faked one.
146 next = tb.tb_next
147
148 # fake template exceptions
Armin Ronacher8e8d0712008-04-16 23:10:49 +0200149 template = tb.tb_frame.f_globals.get('__jinja_template__')
150 if template is not None:
151 lineno = template.get_corresponding_lineno(tb.tb_lineno)
152 tb = fake_exc_info(exc_info[:2] + (tb,), template.filename,
Armin Ronachera18872d2009-03-05 23:47:00 +0100153 lineno)[2]
Armin Ronacher8e8d0712008-04-16 23:10:49 +0200154
Armin Ronachera18872d2009-03-05 23:47:00 +0100155 frames.append(TracebackFrameProxy(tb))
156 tb = next
157
158 # if we don't have any exceptions in the frames left, we have to
159 # reraise it unchanged.
160 # XXX: can we backup here? when could this happen?
161 if not frames:
162 raise exc_info[0], exc_info[1], exc_info[2]
163
164 traceback = ProcessedTraceback(exc_info[0], exc_info[1], frames)
165 if tb_set_next is not None:
166 traceback.chain_frames()
167 return traceback
Armin Ronacherba3757b2008-04-16 19:43:16 +0200168
169
Armin Ronachera18872d2009-03-05 23:47:00 +0100170def fake_exc_info(exc_info, filename, lineno):
Armin Ronacher8e8d0712008-04-16 23:10:49 +0200171 """Helper for `translate_exception`."""
Armin Ronacherba3757b2008-04-16 19:43:16 +0200172 exc_type, exc_value, tb = exc_info
173
174 # figure the real context out
Armin Ronacherd416a972009-02-24 22:58:00 +0100175 if tb is not None:
176 real_locals = tb.tb_frame.f_locals.copy()
177 ctx = real_locals.get('context')
178 if ctx:
179 locals = ctx.get_all()
180 else:
181 locals = {}
182 for name, value in real_locals.iteritems():
183 if name.startswith('l_') and value is not missing:
184 locals[name[2:]] = value
185
186 # if there is a local called __jinja_exception__, we get
187 # rid of it to not break the debug functionality.
188 locals.pop('__jinja_exception__', None)
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200189 else:
190 locals = {}
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200191
Armin Ronacherba3757b2008-04-16 19:43:16 +0200192 # assamble fake globals we need
193 globals = {
194 '__name__': filename,
195 '__file__': filename,
Armin Ronacher94ee6aa2009-04-17 11:21:00 +0200196 '__jinja_exception__': exc_info[:2],
197
198 # we don't want to keep the reference to the template around
199 # to not cause circular dependencies, but we mark it as Jinja
200 # frame for the ProcessedTraceback
201 '__jinja_template__': None
Armin Ronacherba3757b2008-04-16 19:43:16 +0200202 }
203
204 # and fake the exception
Armin Ronacherbd357722009-08-05 20:25:06 +0200205 code = compile('\n' * (lineno - 1) + raise_helper, filename, 'exec')
Armin Ronacher32a910f2008-04-26 23:21:03 +0200206
207 # if it's possible, change the name of the code. This won't work
208 # on some python environments such as google appengine
209 try:
Armin Ronacherd416a972009-02-24 22:58:00 +0100210 if tb is None:
Armin Ronacher32a910f2008-04-26 23:21:03 +0200211 location = 'template'
Armin Ronacherd416a972009-02-24 22:58:00 +0100212 else:
213 function = tb.tb_frame.f_code.co_name
214 if function == 'root':
215 location = 'top-level template code'
216 elif function.startswith('block_'):
217 location = 'block "%s"' % function[6:]
218 else:
219 location = 'template'
Armin Ronacher32a910f2008-04-26 23:21:03 +0200220 code = CodeType(0, code.co_nlocals, code.co_stacksize,
221 code.co_flags, code.co_code, code.co_consts,
222 code.co_names, code.co_varnames, filename,
223 location, code.co_firstlineno,
224 code.co_lnotab, (), ())
225 except:
226 pass
227
228 # execute the code and catch the new traceback
Armin Ronacherba3757b2008-04-16 19:43:16 +0200229 try:
230 exec code in globals, locals
231 except:
232 exc_info = sys.exc_info()
Armin Ronacher32a910f2008-04-26 23:21:03 +0200233 new_tb = exc_info[2].tb_next
Armin Ronacherba3757b2008-04-16 19:43:16 +0200234
Armin Ronacher6cc8dd02008-04-16 23:15:15 +0200235 # return without this frame
Armin Ronacher32a910f2008-04-26 23:21:03 +0200236 return exc_info[:2] + (new_tb,)
Armin Ronacherba3757b2008-04-16 19:43:16 +0200237
238
Armin Ronacherba3757b2008-04-16 19:43:16 +0200239def _init_ugly_crap():
240 """This function implements a few ugly things so that we can patch the
241 traceback objects. The function returned allows resetting `tb_next` on
242 any python traceback object.
243 """
244 import ctypes
245 from types import TracebackType
246
247 # figure out side of _Py_ssize_t
248 if hasattr(ctypes.pythonapi, 'Py_InitModule4_64'):
249 _Py_ssize_t = ctypes.c_int64
250 else:
251 _Py_ssize_t = ctypes.c_int
252
253 # regular python
254 class _PyObject(ctypes.Structure):
255 pass
256 _PyObject._fields_ = [
257 ('ob_refcnt', _Py_ssize_t),
258 ('ob_type', ctypes.POINTER(_PyObject))
259 ]
260
261 # python with trace
262 if object.__basicsize__ != ctypes.sizeof(_PyObject):
263 class _PyObject(ctypes.Structure):
264 pass
265 _PyObject._fields_ = [
266 ('_ob_next', ctypes.POINTER(_PyObject)),
267 ('_ob_prev', ctypes.POINTER(_PyObject)),
268 ('ob_refcnt', _Py_ssize_t),
269 ('ob_type', ctypes.POINTER(_PyObject))
270 ]
271
272 class _Traceback(_PyObject):
273 pass
274 _Traceback._fields_ = [
275 ('tb_next', ctypes.POINTER(_Traceback)),
276 ('tb_frame', ctypes.POINTER(_PyObject)),
277 ('tb_lasti', ctypes.c_int),
278 ('tb_lineno', ctypes.c_int)
279 ]
280
281 def tb_set_next(tb, next):
Armin Ronacher8e8d0712008-04-16 23:10:49 +0200282 """Set the tb_next attribute of a traceback object."""
Armin Ronacherba3757b2008-04-16 19:43:16 +0200283 if not (isinstance(tb, TracebackType) and
Armin Ronacher8e8d0712008-04-16 23:10:49 +0200284 (next is None or isinstance(next, TracebackType))):
Armin Ronacherba3757b2008-04-16 19:43:16 +0200285 raise TypeError('tb_set_next arguments must be traceback objects')
286 obj = _Traceback.from_address(id(tb))
Armin Ronacher8e8d0712008-04-16 23:10:49 +0200287 if tb.tb_next is not None:
288 old = _Traceback.from_address(id(tb.tb_next))
289 old.ob_refcnt -= 1
290 if next is None:
291 obj.tb_next = ctypes.POINTER(_Traceback)()
292 else:
293 next = _Traceback.from_address(id(next))
294 next.ob_refcnt += 1
295 obj.tb_next = ctypes.pointer(next)
Armin Ronacherba3757b2008-04-16 19:43:16 +0200296
297 return tb_set_next
298
299
Armin Ronacherbd33f112008-04-18 09:17:32 +0200300# try to get a tb_set_next implementation
Armin Ronacherba3757b2008-04-16 19:43:16 +0200301try:
Armin Ronacherbd33f112008-04-18 09:17:32 +0200302 from jinja2._speedups import tb_set_next
303except ImportError:
304 try:
305 tb_set_next = _init_ugly_crap()
306 except:
307 tb_set_next = None
Armin Ronacherba3757b2008-04-16 19:43:16 +0200308del _init_ugly_crap