blob: aa78ef0f194dfa8ca4f4fbf1e9608aa802464120 [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 Ronacher62ccd1b2009-01-04 14:26:19 +010010 :copyright: (c) 2009 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
19class TracebackFrameProxy(object):
20 """Proxies a traceback frame."""
21
22 def __init__(self, tb):
23 self.tb = tb
24
25 def _set_tb_next(self, next):
26 if tb_set_next is not None:
27 tb_set_next(self.tb, next and next.tb or None)
28 self._tb_next = next
29
30 def _get_tb_next(self):
31 return self._tb_next
32
33 tb_next = property(_get_tb_next, _set_tb_next)
34 del _get_tb_next, _set_tb_next
35
36 @property
37 def is_jinja_frame(self):
38 return '__jinja_template__' in self.tb.tb_frame.f_globals
39
40 def __getattr__(self, name):
41 return getattr(self.tb, name)
42
43
44class ProcessedTraceback(object):
45 """Holds a Jinja preprocessed traceback for priting or reraising."""
46
47 def __init__(self, exc_type, exc_value, frames):
48 assert frames, 'no frames for this traceback?'
49 self.exc_type = exc_type
50 self.exc_value = exc_value
51 self.frames = frames
52
53 def chain_frames(self):
54 """Chains the frames. Requires ctypes or the speedups extension."""
55 prev_tb = None
56 for tb in self.frames:
57 if prev_tb is not None:
58 prev_tb.tb_next = tb
59 prev_tb = tb
60 prev_tb.tb_next = None
61
62 def render_as_text(self, limit=None):
63 """Return a string with the traceback."""
64 lines = traceback.format_exception(self.exc_type, self.exc_value,
65 self.frames[0], limit=limit)
66 return ''.join(lines).rstrip()
67
Armin Ronacher32ed6c92009-04-02 14:04:41 +020068 def render_as_html(self, full=False):
69 """Return a unicode string with the traceback as rendered HTML."""
70 from jinja2.debugrenderer import render_traceback
71 return u'%s\n\n<!--\n%s\n-->' % (
72 render_traceback(self, full=full),
73 self.render_as_text().decode('utf-8', 'replace')
74 )
75
Armin Ronachera18872d2009-03-05 23:47:00 +010076 @property
77 def is_template_syntax_error(self):
78 """`True` if this is a template syntax error."""
79 return isinstance(self.exc_value, TemplateSyntaxError)
80
81 @property
82 def exc_info(self):
83 """Exception info tuple with a proxy around the frame objects."""
84 return self.exc_type, self.exc_value, self.frames[0]
85
86 @property
87 def standard_exc_info(self):
88 """Standard python exc_info for re-raising"""
89 return self.exc_type, self.exc_value, self.frames[0].tb
90
91
92def make_traceback(exc_info, source_hint=None):
93 """Creates a processed traceback object from the exc_info."""
94 exc_type, exc_value, tb = exc_info
95 if isinstance(exc_value, TemplateSyntaxError):
96 exc_info = translate_syntax_error(exc_value, source_hint)
Armin Ronacher2a791922009-04-16 23:15:22 +020097 initial_skip = 0
98 else:
99 initial_skip = 1
100 return translate_exception(exc_info, initial_skip)
Armin Ronacherd416a972009-02-24 22:58:00 +0100101
102
103def translate_syntax_error(error, source=None):
104 """Rewrites a syntax error to please traceback systems."""
105 error.source = source
106 error.translated = True
107 exc_info = (type(error), error, None)
108 filename = error.filename
109 if filename is None:
110 filename = '<unknown>'
111 return fake_exc_info(exc_info, filename, error.lineno)
Armin Ronacherba3757b2008-04-16 19:43:16 +0200112
113
Armin Ronacher2a791922009-04-16 23:15:22 +0200114def translate_exception(exc_info, initial_skip=0):
Armin Ronacher8e8d0712008-04-16 23:10:49 +0200115 """If passed an exc_info it will automatically rewrite the exceptions
116 all the way down to the correct line numbers and frames.
117 """
Armin Ronacher2a791922009-04-16 23:15:22 +0200118 tb = exc_info[2]
Armin Ronachera18872d2009-03-05 23:47:00 +0100119 frames = []
Armin Ronacher8e8d0712008-04-16 23:10:49 +0200120
Armin Ronacher2a791922009-04-16 23:15:22 +0200121 # skip some internal frames if wanted
122 for x in xrange(initial_skip):
123 if tb is not None:
124 tb = tb.tb_next
125 initial_tb = tb
126
Armin Ronacher8e8d0712008-04-16 23:10:49 +0200127 while tb is not None:
Armin Ronacherd416a972009-02-24 22:58:00 +0100128 # skip frames decorated with @internalcode. These are internal
129 # calls we can't avoid and that are useless in template debugging
130 # output.
Armin Ronachera18872d2009-03-05 23:47:00 +0100131 if tb.tb_frame.f_code in internal_code:
Armin Ronacherd416a972009-02-24 22:58:00 +0100132 tb = tb.tb_next
133 continue
134
Armin Ronachera18872d2009-03-05 23:47:00 +0100135 # save a reference to the next frame if we override the current
136 # one with a faked one.
137 next = tb.tb_next
138
139 # fake template exceptions
Armin Ronacher8e8d0712008-04-16 23:10:49 +0200140 template = tb.tb_frame.f_globals.get('__jinja_template__')
141 if template is not None:
142 lineno = template.get_corresponding_lineno(tb.tb_lineno)
143 tb = fake_exc_info(exc_info[:2] + (tb,), template.filename,
Armin Ronachera18872d2009-03-05 23:47:00 +0100144 lineno)[2]
Armin Ronacher8e8d0712008-04-16 23:10:49 +0200145
Armin Ronachera18872d2009-03-05 23:47:00 +0100146 frames.append(TracebackFrameProxy(tb))
147 tb = next
148
149 # if we don't have any exceptions in the frames left, we have to
150 # reraise it unchanged.
151 # XXX: can we backup here? when could this happen?
152 if not frames:
153 raise exc_info[0], exc_info[1], exc_info[2]
154
155 traceback = ProcessedTraceback(exc_info[0], exc_info[1], frames)
156 if tb_set_next is not None:
157 traceback.chain_frames()
158 return traceback
Armin Ronacherba3757b2008-04-16 19:43:16 +0200159
160
Armin Ronachera18872d2009-03-05 23:47:00 +0100161def fake_exc_info(exc_info, filename, lineno):
Armin Ronacher8e8d0712008-04-16 23:10:49 +0200162 """Helper for `translate_exception`."""
Armin Ronacherba3757b2008-04-16 19:43:16 +0200163 exc_type, exc_value, tb = exc_info
164
165 # figure the real context out
Armin Ronacherd416a972009-02-24 22:58:00 +0100166 if tb is not None:
167 real_locals = tb.tb_frame.f_locals.copy()
168 ctx = real_locals.get('context')
169 if ctx:
170 locals = ctx.get_all()
171 else:
172 locals = {}
173 for name, value in real_locals.iteritems():
174 if name.startswith('l_') and value is not missing:
175 locals[name[2:]] = value
176
177 # if there is a local called __jinja_exception__, we get
178 # rid of it to not break the debug functionality.
179 locals.pop('__jinja_exception__', None)
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200180 else:
181 locals = {}
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200182
Armin Ronacherba3757b2008-04-16 19:43:16 +0200183 # assamble fake globals we need
184 globals = {
185 '__name__': filename,
186 '__file__': filename,
Armin Ronacher94ee6aa2009-04-17 11:21:00 +0200187 '__jinja_exception__': exc_info[:2],
188
189 # we don't want to keep the reference to the template around
190 # to not cause circular dependencies, but we mark it as Jinja
191 # frame for the ProcessedTraceback
192 '__jinja_template__': None
Armin Ronacherba3757b2008-04-16 19:43:16 +0200193 }
194
195 # and fake the exception
196 code = compile('\n' * (lineno - 1) + 'raise __jinja_exception__[0], ' +
197 '__jinja_exception__[1]', filename, 'exec')
Armin Ronacher32a910f2008-04-26 23:21:03 +0200198
199 # if it's possible, change the name of the code. This won't work
200 # on some python environments such as google appengine
201 try:
Armin Ronacherd416a972009-02-24 22:58:00 +0100202 if tb is None:
Armin Ronacher32a910f2008-04-26 23:21:03 +0200203 location = 'template'
Armin Ronacherd416a972009-02-24 22:58:00 +0100204 else:
205 function = tb.tb_frame.f_code.co_name
206 if function == 'root':
207 location = 'top-level template code'
208 elif function.startswith('block_'):
209 location = 'block "%s"' % function[6:]
210 else:
211 location = 'template'
Armin Ronacher32a910f2008-04-26 23:21:03 +0200212 code = CodeType(0, code.co_nlocals, code.co_stacksize,
213 code.co_flags, code.co_code, code.co_consts,
214 code.co_names, code.co_varnames, filename,
215 location, code.co_firstlineno,
216 code.co_lnotab, (), ())
217 except:
218 pass
219
220 # execute the code and catch the new traceback
Armin Ronacherba3757b2008-04-16 19:43:16 +0200221 try:
222 exec code in globals, locals
223 except:
224 exc_info = sys.exc_info()
Armin Ronacher32a910f2008-04-26 23:21:03 +0200225 new_tb = exc_info[2].tb_next
Armin Ronacherba3757b2008-04-16 19:43:16 +0200226
Armin Ronacher6cc8dd02008-04-16 23:15:15 +0200227 # return without this frame
Armin Ronacher32a910f2008-04-26 23:21:03 +0200228 return exc_info[:2] + (new_tb,)
Armin Ronacherba3757b2008-04-16 19:43:16 +0200229
230
Armin Ronacherba3757b2008-04-16 19:43:16 +0200231def _init_ugly_crap():
232 """This function implements a few ugly things so that we can patch the
233 traceback objects. The function returned allows resetting `tb_next` on
234 any python traceback object.
235 """
236 import ctypes
237 from types import TracebackType
238
239 # figure out side of _Py_ssize_t
240 if hasattr(ctypes.pythonapi, 'Py_InitModule4_64'):
241 _Py_ssize_t = ctypes.c_int64
242 else:
243 _Py_ssize_t = ctypes.c_int
244
245 # regular python
246 class _PyObject(ctypes.Structure):
247 pass
248 _PyObject._fields_ = [
249 ('ob_refcnt', _Py_ssize_t),
250 ('ob_type', ctypes.POINTER(_PyObject))
251 ]
252
253 # python with trace
254 if object.__basicsize__ != ctypes.sizeof(_PyObject):
255 class _PyObject(ctypes.Structure):
256 pass
257 _PyObject._fields_ = [
258 ('_ob_next', ctypes.POINTER(_PyObject)),
259 ('_ob_prev', ctypes.POINTER(_PyObject)),
260 ('ob_refcnt', _Py_ssize_t),
261 ('ob_type', ctypes.POINTER(_PyObject))
262 ]
263
264 class _Traceback(_PyObject):
265 pass
266 _Traceback._fields_ = [
267 ('tb_next', ctypes.POINTER(_Traceback)),
268 ('tb_frame', ctypes.POINTER(_PyObject)),
269 ('tb_lasti', ctypes.c_int),
270 ('tb_lineno', ctypes.c_int)
271 ]
272
273 def tb_set_next(tb, next):
Armin Ronacher8e8d0712008-04-16 23:10:49 +0200274 """Set the tb_next attribute of a traceback object."""
Armin Ronacherba3757b2008-04-16 19:43:16 +0200275 if not (isinstance(tb, TracebackType) and
Armin Ronacher8e8d0712008-04-16 23:10:49 +0200276 (next is None or isinstance(next, TracebackType))):
Armin Ronacherba3757b2008-04-16 19:43:16 +0200277 raise TypeError('tb_set_next arguments must be traceback objects')
278 obj = _Traceback.from_address(id(tb))
Armin Ronacher8e8d0712008-04-16 23:10:49 +0200279 if tb.tb_next is not None:
280 old = _Traceback.from_address(id(tb.tb_next))
281 old.ob_refcnt -= 1
282 if next is None:
283 obj.tb_next = ctypes.POINTER(_Traceback)()
284 else:
285 next = _Traceback.from_address(id(next))
286 next.ob_refcnt += 1
287 obj.tb_next = ctypes.pointer(next)
Armin Ronacherba3757b2008-04-16 19:43:16 +0200288
289 return tb_set_next
290
291
Armin Ronacherbd33f112008-04-18 09:17:32 +0200292# try to get a tb_set_next implementation
Armin Ronacherba3757b2008-04-16 19:43:16 +0200293try:
Armin Ronacherbd33f112008-04-18 09:17:32 +0200294 from jinja2._speedups import tb_set_next
295except ImportError:
296 try:
297 tb_set_next = _init_ugly_crap()
298 except:
299 tb_set_next = None
Armin Ronacherba3757b2008-04-16 19:43:16 +0200300del _init_ugly_crap