blob: 9209054848d2f1cba4673b825f5adb75e3717777 [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
10 :copyright: Copyright 2008 by Armin Ronacher.
11 :license: BSD.
12"""
Armin Ronacherba3757b2008-04-16 19:43:16 +020013import sys
Armin Ronacher32a910f2008-04-26 23:21:03 +020014from types import CodeType
Armin Ronacherba3757b2008-04-16 19:43:16 +020015
16
Armin Ronacher8e8d0712008-04-16 23:10:49 +020017def translate_exception(exc_info):
18 """If passed an exc_info it will automatically rewrite the exceptions
19 all the way down to the correct line numbers and frames.
20 """
21 result_tb = prev_tb = None
Armin Ronacher6cc8dd02008-04-16 23:15:15 +020022 initial_tb = tb = exc_info[2].tb_next
Armin Ronacher8e8d0712008-04-16 23:10:49 +020023
24 while tb is not None:
25 template = tb.tb_frame.f_globals.get('__jinja_template__')
26 if template is not None:
27 lineno = template.get_corresponding_lineno(tb.tb_lineno)
28 tb = fake_exc_info(exc_info[:2] + (tb,), template.filename,
29 lineno, prev_tb)[2]
30 if result_tb is None:
31 result_tb = tb
32 prev_tb = tb
33 tb = tb.tb_next
34
35 return exc_info[:2] + (result_tb or initial_tb,)
Armin Ronacherba3757b2008-04-16 19:43:16 +020036
37
Armin Ronacheraaf010d2008-05-01 13:14:30 +020038def translate_syntax_error(error):
39 """When passed a syntax error it will generate a new traceback with
40 more debugging information.
41 """
42 filename = error.filename
43 if filename is None:
44 filename = '<template>'
45 elif isinstance(filename, unicode):
46 filename = filename.encode('utf-8')
47 code = compile('\n' * (error.lineno - 1) + 'raise __jinja_exception__',
48 filename, 'exec')
49 try:
50 exec code in {'__jinja_exception__': error}
51 except:
52 exc_info = sys.exc_info()
53 return exc_info[:2] + (exc_info[2].tb_next,)
54
55
Armin Ronacherba3757b2008-04-16 19:43:16 +020056def fake_exc_info(exc_info, filename, lineno, tb_back=None):
Armin Ronacher8e8d0712008-04-16 23:10:49 +020057 """Helper for `translate_exception`."""
Armin Ronacherba3757b2008-04-16 19:43:16 +020058 exc_type, exc_value, tb = exc_info
59
60 # figure the real context out
61 real_locals = tb.tb_frame.f_locals.copy()
Armin Ronacher203bfcb2008-04-24 21:54:44 +020062 ctx = real_locals.get('context')
63 if ctx:
64 locals = ctx.get_all()
65 else:
66 locals = {}
Armin Ronacherba3757b2008-04-16 19:43:16 +020067 for name, value in real_locals.iteritems():
68 if name.startswith('l_'):
Armin Ronacherd1ff8582008-05-11 00:30:43 +020069 locals[name[2:]] = value
Armin Ronacherba3757b2008-04-16 19:43:16 +020070
Armin Ronacher18c6ca02008-04-17 10:03:29 +020071 # if there is a local called __jinja_exception__, we get
72 # rid of it to not break the debug functionality.
73 locals.pop('__jinja_exception__', None)
74
Armin Ronacherba3757b2008-04-16 19:43:16 +020075 # assamble fake globals we need
76 globals = {
77 '__name__': filename,
78 '__file__': filename,
79 '__jinja_exception__': exc_info[:2]
80 }
81
82 # and fake the exception
83 code = compile('\n' * (lineno - 1) + 'raise __jinja_exception__[0], ' +
84 '__jinja_exception__[1]', filename, 'exec')
Armin Ronacher32a910f2008-04-26 23:21:03 +020085
86 # if it's possible, change the name of the code. This won't work
87 # on some python environments such as google appengine
88 try:
89 function = tb.tb_frame.f_code.co_name
90 if function == 'root':
91 location = 'top-level template code'
92 elif function.startswith('block_'):
93 location = 'block "%s"' % function[6:]
94 else:
95 location = 'template'
96 code = CodeType(0, code.co_nlocals, code.co_stacksize,
97 code.co_flags, code.co_code, code.co_consts,
98 code.co_names, code.co_varnames, filename,
99 location, code.co_firstlineno,
100 code.co_lnotab, (), ())
101 except:
102 pass
103
104 # execute the code and catch the new traceback
Armin Ronacherba3757b2008-04-16 19:43:16 +0200105 try:
106 exec code in globals, locals
107 except:
108 exc_info = sys.exc_info()
Armin Ronacher32a910f2008-04-26 23:21:03 +0200109 new_tb = exc_info[2].tb_next
Armin Ronacherba3757b2008-04-16 19:43:16 +0200110
111 # now we can patch the exc info accordingly
112 if tb_set_next is not None:
113 if tb_back is not None:
Armin Ronacher32a910f2008-04-26 23:21:03 +0200114 tb_set_next(tb_back, new_tb)
Armin Ronacherba3757b2008-04-16 19:43:16 +0200115 if tb is not None:
Armin Ronacher32a910f2008-04-26 23:21:03 +0200116 tb_set_next(new_tb, tb.tb_next)
Armin Ronacher6cc8dd02008-04-16 23:15:15 +0200117
118 # return without this frame
Armin Ronacher32a910f2008-04-26 23:21:03 +0200119 return exc_info[:2] + (new_tb,)
Armin Ronacherba3757b2008-04-16 19:43:16 +0200120
121
Armin Ronacherba3757b2008-04-16 19:43:16 +0200122def _init_ugly_crap():
123 """This function implements a few ugly things so that we can patch the
124 traceback objects. The function returned allows resetting `tb_next` on
125 any python traceback object.
126 """
127 import ctypes
128 from types import TracebackType
129
130 # figure out side of _Py_ssize_t
131 if hasattr(ctypes.pythonapi, 'Py_InitModule4_64'):
132 _Py_ssize_t = ctypes.c_int64
133 else:
134 _Py_ssize_t = ctypes.c_int
135
136 # regular python
137 class _PyObject(ctypes.Structure):
138 pass
139 _PyObject._fields_ = [
140 ('ob_refcnt', _Py_ssize_t),
141 ('ob_type', ctypes.POINTER(_PyObject))
142 ]
143
144 # python with trace
145 if object.__basicsize__ != ctypes.sizeof(_PyObject):
146 class _PyObject(ctypes.Structure):
147 pass
148 _PyObject._fields_ = [
149 ('_ob_next', ctypes.POINTER(_PyObject)),
150 ('_ob_prev', ctypes.POINTER(_PyObject)),
151 ('ob_refcnt', _Py_ssize_t),
152 ('ob_type', ctypes.POINTER(_PyObject))
153 ]
154
155 class _Traceback(_PyObject):
156 pass
157 _Traceback._fields_ = [
158 ('tb_next', ctypes.POINTER(_Traceback)),
159 ('tb_frame', ctypes.POINTER(_PyObject)),
160 ('tb_lasti', ctypes.c_int),
161 ('tb_lineno', ctypes.c_int)
162 ]
163
164 def tb_set_next(tb, next):
Armin Ronacher8e8d0712008-04-16 23:10:49 +0200165 """Set the tb_next attribute of a traceback object."""
Armin Ronacherba3757b2008-04-16 19:43:16 +0200166 if not (isinstance(tb, TracebackType) and
Armin Ronacher8e8d0712008-04-16 23:10:49 +0200167 (next is None or isinstance(next, TracebackType))):
Armin Ronacherba3757b2008-04-16 19:43:16 +0200168 raise TypeError('tb_set_next arguments must be traceback objects')
169 obj = _Traceback.from_address(id(tb))
Armin Ronacher8e8d0712008-04-16 23:10:49 +0200170 if tb.tb_next is not None:
171 old = _Traceback.from_address(id(tb.tb_next))
172 old.ob_refcnt -= 1
173 if next is None:
174 obj.tb_next = ctypes.POINTER(_Traceback)()
175 else:
176 next = _Traceback.from_address(id(next))
177 next.ob_refcnt += 1
178 obj.tb_next = ctypes.pointer(next)
Armin Ronacherba3757b2008-04-16 19:43:16 +0200179
180 return tb_set_next
181
182
Armin Ronacherbd33f112008-04-18 09:17:32 +0200183# try to get a tb_set_next implementation
Armin Ronacherba3757b2008-04-16 19:43:16 +0200184try:
Armin Ronacherbd33f112008-04-18 09:17:32 +0200185 from jinja2._speedups import tb_set_next
186except ImportError:
187 try:
188 tb_set_next = _init_ugly_crap()
189 except:
190 tb_set_next = None
Armin Ronacherba3757b2008-04-16 19:43:16 +0200191del _init_ugly_crap