debug.py 12 KB

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