示例#1
0
文件: __init__.py 项目: arthru/wdb
class WdbRequest(object, Bdb):
    """Wdb debugger main class"""

    __metaclass__ = MetaWdbRequest

    def __init__(self, ports, skip=None):
        MetaWdbRequest._last_inst = self
        self.obj_cache = {}

        try:
            Bdb.__init__(self, skip=skip)
        except TypeError:
            Bdb.__init__(self)
        self.begun = False
        self.connected = False
        self.make_web_socket(ports)
        breaks_per_file_lno = Breakpoint.bplist.values()
        for bps in breaks_per_file_lno:
            breaks = list(bps)
            for bp in breaks:
                args = bp.file, bp.line, bp.temporary, bp.cond
                self.set_break(*args)
                log.info("Resetting break %s" % repr(args))

    def better_repr(self, obj):
        try:
            if isinstance(obj, basestring):
                raise TypeError()
            iter(obj)
        except TypeError:
            self.obj_cache[id(obj)] = obj
            return '<a href="%d" class="inspect">%s</a>' % (id(obj), escape(repr(obj)))

        if isinstance(obj, dict):
            if type(obj) != dict:
                dict_repr = type(obj).__name__ + "({"
                closer = "})"
            else:
                dict_repr = "{"
                closer = "}"
            dict_repr += ", ".join([repr(key) + ": " + self.better_repr(val) for key, val in obj.items()])

            dict_repr += closer
            return dict_repr

        if type(obj) == list:
            iter_repr = "["
            closer = "]"
        elif type(obj) == set:
            iter_repr = "{"
            closer = "}"
        elif type(obj) == tuple:
            iter_repr = "("
            closer = ")"
        else:
            iter_repr = escape(obj.__class__.__name__) + "(["
            closer = "])"

        iter_repr += ", ".join([self.better_repr(val) for val in obj])
        iter_repr += closer

        return iter_repr

    @contextmanager
    def capture_output(self, with_hook=True):
        self.hooked = ""

        def display_hook(obj):
            # That's some dirty hack
            self.hooked += self.better_repr(obj)

        stdout, stderr = sys.stdout, sys.stderr
        if with_hook:
            d_hook = sys.displayhook
            sys.displayhook = display_hook

        sys.stdout, sys.stderr = StringIO(), StringIO()
        out, err = [], []
        try:
            yield out, err
        finally:
            out.extend(sys.stdout.getvalue().splitlines())
            err.extend(sys.stderr.getvalue().splitlines())
            if with_hook:
                sys.displayhook = d_hook

            sys.stdout, sys.stderr = stdout, stderr

    def dmp(self, thing):
        return dict(
            (escape(key), {"val": self.better_repr(getattr(thing, key)), "type": type(getattr(thing, key)).__name__})
            for key in dir(thing)
        )

    def make_web_socket(self, ports):
        log.info("Creating WebSocket")
        for port in ports:
            self.ws = WebSocket("0.0.0.0", port)
            if self.ws.status == "OK":
                return
            time.sleep(0.100)

        raise WsError("No port could be opened")

    def wsgi_trace(self, app, environ, start_response):
        def wsgi_with_trace(environ, start_response):
            self.quitting = 0
            self.begun = False
            self.reset()
            frame = sys._getframe()
            while frame:
                frame.f_trace = self.trace_dispatch
                self.botframe = frame
                frame = frame.f_back
            self.stopframe = sys._getframe().f_back
            self.stoplineno = -1
            sys.settrace(self.trace_dispatch)

            try:
                appiter = app(environ, start_response)
            except BdbQuit:
                sys.settrace(None)
                start_response("200 OK", [("Content-Type", "text/html")])
                yield "<h1>BdbQuit</h1><p>Wdb was interrupted</p>"
            else:
                for item in appiter:
                    yield item
                hasattr(appiter, "close") and appiter.close()
                sys.settrace(None)
                self.ws.force_close()

        return wsgi_with_trace(environ, start_response)

    def get_file(self, filename, html_escape=True):
        checkcache(filename)
        file_ = "".join(getlines(filename))
        if not html_escape:
            return file_
        return escape(file_)

    def handle_connection(self):
        if self.connected:
            try:
                self.send("Ping")
            except:
                log.exception("Ping Failed")
                self.connected = False
        if not self.connected:
            self.ws.wait_for_connect()
            self.connected = True

    def get_trace(self, frame, tb, w_code=None):
        frames = []
        stack, current = self.get_stack(frame, tb)

        for i, (frame, lno) in enumerate(stack):
            code = frame.f_code
            filename = code.co_filename
            if filename == "<wdb>" and w_code:
                line = w_code
            else:
                checkcache(filename)
                line = getline(filename, lno, frame.f_globals)
                line = line and line.strip()

            startlnos = dis.findlinestarts(code)
            lastlineno = list(startlnos)[-1][1]
            frames.append(
                {
                    "file": filename,
                    "function": code.co_name,
                    "flno": code.co_firstlineno,
                    "llno": lastlineno,
                    "lno": lno,
                    "code": escape(line),
                    "level": i,
                }
            )

        return stack, frames, current

    def handle_exc(self):
        type_, value = exc_info()[:2]
        return '<a title="%s">%s: %s</a>' % (
            escape(traceback.format_exc().replace('"', "'")),
            escape(type_.__name__),
            escape(str(value)),
        )

    def interaction(self, frame, tb=None, exception="Wdb", exception_description="Set Trace"):
        if not self.ws:
            raise BdbQuit()
        try:
            self._interaction(frame, tb, exception, exception_description)
        except WsError:
            log.exception("Websocket Error during interaction. Starting again")
            self.handle_connection()
            self.interaction(frame, tb, exception, exception_description)

    def send(self, data):
        self.ws.send(data)

    def receive(self):
        message = None
        while not message:
            rv = self.ws.receive()
            if rv == "CLOSED":
                raise WsError
            message = rv
        return message

    def _interaction(self, frame, tb, exception, exception_description):

        log.debug("Interaction for %r %r %r %r" % (frame, tb, exception, exception_description))
        stack, trace, current_index = self.get_trace(frame, tb)
        current = trace[current_index]
        locals_ = map(lambda x: x[0].f_locals, stack)

        if self.begun:
            self.send("Trace|%s" % dump({"trace": trace, "cwd": os.getcwd()}))
            current_file = current["file"]
            self.send(
                "Check|%s" % dump({"name": current_file, "sha512": sha512(self.get_file(current_file)).hexdigest()})
            )
        else:
            self.begun = True

        while True:
            try:
                message = self.receive()
                if "|" in message:
                    pipe = message.index("|")
                    cmd = message[:pipe]
                    data = message[pipe + 1 :]
                else:
                    cmd = message
                    data = ""

                def fail(title=None, message=None):
                    if message is None:
                        message = self.handle_exc()
                    else:
                        message = escape(message)
                    self.send("Echo|%s" % dump({"for": escape(title or "%s failed" % cmd), "val": message}))

                log.debug("Cmd %s #Data %d" % (cmd, len(data)))
                if cmd == "Start":
                    self.send("Init|%s" % dump({"cwd": os.getcwd()}))
                    self.send("Title|%s" % dump({"title": exception, "subtitle": exception_description}))
                    self.send("Trace|%s" % dump({"trace": trace}))
                    current_file = current["file"]
                    self.send(
                        "Check|%s"
                        % dump({"name": current_file, "sha512": sha512(self.get_file(current_file)).hexdigest()})
                    )

                elif cmd == "Select":
                    current_index = int(data)
                    current = trace[current_index]
                    current_file = current["file"]
                    self.send(
                        "Check|%s"
                        % dump({"name": current_file, "sha512": sha512(self.get_file(current_file)).hexdigest()})
                    )

                elif cmd == "File":
                    current_file = current["file"]
                    self.send(
                        "Select|%s"
                        % dump(
                            {
                                "frame": current,
                                "breaks": self.get_file_breaks(current_file),
                                "file": self.get_file(current_file),
                                "name": current_file,
                                "sha512": sha512(self.get_file(current_file)).hexdigest(),
                            }
                        )
                    )

                elif cmd == "NoFile":
                    self.send("Select|%s" % dump({"frame": current, "breaks": self.get_file_breaks(current["file"])}))

                elif cmd == "Inspect":
                    try:
                        thing = self.obj_cache.get(int(data))
                    except Exception:
                        fail()
                        continue
                    self.send("Dump|%s" % dump({"for": escape(repr(thing)), "val": self.dmp(thing)}))

                elif cmd == "Dump":
                    globals_ = dict(stack[current_index][0].f_globals)
                    try:
                        thing = eval(data, globals_, locals_[current_index])
                    except Exception:
                        fail()
                        continue
                    else:
                        self.send(
                            "Dump|%s" % dump({"for": escape(u"%s ⟶ %s " % (data, repr(thing))), "val": self.dmp(thing)})
                        )

                elif cmd == "Trace":
                    self.send("Trace|%s" % dump({"trace": trace}))

                elif cmd == "Eval":
                    redir = None
                    raw_data = data = data.strip()
                    if "!>" in data:
                        data, redir = data.split("!>")
                        data = data.strip()
                        redir = redir.strip()
                    elif data.startswith("!<"):
                        filename = data[2:].strip()
                        try:
                            with open(filename, "r") as f:
                                data = f.read()
                        except Exception:
                            fail("Unable to read from file %s" % filename)
                            continue
                    globals_ = dict(stack[current_index][0].f_globals)
                    # Hack for function scope eval
                    globals_.update(locals_[current_index])
                    globals_.setdefault("_pprint", pprint)
                    with self.capture_output(with_hook=redir is None) as (out, err):
                        try:
                            compiled_code = compile(data, "<stdin>", "single")
                            l = locals_[current_index]
                            exec compiled_code in globals_, l
                        except Exception:
                            self.hooked = self.handle_exc()
                    if redir:
                        try:
                            with open(redir, "w") as f:
                                f.write("\n".join(out) + "\n".join(err) + "\n")
                        except Exception:
                            fail("Unable to write to file %s" % redir)
                            continue
                        self.send(
                            "Print|%s" % dump({"for": escape(raw_data), "result": escape("Written to file %s" % redir)})
                        )
                    else:
                        self.send(
                            "Print|%s"
                            % dump(
                                {
                                    "for": escape(raw_data),
                                    "result": self.hooked + escape("\n".join(out) + "\n".join(err)),
                                }
                            )
                        )

                elif cmd == "Ping":
                    self.send("Pong")

                elif cmd == "Step":
                    if hasattr(self, "botframe"):
                        self.set_step()
                    break

                elif cmd == "Next":
                    if hasattr(self, "botframe"):
                        self.set_next(stack[current_index][0])
                    break

                elif cmd == "Continue":
                    if hasattr(self, "botframe"):
                        self.set_continue()
                    break

                elif cmd == "Return":
                    if hasattr(self, "botframe"):
                        self.set_return(stack[current_index][0])
                    break

                elif cmd == "Until":
                    if hasattr(self, "botframe"):
                        self.set_until(stack[current_index][0])
                    break

                elif cmd in ("TBreak", "Break"):
                    break_fail = lambda x: fail("Break on %s failed" % data, message=x)
                    if ":" in data:
                        fn, lno = data.split(":")
                    else:
                        fn, lno = current["file"], data
                    cond = None
                    if "," in lno:
                        lno, cond = lno.split(",")
                        cond = cond.lstrip()

                    try:
                        lno = int(lno)
                    except:
                        break_fail("Wrong breakpoint format must be " "[file:]lno[,cond].")
                        continue

                    line = getline(fn, lno, stack[current_index][0].f_globals)
                    if not line:
                        break_fail("Line does not exist")
                        continue
                    line = line.strip()
                    if not line or (line[0] == "#") or (line[:3] == '"""') or line[:3] == "'''":
                        break_fail("Blank line or comment")
                        continue

                    first_rv = rv = self.set_break(fn, lno, int(cmd == "TBreak"), cond)
                    if rv is not None:
                        for path in sys.path:
                            rv = self.set_break(os.path.join(path, fn), lno, int(cmd == "TBreak"), cond)
                            if rv is None:
                                break
                    if rv is None:
                        log.info("Break set at %s:%d [%s]" % (fn, lno, rv))
                        if fn == current["file"]:
                            self.send("BreakSet|%s" % dump({"lno": lno, "cond": cond}))
                        else:
                            self.send("BreakSet|%s" % dump({}))
                    else:
                        break_fail(first_rv)

                elif cmd == "Unbreak":
                    lno = int(data)
                    current_file = current["file"]
                    log.info("Break unset at %s:%d" % (current_file, lno))
                    self.clear_break(current_file, lno)
                    self.send("BreakUnset|%s" % dump({"lno": lno}))

                elif cmd == "Jump":
                    lno = int(data)
                    if current_index != len(trace) - 1:
                        log.error("Must be at bottom frame")
                        continue

                    try:
                        stack[current_index][0].f_lineno = lno
                    except ValueError:
                        fail()
                        continue

                    trace[current_index]["lno"] = lno
                    self.send("Trace|%s" % dump({"trace": trace}))
                    self.send("Select|%s" % dump({"frame": current, "breaks": self.get_file_breaks(current["file"])}))

                elif cmd == "Complete":
                    current_file = current["file"]
                    file_ = self.get_file(current_file, False).decode("utf-8")
                    lines = file_.split(u"\n")
                    lno = trace[current_index]["lno"]
                    line_before = lines[lno - 1]
                    indent = len(line_before) - len(line_before.lstrip())
                    segments = data.split(u"\n")
                    for segment in reversed(segments):
                        line = u" " * indent + segment
                        lines.insert(lno - 1, line)
                    script = Script(u"\n".join(lines), lno - 1 + len(segments), len(segments[-1]) + indent, "")
                    try:
                        completions = script.complete()
                    except:
                        log.exception("Completion failed")
                        self.send(
                            "Log|%s" % dump({"message": "Completion failed for %s" % "\n".join(reversed(segments))})
                        )
                    else:
                        fun = script.get_in_function_call()
                        self.send(
                            "Suggest|%s"
                            % dump(
                                {
                                    "params": {
                                        "params": [p.get_code().replace("\n", "") for p in fun.params],
                                        "index": fun.index,
                                        "module": fun.module.path,
                                        "call_name": fun.call_name,
                                    }
                                    if fun
                                    else None,
                                    "completions": [
                                        {
                                            "base": comp.word[: len(comp.word) - len(comp.complete)],
                                            "complete": comp.complete,
                                            "description": comp.description,
                                        }
                                        for comp in completions
                                        if comp.word.endswith(comp.complete)
                                    ],
                                }
                            )
                        )

                elif cmd == "Quit":
                    if hasattr(self, "botframe"):
                        self.set_continue()
                        raise BdbQuit()
                    break

                else:
                    log.warn("Unknown command %s" % cmd)

            except BdbQuit:
                raise
            except Exception:
                try:
                    exc = self.handle_exc()
                    type_, value = exc_info()[:2]
                    link = (
                        '<a href="https://github.com/Kozea/wdb/issues/new?'
                        'title=%s&body=%s&labels=defect" class="nogood">'
                        "Please click here to report it on Github</a>"
                    ) % (
                        quote("%s: %s" % (type_.__name__, str(value))),
                        quote("```\n%s\n```\n" % traceback.format_exc()),
                    )
                    self.send(
                        "Echo|%s" % dump({"for": escape("Error in Wdb, this is bad"), "val": exc + "<br>" + link})
                    )
                except:
                    self.send(
                        "Echo|%s"
                        % dump(
                            {
                                "for": escape("Too many errors"),
                                "val": escape("Don't really know what to say. " "Maybe it will work tomorrow."),
                            }
                        )
                    )
                    continue

    def user_call(self, frame, argument_list):
        """This method is called when there is the remote possibility
        that we ever need to stop in this function."""
        if self.stop_here(frame):
            fun = frame.f_code.co_name
            log.info("Calling: %r" % fun)
            self.handle_connection()
            self.send("Echo|%s" % dump({"for": "__call__", "val": fun}))
            self.interaction(frame)

    def user_line(self, frame):
        """This function is called when we stop or break at this line."""
        log.info("Stopping at line %r:%d" % (frame.f_code.co_filename, frame.f_lineno))
        self.handle_connection()
        log.debug("User Line Interaction for %r" % frame)
        self.interaction(frame)

    def user_return(self, frame, return_value):
        """This function is called when a return trap is set here."""
        frame.f_locals["__return__"] = return_value
        log.info("Returning from %r with value: %r" % (frame.f_code.co_name, return_value))
        self.handle_connection()
        self.send("Echo|%s" % dump({"for": "__return__", "val": return_value}))
        self.interaction(frame)

    def user_exception(self, frame, exc_info):
        """This function is called if an exception occurs,
        but only if we are to stop at or just below this level."""
        log.error("Exception", exc_info=exc_info)
        type_, value, tb = exc_info
        frame.f_locals["__exception__"] = type_, value
        exception = type_.__name__
        exception_description = str(value)
        self.handle_connection()
        self.send(
            "Echo|%s" % dump({"for": "__exception__", "val": escape("%s: %s") % (exception, exception_description)})
        )
        if not self.begun:
            frame = None
        self.interaction(frame, tb, exception, exception_description)

    def do_clear(self, arg):
        log.info("Closing %r" % arg)
        self.clear_bpbynumber(arg)

    def dispatch_exception(self, frame, arg):
        self.user_exception(frame, arg)
        if self.quitting:
            raise BdbQuit
        return self.trace_dispatch

    def recursive(self, g, l):
        # Inspect curent debugger vars through pdb
        sys.settrace(None)
        from pdb import Pdb

        p = Pdb()
        sys.call_tracing(p.run, ("1/0", g, l))
        sys.settrace(self.trace_dispatch)
        self.lastcmd = p.lastcmd
