File: pdbplus.py

package info (click to toggle)
pypy 7.0.0%2Bdfsg-3
  • links: PTS, VCS
  • area: main
  • in suites: buster
  • size: 107,216 kB
  • sloc: python: 1,201,787; ansic: 62,419; asm: 5,169; cpp: 3,017; sh: 2,534; makefile: 545; xml: 243; lisp: 45; awk: 4
file content (459 lines) | stat: -rw-r--r-- 15,776 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
import pdb, bdb
import types
import code
import sys
from rpython.flowspace.model import FunctionGraph

class NoTTY(Exception):
    pass

class PdbPlusShow(pdb.Pdb):

    def __init__(self, translator):
        pdb.Pdb.__init__(self)
        if self.prompt == "(Pdb) ":
            self.prompt = "(Pdb+) "
        else:
            self.prompt = self.prompt.replace("(", "(Pdb+ on ", 1)
        self.translator = translator
        self.exposed = {}

    def post_mortem(self, t):
        self.reset()
        while t.tb_next is not None:
            t = t.tb_next
        self.interaction(t.tb_frame, t)

    def preloop(self):
        if not hasattr(sys.stdout, 'isatty') or not sys.stdout.isatty():
            raise NoTTY("Cannot start the debugger when stdout is captured.")
        pdb.Pdb.preloop(self)

    def expose(self, d):
        self.exposed.update(d)

    def _show(self, page):
        page.display_background()

    def _importobj(self, fullname):
        obj = None
        name = ''
        for comp in fullname.split('.'):
            name += comp
            obj = getattr(obj, comp, None)
            if obj is None:
                try:
                    obj = __import__(name, {}, {}, ['*'])
                except ImportError:
                    raise NameError
            name += '.'
        return obj

    TRYPREFIXES = ['','pypy.','pypy.objspace.','pypy.interpreter.', 'pypy.objspace.std.' ]

    def _mygetval(self, arg, errmsg):
        try:
            return eval(arg, self.curframe.f_globals,
                    self.curframe.f_locals)
        except:
            t, v = sys.exc_info()[:2]
            if isinstance(t, str):
                exc_type_name = t
            else: exc_type_name = t.__name__
            if not isinstance(arg, str):
                print '*** %s' % errmsg, "\t[%s: %s]" % (exc_type_name, v)
            else:
                print '*** %s:' % errmsg, arg, "\t[%s: %s]" % (exc_type_name, v)
            raise

    def _getobj(self, name):
        if '.' in name:
            for pfx in self.TRYPREFIXES:
                try:
                    return self._importobj(pfx+name)
                except NameError:
                    pass
        try:
            return self._mygetval(name, "Not found")
        except (KeyboardInterrupt, SystemExit, MemoryError):
            raise
        except:
            pass
        return None

    def do_find(self, arg):
        """find obj [as var]
find dotted named obj, possibly using prefixing with some packages
in pypy (see help pypyprefixes); the result is assigned to var or _."""
        objarg, var = self._parse_modif(arg)
        obj = self._getobj(objarg)
        if obj is None:
            return
        print obj
        self._setvar(var, obj)

    def _parse_modif(self, arg, modif='as'):
        var = '_'
        aspos = arg.rfind(modif+' ')
        if aspos != -1:
            objarg = arg[:aspos].strip()
            var = arg[aspos+(1+len(modif)):].strip()
        else:
            objarg = arg
        return objarg, var

    def _setvar(self, var, obj):
        self.curframe.f_locals[var] = obj

    class GiveUp(Exception):
        pass

    def _getcdef(self, cls):
        try:
            return self.translator.annotator.bookkeeper.getuniqueclassdef(cls)
        except Exception:
            print "*** cannot get classdef: %s" % cls
            return None

    def _make_flt(self, expr):
        try:
            expr = compile(expr, '<filter>', 'eval')
        except SyntaxError:
            print "*** syntax: %s" % expr
            return None
        def flt(c):
            marker = object()
            try:
                old = self.curframe.f_locals.get('cand', marker)
                self.curframe.f_locals['cand'] = c
                try:
                    return self._mygetval(expr, "oops")
                except (KeyboardInterrupt, SystemExit, MemoryError):
                    raise
                except:
                    raise self.GiveUp
            finally:
                if old is not marker:
                    self.curframe.f_locals['cand'] = old
                else:
                    del self.curframe.f_locals['cand']
        return flt

    def do_finddescs(self, arg):
        """finddescs kind expr [as var]
find annotation descs of kind (ClassDesc|FuncionDesc|...)
 for which expr is true, cand in it referes to
the candidate desc; the result list is assigned to var or _."""
        expr, var = self._parse_modif(arg)
        kind, expr = expr.split(None, 1)
        flt = self._make_flt(expr)
        if flt is None:
            return
        from rpython.annotator import description
        kind_cls = getattr(description, kind, None)
        if kind_cls is None:
            kind = kind.title()+'Desc'
            kind_cls = getattr(description, kind, None)
        if kind_cls is None:
            return

        descs = []
        try:
            for c in self.translator.annotator.bookkeeper.descs.itervalues():
                if isinstance(c, kind_cls) and flt(c):
                    descs.append(c)
        except self.GiveUp:
            return
        self._setvar(var, descs)

    def do_showg(self, arg):
        """showg obj
show graph for obj, obj can be an expression or a dotted name
(in which case prefixing with some packages in pypy is tried (see help pypyprefixes)).
if obj is a function or method, the localized call graph is shown;
if obj is a class or ClassDef the class definition graph is shown"""
        from rpython.annotator.classdesc import ClassDef
        from rpython.translator.tool import graphpage
        translator = self.translator
        obj = self._getobj(arg)
        if obj is None:
            return
        if hasattr(obj, 'im_func'):
            obj = obj.im_func
        if isinstance(obj, types.FunctionType):
            page = graphpage.LocalizedCallGraphPage(translator, self._allgraphs(obj))
        elif isinstance(obj, FunctionGraph):
            page = graphpage.FlowGraphPage(translator, [obj])
        elif isinstance(obj, (type, types.ClassType)):
            classdef = self._getcdef(obj)
            if classdef is None:
                return
            page = graphpage.ClassDefPage(translator, classdef)
        elif isinstance(obj, ClassDef):
            page = graphpage.ClassDefPage(translator, obj)
        else:
            print "*** Nothing to do"
            return
        self._show(page)

    def do_findv(self, varname):
        """ findv [varname]
find a stack frame that has a certain variable (the default is "graph")
"""
        if not varname:
            varname = "graph"
        printfr = self.print_stack_entry
        self.print_stack_entry = lambda *args: None
        try:
            num = 0
            while self.curindex:
                frame = self.curframe
                if varname in frame.f_locals:
                    printfr(self.stack[self.curindex])
                    print "%s = %s" % (varname, frame.f_locals[varname])
                    return
                num += 1
                self.do_up(None)
            print "no %s found" % (varname, )
            for i in range(num):
                self.do_down(None)
        finally:
            del self.print_stack_entry

    def _attrs(self, arg, pr):
        arg, expr = self._parse_modif(arg, 'match')
        if expr == '_':
            expr = 'True'
        obj = self._getobj(arg)
        if obj is None:
            return
        try:
            obj = list(obj)
        except:
            obj = [obj]
        clsdefs = []
        for x in obj:
            if isinstance(x, (type, types.ClassType)):
                cdef = self._getcdef(x)
                if cdef is None:
                    continue
                clsdefs.append(cdef)
            else:
                clsdefs.append(x)

        def longname(c):
            return c.name
        clsdefs.sort(lambda x,y: cmp(longname(x), longname(y)))
        flt = self._make_flt(expr)
        if flt is None:
            return
        for cdef in clsdefs:
            try:
                attrs = [a for a in cdef.attrs.itervalues() if flt(a)]
            except self.GiveUp:
                return
            if attrs:
                print "%s:" % cdef.name
                pr(attrs)

    def do_attrs(self, arg):
        """attrs obj [match expr]
list annotated attrs of class|def obj or list of classe(def)s obj,
obj can be an expression or a dotted name
(in which case prefixing with some packages in pypy is tried (see help pypyprefixes));
expr is an optional filtering expression; cand in it refer to the candidate Attribute
information object, which has a .name and .s_value."""
        def pr(attrs):
            print " " + ' '.join([a.name for a in attrs])
        self._attrs(arg, pr)

    def do_attrsann(self, arg):
        """attrsann obj [match expr]
list with their annotation annotated attrs of class|def obj or list of classe(def)s obj,
obj can be an expression or a dotted name
(in which case prefixing with some packages in pypy is tried (see help pypyprefixes));
expr is an optional filtering expression; cand in it refer to the candidate Attribute
information object, which has a .name and .s_value."""
        def pr(attrs):
            for a in attrs:
                print ' %s %s' % (a.name, a.s_value)
        self._attrs(arg, pr)

    def do_readpos(self, arg):
        """readpos obj attrname [match expr] [as var]
list the read positions of annotated attr with attrname of class or classdef obj,
obj can be an expression or a dotted name
(in which case prefixing with some packages in pypy is tried (see help pypyprefixes));
expr is an optional filtering expression; cand in it refer to the candidate read
position information, which has a .func (which can be None), a .graph  and .block and .i;
the list of the read positions functions is set to var or _."""
        class Pos:
            def __init__(self, graph, func, block, i):
                self.graph = graph
                self.func = func
                self.block = block
                self.i = i
        arg, var = self._parse_modif(arg, 'as')
        arg, expr = self._parse_modif(arg, 'match')
        if expr == '_':
            expr = 'True'
        args = arg.split()
        if len(args) != 2:
            print "*** expected obj attrname:", arg
            return
        arg, attrname = args
        # allow quotes around attrname
        if (attrname.startswith("'") and attrname.endswith("'")
            or attrname.startswith('"') and attrname.endswith('"')):
            attrname = attrname[1:-1]

        obj = self._getobj(arg)
        if obj is None:
            return
        if isinstance(obj, (type, types.ClassType)):
            obj = self._getcdef(obj)
            if obj is None:
                return
        bk = self.translator.annotator.bookkeeper
        attrs = obj.attrs
        if attrname not in attrs:
            print "*** bogus:", attrname
            return
        pos = bk.getattr_locations(obj.classdesc, attrname)
        if not pos:
            return
        flt = self._make_flt(expr)
        if flt is None:
            return
        r = {}
        try:
            for p in pos:
                graph, block, i = p
                if hasattr(graph, 'func'):
                    func = graph.func
                else:
                    func = None
                if flt(Pos(graph, func, block, i)):
                    if func is not None:
                        print func.__module__ or '?', func.__name__, block, i
                    else:
                        print graph, block, i
                    if i >= 0:
                        op = block.operations[i]
                        print " ", op
                        print " ",
                        for arg in op.args:
                            print "%s: %s" % (arg, self.translator.annotator.binding(arg)),
                        print

                    r[func] = True
        except self.GiveUp:
            return
        self._setvar(var, r.keys())


    def do_flowg(self, arg):
        """flowg obj
show flow graph for function obj, obj can be an expression or a dotted name
(in which case prefixing with some packages in pypy is tried (see help pypyprefixes))"""
        from rpython.translator.tool import graphpage
        obj = self._getobj(arg)
        if obj is None:
            return
        if hasattr(obj, 'im_func'):
            obj = obj.im_func
        if isinstance(obj, types.FunctionType):
            graphs = self._allgraphs(obj)
        elif isinstance(obj, FunctionGraph):
            graphs = [obj]
        else:
            print "*** Not a function"
            return
        self._show(graphpage.FlowGraphPage(self.translator, graphs))

    def _allgraphs(self, func):
        graphs = {}
        funcdesc = self.translator.annotator.bookkeeper.getdesc(func)
        for graph in funcdesc._cache.itervalues():
            graphs[graph] = True
        for graph in self.translator.graphs:
            if getattr(graph, 'func', None) is func:
                graphs[graph] = True
        return graphs.keys()


    def do_callg(self, arg):
        """callg obj
show localized call-graph for function obj, obj can be an expression or a dotted name
(in which case prefixing with some packages in pypy is tried (see help pypyprefixes))"""
        from rpython.translator.tool import graphpage
        obj = self._getobj(arg)
        if obj is None:
            return
        if hasattr(obj, 'im_func'):
            obj = obj.im_func
        if isinstance(obj, types.FunctionType):
            graphs = self._allgraphs(obj)
        elif isinstance(obj, FunctionGraph):
            graphs = [obj]
        else:
            print "*** Not a function"
            return
        self._show(graphpage.LocalizedCallGraphPage(self.translator, graphs))

    def do_classhier(self, arg):
        """classhier
show class hierarchy graph"""
        from rpython.translator.tool import graphpage
        self._show(graphpage.ClassHierarchyPage(self.translator))

    def do_callgraph(self, arg):
        """callgraph
show the program's call graph"""
        from rpython.translator.tool import graphpage
        self._show(graphpage.TranslatorPage(self.translator, 100))

    def do_interact(self, arg):
        """invoke a code.py sub prompt"""
        ns = self.curframe.f_globals.copy()
        ns.update(self.curframe.f_locals)
        code.interact("*interactive*", local=ns)

    def help_graphs(self):
        print "graph commands are: callgraph, showg, flowg, callg, classhier"

    def help_ann_other(self):
        print "other annotation related commands are: find, finddescs, attrs, attrsann, readpos"

    def help_pypyprefixes(self):
        print "these prefixes are tried for dotted names in graph commands:"
        print self.TRYPREFIXES

    # start helpers
    def start(self, tb):
        if tb is None:
            fn, args = self.set_trace, ()
        else:
            fn, args = self.post_mortem, (tb,)
        try:
            t = self.translator # define enviroments, xxx more stuff
            exec ""
            locals().update(self.exposed)
            fn(*args)
            pass # for debugger to land
        except bdb.BdbQuit:
            pass


def pdbcatch(f):
    "A decorator that throws you in a pdbplus if the given function raises."
    from rpython.tool.sourcetools import func_with_new_name
    def wrapper(*args, **kwds):
        try:
            return f(*args, **kwds)
        except:
            import sys
            PdbPlusShow(None).post_mortem(sys.exc_info()[2])
            raise
    wrapper = func_with_new_name(wrapper, f.__name__)
    return wrapper