blob: 38fa012fd4f62147a07e6965645e668a6b062bec [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 Ronacherba3757b2008-04-16 19:43:16 +020011 :license: BSD.
12"""
Armin Ronacherba3757b2008-04-16 19:43:16 +020013import sys
Armin Ronacher9a0078d2008-08-13 18:24:17 +020014from jinja2.utils 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
38def fake_exc_info(exc_info, filename, lineno, tb_back=None):
Armin Ronacher8e8d0712008-04-16 23:10:49 +020039 """Helper for `translate_exception`."""
Armin Ronacherba3757b2008-04-16 19:43:16 +020040 exc_type, exc_value, tb = exc_info
41
42 # figure the real context out
43 real_locals = tb.tb_frame.f_locals.copy()
Armin Ronacher203bfcb2008-04-24 21:54:44 +020044 ctx = real_locals.get('context')
45 if ctx:
46 locals = ctx.get_all()
47 else:
48 locals = {}
Armin Ronacherba3757b2008-04-16 19:43:16 +020049 for name, value in real_locals.iteritems():
50 if name.startswith('l_'):
Armin Ronacherd1ff8582008-05-11 00:30:43 +020051 locals[name[2:]] = value
Armin Ronacherba3757b2008-04-16 19:43:16 +020052
Armin Ronacher18c6ca02008-04-17 10:03:29 +020053 # if there is a local called __jinja_exception__, we get
54 # rid of it to not break the debug functionality.
55 locals.pop('__jinja_exception__', None)
56
Armin Ronacherba3757b2008-04-16 19:43:16 +020057 # assamble fake globals we need
58 globals = {
59 '__name__': filename,
60 '__file__': filename,
61 '__jinja_exception__': exc_info[:2]
62 }
63
64 # and fake the exception
65 code = compile('\n' * (lineno - 1) + 'raise __jinja_exception__[0], ' +
66 '__jinja_exception__[1]', filename, 'exec')
Armin Ronacher32a910f2008-04-26 23:21:03 +020067
68 # if it's possible, change the name of the code. This won't work
69 # on some python environments such as google appengine
70 try:
71 function = tb.tb_frame.f_code.co_name
72 if function == 'root':
73 location = 'top-level template code'
74 elif function.startswith('block_'):
75 location = 'block "%s"' % function[6:]
76 else:
77 location = 'template'
78 code = CodeType(0, code.co_nlocals, code.co_stacksize,
79 code.co_flags, code.co_code, code.co_consts,
80 code.co_names, code.co_varnames, filename,
81 location, code.co_firstlineno,
82 code.co_lnotab, (), ())
83 except:
84 pass
85
86 # execute the code and catch the new traceback
Armin Ronacherba3757b2008-04-16 19:43:16 +020087 try:
88 exec code in globals, locals
89 except:
90 exc_info = sys.exc_info()
Armin Ronacher32a910f2008-04-26 23:21:03 +020091 new_tb = exc_info[2].tb_next
Armin Ronacherba3757b2008-04-16 19:43:16 +020092
93 # now we can patch the exc info accordingly
94 if tb_set_next is not None:
95 if tb_back is not None:
Armin Ronacher32a910f2008-04-26 23:21:03 +020096 tb_set_next(tb_back, new_tb)
Armin Ronacherba3757b2008-04-16 19:43:16 +020097 if tb is not None:
Armin Ronacher32a910f2008-04-26 23:21:03 +020098 tb_set_next(new_tb, tb.tb_next)
Armin Ronacher6cc8dd02008-04-16 23:15:15 +020099
100 # return without this frame
Armin Ronacher32a910f2008-04-26 23:21:03 +0200101 return exc_info[:2] + (new_tb,)
Armin Ronacherba3757b2008-04-16 19:43:16 +0200102
103
Armin Ronacherba3757b2008-04-16 19:43:16 +0200104def _init_ugly_crap():
105 """This function implements a few ugly things so that we can patch the
106 traceback objects. The function returned allows resetting `tb_next` on
107 any python traceback object.
108 """
109 import ctypes
110 from types import TracebackType
111
112 # figure out side of _Py_ssize_t
113 if hasattr(ctypes.pythonapi, 'Py_InitModule4_64'):
114 _Py_ssize_t = ctypes.c_int64
115 else:
116 _Py_ssize_t = ctypes.c_int
117
118 # regular python
119 class _PyObject(ctypes.Structure):
120 pass
121 _PyObject._fields_ = [
122 ('ob_refcnt', _Py_ssize_t),
123 ('ob_type', ctypes.POINTER(_PyObject))
124 ]
125
126 # python with trace
127 if object.__basicsize__ != ctypes.sizeof(_PyObject):
128 class _PyObject(ctypes.Structure):
129 pass
130 _PyObject._fields_ = [
131 ('_ob_next', ctypes.POINTER(_PyObject)),
132 ('_ob_prev', ctypes.POINTER(_PyObject)),
133 ('ob_refcnt', _Py_ssize_t),
134 ('ob_type', ctypes.POINTER(_PyObject))
135 ]
136
137 class _Traceback(_PyObject):
138 pass
139 _Traceback._fields_ = [
140 ('tb_next', ctypes.POINTER(_Traceback)),
141 ('tb_frame', ctypes.POINTER(_PyObject)),
142 ('tb_lasti', ctypes.c_int),
143 ('tb_lineno', ctypes.c_int)
144 ]
145
146 def tb_set_next(tb, next):
Armin Ronacher8e8d0712008-04-16 23:10:49 +0200147 """Set the tb_next attribute of a traceback object."""
Armin Ronacherba3757b2008-04-16 19:43:16 +0200148 if not (isinstance(tb, TracebackType) and
Armin Ronacher8e8d0712008-04-16 23:10:49 +0200149 (next is None or isinstance(next, TracebackType))):
Armin Ronacherba3757b2008-04-16 19:43:16 +0200150 raise TypeError('tb_set_next arguments must be traceback objects')
151 obj = _Traceback.from_address(id(tb))
Armin Ronacher8e8d0712008-04-16 23:10:49 +0200152 if tb.tb_next is not None:
153 old = _Traceback.from_address(id(tb.tb_next))
154 old.ob_refcnt -= 1
155 if next is None:
156 obj.tb_next = ctypes.POINTER(_Traceback)()
157 else:
158 next = _Traceback.from_address(id(next))
159 next.ob_refcnt += 1
160 obj.tb_next = ctypes.pointer(next)
Armin Ronacherba3757b2008-04-16 19:43:16 +0200161
162 return tb_set_next
163
164
Armin Ronacherbd33f112008-04-18 09:17:32 +0200165# try to get a tb_set_next implementation
Armin Ronacherba3757b2008-04-16 19:43:16 +0200166try:
Armin Ronacherbd33f112008-04-18 09:17:32 +0200167 from jinja2._speedups import tb_set_next
168except ImportError:
169 try:
170 tb_set_next = _init_ugly_crap()
171 except:
172 tb_set_next = None
Armin Ronacherba3757b2008-04-16 19:43:16 +0200173del _init_ugly_crap