示例#2
0
文件: __init__.py 项目: chiehwen/wdb
class WdbRequest(object, Bdb):
    """Wdb debugger main class"""
    __metaclass__ = MetaWdbRequest

    def __init__(self, port, skip=None):
        MetaWdbRequest._last_inst = self
        try:
            Bdb.__init__(self, skip=skip)
        except TypeError:
            Bdb.__init__(self)
        self.begun = False
        self.connected = False
        self.make_web_socket(port)
        breaks_per_file_lno = Breakpoint.bplist.values()
        for bps in breaks_per_file_lno:
            breaks = list(bps)
            for bp in breaks:
                args = bp.file, bp.line, bp.temporary, bp.cond
                self.set_break(*args)
                log.info('Resetting break %s' % repr(args))

    def make_web_socket(self, port):
        log.info('Creating WebSocket')
        self.ws = WebSocket('0.0.0.0', port)

    def wsgi_trace(self, app, environ, start_response):
        def wsgi_with_trace(environ, start_response):
            self.quitting = 0
            self.begun = False
            self.reset()
            frame = sys._getframe()
            while frame:
                frame.f_trace = self.trace_dispatch
                self.botframe = frame
                frame = frame.f_back
            self.stopframe = sys._getframe().f_back
            self.stoplineno = -1
            sys.settrace(self.trace_dispatch)

            try:
                appiter = app(environ, start_response)
            except BdbQuit:
                sys.settrace(None)
                start_response('200 OK', [('Content-Type', 'text/html')])
                yield '<h1>BdbQuit</h1><p>Wdb was interrupted</p>'
            else:
                for item in appiter:
                    yield item
                hasattr(appiter, 'close') and appiter.close()
                sys.settrace(None)
                self.ws.force_close()
            MetaWdbRequest._last_inst = None
        return wsgi_with_trace(environ, start_response)

    def get_file(self, filename, html_escape=True):
        checkcache(filename)
        file_ = ''.join(getlines(filename))
        if not html_escape:
            return file_
        return escape(file_)

    def handle_connection(self):
        if self.connected:
            try:
                self.send('Ping')
            except:
                log.exception('Ping Failed')
                self.connected = False
        if not self.connected:
            self.ws.wait_for_connect()
            self.connected = True

    def get_trace(self, frame, tb, w_code=None):
        frames = []
        stack, current = self.get_stack(frame, tb)

        for i, (frame, lno) in enumerate(stack):
            code = frame.f_code
            filename = code.co_filename
            if filename == '<wdb>' and w_code:
                line = w_code
            else:
                checkcache(filename)
                line = getline(filename, lno, frame.f_globals)
                line = line and line.strip()

            startlnos = dis.findlinestarts(code)
            lastlineno = list(startlnos)[-1][1]
            frames.append({
                'file': filename,
                'function': code.co_name,
                'flno': code.co_firstlineno,
                'llno': lastlineno,
                'lno': lno,
                'code': escape(line),
                'level': i
            })

        return stack, frames, current

    def interaction(
            self, frame, tb=None,
            exception='Wdb', exception_description='Set Trace'):
        if not self.ws:
            raise BdbQuit()
        try:
            self._interaction(
                frame, tb, exception, exception_description)
        except WsError:
            log.exception('Websocket Error during interaction. Starting again')
            self.handle_connection()
            self.interaction(
                frame, tb, exception, exception_description)

    def send(self, data):
        self.ws.send(data)

    def receive(self):
        message = None
        while not message:
            rv = self.ws.receive()
            if rv == 'CLOSED':
                raise WsError
            message = rv
        return message

    def _interaction(
            self, frame, tb,
            exception, exception_description):
        log.debug('Interaction for %r %r %r %r' % (
            frame, tb, exception, exception_description))
        stack, trace, current_index = self.get_trace(frame, tb)
        current = trace[current_index]
        locals_ = map(lambda x: x[0].f_locals, stack)

        if self.begun:
            self.send('Trace|%s' % dump({
                'trace': trace,
                'cwd': os.getcwd()
            }))
            current_file = current['file']
            self.send('Check|%s' % dump({
                'name': current_file,
                'sha512': sha512(self.get_file(current_file)).hexdigest()
            }))
        else:
            self.begun = True

        while True:
            message = self.receive()
            if '|' in message:
                pipe = message.index('|')
                cmd = message[:pipe]
                data = message[pipe + 1:]
            else:
                cmd = message
                data = ''

            log.debug('Cmd %s #Data %d' % (cmd, len(data)))
            if cmd == 'Start':
                self.send('Init|%s' % dump({
                    'cwd': os.getcwd()
                }))
                self.send('Title|%s' % dump({
                    'title': exception,
                    'subtitle': exception_description
                }))
                self.send('Trace|%s' % dump({
                    'trace': trace
                }))
                current_file = current['file']
                self.send('Check|%s' % dump({
                    'name': current_file,
                    'sha512': sha512(self.get_file(current_file)).hexdigest()
                }))

            elif cmd == 'Select':
                current_index = int(data)
                current = trace[current_index]
                current_file = current['file']
                self.send('Check|%s' % dump({
                    'name': current_file,
                    'sha512': sha512(self.get_file(current_file)).hexdigest()
                }))

            elif cmd == 'File':
                current_file = current['file']
                self.send('Select|%s' % dump({
                    'frame': current,
                    'breaks': self.get_file_breaks(current_file),
                    'file': self.get_file(current_file),
                    'name': current_file,
                    'sha512': sha512(self.get_file(current_file)).hexdigest()
                }))

            elif cmd == 'NoFile':
                self.send('Select|%s' % dump({
                    'frame': current,
                    'breaks': self.get_file_breaks(current['file'])
                }))

            elif cmd == 'Inspect':
                try:
                    thing = reverse_id(int(data))
                except:
                    continue
                self.send('Dump|%s' % dump({
                    'for': escape(repr(thing)),
                    'val': dmp(thing)}))

            elif cmd == 'Dump':
                globals_ = dict(stack[current_index][0].f_globals)
                try:
                    thing = eval(data, globals_, locals_[current_index])
                except:
                    continue

                self.send('Dump|%s' % dump({
                    'for': escape(repr(thing)),
                    'val': dmp(thing)}))

            elif cmd == 'Trace':
                self.send('Trace|%s' % dump({
                    'trace': trace
                }))

            elif cmd == 'Eval':
                globals_ = dict(stack[current_index][0].f_globals)
                # Hack for function scope eval
                globals_.update(locals_[current_index])
                globals_.setdefault('_pprint', pprint)
                globals_.setdefault('_dump', dmp)
                with capture_output() as (out, err):
                    try:
                        compiled_code = compile(data, '<stdin>', 'single')
                        exec compiled_code in globals_, locals_[current_index]
                    except Exception:
                        type_, value, tb = exc_info()
                        print '%s: %s' % (type_.__name__, str(value))
                self.send('Print|%s' % dump({
                    'result': escape('\n'.join(out) + '\n'.join(err))
                }))

            elif cmd == 'Ping':
                self.send('Pong')

            elif cmd == 'Step':
                if hasattr(self, 'botframe'):
                    self.set_step()
                break

            elif cmd == 'Next':
                if hasattr(self, 'botframe'):
                    self.set_next(stack[current_index][0])
                break

            elif cmd == 'Continue':
                if hasattr(self, 'botframe'):
                    self.set_continue()
                break

            elif cmd == 'Return':
                if hasattr(self, 'botframe'):
                    self.set_return(stack[current_index][0])
                break

            elif cmd == 'Until':
                if hasattr(self, 'botframe'):
                    self.set_until(stack[current_index][0])
                break

            elif cmd in ('TBreak', 'Break'):
                if ':' in data:
                    fn, lno = data.split(':')
                else:
                    fn, lno = current['file'], data
                cond = None
                if ',' in lno:
                    lno, cond = lno.split(',')
                    cond = cond.lstrip()

                lno = int(lno)
                rv = self.set_break(fn, lno, int(cmd == 'TBreak'), cond)
                if rv is not None:
                    for path in sys.path:
                        rv = self.set_break(
                            os.path.join(path, fn),
                            lno, int(cmd == 'TBreak'), cond)
                        if rv is None:
                            break
                if rv is None:
                    log.info('Break set at %s:%d [%s]' % (fn, lno, rv))
                    if fn == current['file']:
                        self.send('BreakSet|%s' % dump({
                            'lno': lno, 'cond': cond
                        }))
                    else:
                        self.send('BreakSet|%s' % dump({}))
                else:
                    self.send('Log|%s' % dump({
                        'message': rv
                    }))

            elif cmd == 'Unbreak':
                lno = int(data)
                current_file = current['file']
                log.info('Break unset at %s:%d' % (current_file, lno))
                self.clear_break(current_file, lno)
                self.send('BreakUnset|%s' % dump({'lno': lno}))

            elif cmd == 'Jump':
                lno = int(data)
                if current_index != len(trace) - 1:
                    log.error('Must be at bottom frame')
                    continue

                try:
                    stack[current_index][0].f_lineno = lno
                except ValueError:
                    log.error('Jump failed')
                    continue

                trace[current_index]['lno'] = lno
                self.send('Trace|%s' % dump({
                    'trace': trace
                }))
                self.send('Select|%s' % dump({
                    'frame': current,
                    'breaks': self.get_file_breaks(current['file'])
                }))

            elif cmd == 'Complete':
                current_file = current['file']
                file_ = self.get_file(current_file, False).decode('utf-8')
                lines = file_.split(u'\n')
                lno = trace[current_index]['lno']
                line_before = lines[lno - 1]
                indent = len(line_before) - len(line_before.lstrip())
                segments = data.split(u'\n')
                for segment in reversed(segments):
                    line = u' ' * indent + segment
                    lines.insert(lno - 1, line)
                script = Script(
                    u'\n'.join(lines), lno - 1 + len(segments),
                    len(segments[-1]) + indent, '')
                try:
                    completions = script.complete()
                except:
                    self.send('Log|%s' % dump({
                        'message': 'Completion failed for %s' %
                        '\n'.join(reversed(segments))
                    }))
                else:
                    fun = script.get_in_function_call()
                    self.send('Suggest|%s' % dump({
                        'params': {
                            'params': [p.get_code().replace('\n', '')
                                       for p in fun.params],
                            'index': fun.index,
                            'module': fun.module.path,
                            'call_name': fun.call_name} if fun else None,
                        'completions': [{
                            'base': comp.word[
                                :len(comp.word) - len(comp.complete)],
                            'complete': comp.complete,
                            'description': comp.description
                        } for comp in completions if comp.word.endswith(
                            comp.complete)]
                    }))

            elif cmd == 'Quit':
                if hasattr(self, 'botframe'):
                    self.set_continue()
                    raise BdbQuit()
                break

            else:
                log.warn('Unknown command %s' % cmd)

    def user_call(self, frame, argument_list):
        """This method is called when there is the remote possibility
        that we ever need to stop in this function."""
        if self.stop_here(frame):
            fun = frame.f_code.co_name
            log.info('Calling: %r' % fun)
            self.handle_connection()
            self.send('Echo|%s' % dump({
                'for': '__call__',
                'val': fun}))
            self.interaction(frame)

    def user_line(self, frame):
        """This function is called when we stop or break at this line."""
        log.info('Stopping at line %r:%d' % (
            frame.f_code.co_filename, frame.f_lineno))
        self.handle_connection()
        log.debug('User Line Interaction for %r' % frame)
        self.interaction(frame)

    def user_return(self, frame, return_value):
        """This function is called when a return trap is set here."""
        frame.f_locals['__return__'] = return_value
        log.info('Returning from %r with value: %r' % (
            frame.f_code.co_name, return_value))
        self.handle_connection()
        self.send('Echo|%s' % dump({
            'for': '__return__',
            'val': return_value
        }))
        self.interaction(frame)

    def user_exception(self, frame, exc_info):
        """This function is called if an exception occurs,
        but only if we are to stop at or just below this level."""
        log.error('Exception', exc_info=exc_info)
        type_, value, tb = exc_info
        frame.f_locals['__exception__'] = type_, value
        exception = type_.__name__
        exception_description = str(value)
        self.handle_connection()
        self.send('Echo|%s' % dump({
            'for': '__exception__',
            'val': '%s: %s' % (
            exception, exception_description)}))
        if not self.begun:
            frame = None
        self.interaction(frame, tb, exception, exception_description)

    def do_clear(self, arg):
        log.info('Closing %r' % arg)
        self.clear_bpbynumber(arg)

    def dispatch_exception(self, frame, arg):
        self.user_exception(frame, arg)
        if self.quitting:
            raise BdbQuit
        return self.trace_dispatch

    def recursive(self, g, l):
        # Inspect curent debugger vars through pdb
        sys.settrace(None)
        from pdb import Pdb
        p = Pdb()
        sys.call_tracing(p.run, ('1/0', g, l))
        sys.settrace(self.trace_dispatch)
        self.lastcmd = p.lastcmd
