blob: a9b4439f31b38b04ce90eb4b1d2702e262571b3b [file] [log] [blame]
Armin Ronacherba3757b2008-04-16 19:43:16 +02001# -*- coding: utf-8 -*-
2"""
3 jinja2.debug
4 ~~~~~~~~~~~~
5
6 Implements the debug interface for Jinja.
7
8 :copyright: Copyright 2008 by Armin Ronacher.
9 :license: BSD.
10"""
Armin Ronacherba3757b2008-04-16 19:43:16 +020011import sys
Armin Ronacher32a910f2008-04-26 23:21:03 +020012from types import CodeType
Armin Ronacherba3757b2008-04-16 19:43:16 +020013
14
Armin Ronacher8e8d0712008-04-16 23:10:49 +020015def translate_exception(exc_info):
16 """If passed an exc_info it will automatically rewrite the exceptions
17 all the way down to the correct line numbers and frames.
18 """
19 result_tb = prev_tb = None
Armin Ronacher6cc8dd02008-04-16 23:15:15 +020020 initial_tb = tb = exc_info[2].tb_next
Armin Ronacher8e8d0712008-04-16 23:10:49 +020021
22 while tb is not None:
23 template = tb.tb_frame.f_globals.get('__jinja_template__')
24 if template is not None:
25 lineno = template.get_corresponding_lineno(tb.tb_lineno)
26 tb = fake_exc_info(exc_info[:2] + (tb,), template.filename,
27 lineno, prev_tb)[2]
28 if result_tb is None:
29 result_tb = tb
30 prev_tb = tb
31 tb = tb.tb_next
32
33 return exc_info[:2] + (result_tb or initial_tb,)
Armin Ronacherba3757b2008-04-16 19:43:16 +020034
35
Armin Ronacheraaf010d2008-05-01 13:14:30 +020036def translate_syntax_error(error):
37 """When passed a syntax error it will generate a new traceback with
38 more debugging information.
39 """
40 filename = error.filename
41 if filename is None:
42 filename = '<template>'
43 elif isinstance(filename, unicode):
44 filename = filename.encode('utf-8')
45 code = compile('\n' * (error.lineno - 1) + 'raise __jinja_exception__',
46 filename, 'exec')
47 try:
48 exec code in {'__jinja_exception__': error}
49 except:
50 exc_info = sys.exc_info()
51 return exc_info[:2] + (exc_info[2].tb_next,)
52
53
Armin Ronacherba3757b2008-04-16 19:43:16 +020054def fake_exc_info(exc_info, filename, lineno, tb_back=None):
Armin Ronacher8e8d0712008-04-16 23:10:49 +020055 """Helper for `translate_exception`."""
Armin Ronacherba3757b2008-04-16 19:43:16 +020056 exc_type, exc_value, tb = exc_info
57
58 # figure the real context out
59 real_locals = tb.tb_frame.f_locals.copy()
Armin Ronacher203bfcb2008-04-24 21:54:44 +020060 ctx = real_locals.get('context')
61 if ctx:
62 locals = ctx.get_all()
63 else:
64 locals = {}
Armin Ronacherba3757b2008-04-16 19:43:16 +020065 for name, value in real_locals.iteritems():
66 if name.startswith('l_'):
67 locals[name[2:]] = value
68
Armin Ronacher18c6ca02008-04-17 10:03:29 +020069 # if there is a local called __jinja_exception__, we get
70 # rid of it to not break the debug functionality.
71 locals.pop('__jinja_exception__', None)
72
Armin Ronacherba3757b2008-04-16 19:43:16 +020073 # assamble fake globals we need
74 globals = {
75 '__name__': filename,
76 '__file__': filename,
77 '__jinja_exception__': exc_info[:2]
78 }
79
80 # and fake the exception
81 code = compile('\n' * (lineno - 1) + 'raise __jinja_exception__[0], ' +
82 '__jinja_exception__[1]', filename, 'exec')
Armin Ronacher32a910f2008-04-26 23:21:03 +020083
84 # if it's possible, change the name of the code. This won't work
85 # on some python environments such as google appengine
86 try:
87 function = tb.tb_frame.f_code.co_name
88 if function == 'root':
89 location = 'top-level template code'
90 elif function.startswith('block_'):
91 location = 'block "%s"' % function[6:]
92 else:
93 location = 'template'
94 code = CodeType(0, code.co_nlocals, code.co_stacksize,
95 code.co_flags, code.co_code, code.co_consts,
96 code.co_names, code.co_varnames, filename,
97 location, code.co_firstlineno,
98 code.co_lnotab, (), ())
99 except:
100 pass
101
102 # execute the code and catch the new traceback
Armin Ronacherba3757b2008-04-16 19:43:16 +0200103 try:
104 exec code in globals, locals
105 except:
106 exc_info = sys.exc_info()
Armin Ronacher32a910f2008-04-26 23:21:03 +0200107 new_tb = exc_info[2].tb_next
Armin Ronacherba3757b2008-04-16 19:43:16 +0200108
109 # now we can patch the exc info accordingly
110 if tb_set_next is not None:
111 if tb_back is not None:
Armin Ronacher32a910f2008-04-26 23:21:03 +0200112 tb_set_next(tb_back, new_tb)
Armin Ronacherba3757b2008-04-16 19:43:16 +0200113 if tb is not None:
Armin Ronacher32a910f2008-04-26 23:21:03 +0200114 tb_set_next(new_tb, tb.tb_next)
Armin Ronacher6cc8dd02008-04-16 23:15:15 +0200115
116 # return without this frame
Armin Ronacher32a910f2008-04-26 23:21:03 +0200117 return exc_info[:2] + (new_tb,)
Armin Ronacherba3757b2008-04-16 19:43:16 +0200118
119
Armin Ronacherba3757b2008-04-16 19:43:16 +0200120def _init_ugly_crap():
121 """This function implements a few ugly things so that we can patch the
122 traceback objects. The function returned allows resetting `tb_next` on
123 any python traceback object.
124 """
125 import ctypes
126 from types import TracebackType
127
128 # figure out side of _Py_ssize_t
129 if hasattr(ctypes.pythonapi, 'Py_InitModule4_64'):
130 _Py_ssize_t = ctypes.c_int64
131 else:
132 _Py_ssize_t = ctypes.c_int
133
134 # regular python
135 class _PyObject(ctypes.Structure):
136 pass
137 _PyObject._fields_ = [
138 ('ob_refcnt', _Py_ssize_t),
139 ('ob_type', ctypes.POINTER(_PyObject))
140 ]
141
142 # python with trace
143 if object.__basicsize__ != ctypes.sizeof(_PyObject):
144 class _PyObject(ctypes.Structure):
145 pass
146 _PyObject._fields_ = [
147 ('_ob_next', ctypes.POINTER(_PyObject)),
148 ('_ob_prev', ctypes.POINTER(_PyObject)),
149 ('ob_refcnt', _Py_ssize_t),
150 ('ob_type', ctypes.POINTER(_PyObject))
151 ]
152
153 class _Traceback(_PyObject):
154 pass
155 _Traceback._fields_ = [
156 ('tb_next', ctypes.POINTER(_Traceback)),
157 ('tb_frame', ctypes.POINTER(_PyObject)),
158 ('tb_lasti', ctypes.c_int),
159 ('tb_lineno', ctypes.c_int)
160 ]
161
162 def tb_set_next(tb, next):
Armin Ronacher8e8d0712008-04-16 23:10:49 +0200163 """Set the tb_next attribute of a traceback object."""
Armin Ronacherba3757b2008-04-16 19:43:16 +0200164 if not (isinstance(tb, TracebackType) and
Armin Ronacher8e8d0712008-04-16 23:10:49 +0200165 (next is None or isinstance(next, TracebackType))):
Armin Ronacherba3757b2008-04-16 19:43:16 +0200166 raise TypeError('tb_set_next arguments must be traceback objects')
167 obj = _Traceback.from_address(id(tb))
Armin Ronacher8e8d0712008-04-16 23:10:49 +0200168 if tb.tb_next is not None:
169 old = _Traceback.from_address(id(tb.tb_next))
170 old.ob_refcnt -= 1
171 if next is None:
172 obj.tb_next = ctypes.POINTER(_Traceback)()
173 else:
174 next = _Traceback.from_address(id(next))
175 next.ob_refcnt += 1
176 obj.tb_next = ctypes.pointer(next)
Armin Ronacherba3757b2008-04-16 19:43:16 +0200177
178 return tb_set_next
179
180
Armin Ronacherbd33f112008-04-18 09:17:32 +0200181# try to get a tb_set_next implementation
Armin Ronacherba3757b2008-04-16 19:43:16 +0200182try:
Armin Ronacherbd33f112008-04-18 09:17:32 +0200183 from jinja2._speedups import tb_set_next
184except ImportError:
185 try:
186 tb_set_next = _init_ugly_crap()
187 except:
188 tb_set_next = None
Armin Ronacherba3757b2008-04-16 19:43:16 +0200189del _init_ugly_crap