xref: /aosp_15_r20/prebuilts/build-tools/common/py3-stdlib/cgitb.py (revision cda5da8d549138a6648c5ee6d7a49cf8f4a657be)
1"""More comprehensive traceback formatting for Python scripts.
2
3To enable this module, do:
4
5    import cgitb; cgitb.enable()
6
7at the top of your script.  The optional arguments to enable() are:
8
9    display     - if true, tracebacks are displayed in the web browser
10    logdir      - if set, tracebacks are written to files in this directory
11    context     - number of lines of source code to show for each stack frame
12    format      - 'text' or 'html' controls the output format
13
14By default, tracebacks are displayed but not saved, the context is 5 lines
15and the output format is 'html' (for backwards compatibility with the
16original use of this module)
17
18Alternatively, if you have caught an exception and want cgitb to display it
19for you, call cgitb.handler().  The optional argument to handler() is a
203-item tuple (etype, evalue, etb) just like the value of sys.exc_info().
21The default handler displays output as HTML.
22
23"""
24import inspect
25import keyword
26import linecache
27import os
28import pydoc
29import sys
30import tempfile
31import time
32import tokenize
33import traceback
34import warnings
35from html import escape as html_escape
36
37warnings._deprecated(__name__, remove=(3, 13))
38
39
40def reset():
41    """Return a string that resets the CGI and browser to a known state."""
42    return '''<!--: spam
43Content-Type: text/html
44
45<body bgcolor="#f0f0f8"><font color="#f0f0f8" size="-5"> -->
46<body bgcolor="#f0f0f8"><font color="#f0f0f8" size="-5"> --> -->
47</font> </font> </font> </script> </object> </blockquote> </pre>
48</table> </table> </table> </table> </table> </font> </font> </font>'''
49
50__UNDEF__ = []                          # a special sentinel object
51def small(text):
52    if text:
53        return '<small>' + text + '</small>'
54    else:
55        return ''
56
57def strong(text):
58    if text:
59        return '<strong>' + text + '</strong>'
60    else:
61        return ''
62
63def grey(text):
64    if text:
65        return '<font color="#909090">' + text + '</font>'
66    else:
67        return ''
68
69def lookup(name, frame, locals):
70    """Find the value for a given name in the given environment."""
71    if name in locals:
72        return 'local', locals[name]
73    if name in frame.f_globals:
74        return 'global', frame.f_globals[name]
75    if '__builtins__' in frame.f_globals:
76        builtins = frame.f_globals['__builtins__']
77        if type(builtins) is type({}):
78            if name in builtins:
79                return 'builtin', builtins[name]
80        else:
81            if hasattr(builtins, name):
82                return 'builtin', getattr(builtins, name)
83    return None, __UNDEF__
84
85def scanvars(reader, frame, locals):
86    """Scan one logical line of Python and look up values of variables used."""
87    vars, lasttoken, parent, prefix, value = [], None, None, '', __UNDEF__
88    for ttype, token, start, end, line in tokenize.generate_tokens(reader):
89        if ttype == tokenize.NEWLINE: break
90        if ttype == tokenize.NAME and token not in keyword.kwlist:
91            if lasttoken == '.':
92                if parent is not __UNDEF__:
93                    value = getattr(parent, token, __UNDEF__)
94                    vars.append((prefix + token, prefix, value))
95            else:
96                where, value = lookup(token, frame, locals)
97                vars.append((token, where, value))
98        elif token == '.':
99            prefix += lasttoken + '.'
100            parent = value
101        else:
102            parent, prefix = None, ''
103        lasttoken = token
104    return vars
105
106def html(einfo, context=5):
107    """Return a nice HTML document describing a given traceback."""
108    etype, evalue, etb = einfo
109    if isinstance(etype, type):
110        etype = etype.__name__
111    pyver = 'Python ' + sys.version.split()[0] + ': ' + sys.executable
112    date = time.ctime(time.time())
113    head = f'''
114<body bgcolor="#f0f0f8">
115<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="heading">
116<tr bgcolor="#6622aa">
117<td valign=bottom>&nbsp;<br>
118<font color="#ffffff" face="helvetica, arial">&nbsp;<br>
119<big><big><strong>{html_escape(str(etype))}</strong></big></big></font></td>
120<td align=right valign=bottom>
121<font color="#ffffff" face="helvetica, arial">{pyver}<br>{date}</font></td>
122</tr></table>
123<p>A problem occurred in a Python script.  Here is the sequence of
124function calls leading up to the error, in the order they occurred.</p>'''
125
126    indent = '<tt>' + small('&nbsp;' * 5) + '&nbsp;</tt>'
127    frames = []
128    records = inspect.getinnerframes(etb, context)
129    for frame, file, lnum, func, lines, index in records:
130        if file:
131            file = os.path.abspath(file)
132            link = '<a href="file://%s">%s</a>' % (file, pydoc.html.escape(file))
133        else:
134            file = link = '?'
135        args, varargs, varkw, locals = inspect.getargvalues(frame)
136        call = ''
137        if func != '?':
138            call = 'in ' + strong(pydoc.html.escape(func))
139            if func != "<module>":
140                call += inspect.formatargvalues(args, varargs, varkw, locals,
141                    formatvalue=lambda value: '=' + pydoc.html.repr(value))
142
143        highlight = {}
144        def reader(lnum=[lnum]):
145            highlight[lnum[0]] = 1
146            try: return linecache.getline(file, lnum[0])
147            finally: lnum[0] += 1
148        vars = scanvars(reader, frame, locals)
149
150        rows = ['<tr><td bgcolor="#d8bbff">%s%s %s</td></tr>' %
151                ('<big>&nbsp;</big>', link, call)]
152        if index is not None:
153            i = lnum - index
154            for line in lines:
155                num = small('&nbsp;' * (5-len(str(i))) + str(i)) + '&nbsp;'
156                if i in highlight:
157                    line = '<tt>=&gt;%s%s</tt>' % (num, pydoc.html.preformat(line))
158                    rows.append('<tr><td bgcolor="#ffccee">%s</td></tr>' % line)
159                else:
160                    line = '<tt>&nbsp;&nbsp;%s%s</tt>' % (num, pydoc.html.preformat(line))
161                    rows.append('<tr><td>%s</td></tr>' % grey(line))
162                i += 1
163
164        done, dump = {}, []
165        for name, where, value in vars:
166            if name in done: continue
167            done[name] = 1
168            if value is not __UNDEF__:
169                if where in ('global', 'builtin'):
170                    name = ('<em>%s</em> ' % where) + strong(name)
171                elif where == 'local':
172                    name = strong(name)
173                else:
174                    name = where + strong(name.split('.')[-1])
175                dump.append('%s&nbsp;= %s' % (name, pydoc.html.repr(value)))
176            else:
177                dump.append(name + ' <em>undefined</em>')
178
179        rows.append('<tr><td>%s</td></tr>' % small(grey(', '.join(dump))))
180        frames.append('''
181<table width="100%%" cellspacing=0 cellpadding=0 border=0>
182%s</table>''' % '\n'.join(rows))
183
184    exception = ['<p>%s: %s' % (strong(pydoc.html.escape(str(etype))),
185                                pydoc.html.escape(str(evalue)))]
186    for name in dir(evalue):
187        if name[:1] == '_': continue
188        value = pydoc.html.repr(getattr(evalue, name))
189        exception.append('\n<br>%s%s&nbsp;=\n%s' % (indent, name, value))
190
191    return head + ''.join(frames) + ''.join(exception) + '''
192
193
194<!-- The above is a description of an error in a Python program, formatted
195     for a web browser because the 'cgitb' module was enabled.  In case you
196     are not reading this in a web browser, here is the original traceback:
197
198%s
199-->
200''' % pydoc.html.escape(
201          ''.join(traceback.format_exception(etype, evalue, etb)))
202
203def text(einfo, context=5):
204    """Return a plain text document describing a given traceback."""
205    etype, evalue, etb = einfo
206    if isinstance(etype, type):
207        etype = etype.__name__
208    pyver = 'Python ' + sys.version.split()[0] + ': ' + sys.executable
209    date = time.ctime(time.time())
210    head = "%s\n%s\n%s\n" % (str(etype), pyver, date) + '''
211A problem occurred in a Python script.  Here is the sequence of
212function calls leading up to the error, in the order they occurred.
213'''
214
215    frames = []
216    records = inspect.getinnerframes(etb, context)
217    for frame, file, lnum, func, lines, index in records:
218        file = file and os.path.abspath(file) or '?'
219        args, varargs, varkw, locals = inspect.getargvalues(frame)
220        call = ''
221        if func != '?':
222            call = 'in ' + func
223            if func != "<module>":
224                call += inspect.formatargvalues(args, varargs, varkw, locals,
225                    formatvalue=lambda value: '=' + pydoc.text.repr(value))
226
227        highlight = {}
228        def reader(lnum=[lnum]):
229            highlight[lnum[0]] = 1
230            try: return linecache.getline(file, lnum[0])
231            finally: lnum[0] += 1
232        vars = scanvars(reader, frame, locals)
233
234        rows = [' %s %s' % (file, call)]
235        if index is not None:
236            i = lnum - index
237            for line in lines:
238                num = '%5d ' % i
239                rows.append(num+line.rstrip())
240                i += 1
241
242        done, dump = {}, []
243        for name, where, value in vars:
244            if name in done: continue
245            done[name] = 1
246            if value is not __UNDEF__:
247                if where == 'global': name = 'global ' + name
248                elif where != 'local': name = where + name.split('.')[-1]
249                dump.append('%s = %s' % (name, pydoc.text.repr(value)))
250            else:
251                dump.append(name + ' undefined')
252
253        rows.append('\n'.join(dump))
254        frames.append('\n%s\n' % '\n'.join(rows))
255
256    exception = ['%s: %s' % (str(etype), str(evalue))]
257    for name in dir(evalue):
258        value = pydoc.text.repr(getattr(evalue, name))
259        exception.append('\n%s%s = %s' % (" "*4, name, value))
260
261    return head + ''.join(frames) + ''.join(exception) + '''
262
263The above is a description of an error in a Python program.  Here is
264the original traceback:
265
266%s
267''' % ''.join(traceback.format_exception(etype, evalue, etb))
268
269class Hook:
270    """A hook to replace sys.excepthook that shows tracebacks in HTML."""
271
272    def __init__(self, display=1, logdir=None, context=5, file=None,
273                 format="html"):
274        self.display = display          # send tracebacks to browser if true
275        self.logdir = logdir            # log tracebacks to files if not None
276        self.context = context          # number of source code lines per frame
277        self.file = file or sys.stdout  # place to send the output
278        self.format = format
279
280    def __call__(self, etype, evalue, etb):
281        self.handle((etype, evalue, etb))
282
283    def handle(self, info=None):
284        info = info or sys.exc_info()
285        if self.format == "html":
286            self.file.write(reset())
287
288        formatter = (self.format=="html") and html or text
289        plain = False
290        try:
291            doc = formatter(info, self.context)
292        except:                         # just in case something goes wrong
293            doc = ''.join(traceback.format_exception(*info))
294            plain = True
295
296        if self.display:
297            if plain:
298                doc = pydoc.html.escape(doc)
299                self.file.write('<pre>' + doc + '</pre>\n')
300            else:
301                self.file.write(doc + '\n')
302        else:
303            self.file.write('<p>A problem occurred in a Python script.\n')
304
305        if self.logdir is not None:
306            suffix = ['.txt', '.html'][self.format=="html"]
307            (fd, path) = tempfile.mkstemp(suffix=suffix, dir=self.logdir)
308
309            try:
310                with os.fdopen(fd, 'w') as file:
311                    file.write(doc)
312                msg = '%s contains the description of this error.' % path
313            except:
314                msg = 'Tried to save traceback to %s, but failed.' % path
315
316            if self.format == 'html':
317                self.file.write('<p>%s</p>\n' % msg)
318            else:
319                self.file.write(msg + '\n')
320        try:
321            self.file.flush()
322        except: pass
323
324handler = Hook().handle
325def enable(display=1, logdir=None, context=5, format="html"):
326    """Install an exception handler that formats tracebacks as HTML.
327
328    The optional argument 'display' can be set to 0 to suppress sending the
329    traceback to the browser, and 'logdir' can be set to a directory to cause
330    tracebacks to be written to files there."""
331    sys.excepthook = Hook(display=display, logdir=logdir,
332                          context=context, format=format)
333