示例#3
0
class WdbRequest(object, Bdb):
    """Wdb debugger main class"""
    __metaclass__ = MetaWdbRequest

    def __init__(self, ports, skip=None):
        MetaWdbRequest._last_inst = self
        self.obj_cache = {}

        try:
            Bdb.__init__(self, skip=skip)
        except TypeError:
            Bdb.__init__(self)
        self.begun = False
        self.connected = False
        self.make_web_socket(ports)
        breaks_per_file_lno = Breakpoint.bplist.values()
        for bps in breaks_per_file_lno:
            breaks = list(bps)
            for bp in breaks:
                args = bp.file, bp.line, bp.temporary, bp.cond
                self.set_break(*args)
                log.info('Resetting break %s' % repr(args))

    def safe_repr(self, obj):
        try:
            return repr(obj)
        except Exception as e:
            return '??? Broken repr (%s: %s)' % (type(e).__name__, e)

    def safe_better_repr(self, obj):
        try:
            rv = self.better_repr(obj)
        except Exception:
            rv = None
        if rv:
            return rv

        self.obj_cache[id(obj)] = obj
        return '<a href="%d" class="inspect">%s</a>' % (
            id(obj), escape(repr(obj)))

    def better_repr(self, obj):
        if isinstance(obj, dict):
            if type(obj) != dict:
                dict_repr = type(obj).__name__ + '({'
                closer = '})'
            else:
                dict_repr = '{'
                closer = '}'
            dict_repr += ', '.join([
                self.safe_repr(key) + ': ' + self.safe_better_repr(val)
                for key, val in obj.items()])

            dict_repr += closer
            return dict_repr

        if any([
                isinstance(obj, list),
                isinstance(obj, set),
                isinstance(obj, tuple)]):
            if type(obj) == list:
                iter_repr = '['
                closer = ']'
            elif type(obj) == set:
                iter_repr = '{'
                closer = '}'
            elif type(obj) == tuple:
                iter_repr = '('
                closer = ')'
            else:
                iter_repr = escape(obj.__class__.__name__) + '(['
                closer = '])'

            iter_repr += ', '.join([self.safe_better_repr(val) for val in obj])
            iter_repr += closer
            return iter_repr

    @contextmanager
    def capture_output(self, with_hook=True):
        self.hooked = ''

        def display_hook(obj):
            # That's some dirty hack
            self.hooked += self.safe_better_repr(obj)

        stdout, stderr = sys.stdout, sys.stderr
        if with_hook:
            d_hook = sys.displayhook
            sys.displayhook = display_hook

        sys.stdout, sys.stderr = StringIO(), StringIO()
        out, err = [], []
        try:
            yield out, err
        finally:
            out.extend(sys.stdout.getvalue().splitlines())
            err.extend(sys.stderr.getvalue().splitlines())
            if with_hook:
                sys.displayhook = d_hook

            sys.stdout, sys.stderr = stdout, stderr

    def dmp(self, thing):
        def safe_getattr(key):
            try:
                return getattr(thing, key)
            except Exception as e:
                return 'Error getting attr "%s" from "%s" (%s: %s)' % (
                    key, thing,
                    type(e).__name__, e)

        return dict(
            (escape(key), {
                'val': self.safe_better_repr(safe_getattr(key)),
                'type': type(safe_getattr(key)).__name__
            })
            for key in dir(thing)
        )

    def make_web_socket(self, ports):
        log.info('Creating WebSocket')
        for port in ports:
            self.ws = WebSocket('0.0.0.0', port)
            if self.ws.status == 'OK':
                return
            time.sleep(.100)

        raise WsError('No port could be opened')

    def wsgi_trace(self, app, environ, start_response):
        def wsgi_with_trace(environ, start_response):
            self.quitting = 0
            self.begun = False
            self.reset()
            frame = sys._getframe()
            while frame:
                frame.f_trace = self.trace_dispatch
                self.botframe = frame
                frame = frame.f_back
            self.stopframe = sys._getframe().f_back
            self.stoplineno = -1
            sys.settrace(self.trace_dispatch)

            try:
                appiter = app(environ, start_response)
            except BdbQuit:
                sys.settrace(None)
                start_response('200 OK', [('Content-Type', 'text/html')])
                yield '<h1>BdbQuit</h1><p>Wdb was interrupted</p>'
            else:
                for item in appiter:
                    yield item
                hasattr(appiter, 'close') and appiter.close()
                sys.settrace(None)
                self.ws.force_close()
        return wsgi_with_trace(environ, start_response)

    def get_file(self, filename, html_escape=True):
        checkcache(filename)
        file_ = ''.join(getlines(filename))
        if not html_escape:
            return file_
        return escape(file_)

    def handle_connection(self):
        if self.connected:
            try:
                self.send('Ping')
            except:
                log.exception('Ping Failed')
                self.connected = False
        if not self.connected:
            self.ws.wait_for_connect()
            self.connected = True

    def get_trace(self, frame, tb, w_code=None):
        frames = []
        stack, current = self.get_stack(frame, tb)

        for i, (frame, lno) in enumerate(stack):
            code = frame.f_code
            filename = code.co_filename
            if filename == '<wdb>' and w_code:
                line = w_code
            else:
                checkcache(filename)
                line = getline(filename, lno, frame.f_globals)
                line = line and line.strip()

            startlnos = dis.findlinestarts(code)
            lastlineno = list(startlnos)[-1][1]
            frames.append({
                'file': filename,
                'function': code.co_name,
                'flno': code.co_firstlineno,
                'llno': lastlineno,
                'lno': lno,
                'code': escape(line),
                'level': i
            })

        return stack, frames, current

    def handle_exc(self):
        type_, value = exc_info()[:2]
        return '<a title="%s">%s: %s</a>' % (
            escape(traceback.format_exc().replace('"', '\'')),
            escape(type_.__name__), escape(str(value)))

    def interaction(
            self, frame, tb=None,
            exception='Wdb', exception_description='Set Trace'):
        if not self.ws:
            raise BdbQuit()
        try:
            self._interaction(
                frame, tb, exception, exception_description)
        except WsError:
            log.exception('Websocket Error during interaction. Starting again')
            self.handle_connection()
            self.interaction(
                frame, tb, exception, exception_description)

    def send(self, data):
        self.ws.send(data)

    def receive(self):
        message = None
        while not message:
            rv = self.ws.receive()
            if rv == 'CLOSED':
                raise WsError
            message = rv
        return message

    def _interaction(
            self, frame, tb,
            exception, exception_description):

        log.debug('Interaction for %r %r %r %r' % (
            frame, tb, exception, exception_description))
        stack, trace, current_index = self.get_trace(frame, tb)
        current = trace[current_index]
        locals_ = map(lambda x: x[0].f_locals, stack)

        if self.begun:
            self.send('Trace|%s' % dump({
                'trace': trace,
                'cwd': os.getcwd()
            }))
            current_file = current['file']
            self.send('Check|%s' % dump({
                'name': current_file,
                'sha512': sha512(self.get_file(current_file)).hexdigest()
            }))
        else:
            self.begun = True

        while True:
            try:
                message = self.receive()
                if '|' in message:
                    pipe = message.index('|')
                    cmd = message[:pipe]
                    data = message[pipe + 1:]
                else:
                    cmd = message
                    data = ''

                def fail(title=None, message=None):
                    if message is None:
                        message = self.handle_exc()
                    else:
                        message = escape(message)
                    self.send('Echo|%s' % dump({
                        'for': escape(title or '%s failed' % cmd),
                        'val': message
                    }))

                log.debug('Cmd %s #Data %d' % (cmd, len(data)))
                if cmd == 'Start':
                    self.send('Init|%s' % dump({
                        'cwd': os.getcwd()
                    }))
                    self.send('Title|%s' % dump({
                        'title': exception,
                        'subtitle': exception_description
                    }))
                    self.send('Trace|%s' % dump({
                        'trace': trace
                    }))
                    current_file = current['file']
                    self.send('Check|%s' % dump({
                        'name': current_file,
                        'sha512': sha512(
                            self.get_file(current_file)).hexdigest()
                    }))

                elif cmd == 'Select':
                    current_index = int(data)
                    current = trace[current_index]
                    current_file = current['file']
                    self.send('Check|%s' % dump({
                        'name': current_file,
                        'sha512': sha512(
                            self.get_file(current_file)).hexdigest()
                    }))

                elif cmd == 'File':
                    current_file = current['file']
                    self.send('Select|%s' % dump({
                        'frame': current,
                        'breaks': self.get_file_breaks(current_file),
                        'file': self.get_file(current_file),
                        'name': current_file,
                        'sha512': sha512(
                            self.get_file(current_file)).hexdigest()
                    }))

                elif cmd == 'NoFile':
                    self.send('Select|%s' % dump({
                        'frame': current,
                        'breaks': self.get_file_breaks(current['file'])
                    }))

                elif cmd == 'Inspect':
                    try:
                        thing = self.obj_cache.get(int(data))
                    except Exception:
                        fail()
                        continue
                    self.send('Dump|%s' % dump({
                        'for': escape(repr(thing)),
                        'val': self.dmp(thing)}))

                elif cmd == 'Dump':
                    globals_ = dict(stack[current_index][0].f_globals)
                    try:
                        thing = eval(data, globals_, locals_[current_index])
                    except Exception:
                        fail()
                        continue
                    else:
                        self.send('Dump|%s' % dump({
                            'for': escape(u'%s ⟶ %s ' % (data, repr(thing))),
                            'val': self.dmp(thing)}))

                elif cmd == 'Trace':
                    self.send('Trace|%s' % dump({
                        'trace': trace
                    }))

                elif cmd == 'Eval':
                    redir = None
                    raw_data = data = data.strip()
                    if '!>' in data:
                        data, redir = data.split('!>')
                        data = data.strip()
                        redir = redir.strip()
                    elif data.startswith('!<'):
                        filename = data[2:].strip()
                        try:
                            with open(filename, 'r') as f:
                                data = f.read()
                        except Exception:
                            fail('Unable to read from file %s' % filename)
                            continue
                    globals_ = dict(stack[current_index][0].f_globals)
                    # Hack for function scope eval
                    globals_.update(locals_[current_index])
                    globals_.setdefault('_pprint', pprint)
                    with self.capture_output(
                            with_hook=redir is None) as (out, err):
                        try:
                            compiled_code = compile(data, '<stdin>', 'single')
                            l = locals_[current_index]
                            exec compiled_code in globals_, l
                        except Exception:
                            self.hooked = self.handle_exc()
                    if redir:
                        try:
                            with open(redir, 'w') as f:
                                f.write('\n'.join(out) + '\n'.join(err) + '\n')
                        except Exception:
                            fail('Unable to write to file %s' % redir)
                            continue
                        self.send('Print|%s' % dump({
                            'for': escape(raw_data),
                            'result': escape('Written to file %s' % redir)
                        }))
                    else:
                        self.send('Print|%s' % dump({
                            'for': escape(raw_data),
                            'result': self.hooked + escape(
                                '\n'.join(out) + '\n'.join(err))
                        }))

                elif cmd == 'Ping':
                    self.send('Pong')

                elif cmd == 'Step':
                    if hasattr(self, 'botframe'):
                        self.set_step()
                    break

                elif cmd == 'Next':
                    if hasattr(self, 'botframe'):
                        self.set_next(stack[current_index][0])
                    break

                elif cmd == 'Continue':
                    if hasattr(self, 'botframe'):
                        self.set_continue()
                    break

                elif cmd == 'Return':
                    if hasattr(self, 'botframe'):
                        self.set_return(stack[current_index][0])
                    break

                elif cmd == 'Until':
                    if hasattr(self, 'botframe'):
                        self.set_until(stack[current_index][0])
                    break

                elif cmd in ('TBreak', 'Break'):
                    break_fail = lambda x: fail(
                        'Break on %s failed' % data, message=x)
                    if ':' in data:
                        fn, lno = data.split(':')
                    else:
                        fn, lno = current['file'], data
                    cond = None
                    if ',' in lno:
                        lno, cond = lno.split(',')
                        cond = cond.lstrip()

                    try:
                        lno = int(lno)
                    except:
                        break_fail(
                            'Wrong breakpoint format must be '
                            '[file:]lno[,cond].')
                        continue

                    line = getline(
                        fn, lno, stack[current_index][0].f_globals)
                    if not line:
                        break_fail('Line does not exist')
                        continue
                    line = line.strip()
                    if ((not line or (line[0] == '#') or
                         (line[:3] == '"""') or
                         line[:3] == "'''")):
                        break_fail('Blank line or comment')
                        continue

                    first_rv = rv = self.set_break(
                        fn, lno, int(cmd == 'TBreak'), cond)
                    if rv is not None:
                        for path in sys.path:
                            rv = self.set_break(
                                os.path.join(path, fn),
                                lno, int(cmd == 'TBreak'), cond)
                            if rv is None:
                                break
                    if rv is None:
                        log.info('Break set at %s:%d [%s]' % (fn, lno, rv))
                        if fn == current['file']:
                            self.send('BreakSet|%s' % dump({
                                'lno': lno, 'cond': cond
                            }))
                        else:
                            self.send('BreakSet|%s' % dump({}))
                    else:
                        break_fail(first_rv)

                elif cmd == 'Unbreak':
                    lno = int(data)
                    current_file = current['file']
                    log.info('Break unset at %s:%d' % (current_file, lno))
                    self.clear_break(current_file, lno)
                    self.send('BreakUnset|%s' % dump({'lno': lno}))

                elif cmd == 'Jump':
                    lno = int(data)
                    if current_index != len(trace) - 1:
                        log.error('Must be at bottom frame')
                        continue

                    try:
                        stack[current_index][0].f_lineno = lno
                    except ValueError:
                        fail()
                        continue

                    trace[current_index]['lno'] = lno
                    self.send('Trace|%s' % dump({
                        'trace': trace
                    }))
                    self.send('Select|%s' % dump({
                        'frame': current,
                        'breaks': self.get_file_breaks(current['file'])
                    }))

                elif cmd == 'Complete':
                    current_file = current['file']
                    file_ = self.get_file(current_file, False).decode('utf-8')
                    lines = file_.split(u'\n')
                    lno = trace[current_index]['lno']
                    line_before = lines[lno - 1]
                    indent = len(line_before) - len(line_before.lstrip())
                    segments = data.split(u'\n')
                    for segment in reversed(segments):
                        line = u' ' * indent + segment
                        lines.insert(lno - 1, line)
                    script = Script(
                        u'\n'.join(lines), lno - 1 + len(segments),
                        len(segments[-1]) + indent, '')
                    try:
                        completions = script.complete()
                    except:
                        log.exception('Completion failed')
                        self.send('Log|%s' % dump({
                            'message': 'Completion failed for %s' %
                            '\n'.join(reversed(segments))
                        }))
                    else:
                        fun = script.get_in_function_call()
                        self.send('Suggest|%s' % dump({
                            'params': {
                                'params': [p.get_code().replace('\n', '')
                                           for p in fun.params],
                                'index': fun.index,
                                'module': fun.module.path,
                                'call_name': fun.call_name} if fun else None,
                            'completions': [{
                                'base': comp.word[
                                    :len(comp.word) - len(comp.complete)],
                                'complete': comp.complete,
                                'description': comp.description
                            } for comp in completions if comp.word.endswith(
                                comp.complete)]
                        }))

                elif cmd == 'Quit':
                    if hasattr(self, 'botframe'):
                        self.set_continue()
                        raise BdbQuit()
                    break

                else:
                    log.warn('Unknown command %s' % cmd)

            except BdbQuit:
                raise
            except Exception:
                try:
                    exc = self.handle_exc()
                    type_, value = exc_info()[:2]
                    link = ('<a href="https://github.com/Kozea/wdb/issues/new?'
                            'title=%s&body=%s&labels=defect" class="nogood">'
                            'Please click here to report it on Github</a>') % (
                                quote('%s: %s' % (type_.__name__, str(value))),
                                quote('```\n%s\n```\n' %
                                      traceback.format_exc()))
                    self.send('Echo|%s' % dump({
                        'for': escape('Error in Wdb, this is bad'),
                        'val': exc + '<br>' + link
                    }))
                except:
                    self.send('Echo|%s' % dump({
                        'for': escape('Too many errors'),
                        'val': escape("Don't really know what to say. "
                                      "Maybe it will work tomorrow.")
                    }))
                    continue

    def user_call(self, frame, argument_list):
        """This method is called when there is the remote possibility
        that we ever need to stop in this function."""
        if self.stop_here(frame):
            fun = frame.f_code.co_name
            log.info('Calling: %r' % fun)
            self.handle_connection()
            self.send('Echo|%s' % dump({
                'for': '__call__',
                'val': fun}))
            self.interaction(frame)

    def user_line(self, frame):
        """This function is called when we stop or break at this line."""
        log.info('Stopping at line %r:%d' % (
            frame.f_code.co_filename, frame.f_lineno))
        self.handle_connection()
        log.debug('User Line Interaction for %r' % frame)
        self.interaction(frame)

    def user_return(self, frame, return_value):
        """This function is called when a return trap is set here."""
        frame.f_locals['__return__'] = return_value
        log.info('Returning from %r with value: %r' % (
            frame.f_code.co_name, return_value))
        self.handle_connection()
        self.send('Echo|%s' % dump({
            'for': '__return__',
            'val': return_value
        }))
        self.interaction(frame)

    def user_exception(self, frame, exc_info):
        """This function is called if an exception occurs,
        but only if we are to stop at or just below this level."""
        log.error('Exception', exc_info=exc_info)
        type_, value, tb = exc_info
        frame.f_locals['__exception__'] = type_, value
        exception = type_.__name__
        exception_description = str(value)
        self.handle_connection()
        self.send('Echo|%s' % dump({
            'for': '__exception__',
            'val': escape('%s: %s') % (
                exception, exception_description)}))
        if not self.begun:
            frame = None
        self.interaction(frame, tb, exception, exception_description)

    def do_clear(self, arg):
        log.info('Closing %r' % arg)
        self.clear_bpbynumber(arg)

    def dispatch_exception(self, frame, arg):
        self.user_exception(frame, arg)
        if self.quitting:
            raise BdbQuit
        return self.trace_dispatch

    def recursive(self, g, l):
        # Inspect curent debugger vars through pdb
        sys.settrace(None)
        from pdb import Pdb
        p = Pdb()
        sys.call_tracing(p.run, ('1/0', g, l))
        sys.settrace(self.trace_dispatch)
        self.lastcmd = p.lastcmd