With number of course. Jinja2.pdf not Jinja.pdf
[jinja2.git] / jinja2 / debug.py
1 # -*- coding: utf-8 -*-
2 """
3     jinja2.debug
4     ~~~~~~~~~~~~
5
6     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.
9
10     :copyright: (c) 2010 by the Jinja Team.
11     :license: BSD, see LICENSE for more details.
12 """
13 import sys
14 import traceback
15 from types import TracebackType
16 from jinja2.utils import CodeType, missing, internal_code
17 from jinja2.exceptions import TemplateSyntaxError
18
19 # on pypy we can take advantage of transparent proxies
20 try:
21     from __pypy__ import tproxy
22 except ImportError:
23     tproxy = None
24
25
26 # how does the raise helper look like?
27 try:
28     exec "raise TypeError, 'foo'"
29 except SyntaxError:
30     raise_helper = 'raise __jinja_exception__[1]'
31 except TypeError:
32     raise_helper = 'raise __jinja_exception__[0], __jinja_exception__[1]'
33
34
35 class TracebackFrameProxy(object):
36     """Proxies a traceback frame."""
37
38     def __init__(self, tb):
39         self.tb = tb
40         self._tb_next = None
41
42     @property
43     def tb_next(self):
44         return self._tb_next
45
46     def set_next(self, next):
47         if tb_set_next is not None:
48             tb_set_next(self.tb, next and next.tb or None)
49         self._tb_next = next
50
51     @property
52     def is_jinja_frame(self):
53         return '__jinja_template__' in self.tb.tb_frame.f_globals
54
55     def __getattr__(self, name):
56         return getattr(self.tb, name)
57
58
59 def make_frame_proxy(frame):
60     proxy = TracebackFrameProxy(frame)
61     if tproxy is None:
62         return proxy
63     def operation_handler(operation, *args, **kwargs):
64         if operation in ('__getattribute__', '__getattr__'):
65             return getattr(proxy, args[0])
66         elif operation == '__setattr__':
67             proxy.__setattr__(*args, **kwargs)
68         else:
69             return getattr(proxy, operation)(*args, **kwargs)
70     return tproxy(TracebackType, operation_handler)
71
72
73 class ProcessedTraceback(object):
74     """Holds a Jinja preprocessed traceback for priting or reraising."""
75
76     def __init__(self, exc_type, exc_value, frames):
77         assert frames, 'no frames for this traceback?'
78         self.exc_type = exc_type
79         self.exc_value = exc_value
80         self.frames = frames
81
82         # newly concatenate the frames (which are proxies)
83         prev_tb = None
84         for tb in self.frames:
85             if prev_tb is not None:
86                 prev_tb.set_next(tb)
87             prev_tb = tb
88         prev_tb.set_next(None)
89
90     def render_as_text(self, limit=None):
91         """Return a string with the traceback."""
92         lines = traceback.format_exception(self.exc_type, self.exc_value,
93                                            self.frames[0], limit=limit)
94         return ''.join(lines).rstrip()
95
96     def render_as_html(self, full=False):
97         """Return a unicode string with the traceback as rendered HTML."""
98         from jinja2.debugrenderer import render_traceback
99         return u'%s\n\n<!--\n%s\n-->' % (
100             render_traceback(self, full=full),
101             self.render_as_text().decode('utf-8', 'replace')
102         )
103
104     @property
105     def is_template_syntax_error(self):
106         """`True` if this is a template syntax error."""
107         return isinstance(self.exc_value, TemplateSyntaxError)
108
109     @property
110     def exc_info(self):
111         """Exception info tuple with a proxy around the frame objects."""
112         return self.exc_type, self.exc_value, self.frames[0]
113
114     @property
115     def standard_exc_info(self):
116         """Standard python exc_info for re-raising"""
117         tb = self.frames[0]
118         # the frame will be an actual traceback (or transparent proxy) if
119         # we are on pypy or a python implementation with support for tproxy
120         if type(tb) is not TracebackType:
121             tb = tb.tb
122         return self.exc_type, self.exc_value, tb
123
124
125 def make_traceback(exc_info, source_hint=None):
126     """Creates a processed traceback object from the exc_info."""
127     exc_type, exc_value, tb = exc_info
128     if isinstance(exc_value, TemplateSyntaxError):
129         exc_info = translate_syntax_error(exc_value, source_hint)
130         initial_skip = 0
131     else:
132         initial_skip = 1
133     return translate_exception(exc_info, initial_skip)
134
135
136 def translate_syntax_error(error, source=None):
137     """Rewrites a syntax error to please traceback systems."""
138     error.source = source
139     error.translated = True
140     exc_info = (error.__class__, error, None)
141     filename = error.filename
142     if filename is None:
143         filename = '<unknown>'
144     return fake_exc_info(exc_info, filename, error.lineno)
145
146
147 def translate_exception(exc_info, initial_skip=0):
148     """If passed an exc_info it will automatically rewrite the exceptions
149     all the way down to the correct line numbers and frames.
150     """
151     tb = exc_info[2]
152     frames = []
153
154     # skip some internal frames if wanted
155     for x in xrange(initial_skip):
156         if tb is not None:
157             tb = tb.tb_next
158     initial_tb = tb
159
160     while tb is not None:
161         # skip frames decorated with @internalcode.  These are internal
162         # calls we can't avoid and that are useless in template debugging
163         # output.
164         if tb.tb_frame.f_code in internal_code:
165             tb = tb.tb_next
166             continue
167
168         # save a reference to the next frame if we override the current
169         # one with a faked one.
170         next = tb.tb_next
171
172         # fake template exceptions
173         template = tb.tb_frame.f_globals.get('__jinja_template__')
174         if template is not None:
175             lineno = template.get_corresponding_lineno(tb.tb_lineno)
176             tb = fake_exc_info(exc_info[:2] + (tb,), template.filename,
177                                lineno)[2]
178
179         frames.append(make_frame_proxy(tb))
180         tb = next
181
182     # if we don't have any exceptions in the frames left, we have to
183     # reraise it unchanged.
184     # XXX: can we backup here?  when could this happen?
185     if not frames:
186         raise exc_info[0], exc_info[1], exc_info[2]
187
188     return ProcessedTraceback(exc_info[0], exc_info[1], frames)
189
190
191 def fake_exc_info(exc_info, filename, lineno):
192     """Helper for `translate_exception`."""
193     exc_type, exc_value, tb = exc_info
194
195     # figure the real context out
196     if tb is not None:
197         real_locals = tb.tb_frame.f_locals.copy()
198         ctx = real_locals.get('context')
199         if ctx:
200             locals = ctx.get_all()
201         else:
202             locals = {}
203         for name, value in real_locals.iteritems():
204             if name.startswith('l_') and value is not missing:
205                 locals[name[2:]] = value
206
207         # if there is a local called __jinja_exception__, we get
208         # rid of it to not break the debug functionality.
209         locals.pop('__jinja_exception__', None)
210     else:
211         locals = {}
212
213     # assamble fake globals we need
214     globals = {
215         '__name__':             filename,
216         '__file__':             filename,
217         '__jinja_exception__':  exc_info[:2],
218
219         # we don't want to keep the reference to the template around
220         # to not cause circular dependencies, but we mark it as Jinja
221         # frame for the ProcessedTraceback
222         '__jinja_template__':   None
223     }
224
225     # and fake the exception
226     code = compile('\n' * (lineno - 1) + raise_helper, filename, 'exec')
227
228     # if it's possible, change the name of the code.  This won't work
229     # on some python environments such as google appengine
230     try:
231         if tb is None:
232             location = 'template'
233         else:
234             function = tb.tb_frame.f_code.co_name
235             if function == 'root':
236                 location = 'top-level template code'
237             elif function.startswith('block_'):
238                 location = 'block "%s"' % function[6:]
239             else:
240                 location = 'template'
241         code = CodeType(0, code.co_nlocals, code.co_stacksize,
242                         code.co_flags, code.co_code, code.co_consts,
243                         code.co_names, code.co_varnames, filename,
244                         location, code.co_firstlineno,
245                         code.co_lnotab, (), ())
246     except:
247         pass
248
249     # execute the code and catch the new traceback
250     try:
251         exec code in globals, locals
252     except:
253         exc_info = sys.exc_info()
254         new_tb = exc_info[2].tb_next
255
256     # return without this frame
257     return exc_info[:2] + (new_tb,)
258
259
260 def _init_ugly_crap():
261     """This function implements a few ugly things so that we can patch the
262     traceback objects.  The function returned allows resetting `tb_next` on
263     any python traceback object.  Do not attempt to use this on non cpython
264     interpreters
265     """
266     import ctypes
267     from types import TracebackType
268
269     # figure out side of _Py_ssize_t
270     if hasattr(ctypes.pythonapi, 'Py_InitModule4_64'):
271         _Py_ssize_t = ctypes.c_int64
272     else:
273         _Py_ssize_t = ctypes.c_int
274
275     # regular python
276     class _PyObject(ctypes.Structure):
277         pass
278     _PyObject._fields_ = [
279         ('ob_refcnt', _Py_ssize_t),
280         ('ob_type', ctypes.POINTER(_PyObject))
281     ]
282
283     # python with trace
284     if hasattr(sys, 'getobjects'):
285         class _PyObject(ctypes.Structure):
286             pass
287         _PyObject._fields_ = [
288             ('_ob_next', ctypes.POINTER(_PyObject)),
289             ('_ob_prev', ctypes.POINTER(_PyObject)),
290             ('ob_refcnt', _Py_ssize_t),
291             ('ob_type', ctypes.POINTER(_PyObject))
292         ]
293
294     class _Traceback(_PyObject):
295         pass
296     _Traceback._fields_ = [
297         ('tb_next', ctypes.POINTER(_Traceback)),
298         ('tb_frame', ctypes.POINTER(_PyObject)),
299         ('tb_lasti', ctypes.c_int),
300         ('tb_lineno', ctypes.c_int)
301     ]
302
303     def tb_set_next(tb, next):
304         """Set the tb_next attribute of a traceback object."""
305         if not (isinstance(tb, TracebackType) and
306                 (next is None or isinstance(next, TracebackType))):
307             raise TypeError('tb_set_next arguments must be traceback objects')
308         obj = _Traceback.from_address(id(tb))
309         if tb.tb_next is not None:
310             old = _Traceback.from_address(id(tb.tb_next))
311             old.ob_refcnt -= 1
312         if next is None:
313             obj.tb_next = ctypes.POINTER(_Traceback)()
314         else:
315             next = _Traceback.from_address(id(next))
316             next.ob_refcnt += 1
317             obj.tb_next = ctypes.pointer(next)
318
319     return tb_set_next
320
321
322 # try to get a tb_set_next implementation if we don't have transparent
323 # proxies.
324 tb_set_next = None
325 if tproxy is None:
326     try:
327         from jinja2._debugsupport import tb_set_next
328     except ImportError:
329         try:
330             tb_set_next = _init_ugly_crap()
331         except:
332             pass
333     del _init_ugly_crap