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