blob: 815cc18a4f8fd76e747835f24c1297a7d2264628 [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 Ronacher40c593e2010-11-29 10:50:34 +010015from types import TracebackType
Armin Ronachere9098672013-05-19 14:16:13 +010016from jinja2.utils import missing, internal_code
Armin Ronachera18872d2009-03-05 23:47:00 +010017from jinja2.exceptions import TemplateSyntaxError
Armin Ronachere9098672013-05-19 14:16:13 +010018from jinja2._compat import iteritems, reraise, code_type
Armin Ronachera18872d2009-03-05 23:47:00 +010019
Armin Ronacher40c593e2010-11-29 10:50:34 +010020# on pypy we can take advantage of transparent proxies
21try:
22 from __pypy__ import tproxy
23except ImportError:
24 tproxy = None
25
Armin Ronachera18872d2009-03-05 23:47:00 +010026
Armin Ronacherbd357722009-08-05 20:25:06 +020027# how does the raise helper look like?
28try:
Thomas Waldmanne0003552013-05-17 23:52:14 +020029 exec("raise TypeError, 'foo'")
Armin Ronacherbd357722009-08-05 20:25:06 +020030except SyntaxError:
31 raise_helper = 'raise __jinja_exception__[1]'
32except TypeError:
33 raise_helper = 'raise __jinja_exception__[0], __jinja_exception__[1]'
34
35
Armin Ronachera18872d2009-03-05 23:47:00 +010036class TracebackFrameProxy(object):
37 """Proxies a traceback frame."""
38
39 def __init__(self, tb):
40 self.tb = tb
Armin Ronacher40c593e2010-11-29 10:50:34 +010041 self._tb_next = None
Armin Ronachera18872d2009-03-05 23:47:00 +010042
Armin Ronacher9e5d0832010-11-29 12:16:17 +010043 @property
44 def tb_next(self):
45 return self._tb_next
46
47 def set_next(self, next):
Armin Ronachera18872d2009-03-05 23:47:00 +010048 if tb_set_next is not None:
Armin Ronacher75ffeb62011-04-18 16:27:14 +020049 try:
50 tb_set_next(self.tb, next and next.tb or None)
51 except Exception:
52 # this function can fail due to all the hackery it does
53 # on various python implementations. We just catch errors
54 # down and ignore them if necessary.
55 pass
Armin Ronachera18872d2009-03-05 23:47:00 +010056 self._tb_next = next
57
Armin Ronachera18872d2009-03-05 23:47:00 +010058 @property
59 def is_jinja_frame(self):
60 return '__jinja_template__' in self.tb.tb_frame.f_globals
61
62 def __getattr__(self, name):
63 return getattr(self.tb, name)
64
65
Armin Ronacher40c593e2010-11-29 10:50:34 +010066def make_frame_proxy(frame):
67 proxy = TracebackFrameProxy(frame)
68 if tproxy is None:
69 return proxy
70 def operation_handler(operation, *args, **kwargs):
Armin Ronacher7ae54822010-11-29 12:24:03 +010071 if operation in ('__getattribute__', '__getattr__'):
72 return getattr(proxy, args[0])
73 elif operation == '__setattr__':
74 proxy.__setattr__(*args, **kwargs)
75 else:
76 return getattr(proxy, operation)(*args, **kwargs)
Armin Ronacher40c593e2010-11-29 10:50:34 +010077 return tproxy(TracebackType, operation_handler)
78
79
Armin Ronachera18872d2009-03-05 23:47:00 +010080class ProcessedTraceback(object):
Armin Ronacher345443f2011-09-26 17:22:22 +020081 """Holds a Jinja preprocessed traceback for printing or reraising."""
Armin Ronachera18872d2009-03-05 23:47:00 +010082
83 def __init__(self, exc_type, exc_value, frames):
84 assert frames, 'no frames for this traceback?'
85 self.exc_type = exc_type
86 self.exc_value = exc_value
87 self.frames = frames
88
Armin Ronacher40c593e2010-11-29 10:50:34 +010089 # newly concatenate the frames (which are proxies)
Armin Ronachera18872d2009-03-05 23:47:00 +010090 prev_tb = None
91 for tb in self.frames:
92 if prev_tb is not None:
Armin Ronacher9e5d0832010-11-29 12:16:17 +010093 prev_tb.set_next(tb)
Armin Ronachera18872d2009-03-05 23:47:00 +010094 prev_tb = tb
Armin Ronacher9e5d0832010-11-29 12:16:17 +010095 prev_tb.set_next(None)
Armin Ronachera18872d2009-03-05 23:47:00 +010096
97 def render_as_text(self, limit=None):
98 """Return a string with the traceback."""
99 lines = traceback.format_exception(self.exc_type, self.exc_value,
100 self.frames[0], limit=limit)
101 return ''.join(lines).rstrip()
102
Armin Ronacher32ed6c92009-04-02 14:04:41 +0200103 def render_as_html(self, full=False):
104 """Return a unicode string with the traceback as rendered HTML."""
105 from jinja2.debugrenderer import render_traceback
106 return u'%s\n\n<!--\n%s\n-->' % (
107 render_traceback(self, full=full),
108 self.render_as_text().decode('utf-8', 'replace')
109 )
110
Armin Ronachera18872d2009-03-05 23:47:00 +0100111 @property
112 def is_template_syntax_error(self):
113 """`True` if this is a template syntax error."""
114 return isinstance(self.exc_value, TemplateSyntaxError)
115
116 @property
117 def exc_info(self):
118 """Exception info tuple with a proxy around the frame objects."""
119 return self.exc_type, self.exc_value, self.frames[0]
120
121 @property
122 def standard_exc_info(self):
123 """Standard python exc_info for re-raising"""
Armin Ronacher40c593e2010-11-29 10:50:34 +0100124 tb = self.frames[0]
125 # the frame will be an actual traceback (or transparent proxy) if
126 # we are on pypy or a python implementation with support for tproxy
127 if type(tb) is not TracebackType:
128 tb = tb.tb
129 return self.exc_type, self.exc_value, tb
Armin Ronachera18872d2009-03-05 23:47:00 +0100130
131
132def make_traceback(exc_info, source_hint=None):
133 """Creates a processed traceback object from the exc_info."""
134 exc_type, exc_value, tb = exc_info
135 if isinstance(exc_value, TemplateSyntaxError):
136 exc_info = translate_syntax_error(exc_value, source_hint)
Armin Ronacher2a791922009-04-16 23:15:22 +0200137 initial_skip = 0
138 else:
139 initial_skip = 1
140 return translate_exception(exc_info, initial_skip)
Armin Ronacherd416a972009-02-24 22:58:00 +0100141
142
143def translate_syntax_error(error, source=None):
144 """Rewrites a syntax error to please traceback systems."""
145 error.source = source
146 error.translated = True
Armin Ronacher821a4232010-02-17 07:59:38 +0100147 exc_info = (error.__class__, error, None)
Armin Ronacherd416a972009-02-24 22:58:00 +0100148 filename = error.filename
149 if filename is None:
150 filename = '<unknown>'
151 return fake_exc_info(exc_info, filename, error.lineno)
Armin Ronacherba3757b2008-04-16 19:43:16 +0200152
153
Armin Ronacher2a791922009-04-16 23:15:22 +0200154def translate_exception(exc_info, initial_skip=0):
Armin Ronacher8e8d0712008-04-16 23:10:49 +0200155 """If passed an exc_info it will automatically rewrite the exceptions
156 all the way down to the correct line numbers and frames.
157 """
Armin Ronacher2a791922009-04-16 23:15:22 +0200158 tb = exc_info[2]
Armin Ronachera18872d2009-03-05 23:47:00 +0100159 frames = []
Armin Ronacher8e8d0712008-04-16 23:10:49 +0200160
Armin Ronacher2a791922009-04-16 23:15:22 +0200161 # skip some internal frames if wanted
Thomas Waldmanne0003552013-05-17 23:52:14 +0200162 for x in range(initial_skip):
Armin Ronacher2a791922009-04-16 23:15:22 +0200163 if tb is not None:
164 tb = tb.tb_next
165 initial_tb = tb
166
Armin Ronacher8e8d0712008-04-16 23:10:49 +0200167 while tb is not None:
Armin Ronacherd416a972009-02-24 22:58:00 +0100168 # skip frames decorated with @internalcode. These are internal
169 # calls we can't avoid and that are useless in template debugging
170 # output.
Armin Ronachera18872d2009-03-05 23:47:00 +0100171 if tb.tb_frame.f_code in internal_code:
Armin Ronacherd416a972009-02-24 22:58:00 +0100172 tb = tb.tb_next
173 continue
174
Armin Ronachera18872d2009-03-05 23:47:00 +0100175 # save a reference to the next frame if we override the current
176 # one with a faked one.
177 next = tb.tb_next
178
179 # fake template exceptions
Armin Ronacher8e8d0712008-04-16 23:10:49 +0200180 template = tb.tb_frame.f_globals.get('__jinja_template__')
181 if template is not None:
182 lineno = template.get_corresponding_lineno(tb.tb_lineno)
183 tb = fake_exc_info(exc_info[:2] + (tb,), template.filename,
Armin Ronachera18872d2009-03-05 23:47:00 +0100184 lineno)[2]
Armin Ronacher8e8d0712008-04-16 23:10:49 +0200185
Armin Ronacher40c593e2010-11-29 10:50:34 +0100186 frames.append(make_frame_proxy(tb))
Armin Ronachera18872d2009-03-05 23:47:00 +0100187 tb = next
188
189 # if we don't have any exceptions in the frames left, we have to
190 # reraise it unchanged.
191 # XXX: can we backup here? when could this happen?
192 if not frames:
Armin Ronachere9098672013-05-19 14:16:13 +0100193 reraise(exc_info[0], exc_info[1], exc_info[2])
Armin Ronachera18872d2009-03-05 23:47:00 +0100194
Armin Ronacher40c593e2010-11-29 10:50:34 +0100195 return ProcessedTraceback(exc_info[0], exc_info[1], frames)
Armin Ronacherba3757b2008-04-16 19:43:16 +0200196
197
Armin Ronachera18872d2009-03-05 23:47:00 +0100198def fake_exc_info(exc_info, filename, lineno):
Armin Ronacher8e8d0712008-04-16 23:10:49 +0200199 """Helper for `translate_exception`."""
Armin Ronacherba3757b2008-04-16 19:43:16 +0200200 exc_type, exc_value, tb = exc_info
201
202 # figure the real context out
Armin Ronacherd416a972009-02-24 22:58:00 +0100203 if tb is not None:
204 real_locals = tb.tb_frame.f_locals.copy()
205 ctx = real_locals.get('context')
206 if ctx:
207 locals = ctx.get_all()
208 else:
209 locals = {}
Armin Ronachere9098672013-05-19 14:16:13 +0100210 for name, value in iteritems(real_locals):
Armin Ronacherd416a972009-02-24 22:58:00 +0100211 if name.startswith('l_') and value is not missing:
212 locals[name[2:]] = value
213
214 # if there is a local called __jinja_exception__, we get
215 # rid of it to not break the debug functionality.
216 locals.pop('__jinja_exception__', None)
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200217 else:
218 locals = {}
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200219
Armin Ronacherba3757b2008-04-16 19:43:16 +0200220 # assamble fake globals we need
221 globals = {
222 '__name__': filename,
223 '__file__': filename,
Armin Ronacher94ee6aa2009-04-17 11:21:00 +0200224 '__jinja_exception__': exc_info[:2],
225
226 # we don't want to keep the reference to the template around
227 # to not cause circular dependencies, but we mark it as Jinja
228 # frame for the ProcessedTraceback
229 '__jinja_template__': None
Armin Ronacherba3757b2008-04-16 19:43:16 +0200230 }
231
232 # and fake the exception
Armin Ronacherbd357722009-08-05 20:25:06 +0200233 code = compile('\n' * (lineno - 1) + raise_helper, filename, 'exec')
Armin Ronacher32a910f2008-04-26 23:21:03 +0200234
235 # if it's possible, change the name of the code. This won't work
236 # on some python environments such as google appengine
237 try:
Armin Ronacherd416a972009-02-24 22:58:00 +0100238 if tb is None:
Armin Ronacher32a910f2008-04-26 23:21:03 +0200239 location = 'template'
Armin Ronacherd416a972009-02-24 22:58:00 +0100240 else:
241 function = tb.tb_frame.f_code.co_name
242 if function == 'root':
243 location = 'top-level template code'
244 elif function.startswith('block_'):
245 location = 'block "%s"' % function[6:]
246 else:
247 location = 'template'
Armin Ronachere9098672013-05-19 14:16:13 +0100248 code = code_type(0, code.co_nlocals, code.co_stacksize,
249 code.co_flags, code.co_code, code.co_consts,
250 code.co_names, code.co_varnames, filename,
251 location, code.co_firstlineno,
252 code.co_lnotab, (), ())
Armin Ronacher32a910f2008-04-26 23:21:03 +0200253 except:
254 pass
255
256 # execute the code and catch the new traceback
Armin Ronacherba3757b2008-04-16 19:43:16 +0200257 try:
Thomas Waldmanne0003552013-05-17 23:52:14 +0200258 exec(code, globals, locals)
Armin Ronacherba3757b2008-04-16 19:43:16 +0200259 except:
260 exc_info = sys.exc_info()
Armin Ronacher32a910f2008-04-26 23:21:03 +0200261 new_tb = exc_info[2].tb_next
Armin Ronacherba3757b2008-04-16 19:43:16 +0200262
Armin Ronacher6cc8dd02008-04-16 23:15:15 +0200263 # return without this frame
Armin Ronacher32a910f2008-04-26 23:21:03 +0200264 return exc_info[:2] + (new_tb,)
Armin Ronacherba3757b2008-04-16 19:43:16 +0200265
266
Armin Ronacherba3757b2008-04-16 19:43:16 +0200267def _init_ugly_crap():
268 """This function implements a few ugly things so that we can patch the
269 traceback objects. The function returned allows resetting `tb_next` on
Armin Ronacher40c593e2010-11-29 10:50:34 +0100270 any python traceback object. Do not attempt to use this on non cpython
271 interpreters
Armin Ronacherba3757b2008-04-16 19:43:16 +0200272 """
273 import ctypes
274 from types import TracebackType
275
276 # figure out side of _Py_ssize_t
277 if hasattr(ctypes.pythonapi, 'Py_InitModule4_64'):
278 _Py_ssize_t = ctypes.c_int64
279 else:
280 _Py_ssize_t = ctypes.c_int
281
282 # regular python
283 class _PyObject(ctypes.Structure):
284 pass
285 _PyObject._fields_ = [
286 ('ob_refcnt', _Py_ssize_t),
287 ('ob_type', ctypes.POINTER(_PyObject))
288 ]
289
290 # python with trace
Armin Ronacherda8d68f2010-10-17 16:47:06 +0200291 if hasattr(sys, 'getobjects'):
Armin Ronacherba3757b2008-04-16 19:43:16 +0200292 class _PyObject(ctypes.Structure):
293 pass
294 _PyObject._fields_ = [
295 ('_ob_next', ctypes.POINTER(_PyObject)),
296 ('_ob_prev', ctypes.POINTER(_PyObject)),
297 ('ob_refcnt', _Py_ssize_t),
298 ('ob_type', ctypes.POINTER(_PyObject))
299 ]
300
301 class _Traceback(_PyObject):
302 pass
303 _Traceback._fields_ = [
304 ('tb_next', ctypes.POINTER(_Traceback)),
305 ('tb_frame', ctypes.POINTER(_PyObject)),
306 ('tb_lasti', ctypes.c_int),
307 ('tb_lineno', ctypes.c_int)
308 ]
309
310 def tb_set_next(tb, next):
Armin Ronacher8e8d0712008-04-16 23:10:49 +0200311 """Set the tb_next attribute of a traceback object."""
Armin Ronacherba3757b2008-04-16 19:43:16 +0200312 if not (isinstance(tb, TracebackType) and
Armin Ronacher8e8d0712008-04-16 23:10:49 +0200313 (next is None or isinstance(next, TracebackType))):
Armin Ronacherba3757b2008-04-16 19:43:16 +0200314 raise TypeError('tb_set_next arguments must be traceback objects')
315 obj = _Traceback.from_address(id(tb))
Armin Ronacher8e8d0712008-04-16 23:10:49 +0200316 if tb.tb_next is not None:
317 old = _Traceback.from_address(id(tb.tb_next))
318 old.ob_refcnt -= 1
319 if next is None:
320 obj.tb_next = ctypes.POINTER(_Traceback)()
321 else:
322 next = _Traceback.from_address(id(next))
323 next.ob_refcnt += 1
324 obj.tb_next = ctypes.pointer(next)
Armin Ronacherba3757b2008-04-16 19:43:16 +0200325
326 return tb_set_next
327
328
Armin Ronacher40c593e2010-11-29 10:50:34 +0100329# try to get a tb_set_next implementation if we don't have transparent
330# proxies.
331tb_set_next = None
332if tproxy is None:
Armin Ronacherbd33f112008-04-18 09:17:32 +0200333 try:
Cory Benfield447d3d22013-05-18 10:35:04 +0100334 tb_set_next = _init_ugly_crap()
335 except:
336 pass
Armin Ronacher40c593e2010-11-29 10:50:34 +0100337 del _init_ugly_crap