Author: Armin Rigo <[email protected]>
Branch: py3.5-reverse-debugger
Changeset: r94657:3d3ad98443d6
Date: 2018-05-23 19:30 +0200
http://bitbucket.org/pypy/pypy/changeset/3d3ad98443d6/
Log: merge the two branches
diff too long, truncating to 2000 out of 7458 lines
diff --git a/.hgtags b/.hgtags
--- a/.hgtags
+++ b/.hgtags
@@ -33,7 +33,12 @@
050d84dd78997f021acf0e133934275d63547cc0 release-pypy2.7-v5.4.1
050d84dd78997f021acf0e133934275d63547cc0 release-pypy2.7-v5.4.1
0e2d9a73f5a1818d0245d75daccdbe21b2d5c3ef release-pypy2.7-v5.4.1
+4909c06daf41ce88f87dc01c57959cadad4df4a8 RevDB-pypy2.7-v5.4.1
+4909c06daf41ce88f87dc01c57959cadad4df4a8 RevDB-pypy2.7-v5.4.1
+d7724c0a5700b895a47de44074cdf5fd659a988f RevDB-pypy2.7-v5.4.1
aff251e543859ce4508159dd9f1a82a2f553de00 release-pypy2.7-v5.6.0
+e90317857d27917bf840caf675832292ee070510 RevDB-pypy2.7-v5.6.1
+a24d6c7000c8099c73d3660857f7e3cee5ac045c RevDB-pypy2.7-v5.6.2
fa3249d55d15b9829e1be69cdf45b5a44cec902d release-pypy2.7-v5.7.0
b16a4363e930f6401bceb499b9520955504c6cb0 release-pypy3.5-v5.7.0
1aa2d8e03cdfab54b7121e93fda7e98ea88a30bf release-pypy2.7-v5.7.1
diff --git a/lib-python/3/opcode.py b/lib-python/3/opcode.py
--- a/lib-python/3/opcode.py
+++ b/lib-python/3/opcode.py
@@ -223,5 +223,6 @@
def_op('CALL_METHOD', 202) # #args not including 'self'
def_op('BUILD_LIST_FROM_ARG', 203)
jrel_op('JUMP_IF_NOT_DEBUG', 204) # jump over assert statements
+def_op('LOAD_REVDB_VAR', 205) # reverse debugger (syntax example: $5)
del def_op, name_op, jrel_op, jabs_op
diff --git a/pypy/config/pypyoption.py b/pypy/config/pypyoption.py
--- a/pypy/config/pypyoption.py
+++ b/pypy/config/pypyoption.py
@@ -61,6 +61,11 @@
"termios", "_minimal_curses",
])
+reverse_debugger_disable_modules = set([
+ "_continuation", "_vmprof", "_multiprocessing",
+ "micronumpy",
+ ])
+
# XXX this should move somewhere else, maybe to platform ("is this posixish"
# check or something)
if sys.platform == "win32":
@@ -297,6 +302,9 @@
modules = working_modules.copy()
if config.translation.sandbox:
modules = default_modules
+ if config.translation.reverse_debugger:
+ for mod in reverse_debugger_disable_modules:
+ setattr(config.objspace.usemodules, mod, False)
# ignore names from 'essential_modules', notably 'exceptions', which
# may not be present in config.objspace.usemodules at all
modules = [name for name in modules if name not in essential_modules]
diff --git a/pypy/interpreter/app_main.py b/pypy/interpreter/app_main.py
--- a/pypy/interpreter/app_main.py
+++ b/pypy/interpreter/app_main.py
@@ -88,11 +88,17 @@
run_protected() handles details like forwarding exceptions to
sys.excepthook(), catching SystemExit, etc.
"""
+ if '__pypy__' in sys.builtin_module_names:
+ from __pypy__ import revdb_stop
+ else:
+ revdb_stop = None
try:
# run it
try:
f(*fargs, **fkwds)
finally:
+ if revdb_stop:
+ revdb_stop()
sys.settrace(None)
sys.setprofile(None)
except SystemExit as e:
diff --git a/pypy/interpreter/astcompiler/assemble.py
b/pypy/interpreter/astcompiler/assemble.py
--- a/pypy/interpreter/astcompiler/assemble.py
+++ b/pypy/interpreter/astcompiler/assemble.py
@@ -692,6 +692,7 @@
# TODO
ops.BUILD_LIST_FROM_ARG: 1,
+ ops.LOAD_REVDB_VAR: 1,
ops.LOAD_CLASSDEREF: 1,
}
diff --git a/pypy/interpreter/astcompiler/ast.py
b/pypy/interpreter/astcompiler/ast.py
--- a/pypy/interpreter/astcompiler/ast.py
+++ b/pypy/interpreter/astcompiler/ast.py
@@ -1733,6 +1733,8 @@
return Num.from_object(space, w_node)
if space.isinstance_w(w_node, get(space).w_Str):
return Str.from_object(space, w_node)
+ if space.isinstance_w(w_node, get(space).w_RevDBMetaVar):
+ return RevDBMetaVar.from_object(space, w_node)
if space.isinstance_w(w_node, get(space).w_FormattedValue):
return FormattedValue.from_object(space, w_node)
if space.isinstance_w(w_node, get(space).w_JoinedStr):
@@ -2643,6 +2645,41 @@
State.ast_type('Str', 'expr', ['s'])
+class RevDBMetaVar(expr):
+
+ def __init__(self, metavar, lineno, col_offset):
+ self.metavar = metavar
+ expr.__init__(self, lineno, col_offset)
+
+ def walkabout(self, visitor):
+ visitor.visit_RevDBMetaVar(self)
+
+ def mutate_over(self, visitor):
+ return visitor.visit_RevDBMetaVar(self)
+
+ def to_object(self, space):
+ w_node = space.call_function(get(space).w_RevDBMetaVar)
+ w_metavar = space.newint(self.metavar) # int
+ space.setattr(w_node, space.newtext('metavar'), w_metavar)
+ w_lineno = space.newint(self.lineno) # int
+ space.setattr(w_node, space.newtext('lineno'), w_lineno)
+ w_col_offset = space.newint(self.col_offset) # int
+ space.setattr(w_node, space.newtext('col_offset'), w_col_offset)
+ return w_node
+
+ @staticmethod
+ def from_object(space, w_node):
+ w_metavar = get_field(space, w_node, 'metavar', False)
+ w_lineno = get_field(space, w_node, 'lineno', False)
+ w_col_offset = get_field(space, w_node, 'col_offset', False)
+ _metavar = space.int_w(w_metavar)
+ _lineno = space.int_w(w_lineno)
+ _col_offset = space.int_w(w_col_offset)
+ return RevDBMetaVar(_metavar, _lineno, _col_offset)
+
+State.ast_type('RevDBMetaVar', 'expr', ['metavar'])
+
+
class FormattedValue(expr):
def __init__(self, value, conversion, format_spec, lineno, col_offset):
@@ -4130,6 +4167,8 @@
return self.default_visitor(node)
def visit_Str(self, node):
return self.default_visitor(node)
+ def visit_RevDBMetaVar(self, node):
+ return self.default_visitor(node)
def visit_FormattedValue(self, node):
return self.default_visitor(node)
def visit_JoinedStr(self, node):
@@ -4363,6 +4402,9 @@
def visit_Str(self, node):
pass
+ def visit_RevDBMetaVar(self, node):
+ pass
+
def visit_FormattedValue(self, node):
node.value.walkabout(self)
if node.format_spec:
diff --git a/pypy/interpreter/astcompiler/astbuilder.py
b/pypy/interpreter/astcompiler/astbuilder.py
--- a/pypy/interpreter/astcompiler/astbuilder.py
+++ b/pypy/interpreter/astcompiler/astbuilder.py
@@ -1264,6 +1264,11 @@
else:
# a dictionary display
return self.handle_dictdisplay(maker, atom_node)
+ elif first_child_type == tokens.REVDBMETAVAR:
+ string = atom_node.get_child(0).get_value()
+ return ast.RevDBMetaVar(int(string[1:]),
+ atom_node.get_lineno(),
+ atom_node.get_column())
else:
raise AssertionError("unknown atom")
diff --git a/pypy/interpreter/astcompiler/codegen.py
b/pypy/interpreter/astcompiler/codegen.py
--- a/pypy/interpreter/astcompiler/codegen.py
+++ b/pypy/interpreter/astcompiler/codegen.py
@@ -1519,6 +1519,20 @@
fmt.format_spec.walkabout(self)
self.emit_op_arg(ops.FORMAT_VALUE, arg)
+ def _revdb_metavar(self, node):
+ # moved in its own function for the import statement
+ from pypy.interpreter.reverse_debugging import dbstate
+ if not dbstate.standard_code:
+ self.emit_op_arg(ops.LOAD_REVDB_VAR, node.metavar)
+ return True
+ return False
+
+ def visit_RevDBMetaVar(self, node):
+ if self.space.reverse_debugging and self._revdb_metavar(node):
+ return
+ self.error("Unknown character ('$NUM' is only valid in the "
+ "reverse-debugger)", node)
+
class TopLevelCodeGenerator(PythonCodeGenerator):
diff --git a/pypy/interpreter/astcompiler/test/test_compiler.py
b/pypy/interpreter/astcompiler/test/test_compiler.py
--- a/pypy/interpreter/astcompiler/test/test_compiler.py
+++ b/pypy/interpreter/astcompiler/test/test_compiler.py
@@ -1240,6 +1240,20 @@
src = """# -*- coding: utf-8 -*-\nz=ord(fr'\xc3\x98')\n"""
yield self.st, src, 'z', 0xd8
+ def test_revdb_metavar(self):
+ from pypy.interpreter.reverse_debugging import dbstate, setup_revdb
+ self.space.config.translation.reverse_debugger = True
+ self.space.reverse_debugging = True
+ try:
+ setup_revdb(self.space)
+ dbstate.standard_code = False
+ dbstate.metavars = [self.space.wrap(6)]
+ self.simple_test("x = 7*$0", "x", 42)
+ dbstate.standard_code = True
+ self.error_test("x = 7*$0", SyntaxError)
+ finally:
+ self.space.reverse_debugging = False
+
class AppTestCompiler:
diff --git a/pypy/interpreter/astcompiler/tools/Python.asdl
b/pypy/interpreter/astcompiler/tools/Python.asdl
--- a/pypy/interpreter/astcompiler/tools/Python.asdl
+++ b/pypy/interpreter/astcompiler/tools/Python.asdl
@@ -70,6 +70,7 @@
| Call(expr func, expr* args, keyword* keywords)
| Num(object n) -- a number as a PyObject.
| Str(string s) -- need to specify raw, unicode, etc?
+ | RevDBMetaVar(int metavar)
| FormattedValue(expr value, int? conversion, expr? format_spec)
| JoinedStr(expr* values)
| Bytes(bytes s)
diff --git a/pypy/interpreter/astcompiler/validate.py
b/pypy/interpreter/astcompiler/validate.py
--- a/pypy/interpreter/astcompiler/validate.py
+++ b/pypy/interpreter/astcompiler/validate.py
@@ -425,6 +425,9 @@
node.slice.walkabout(self)
self._validate_expr(node.value)
+ def visit_RevDBMetaVar(self, node):
+ pass
+
# Subscripts
def visit_Slice(self, node):
if node.lower:
diff --git a/pypy/interpreter/baseobjspace.py b/pypy/interpreter/baseobjspace.py
--- a/pypy/interpreter/baseobjspace.py
+++ b/pypy/interpreter/baseobjspace.py
@@ -402,6 +402,8 @@
"""Base class for the interpreter-level implementations of object spaces.
http://pypy.readthedocs.org/en/latest/objspace.html"""
+ reverse_debugging = False
+
@not_rpython
def __init__(self, config=None):
"Basic initialization of objects."
@@ -413,6 +415,7 @@
from pypy.config.pypyoption import get_pypy_config
config = get_pypy_config(translating=False)
self.config = config
+ self.reverse_debugging = config.translation.reverse_debugger
self.builtin_modules = {}
self.reloading_modules = {}
@@ -430,6 +433,9 @@
def startup(self):
# To be called before using the space
+ if self.reverse_debugging:
+ self._revdb_startup()
+
self.threadlocals.enter_thread(self)
# Initialize already imported builtin modules
@@ -820,7 +826,8 @@
w_u1 = self.interned_strings.get(u)
if w_u1 is None:
w_u1 = w_u
- self.interned_strings.set(u, w_u1)
+ if self._side_effects_ok():
+ self.interned_strings.set(u, w_u1)
return w_u1
def new_interned_str(self, s):
@@ -832,9 +839,39 @@
w_s1 = self.interned_strings.get(u)
if w_s1 is None:
w_s1 = self.newunicode(u)
- self.interned_strings.set(u, w_s1)
+ if self._side_effects_ok():
+ self.interned_strings.set(u, w_s1)
return w_s1
+ def _revdb_startup(self):
+ # moved in its own function for the import statement
+ from pypy.interpreter.reverse_debugging import setup_revdb
+ setup_revdb(self)
+
+ def _revdb_standard_code(self):
+ # moved in its own function for the import statement
+ from pypy.interpreter.reverse_debugging import dbstate
+ return dbstate.standard_code
+
+ def _side_effects_ok(self):
+ # For the reverse debugger: we run compiled watchpoint
+ # expressions in a fast way that will crash if they have
+ # side-effects. The obvious Python code with side-effects is
+ # documented "don't do that"; but some non-obvious side
+ # effects are also common, like interning strings (from
+ # unmarshalling the code object containing the watchpoint
+ # expression) to the two attribute caches in mapdict.py and
+ # typeobject.py. For now, we have to identify such places
+ # that are not acceptable for "reasonable" read-only
+ # watchpoint expressions, and write:
+ #
+ # if not space._side_effects_ok():
+ # don't cache.
+ #
+ if self.reverse_debugging:
+ return self._revdb_standard_code()
+ return True
+
def get_interned_str(self, s):
"""Assumes an identifier (utf-8 encoded str). Returns None if
the identifier is not interned, or not a valid utf-8 string at all.
diff --git a/pypy/interpreter/executioncontext.py
b/pypy/interpreter/executioncontext.py
--- a/pypy/interpreter/executioncontext.py
+++ b/pypy/interpreter/executioncontext.py
@@ -71,6 +71,8 @@
return frame
def enter(self, frame):
+ if self.space.reverse_debugging:
+ self._revdb_enter(frame)
frame.f_backref = self.topframeref
self.topframeref = jit.virtual_ref(frame)
@@ -91,6 +93,8 @@
# be accessed also later
frame_vref()
jit.virtual_ref_finish(frame_vref, frame)
+ if self.space.reverse_debugging:
+ self._revdb_leave(got_exception)
# ________________________________________________________________
@@ -160,6 +164,8 @@
Like bytecode_trace() but doesn't invoke any other events besides the
trace function.
"""
+ if self.space.reverse_debugging:
+ self._revdb_potential_stop_point(frame)
if (frame.get_w_f_trace() is None or self.is_tracing or
self.gettrace() is None):
return
@@ -373,6 +379,21 @@
if self.space.check_signal_action is not None:
self.space.check_signal_action.perform(self, None)
+ def _revdb_enter(self, frame):
+ # moved in its own function for the import statement
+ from pypy.interpreter.reverse_debugging import enter_call
+ enter_call(self.topframeref(), frame)
+
+ def _revdb_leave(self, got_exception):
+ # moved in its own function for the import statement
+ from pypy.interpreter.reverse_debugging import leave_call
+ leave_call(self.topframeref(), got_exception)
+
+ def _revdb_potential_stop_point(self, frame):
+ # moved in its own function for the import statement
+ from pypy.interpreter.reverse_debugging import potential_stop_point
+ potential_stop_point(frame)
+
def _freeze_(self):
raise Exception("ExecutionContext instances should not be seen during"
" translation. Now is a good time to inspect the"
diff --git a/pypy/interpreter/pyframe.py b/pypy/interpreter/pyframe.py
--- a/pypy/interpreter/pyframe.py
+++ b/pypy/interpreter/pyframe.py
@@ -774,9 +774,11 @@
def fget_f_builtins(self, space):
return self.get_builtin().getdict(space)
+ def get_f_back(self):
+ return ExecutionContext.getnextframe_nohidden(self)
+
def fget_f_back(self, space):
- f_back = ExecutionContext.getnextframe_nohidden(self)
- return f_back
+ return self.get_f_back()
def fget_f_lasti(self, space):
return self.space.newint(self.last_instr)
diff --git a/pypy/interpreter/pyopcode.py b/pypy/interpreter/pyopcode.py
--- a/pypy/interpreter/pyopcode.py
+++ b/pypy/interpreter/pyopcode.py
@@ -438,6 +438,8 @@
self.FORMAT_VALUE(oparg, next_instr)
elif opcode == opcodedesc.BUILD_STRING.index:
self.BUILD_STRING(oparg, next_instr)
+ elif opcode == opcodedesc.LOAD_REVDB_VAR.index:
+ self.LOAD_REVDB_VAR(oparg, next_instr)
else:
self.MISSING_OPCODE(oparg, next_instr)
@@ -1114,9 +1116,16 @@
# final result and returns. In that case, we can just continue
# with the next bytecode.
+ def _revdb_jump_backward(self, jumpto):
+ # moved in its own function for the import statement
+ from pypy.interpreter.reverse_debugging import jump_backward
+ jump_backward(self, jumpto)
+
def jump_absolute(self, jumpto, ec):
# this function is overridden by pypy.module.pypyjit.interp_jit
check_nonneg(jumpto)
+ if self.space.reverse_debugging:
+ self._revdb_jump_backward(jumpto)
return jumpto
def JUMP_FORWARD(self, jumpby, next_instr):
@@ -1656,6 +1665,19 @@
w_res = space.newunicode(u''.join(lst))
self.pushvalue(w_res)
+ def _revdb_load_var(self, oparg):
+ # moved in its own function for the import statement
+ from pypy.interpreter.reverse_debugging import load_metavar
+ w_var = load_metavar(oparg)
+ self.pushvalue(w_var)
+
+ def LOAD_REVDB_VAR(self, oparg, next_instr):
+ if self.space.reverse_debugging:
+ self._revdb_load_var(oparg)
+ else:
+ self.MISSING_OPCODE(oparg, next_instr)
+
+
### ____________________________________________________________ ###
class ExitFrame(Exception):
diff --git a/pypy/interpreter/pyparser/data/Grammar2.7
b/pypy/interpreter/pyparser/data/Grammar2.7
--- a/pypy/interpreter/pyparser/data/Grammar2.7
+++ b/pypy/interpreter/pyparser/data/Grammar2.7
@@ -104,7 +104,7 @@
'[' [listmaker] ']' |
'{' [dictorsetmaker] '}' |
'`' testlist1 '`' |
- NAME | NUMBER | STRING+)
+ NAME | NUMBER | STRING+ | '$NUM')
listmaker: test ( list_for | (',' test)* [','] )
testlist_comp: test ( comp_for | (',' test)* [','] )
lambdef: 'lambda' [varargslist] ':' test
diff --git a/pypy/interpreter/pyparser/data/Grammar3.2
b/pypy/interpreter/pyparser/data/Grammar3.2
--- a/pypy/interpreter/pyparser/data/Grammar3.2
+++ b/pypy/interpreter/pyparser/data/Grammar3.2
@@ -103,7 +103,7 @@
atom: ('(' [yield_expr|testlist_comp] ')' |
'[' [testlist_comp] ']' |
'{' [dictorsetmaker] '}' |
- NAME | NUMBER | STRING+ | '...' | 'None' | 'True' | 'False')
+ NAME | NUMBER | STRING+ | '$NUM' | '...' | 'None' | 'True' | 'False')
testlist_comp: (test|star_expr) ( comp_for | (',' (test|star_expr))* [','] )
trailer: '(' [arglist] ')' | '[' subscriptlist ']' | '.' NAME
subscriptlist: subscript (',' subscript)* [',']
diff --git a/pypy/interpreter/pyparser/data/Grammar3.3
b/pypy/interpreter/pyparser/data/Grammar3.3
--- a/pypy/interpreter/pyparser/data/Grammar3.3
+++ b/pypy/interpreter/pyparser/data/Grammar3.3
@@ -103,7 +103,7 @@
atom: ('(' [yield_expr|testlist_comp] ')' |
'[' [testlist_comp] ']' |
'{' [dictorsetmaker] '}' |
- NAME | NUMBER | STRING+ | '...' | 'None' | 'True' | 'False')
+ NAME | NUMBER | STRING+ | '$NUM' | '...' | 'None' | 'True' | 'False')
testlist_comp: (test|star_expr) ( comp_for | (',' (test|star_expr))* [','] )
trailer: '(' [arglist] ')' | '[' subscriptlist ']' | '.' NAME
subscriptlist: subscript (',' subscript)* [',']
diff --git a/pypy/interpreter/pyparser/data/Grammar3.5
b/pypy/interpreter/pyparser/data/Grammar3.5
--- a/pypy/interpreter/pyparser/data/Grammar3.5
+++ b/pypy/interpreter/pyparser/data/Grammar3.5
@@ -108,7 +108,7 @@
atom: ('(' [yield_expr|testlist_comp] ')' |
'[' [testlist_comp] ']' |
'{' [dictorsetmaker] '}' |
- NAME | NUMBER | STRING+ | '...' | 'None' | 'True' | 'False')
+ NAME | NUMBER | STRING+ | '$NUM' | '...' | 'None' | 'True' | 'False')
testlist_comp: (test|star_expr) ( comp_for | (',' (test|star_expr))* [','] )
trailer: '(' [arglist] ')' | '[' subscriptlist ']' | '.' NAME
subscriptlist: subscript (',' subscript)* [',']
diff --git a/pypy/interpreter/pyparser/dfa_generated.py
b/pypy/interpreter/pyparser/dfa_generated.py
--- a/pypy/interpreter/pyparser/dfa_generated.py
+++ b/pypy/interpreter/pyparser/dfa_generated.py
@@ -6,38 +6,39 @@
from pypy.interpreter.pyparser import automata
accepts = [True, True, True, True, True, True, True, True,
True, True, True, False, True, True, True, True,
- True, False, False, False, True, False, False,
- False, True, False, True, False, True, False,
- False, True, False, False, True, False, False,
- True, True, True, False, False, True, False,
- False, False, True]
+ True, False, False, False, False, True, False,
+ False, False, True, False, True, False, True,
+ False, True, False, True, False, False, True,
+ False, False, True, True, True, False, False,
+ True, False, False, False, True]
states = [
# 0
{'\t': 0, '\n': 15, '\x0c': 0,
- '\r': 16, ' ': 0, '!': 11, '"': 18,
- '#': 20, '%': 14, '&': 14, "'": 17,
- '(': 15, ')': 15, '*': 8, '+': 14,
- ',': 15, '-': 12, '.': 7, '/': 13,
- '0': 5, '1': 6, '2': 6, '3': 6,
- '4': 6, '5': 6, '6': 6, '7': 6,
- '8': 6, '9': 6, ':': 15, ';': 15,
- '<': 10, '=': 14, '>': 9, '@': 14,
- 'A': 1, 'B': 2, 'C': 1, 'D': 1,
- 'E': 1, 'F': 2, 'G': 1, 'H': 1,
- 'I': 1, 'J': 1, 'K': 1, 'L': 1,
- 'M': 1, 'N': 1, 'O': 1, 'P': 1,
- 'Q': 1, 'R': 3, 'S': 1, 'T': 1,
- 'U': 4, 'V': 1, 'W': 1, 'X': 1,
- 'Y': 1, 'Z': 1, '[': 15, '\\': 19,
- ']': 15, '^': 14, '_': 1, '`': 15,
- 'a': 1, 'b': 2, 'c': 1, 'd': 1,
- 'e': 1, 'f': 2, 'g': 1, 'h': 1,
- 'i': 1, 'j': 1, 'k': 1, 'l': 1,
- 'm': 1, 'n': 1, 'o': 1, 'p': 1,
- 'q': 1, 'r': 3, 's': 1, 't': 1,
- 'u': 4, 'v': 1, 'w': 1, 'x': 1,
- 'y': 1, 'z': 1, '{': 15, '|': 14,
- '}': 15, '~': 15, '\x80': 1},
+ '\r': 16, ' ': 0, '!': 11, '"': 19,
+ '#': 21, '$': 17, '%': 14, '&': 14,
+ "'": 18, '(': 15, ')': 15, '*': 8,
+ '+': 14, ',': 15, '-': 12, '.': 7,
+ '/': 13, '0': 5, '1': 6, '2': 6,
+ '3': 6, '4': 6, '5': 6, '6': 6,
+ '7': 6, '8': 6, '9': 6, ':': 15,
+ ';': 15, '<': 10, '=': 14, '>': 9,
+ '@': 14, 'A': 1, 'B': 2, 'C': 1,
+ 'D': 1, 'E': 1, 'F': 2, 'G': 1,
+ 'H': 1, 'I': 1, 'J': 1, 'K': 1,
+ 'L': 1, 'M': 1, 'N': 1, 'O': 1,
+ 'P': 1, 'Q': 1, 'R': 3, 'S': 1,
+ 'T': 1, 'U': 4, 'V': 1, 'W': 1,
+ 'X': 1, 'Y': 1, 'Z': 1, '[': 15,
+ '\\': 20, ']': 15, '^': 14, '_': 1,
+ '`': 15, 'a': 1, 'b': 2, 'c': 1,
+ 'd': 1, 'e': 1, 'f': 2, 'g': 1,
+ 'h': 1, 'i': 1, 'j': 1, 'k': 1,
+ 'l': 1, 'm': 1, 'n': 1, 'o': 1,
+ 'p': 1, 'q': 1, 'r': 3, 's': 1,
+ 't': 1, 'u': 4, 'v': 1, 'w': 1,
+ 'x': 1, 'y': 1, 'z': 1, '{': 15,
+ '|': 14, '}': 15, '~': 15,
+ '\x80': 1},
# 1
{'0': 1, '1': 1, '2': 1, '3': 1,
'4': 1, '5': 1, '6': 1, '7': 1,
@@ -56,7 +57,7 @@
't': 1, 'u': 1, 'v': 1, 'w': 1,
'x': 1, 'y': 1, 'z': 1, '\x80': 1},
# 2
- {'"': 18, "'": 17, '0': 1, '1': 1,
+ {'"': 19, "'": 18, '0': 1, '1': 1,
'2': 1, '3': 1, '4': 1, '5': 1,
'6': 1, '7': 1, '8': 1, '9': 1,
'A': 1, 'B': 1, 'C': 1, 'D': 1,
@@ -74,7 +75,7 @@
'v': 1, 'w': 1, 'x': 1, 'y': 1,
'z': 1, '\x80': 1},
# 3
- {'"': 18, "'": 17, '0': 1, '1': 1,
+ {'"': 19, "'": 18, '0': 1, '1': 1,
'2': 1, '3': 1, '4': 1, '5': 1,
'6': 1, '7': 1, '8': 1, '9': 1,
'A': 1, 'B': 4, 'C': 1, 'D': 1,
@@ -92,7 +93,7 @@
'v': 1, 'w': 1, 'x': 1, 'y': 1,
'z': 1, '\x80': 1},
# 4
- {'"': 18, "'": 17, '0': 1, '1': 1,
+ {'"': 19, "'": 18, '0': 1, '1': 1,
'2': 1, '3': 1, '4': 1, '5': 1,
'6': 1, '7': 1, '8': 1, '9': 1,
'A': 1, 'B': 1, 'C': 1, 'D': 1,
@@ -110,21 +111,21 @@
'v': 1, 'w': 1, 'x': 1, 'y': 1,
'z': 1, '\x80': 1},
# 5
- {'.': 26, '0': 24, '1': 25, '2': 25,
- '3': 25, '4': 25, '5': 25, '6': 25,
- '7': 25, '8': 25, '9': 25, 'B': 23,
- 'E': 27, 'J': 15, 'O': 22, 'X': 21,
- 'b': 23, 'e': 27, 'j': 15, 'o': 22,
- 'x': 21},
+ {'.': 27, '0': 25, '1': 26, '2': 26,
+ '3': 26, '4': 26, '5': 26, '6': 26,
+ '7': 26, '8': 26, '9': 26, 'B': 24,
+ 'E': 28, 'J': 15, 'O': 23, 'X': 22,
+ 'b': 24, 'e': 28, 'j': 15, 'o': 23,
+ 'x': 22},
# 6
- {'.': 26, '0': 6, '1': 6, '2': 6,
+ {'.': 27, '0': 6, '1': 6, '2': 6,
'3': 6, '4': 6, '5': 6, '6': 6,
- '7': 6, '8': 6, '9': 6, 'E': 27,
- 'J': 15, 'e': 27, 'j': 15},
+ '7': 6, '8': 6, '9': 6, 'E': 28,
+ 'J': 15, 'e': 28, 'j': 15},
# 7
- {'.': 29, '0': 28, '1': 28, '2': 28,
- '3': 28, '4': 28, '5': 28, '6': 28,
- '7': 28, '8': 28, '9': 28},
+ {'.': 30, '0': 29, '1': 29, '2': 29,
+ '3': 29, '4': 29, '5': 29, '6': 29,
+ '7': 29, '8': 29, '9': 29},
# 8
{'*': 14, '=': 15},
# 9
@@ -144,107 +145,115 @@
# 16
{'\n': 15},
# 17
- {automata.DEFAULT: 33, '\n': 30,
- '\r': 30, "'": 31, '\\': 32},
+ {'0': 31, '1': 31, '2': 31, '3': 31,
+ '4': 31, '5': 31, '6': 31, '7': 31,
+ '8': 31, '9': 31},
# 18
- {automata.DEFAULT: 36, '\n': 30,
- '\r': 30, '"': 34, '\\': 35},
+ {automata.DEFAULT: 35, '\n': 32,
+ '\r': 32, "'": 33, '\\': 34},
# 19
+ {automata.DEFAULT: 38, '\n': 32,
+ '\r': 32, '"': 36, '\\': 37},
+ # 20
{'\n': 15, '\r': 16},
- # 20
- {automata.DEFAULT: 20, '\n': 30, '\r': 30},
# 21
- {'0': 37, '1': 37, '2': 37, '3': 37,
- '4': 37, '5': 37, '6': 37, '7': 37,
- '8': 37, '9': 37, 'A': 37, 'B': 37,
- 'C': 37, 'D': 37, 'E': 37, 'F': 37,
- 'a': 37, 'b': 37, 'c': 37, 'd': 37,
- 'e': 37, 'f': 37},
+ {automata.DEFAULT: 21, '\n': 32, '\r': 32},
# 22
- {'0': 38, '1': 38, '2': 38, '3': 38,
- '4': 38, '5': 38, '6': 38, '7': 38},
+ {'0': 39, '1': 39, '2': 39, '3': 39,
+ '4': 39, '5': 39, '6': 39, '7': 39,
+ '8': 39, '9': 39, 'A': 39, 'B': 39,
+ 'C': 39, 'D': 39, 'E': 39, 'F': 39,
+ 'a': 39, 'b': 39, 'c': 39, 'd': 39,
+ 'e': 39, 'f': 39},
# 23
- {'0': 39, '1': 39},
+ {'0': 40, '1': 40, '2': 40, '3': 40,
+ '4': 40, '5': 40, '6': 40, '7': 40},
# 24
- {'.': 26, '0': 24, '1': 25, '2': 25,
- '3': 25, '4': 25, '5': 25, '6': 25,
- '7': 25, '8': 25, '9': 25, 'E': 27,
- 'J': 15, 'e': 27, 'j': 15},
+ {'0': 41, '1': 41},
# 25
- {'.': 26, '0': 25, '1': 25, '2': 25,
- '3': 25, '4': 25, '5': 25, '6': 25,
- '7': 25, '8': 25, '9': 25, 'E': 27,
- 'J': 15, 'e': 27, 'j': 15},
+ {'.': 27, '0': 25, '1': 26, '2': 26,
+ '3': 26, '4': 26, '5': 26, '6': 26,
+ '7': 26, '8': 26, '9': 26, 'E': 28,
+ 'J': 15, 'e': 28, 'j': 15},
# 26
- {'0': 26, '1': 26, '2': 26, '3': 26,
- '4': 26, '5': 26, '6': 26, '7': 26,
- '8': 26, '9': 26, 'E': 40, 'J': 15,
- 'e': 40, 'j': 15},
+ {'.': 27, '0': 26, '1': 26, '2': 26,
+ '3': 26, '4': 26, '5': 26, '6': 26,
+ '7': 26, '8': 26, '9': 26, 'E': 28,
+ 'J': 15, 'e': 28, 'j': 15},
# 27
- {'+': 41, '-': 41, '0': 42, '1': 42,
- '2': 42, '3': 42, '4': 42, '5': 42,
- '6': 42, '7': 42, '8': 42, '9': 42},
+ {'0': 27, '1': 27, '2': 27, '3': 27,
+ '4': 27, '5': 27, '6': 27, '7': 27,
+ '8': 27, '9': 27, 'E': 42, 'J': 15,
+ 'e': 42, 'j': 15},
# 28
- {'0': 28, '1': 28, '2': 28, '3': 28,
- '4': 28, '5': 28, '6': 28, '7': 28,
- '8': 28, '9': 28, 'E': 40, 'J': 15,
- 'e': 40, 'j': 15},
+ {'+': 43, '-': 43, '0': 44, '1': 44,
+ '2': 44, '3': 44, '4': 44, '5': 44,
+ '6': 44, '7': 44, '8': 44, '9': 44},
# 29
+ {'0': 29, '1': 29, '2': 29, '3': 29,
+ '4': 29, '5': 29, '6': 29, '7': 29,
+ '8': 29, '9': 29, 'E': 42, 'J': 15,
+ 'e': 42, 'j': 15},
+ # 30
{'.': 15},
- # 30
+ # 31
+ {'0': 31, '1': 31, '2': 31, '3': 31,
+ '4': 31, '5': 31, '6': 31, '7': 31,
+ '8': 31, '9': 31},
+ # 32
{},
- # 31
+ # 33
{"'": 15},
- # 32
- {automata.DEFAULT: 43, '\n': 15, '\r': 16},
- # 33
- {automata.DEFAULT: 33, '\n': 30,
- '\r': 30, "'": 15, '\\': 32},
# 34
+ {automata.DEFAULT: 45, '\n': 15, '\r': 16},
+ # 35
+ {automata.DEFAULT: 35, '\n': 32,
+ '\r': 32, "'": 15, '\\': 34},
+ # 36
{'"': 15},
- # 35
- {automata.DEFAULT: 44, '\n': 15, '\r': 16},
- # 36
- {automata.DEFAULT: 36, '\n': 30,
- '\r': 30, '"': 15, '\\': 35},
# 37
- {'0': 37, '1': 37, '2': 37, '3': 37,
- '4': 37, '5': 37, '6': 37, '7': 37,
- '8': 37, '9': 37, 'A': 37, 'B': 37,
- 'C': 37, 'D': 37, 'E': 37, 'F': 37,
- 'a': 37, 'b': 37, 'c': 37, 'd': 37,
- 'e': 37, 'f': 37},
+ {automata.DEFAULT: 46, '\n': 15, '\r': 16},
# 38
- {'0': 38, '1': 38, '2': 38, '3': 38,
- '4': 38, '5': 38, '6': 38, '7': 38},
+ {automata.DEFAULT: 38, '\n': 32,
+ '\r': 32, '"': 15, '\\': 37},
# 39
- {'0': 39, '1': 39},
+ {'0': 39, '1': 39, '2': 39, '3': 39,
+ '4': 39, '5': 39, '6': 39, '7': 39,
+ '8': 39, '9': 39, 'A': 39, 'B': 39,
+ 'C': 39, 'D': 39, 'E': 39, 'F': 39,
+ 'a': 39, 'b': 39, 'c': 39, 'd': 39,
+ 'e': 39, 'f': 39},
# 40
- {'+': 45, '-': 45, '0': 46, '1': 46,
- '2': 46, '3': 46, '4': 46, '5': 46,
- '6': 46, '7': 46, '8': 46, '9': 46},
+ {'0': 40, '1': 40, '2': 40, '3': 40,
+ '4': 40, '5': 40, '6': 40, '7': 40},
# 41
- {'0': 42, '1': 42, '2': 42, '3': 42,
- '4': 42, '5': 42, '6': 42, '7': 42,
- '8': 42, '9': 42},
+ {'0': 41, '1': 41},
# 42
- {'0': 42, '1': 42, '2': 42, '3': 42,
- '4': 42, '5': 42, '6': 42, '7': 42,
- '8': 42, '9': 42, 'J': 15, 'j': 15},
+ {'+': 47, '-': 47, '0': 48, '1': 48,
+ '2': 48, '3': 48, '4': 48, '5': 48,
+ '6': 48, '7': 48, '8': 48, '9': 48},
# 43
- {automata.DEFAULT: 43, '\n': 30,
- '\r': 30, "'": 15, '\\': 32},
+ {'0': 44, '1': 44, '2': 44, '3': 44,
+ '4': 44, '5': 44, '6': 44, '7': 44,
+ '8': 44, '9': 44},
# 44
- {automata.DEFAULT: 44, '\n': 30,
- '\r': 30, '"': 15, '\\': 35},
+ {'0': 44, '1': 44, '2': 44, '3': 44,
+ '4': 44, '5': 44, '6': 44, '7': 44,
+ '8': 44, '9': 44, 'J': 15, 'j': 15},
# 45
- {'0': 46, '1': 46, '2': 46, '3': 46,
- '4': 46, '5': 46, '6': 46, '7': 46,
- '8': 46, '9': 46},
+ {automata.DEFAULT: 45, '\n': 32,
+ '\r': 32, "'": 15, '\\': 34},
# 46
- {'0': 46, '1': 46, '2': 46, '3': 46,
- '4': 46, '5': 46, '6': 46, '7': 46,
- '8': 46, '9': 46, 'J': 15, 'j': 15},
+ {automata.DEFAULT: 46, '\n': 32,
+ '\r': 32, '"': 15, '\\': 37},
+ # 47
+ {'0': 48, '1': 48, '2': 48, '3': 48,
+ '4': 48, '5': 48, '6': 48, '7': 48,
+ '8': 48, '9': 48},
+ # 48
+ {'0': 48, '1': 48, '2': 48, '3': 48,
+ '4': 48, '5': 48, '6': 48, '7': 48,
+ '8': 48, '9': 48, 'J': 15, 'j': 15},
]
pseudoDFA = automata.DFA(states, accepts)
diff --git a/pypy/interpreter/pyparser/gendfa.py
b/pypy/interpreter/pyparser/gendfa.py
--- a/pypy/interpreter/pyparser/gendfa.py
+++ b/pypy/interpreter/pyparser/gendfa.py
@@ -146,7 +146,10 @@
makeEOL(),
chainStr(states, "..."),
groupStr(states, "@:;.,`"))
- funny = group(states, operator, bracket, special)
+ revdb_metavar = chain(states,
+ groupStr(states, "$"),
+ atleastonce(states, makeDigits()))
+ funny = group(states, operator, bracket, special, revdb_metavar)
# ____________________________________________________________
def makeStrPrefix ():
return group(states,
diff --git a/pypy/interpreter/pyparser/pytoken.py
b/pypy/interpreter/pyparser/pytoken.py
--- a/pypy/interpreter/pyparser/pytoken.py
+++ b/pypy/interpreter/pyparser/pytoken.py
@@ -72,5 +72,6 @@
# extra PyPy-specific tokens
_add_tok("COMMENT")
_add_tok("NL")
+_add_tok("REVDBMETAVAR", "$NUM")
del _add_tok
diff --git a/pypy/interpreter/pyparser/pytokenizer.py
b/pypy/interpreter/pyparser/pytokenizer.py
--- a/pypy/interpreter/pyparser/pytokenizer.py
+++ b/pypy/interpreter/pyparser/pytokenizer.py
@@ -329,6 +329,10 @@
last_comment = ''
elif initial == '\\': # continued stmt
continued = 1
+ elif initial == '$':
+ token_list.append((tokens.REVDBMETAVAR, token,
+ lnum, start, line))
+ last_comment = ''
else:
if initial in '([{':
parenstack.append((initial, lnum, start, line))
diff --git a/pypy/interpreter/pyparser/test/test_pyparse.py
b/pypy/interpreter/pyparser/test/test_pyparse.py
--- a/pypy/interpreter/pyparser/test/test_pyparse.py
+++ b/pypy/interpreter/pyparser/test/test_pyparse.py
@@ -147,6 +147,15 @@
tree = self.parse(fmt % linefeed)
assert expected_tree == tree
+ def test_revdb_dollar_num(self):
+ self.parse('$0')
+ self.parse('$5')
+ self.parse('$42')
+ self.parse('2+$42.attrname')
+ py.test.raises(SyntaxError, self.parse, '$')
+ py.test.raises(SyntaxError, self.parse, '$a')
+ py.test.raises(SyntaxError, self.parse, '$.5')
+
def test_py3k_reject_old_binary_literal(self):
py.test.raises(SyntaxError, self.parse, '0777')
diff --git a/pypy/interpreter/reverse_debugging.py
b/pypy/interpreter/reverse_debugging.py
new file mode 100644
--- /dev/null
+++ b/pypy/interpreter/reverse_debugging.py
@@ -0,0 +1,852 @@
+import sys
+from rpython.rlib import revdb
+from rpython.rlib.debug import make_sure_not_resized
+from rpython.rlib.objectmodel import specialize, we_are_translated
+from rpython.rtyper.annlowlevel import cast_gcref_to_instance
+from pypy.interpreter.error import OperationError, oefmt
+from pypy.interpreter.baseobjspace import W_Root
+from pypy.interpreter import gateway, typedef, pycode, pytraceback, pyframe
+from pypy.module.marshal import interp_marshal
+from pypy.interpreter.executioncontext import AbstractActionFlag, ActionFlag
+
+
+class DBState:
+ standard_code = True
+ breakpoint_stack_id = 0
+ breakpoint_funcnames = None
+ breakpoint_filelines = None
+ breakpoint_by_file = None
+ breakpoint_version = 0
+ printed_objects = {}
+ metavars = []
+ watch_progs = []
+ watch_futures = {}
+
+dbstate = DBState()
+
+
+pycode.PyCode.co_revdb_linestarts = None # or a string: see below
+pycode.PyCode.co_revdb_bkpt_version = 0 # see check_and_trigger_bkpt()
+pycode.PyCode.co_revdb_bkpt_cache = None # see check_and_trigger_bkpt()
+
+# invariant: "f_revdb_nextline_instr" is the bytecode offset of
+# the start of the line that follows "last_instr".
+pyframe.PyFrame.f_revdb_nextline_instr = -1
+
+
+# ____________________________________________________________
+
+
+def setup_revdb(space):
+ """Called at run-time, before the space is set up.
+
+ The various register_debug_command() lines attach functions
+ to some commands that 'revdb.py' can call, if we are running
+ in replay mode.
+ """
+ assert space.config.translation.reverse_debugger
+ dbstate.space = space
+
+ make_sure_not_resized(dbstate.watch_progs)
+ make_sure_not_resized(dbstate.metavars)
+
+ revdb.register_debug_command(revdb.CMD_PRINT, lambda_print)
+ revdb.register_debug_command(revdb.CMD_BACKTRACE, lambda_backtrace)
+ revdb.register_debug_command(revdb.CMD_LOCALS, lambda_locals)
+ revdb.register_debug_command(revdb.CMD_BREAKPOINTS, lambda_breakpoints)
+ revdb.register_debug_command(revdb.CMD_STACKID, lambda_stackid)
+ revdb.register_debug_command("ALLOCATING", lambda_allocating)
+ revdb.register_debug_command(revdb.CMD_ATTACHID, lambda_attachid)
+ revdb.register_debug_command(revdb.CMD_COMPILEWATCH, lambda_compilewatch)
+ revdb.register_debug_command(revdb.CMD_CHECKWATCH, lambda_checkwatch)
+ revdb.register_debug_command(revdb.CMD_WATCHVALUES, lambda_watchvalues)
+
+
+# ____________________________________________________________
+
+
+def enter_call(caller_frame, callee_frame):
+ if dbstate.breakpoint_funcnames is not None:
+ name = callee_frame.getcode().co_name
+ if name in dbstate.breakpoint_funcnames:
+ revdb.breakpoint(dbstate.breakpoint_funcnames[name])
+ if dbstate.breakpoint_stack_id != 0 and caller_frame is not None:
+ if dbstate.breakpoint_stack_id == revdb.get_unique_id(caller_frame):
+ revdb.breakpoint(-1)
+ #
+ code = callee_frame.pycode
+ if code.co_revdb_linestarts is None:
+ build_co_revdb_linestarts(code)
+
+def leave_call(caller_frame, got_exception):
+ if dbstate.breakpoint_stack_id != 0 and caller_frame is not None:
+ if dbstate.breakpoint_stack_id == revdb.get_unique_id(caller_frame):
+ revdb.breakpoint(-2)
+ if we_are_translated():
+ stop_point_activate(-2 + got_exception)
+
+def stop_point():
+ if we_are_translated():
+ revdb.breakpoint(-3)
+
+
+def jump_backward(frame, jumpto):
+ # When we see a jump backward, we set 'f_revdb_nextline_instr' in
+ # such a way that the next instruction, at 'jumpto', will trigger
+ # stop_point_activate(). We have to trigger it even if
+ # 'jumpto' is not actually a start of line. For example, after a
+ # 'while foo:', the body ends with a JUMP_ABSOLUTE which
+ # jumps back to the *second* opcode of the while.
+ frame.f_revdb_nextline_instr = jumpto
+
+
+def potential_stop_point(frame):
+ if not we_are_translated():
+ return
+ #
+ # We only record a stop_point at every line, not every bytecode.
+ # Uses roughly the same algo as ExecutionContext.run_trace_func()
+ # to know where the line starts are, but tweaked for speed,
+ # avoiding the quadratic complexity when run N times with a large
+ # code object.
+ #
+ cur = frame.last_instr
+ if cur < frame.f_revdb_nextline_instr:
+ return # fast path: we're still inside the same line as before
+ #
+ call_stop_point_at_line = True
+ co_revdb_linestarts = frame.pycode.co_revdb_linestarts
+ if cur > frame.f_revdb_nextline_instr:
+ #
+ # We jumped forward over the start of the next line. We're
+ # inside a different line, but we will only trigger a stop
+ # point if we're at the starting bytecode of that line. Fetch
+ # from co_revdb_linestarts the start of the line that is at or
+ # follows 'cur'.
+ ch = ord(co_revdb_linestarts[cur])
+ if ch == 0:
+ pass # we are at the start of a line now
+ else:
+ # We are not, so don't call stop_point_activate().
+ # We still have to fill f_revdb_nextline_instr.
+ call_stop_point_at_line = False
+ #
+ if call_stop_point_at_line:
+ stop_point_activate(pycode=frame.pycode, opindex=cur)
+ cur += 1
+ ch = ord(co_revdb_linestarts[cur])
+ #
+ # Update f_revdb_nextline_instr. Check if 'ch' was greater than
+ # 255, in which case it was rounded down to 255 and we have to
+ # continue looking
+ nextline_instr = cur + ch
+ while ch == 255:
+ ch = ord(co_revdb_linestarts[nextline_instr])
+ nextline_instr += ch
+ frame.f_revdb_nextline_instr = nextline_instr
+
+
+def build_co_revdb_linestarts(code):
+ # Inspired by findlinestarts() in the 'dis' standard module.
+ # Set up 'bits' so that it contains \x00 at line starts and \xff
+ # in-between.
+ bits = ['\xff'] * (len(code.co_code) + 1)
+ if not code.hidden_applevel:
+ lnotab = code.co_lnotab
+ addr = 0
+ p = 0
+ newline = 1
+ while p + 1 < len(lnotab):
+ byte_incr = ord(lnotab[p])
+ line_incr = ord(lnotab[p+1])
+ if byte_incr:
+ if newline != 0:
+ bits[addr] = '\x00'
+ newline = 0
+ addr += byte_incr
+ newline |= line_incr
+ p += 2
+ if newline:
+ bits[addr] = '\x00'
+ bits[len(code.co_code)] = '\x00'
+ #
+ # Change 'bits' so that the character at 'i', if not \x00, measures
+ # how far the next \x00 is
+ next_null = len(code.co_code)
+ p = next_null - 1
+ while p >= 0:
+ if bits[p] == '\x00':
+ next_null = p
+ else:
+ ch = next_null - p
+ if ch > 255: ch = 255
+ bits[p] = chr(ch)
+ p -= 1
+ lstart = ''.join(bits)
+ code.co_revdb_linestarts = lstart
+ return lstart
+
+def get_final_lineno(code):
+ lineno = code.co_firstlineno
+ lnotab = code.co_lnotab
+ p = 1
+ while p < len(lnotab):
+ line_incr = ord(lnotab[p])
+ lineno += line_incr
+ p += 2
+ return lineno
+
+def find_line_starts(code):
+ # RPython version of dis.findlinestarts()
+ lnotab = code.co_lnotab
+ lastlineno = -1
+ lineno = code.co_firstlineno
+ addr = 0
+ p = 0
+ result = []
+ while p < len(lnotab) - 1:
+ byte_incr = ord(lnotab[p + 0])
+ line_incr = ord(lnotab[p + 1])
+ if byte_incr:
+ if lineno != lastlineno:
+ result.append((addr, lineno))
+ lastlineno = lineno
+ addr += byte_incr
+ lineno += line_incr
+ p += 2
+ if lineno != lastlineno:
+ result.append((addr, lineno))
+ return result
+
+class NonStandardCode(object):
+ def __enter__(self):
+ dbstate.standard_code = False
+ self.t = dbstate.space.actionflag._ticker
+ self.c = dbstate.space.actionflag._ticker_revdb_count
+ def __exit__(self, *args):
+ dbstate.space.actionflag._ticker = self.t
+ dbstate.space.actionflag._ticker_revdb_count = self.c
+ dbstate.standard_code = True
+non_standard_code = NonStandardCode()
+
+
+def stop_point_activate(place=0, pycode=None, opindex=-1):
+ if revdb.watch_save_state():
+ any_watch_point = False
+ # ^^ this flag is set to True if we must continue to enter this
+ # block of code. If it is still False for watch_restore_state()
+ # below, then future watch_save_state() will return False too---
+ # until the next time revdb.c:set_revdb_breakpoints() is called.
+ space = dbstate.space
+ with non_standard_code:
+ watch_id = -1
+ if dbstate.breakpoint_by_file is not None:
+ any_watch_point = True
+ if pycode is not None:
+ watch_id = check_and_trigger_bkpt(pycode, opindex)
+ if watch_id == -1:
+ for prog, watch_id, expected in dbstate.watch_progs:
+ any_watch_point = True
+ try:
+ got = _run_watch(space, prog)
+ except OperationError as e:
+ got = e.errorstr(space)
+ except Exception:
+ break
+ if got != expected:
+ break
+ else:
+ watch_id = -1
+ revdb.watch_restore_state(any_watch_point)
+ if watch_id != -1:
+ revdb.breakpoint(watch_id)
+ revdb.stop_point(place)
+
+
+def future_object(space):
+ return space.w_Ellipsis # a random prebuilt object
+
+def load_metavar(index):
+ assert index >= 0
+ space = dbstate.space
+ metavars = dbstate.metavars
+ w_var = metavars[index] if index < len(metavars) else None
+ if w_var is None:
+ raise oefmt(space.w_NameError, "no constant object '$%d'",
+ index)
+ if w_var is future_object(space):
+ raise oefmt(space.w_RuntimeError,
+ "'$%d' refers to an object created later in time",
+ index)
+ return w_var
+
+def set_metavar(index, w_obj):
+ assert index >= 0
+ if index >= len(dbstate.metavars):
+ missing = index + 1 - len(dbstate.metavars)
+ dbstate.metavars = dbstate.metavars + [None] * missing
+ dbstate.metavars[index] = w_obj
+
+
+# ____________________________________________________________
+
+
+def fetch_cur_frame(silent=False):
+ ec = dbstate.space.threadlocals.get_ec()
+ if ec is None:
+ frame = None
+ else:
+ frame = ec.topframeref()
+ if frame is None and not silent:
+ revdb.send_output("No stack.\n")
+ return frame
+
+def compile(source, mode):
+ assert not dbstate.standard_code
+ space = dbstate.space
+ compiler = space.createcompiler()
+ code = compiler.compile(source, '<revdb>', mode, 0,
+ hidden_applevel=True)
+ return code
+
+
+class W_RevDBOutput(W_Root):
+ softspace = 0
+
+ def __init__(self, space):
+ self.space = space
+
+ def descr_write(self, w_buffer):
+ assert not dbstate.standard_code
+ space = self.space
+ if space.isinstance_w(w_buffer, space.w_unicode):
+ w_buffer = space.call_method(w_buffer, 'encode',
+ space.newtext('utf-8')) # safe?
+ revdb.send_output(space.bytes_w(w_buffer))
+
+def descr_get_softspace(space, revdb):
+ return space.newint(revdb.softspace)
+def descr_set_softspace(space, revdb, w_newvalue):
+ revdb.softspace = space.int_w(w_newvalue)
+
+W_RevDBOutput.typedef = typedef.TypeDef(
+ "revdb_output",
+ write = gateway.interp2app(W_RevDBOutput.descr_write),
+ softspace = typedef.GetSetProperty(descr_get_softspace,
+ descr_set_softspace,
+ cls=W_RevDBOutput),
+ )
+
+def revdb_displayhook(space, w_obj):
+ """Modified sys.displayhook() that also outputs '$NUM = ',
+ for non-prebuilt objects. Such objects are then recorded in
+ 'printed_objects'.
+ """
+ assert not dbstate.standard_code
+ if space.is_w(w_obj, space.w_None):
+ return
+ uid = revdb.get_unique_id(w_obj)
+ if uid > 0:
+ dbstate.printed_objects[uid] = w_obj
+ revdb.send_nextnid(uid) # outputs '$NUM = '
+ space.setitem(space.builtin.w_dict, space.newtext('_'), w_obj)
+ # do repr() after setitem: if w_obj was produced successfully,
+ # but its repr crashes because it tries to do I/O, then we already
+ # have it recorded in '_' and in '$NUM ='.
+ w_repr = space.repr(w_obj)
+ if space.isinstance_w(w_repr, space.w_unicode):
+ w_repr = space.call_method(w_repr, 'encode',
+ space.newtext('utf-8')) # safe?
+ revdb.send_output(space.bytes_w(w_repr))
+ revdb.send_output("\n")
+
[email protected]_spec(name='text0', level=int)
+def revdb_importhook(space, name, w_globals=None,
+ w_locals=None, w_fromlist=None, level=-1):
+ # Incredibly simplified version of __import__, which only returns
+ # already-imported modules and doesn't call any custom import
+ # hooks. Recognizes only absolute imports. With a 'fromlist'
+ # argument that is a non-empty list, returns the module 'name3' if
+ # the 'name' argument is 'name1.name2.name3'. With an empty or
+ # None 'fromlist' argument, returns the module 'name1' instead.
+ return space.appexec([space.newtext(name), w_fromlist or space.w_None,
+ space.newint(level), space.sys],
+ """(name, fromlist, level, sys):
+ if level > 0:
+ raise ImportError("only absolute imports are "
+ "supported in the debugger")
+ basename = name.split('.')[0]
+ try:
+ basemod = sys.modules[basename]
+ mod = sys.modules[name]
+ except KeyError:
+ raise ImportError("'%s' not found or not imported yet "
+ "(the debugger can't import new modules, "
+ "and only supports absolute imports)" % (name,))
+ if fromlist:
+ return mod
+ return basemod
+ """)
+
[email protected]()
+def get_revdb_displayhook(space):
+ return gateway.interp2app(revdb_displayhook).spacebind(space)
+
[email protected]()
+def get_revdb_importhook(space):
+ return gateway.interp2app(revdb_importhook).spacebind(space)
+
+
+def prepare_print_environment(space):
+ assert not dbstate.standard_code
+ w_revdb_output = W_RevDBOutput(space)
+ w_displayhook = get_revdb_displayhook(space)
+ w_import = get_revdb_importhook(space)
+ space.sys.setdictvalue(space, 'stdout', w_revdb_output)
+ space.sys.setdictvalue(space, 'stderr', w_revdb_output)
+ space.sys.setdictvalue(space, 'displayhook', w_displayhook)
+ space.builtin.setdictvalue(space, '__import__', w_import)
+
+def command_print(cmd, expression):
+ frame = fetch_cur_frame()
+ if frame is None:
+ return
+ space = dbstate.space
+ with non_standard_code:
+ try:
+ prepare_print_environment(space)
+ code = compile(expression, 'single')
+ try:
+ code.exec_code(space,
+ frame.get_w_globals(),
+ frame.getdictscope())
+
+ except OperationError as operationerr:
+ # can't use sys.excepthook: it will likely try to do 'import
+ # traceback', which might not be doable without using I/O
+ tb = operationerr.get_traceback()
+ if tb is not None:
+ revdb.send_output("Traceback (most recent call last):\n")
+ while tb is not None:
+ if not isinstance(tb, pytraceback.PyTraceback):
+ revdb.send_output(" ??? %s\n" % tb)
+ break
+ show_frame(tb.frame, tb.get_lineno(), indent=' ')
+ tb = tb.next
+ revdb.send_output('%s\n' % operationerr.errorstr(space))
+
+ # set the sys.last_xxx attributes
+ w_type = operationerr.w_type
+ w_value = operationerr.get_w_value(space)
+ w_tb = operationerr.get_traceback()
+ w_dict = space.sys.w_dict
+ space.setitem(w_dict, space.newtext('last_type'), w_type)
+ space.setitem(w_dict, space.newtext('last_value'), w_value)
+ space.setitem(w_dict, space.newtext('last_traceback'), w_tb)
+
+ except OperationError as e:
+ revdb.send_output('%s\n' % e.errorstr(space, use_repr=True))
+lambda_print = lambda: command_print
+
+
+def file_and_lineno(frame, lineno):
+ code = frame.getcode()
+ return 'File "%s", line %d in %s' % (
+ code.co_filename, lineno, code.co_name)
+
+def show_frame(frame, lineno=0, indent=''):
+ if lineno == 0:
+ lineno = frame.get_last_lineno()
+ revdb.send_output("%s%s\n%s " % (
+ indent,
+ file_and_lineno(frame, lineno),
+ indent))
+ revdb.send_linecache(frame.getcode().co_filename, lineno)
+
+def display_function_part(frame, max_lines_before, max_lines_after):
+ if frame is None:
+ return
+ code = frame.getcode()
+ if code.co_filename.startswith('<builtin>'):
+ return
+ first_lineno = code.co_firstlineno
+ current_lineno = frame.get_last_lineno()
+ final_lineno = get_final_lineno(code)
+ #
+ ellipsis_after = False
+ if first_lineno < current_lineno - max_lines_before - 1:
+ first_lineno = current_lineno - max_lines_before
+ revdb.send_output("...\n")
+ if final_lineno > current_lineno + max_lines_after + 1:
+ final_lineno = current_lineno + max_lines_after
+ ellipsis_after = True
+ #
+ for i in range(first_lineno, final_lineno + 1):
+ if i == current_lineno:
+ if revdb.current_place() == -2: # <= this is the arg to
stop_point()
+ prompt = "<< " # return
+ elif revdb.current_place() == -1:
+ prompt = "!! " # exceptional return
+ else:
+ prompt = " > " # plain line
+ revdb.send_output(prompt)
+ else:
+ revdb.send_output(" ")
+ revdb.send_linecache(code.co_filename, i, strip=False)
+ #
+ if ellipsis_after:
+ revdb.send_output("...\n")
+
+def command_backtrace(cmd, extra):
+ frame = fetch_cur_frame(silent=True)
+ if cmd.c_arg1 == 0:
+ if frame is not None:
+ revdb.send_output("%s:\n" % (
+ file_and_lineno(frame, frame.get_last_lineno()),))
+ display_function_part(frame, max_lines_before=8, max_lines_after=5)
+ elif cmd.c_arg1 == 2:
+ display_function_part(frame,
max_lines_before=1000,max_lines_after=1000)
+ else:
+ revdb.send_output("Current call stack (most recent call last):\n")
+ if frame is None:
+ revdb.send_output(" (empty)\n")
+ frames = []
+ while frame is not None:
+ frames.append(frame)
+ if len(frames) == 200:
+ revdb.send_output(" ...\n")
+ break
+ frame = frame.get_f_back()
+ while len(frames) > 0:
+ show_frame(frames.pop(), indent=' ')
+lambda_backtrace = lambda: command_backtrace
+
+
+def command_locals(cmd, extra):
+ frame = fetch_cur_frame()
+ if frame is None:
+ return
+ space = dbstate.space
+ with non_standard_code:
+ try:
+ prepare_print_environment(space)
+ space.appexec([space.sys,
+ frame.getdictscope()], """(sys, locals):
+ lst = locals.keys()
+ lst.sort()
+ print 'Locals:'
+ for key in lst:
+ try:
+ print ' %s =' % key,
+ s = '%r' % locals[key]
+ if len(s) > 140:
+ s = s[:100] + '...' + s[-30:]
+ print s
+ except:
+ exc, val, tb = sys.exc_info()
+ print '!<%s: %r>' % (exc, val)
+ """)
+ except OperationError as e:
+ revdb.send_output('%s\n' % e.errorstr(space, use_repr=True))
+lambda_locals = lambda: command_locals
+
+# ____________________________________________________________
+
+
+def check_and_trigger_bkpt(pycode, opindex):
+ # We cache on 'pycode.co_revdb_bkpt_cache' either None or a dict
+ # mapping {opindex: bkpt_num}. This cache is updated when the
+ # version in 'pycode.co_revdb_bkpt_version' does not match
+ # 'dbstate.breakpoint_version' any more.
+ if pycode.co_revdb_bkpt_version != dbstate.breakpoint_version:
+ update_bkpt_cache(pycode)
+ cache = pycode.co_revdb_bkpt_cache
+ if cache is not None and opindex in cache:
+ return cache[opindex]
+ else:
+ return -1
+
+def update_bkpt_cache(pycode):
+ # initialized by command_breakpoints():
+ # dbstate.breakpoint_filelines == [('FILENAME', lineno, bkpt_num)]
+ # computed lazily (here, first half of the logic):
+ # dbstate.breakpoint_by_file == {'co_filename': {lineno: bkpt_num}}
+ # the goal is to set:
+ # pycode.co_revdb_bkpt_cache == {opindex: bkpt_num}
+
+ co_filename = pycode.co_filename
+ try:
+ linenos = dbstate.breakpoint_by_file[co_filename]
+ except KeyError:
+ linenos = None
+ match = co_filename.upper() # ignore cAsE in filename matching
+ for filename, lineno, bkpt_num in dbstate.breakpoint_filelines:
+ if match.endswith(filename) and (
+ len(match) == len(filename) or
+ match[-len(filename)-1] in '/\\'): # a valid prefix
+ if linenos is None:
+ linenos = {}
+ linenos[lineno] = bkpt_num
+ dbstate.breakpoint_by_file[co_filename] = linenos
+
+ newcache = None
+ if linenos is not None:
+ # parse co_lnotab to figure out the opindexes that correspond
+ # to the marked line numbers. here, linenos == {lineno: bkpt_num}
+ for addr, lineno in find_line_starts(pycode):
+ if lineno in linenos:
+ if newcache is None:
+ newcache = {}
+ newcache[addr] = linenos[lineno]
+
+ pycode.co_revdb_bkpt_cache = newcache
+ pycode.co_revdb_bkpt_version = dbstate.breakpoint_version
+
+
+def valid_identifier(s):
+ if not s:
+ return False
+ if s[0].isdigit():
+ return False
+ for c in s:
+ if not (c.isalnum() or c == '_'):
+ return False
+ return True
+
+def add_breakpoint_funcname(name, i):
+ if dbstate.breakpoint_funcnames is None:
+ dbstate.breakpoint_funcnames = {}
+ dbstate.breakpoint_funcnames[name] = i
+
+def add_breakpoint_fileline(filename, lineno, i):
+ # dbstate.breakpoint_filelines is just a list of (FILENAME, lineno, i).
+ # dbstate.breakpoint_by_file is {co_filename: {lineno: i}}, but
+ # computed lazily when we encounter a code object with the given
+ # co_filename. Any suffix 'filename' matches 'co_filename'.
+ if dbstate.breakpoint_filelines is None:
+ dbstate.breakpoint_filelines = []
+ dbstate.breakpoint_by_file = {}
+ dbstate.breakpoint_filelines.append((filename.upper(), lineno, i))
+
+def add_breakpoint(name, i):
+ # if it is empty, complain
+ if not name:
+ revdb.send_output("Empty breakpoint name\n")
+ revdb.send_change_breakpoint(i)
+ return
+ # if it is surrounded by < >, it is the name of a code object
+ if name.startswith('<') and name.endswith('>'):
+ add_breakpoint_funcname(name, i)
+ return
+ # if it has no ':', it can be a valid identifier (which we
+ # register as a function name), or a lineno
+ original_name = name
+ j = name.rfind(':')
+ if j < 0:
+ try:
+ lineno = int(name)
+ except ValueError:
+ if name.endswith('()'):
+ n = len(name) - 2
+ assert n >= 0
+ name = name[:n]
+ if not valid_identifier(name):
+ revdb.send_output(
+ 'Note: "%s()" doesn''t look like a function name. '
+ 'Setting breakpoint anyway\n' % name)
+ add_breakpoint_funcname(name, i)
+ name += '()'
+ if name != original_name:
+ revdb.send_change_breakpoint(i, name)
+ return
+ # "number" does the same as ":number"
+ filename = ''
+ else:
+ # if it has a ':', it must end in ':lineno'
+ try:
+ lineno = int(name[j+1:])
+ except ValueError:
+ revdb.send_output('expected a line number after colon\n')
+ revdb.send_change_breakpoint(i)
+ return
+ filename = name[:j]
+
+ # the text before must be a pathname, possibly a relative one,
+ # or be escaped by < >. if it isn't, make it absolute and normalized
+ # and warn if it doesn't end in '.py'.
+ if filename == '':
+ frame = fetch_cur_frame()
+ if frame is None:
+ revdb.send_change_breakpoint(i)
+ return
+ filename = frame.getcode().co_filename
+ elif filename.startswith('<') and filename.endswith('>'):
+ pass # use unmodified
+ elif not filename.lower().endswith('.py'):
+ # use unmodified, but warn
+ revdb.send_output(
+ 'Note: "%s" doesn''t look like a Python filename. '
+ 'Setting breakpoint anyway\n' % (filename,))
+
+ add_breakpoint_fileline(filename, lineno, i)
+ name = '%s:%d' % (filename, lineno)
+ if name != original_name:
+ revdb.send_change_breakpoint(i, name)
+
+def command_breakpoints(cmd, extra):
+ space = dbstate.space
+ dbstate.breakpoint_stack_id = cmd.c_arg1
+ revdb.set_thread_breakpoint(cmd.c_arg2)
+ dbstate.breakpoint_funcnames = None
+ dbstate.breakpoint_filelines = None
+ dbstate.breakpoint_by_file = None
+ dbstate.breakpoint_version += 1
+ watch_progs = []
+ with non_standard_code:
+ for i, kind, name in revdb.split_breakpoints_arg(extra):
+ if kind == 'B':
+ add_breakpoint(name, i)
+ elif kind == 'W':
+ code = interp_marshal.loads(space, space.newtext(name))
+ watch_progs.append((code, i, ''))
+ dbstate.watch_progs = watch_progs[:]
+lambda_breakpoints = lambda: command_breakpoints
+
+
+def command_watchvalues(cmd, extra):
+ expected = extra.split('\x00')
+ for j in range(len(dbstate.watch_progs)):
+ prog, i, _ = dbstate.watch_progs[j]
+ if i >= len(expected):
+ raise IndexError
+ dbstate.watch_progs[j] = prog, i, expected[i]
+lambda_watchvalues = lambda: command_watchvalues
+
+
+def command_stackid(cmd, extra):
+ frame = fetch_cur_frame(silent=True)
+ if frame is not None and cmd.c_arg1 != 0: # parent_flag
+ frame =
dbstate.space.getexecutioncontext().getnextframe_nohidden(frame)
+ if frame is None:
+ uid = 0
+ else:
+ uid = revdb.get_unique_id(frame)
+ if revdb.current_place() == -2:
+ hidden_level = 1 # hide the "<<" events from next/bnext commands
+ else:
+ hidden_level = 0
+ revdb.send_answer(revdb.ANSWER_STACKID, uid, hidden_level)
+lambda_stackid = lambda: command_stackid
+
+
+def command_allocating(uid, gcref):
+ w_obj = cast_gcref_to_instance(W_Root, gcref)
+ dbstate.printed_objects[uid] = w_obj
+ try:
+ index_metavar = dbstate.watch_futures.pop(uid)
+ except KeyError:
+ pass
+ else:
+ set_metavar(index_metavar, w_obj)
+lambda_allocating = lambda: command_allocating
+
+
+def command_attachid(cmd, extra):
+ space = dbstate.space
+ index_metavar = cmd.c_arg1
+ uid = cmd.c_arg2
+ try:
+ w_obj = dbstate.printed_objects[uid]
+ except KeyError:
+ # uid not found, probably a future object
+ dbstate.watch_futures[uid] = index_metavar
+ w_obj = future_object(space)
+ set_metavar(index_metavar, w_obj)
+lambda_attachid = lambda: command_attachid
+
+
+def command_compilewatch(cmd, expression):
+ space = dbstate.space
+ with non_standard_code:
+ try:
+ code = compile(expression, 'eval')
+ marshalled_code = space.bytes_w(interp_marshal.dumps(
+ space, code,
+ space.newint(interp_marshal.Py_MARSHAL_VERSION)))
+ except OperationError as e:
+ revdb.send_watch(e.errorstr(space), ok_flag=0)
+ else:
+ revdb.send_watch(marshalled_code, ok_flag=1)
+lambda_compilewatch = lambda: command_compilewatch
+
+def command_checkwatch(cmd, marshalled_code):
+ space = dbstate.space
+ with non_standard_code:
+ try:
+ code = interp_marshal.loads(space, space.newbytes(marshalled_code))
+ text = _run_watch(space, code)
+ except OperationError as e:
+ revdb.send_watch(e.errorstr(space), ok_flag=0)
+ else:
+ revdb.send_watch(text, ok_flag=1)
+lambda_checkwatch = lambda: command_checkwatch
+
+
+def _run_watch(space, prog):
+ # must be called from non_standard_code!
+ w_dict = space.builtin.w_dict
+ w_res = prog.exec_code(space, w_dict, w_dict)
+ return space.text_w(space.repr(w_res))
+
+
+# ____________________________________________________________
+
+
+ActionFlag._ticker_revdb_count = -1
+
+class RDBSignalActionFlag(AbstractActionFlag):
+ # Used instead of pypy.module.signal.interp_signal.SignalActionFlag
+ # when we have reverse-debugging. That other class would work too,
+ # but inefficiently: it would generate two words of data per bytecode.
+ # This class is tweaked to generate one byte per _SIG_TICKER_COUNT
+ # bytecodes, at the expense of not reacting to signals instantly.
+
+ # Threads: after 10'000 calls to decrement_ticker(), it should
+ # return -1. It should also return -1 if there was a signal.
+ # This is done by calling _update_ticker_from_signals() every 100
+ # calls, and invoking rsignal.pypysig_check_and_reset(); this in
+ # turn returns -1 if there was a signal or if it was called 100
+ # times.
+
+ _SIG_TICKER_COUNT = 100
+ _ticker = 0
+ _ticker_revdb_count = _SIG_TICKER_COUNT * 10
+
+ def get_ticker(self):
+ return self._ticker
+
+ def reset_ticker(self, value):
+ self._ticker = value
+
+ def rearm_ticker(self):
+ self._ticker = -1
+
+ def decrement_ticker(self, by):
+ if we_are_translated():
+ c = self._ticker_revdb_count - 1
+ if c < 0:
+ c = self._update_ticker_from_signals()
+ self._ticker_revdb_count = c
+ #if self.has_bytecode_counter: # this 'if' is constant-folded
+ # print ("RDBSignalActionFlag: has_bytecode_counter: "
+ # "not supported for now")
+ # raise NotImplementedError
+ return self._ticker
+
+ def _update_ticker_from_signals(self):
+ from rpython.rlib import rsignal
+ if dbstate.standard_code:
+ if rsignal.pypysig_check_and_reset():
+ self.rearm_ticker()
+ return self._SIG_TICKER_COUNT
+ _update_ticker_from_signals._dont_inline_ = True
diff --git a/pypy/interpreter/test/test_reverse_debugging.py
b/pypy/interpreter/test/test_reverse_debugging.py
new file mode 100644
--- /dev/null
+++ b/pypy/interpreter/test/test_reverse_debugging.py
@@ -0,0 +1,121 @@
+import dis
+from pypy.interpreter.reverse_debugging import *
+from pypy.interpreter import reverse_debugging
+from rpython.rlib import revdb
+
+
+class FakeCode:
+ hidden_applevel = False
+ def __init__(self, co_code='', co_lnotab='', co_filename='?'):
+ self.co_firstlineno = 43
+ self.co_code = co_code
+ self.co_lnotab = co_lnotab
+ self.co_revdb_linestarts = None
+ self.co_filename = co_filename
+
+
+try:
+ from hypothesis import given, strategies, example
+except ImportError:
+ pass
+else:
+ @given(strategies.binary())
+ @example("\x01\x02\x03\x04"
+ "\x00\xFF\x20\x30\x00\xFF\x00\x40"
+ "\xFF\x00\x0A\x0B\xFF\x00\x0C\x00")
+ def test_build_co_revdb_linestarts(lnotab):
+ if len(lnotab) & 1:
+ lnotab = lnotab + '\x00' # make the length even
+ code = FakeCode("?" * sum(map(ord, lnotab[0::2])), lnotab)
+ lstart = build_co_revdb_linestarts(code)
+ assert lstart is code.co_revdb_linestarts
+
+ expected_starts = set()
+ for addr, lineno in dis.findlinestarts(code):
+ expected_starts.add(addr)
+
+ next_start = len(code.co_code)
+ for index in range(len(code.co_code), -1, -1):
+ if index in expected_starts:
+ next_start = index
+ assert lstart[index] == chr(next_start - index
+ if next_start - index <= 255
+ else 255)
+
+
+class FakeFrame:
+ def __init__(self, code):
+ self.__code = code
+ def getcode(self):
+ return self.__code
+
+def check_add_breakpoint(input, curfilename=None,
+ expected_funcname=None,
+ expected_fileline=None,
+ expected_output=None,
+ expected_chbkpt=None):
+ dbstate.__dict__.clear()
+ prev = revdb.send_answer, reverse_debugging.fetch_cur_frame
+ try:
+ messages = []
+ def got_message(cmd, arg1=0, arg2=0, arg3=0, extra=""):
+ messages.append((cmd, arg1, arg2, arg3, extra))
+ def my_cur_frame():
+ assert curfilename is not None
+ return FakeFrame(FakeCode(co_filename=curfilename))
+ revdb.send_answer = got_message
+ reverse_debugging.fetch_cur_frame = my_cur_frame
+ add_breakpoint(input, 5)
+ finally:
+ revdb.send_answer, reverse_debugging.fetch_cur_frame = prev
+
+ if expected_funcname is None:
+ assert dbstate.breakpoint_funcnames is None
+ else:
+ assert dbstate.breakpoint_funcnames == {expected_funcname: 5}
+
+ if expected_fileline is None:
+ assert dbstate.breakpoint_filelines is None
+ else:
+ filename, lineno = expected_fileline
+ assert dbstate.breakpoint_filelines == [(filename.upper(), lineno, 5)]
+
+ got_output = None
+ got_chbkpt = None
+ for msg in messages:
+ if msg[0] == revdb.ANSWER_TEXT:
+ assert got_output is None
+ got_output = msg[-1]
+ elif msg[0] == revdb.ANSWER_CHBKPT:
+ assert got_chbkpt is None
+ assert msg[1] == 5
+ got_chbkpt = msg[-1]
+
+ assert got_output == expected_output
+ assert got_chbkpt == expected_chbkpt
+
+def test_add_breakpoint():
+ check_add_breakpoint('', expected_output="Empty breakpoint name\n",
+ expected_chbkpt='')
+ check_add_breakpoint('foo42', expected_funcname="foo42",
+ expected_chbkpt="foo42()")
+ check_add_breakpoint('foo42()', expected_funcname="foo42")
+ check_add_breakpoint('foo.bar', expected_funcname="foo.bar",
+ expected_output='Note: "foo.bar()" doesn''t look like a function name.'
+ ' Setting breakpoint anyway\n',
+ expected_chbkpt="foo.bar()")
+ check_add_breakpoint('<foo.bar>', expected_funcname="<foo.bar>")
+ check_add_breakpoint('42', curfilename='abcd',
+ expected_fileline=('abcd', 42),
+ expected_chbkpt='abcd:42')
+ check_add_breakpoint(':42', curfilename='abcd',
+ expected_fileline=('abcd', 42),
+ expected_chbkpt='abcd:42')
+ check_add_breakpoint('abcd:42', expected_fileline=('abcd', 42),
+ expected_output='Note: "abcd" doesnt look like a Python filename.'
+ ' Setting breakpoint anyway\n')
+ check_add_breakpoint('abcd.py:42',
+ expected_fileline=('abcd.py', 42))
+ check_add_breakpoint('42:abc',
+ expected_output='expected a line number after colon\n',
+ expected_chbkpt='')
diff --git a/pypy/module/__pypy__/__init__.py b/pypy/module/__pypy__/__init__.py
--- a/pypy/module/__pypy__/__init__.py
+++ b/pypy/module/__pypy__/__init__.py
@@ -105,6 +105,7 @@
'_promote' : 'interp_magic._promote',
'normalize_exc' : 'interp_magic.normalize_exc',
'StdErrPrinter' : 'interp_stderrprinter.W_StdErrPrinter',
+ 'side_effects_ok' : 'interp_magic.side_effects_ok',
'stack_almost_full' : 'interp_magic.stack_almost_full',
'fsencode' : 'interp_magic.fsencode',
'fsdecode' : 'interp_magic.fsdecode',
@@ -144,3 +145,7 @@
features = detect_cpu.getcpufeatures(model)
self.extra_interpdef('jit_backend_features',
'space.wrap(%r)' % features)
+ if self.space.config.translation.reverse_debugger:
+ self.extra_interpdef('revdb_stop', 'interp_magic.revdb_stop')
+ else:
+ self.extra_interpdef('revdb_stop', 'space.w_None')
diff --git a/pypy/module/__pypy__/interp_magic.py
b/pypy/module/__pypy__/interp_magic.py
--- a/pypy/module/__pypy__/interp_magic.py
+++ b/pypy/module/__pypy__/interp_magic.py
@@ -189,3 +189,23 @@
def fsdecode(space, w_obj):
"""Direct access to the interp-level fsdecode()"""
return space.fsdecode(w_obj)
+
+def side_effects_ok(space):
+ """For use with the reverse-debugger: this function normally returns
+ True, but will return False if we are evaluating a debugging command
+ like a watchpoint. You are responsible for not doing any side effect
+ at all (including no caching) when evaluating watchpoints. This
+ function is meant to help a bit---you can write:
+
+ if not __pypy__.side_effects_ok():
+ skip the caching logic
+
+ inside getter methods or properties, to make them usable from
+ watchpoints. Note that you need to re-run ``REVDB=.. pypy''
+ after changing the Python code.
+ """
+ return space.newbool(space._side_effects_ok())
+
+def revdb_stop(space):
+ from pypy.interpreter.reverse_debugging import stop_point
+ stop_point()
diff --git a/pypy/module/_cffi_backend/ccallback.py
b/pypy/module/_cffi_backend/ccallback.py
--- a/pypy/module/_cffi_backend/ccallback.py
+++ b/pypy/module/_cffi_backend/ccallback.py
@@ -3,9 +3,9 @@
"""
import sys, os, py
-from rpython.rlib import clibffi, jit, rgc, objectmodel
+from rpython.rlib import clibffi, jit, objectmodel
from rpython.rlib.objectmodel import keepalive_until_here
-from rpython.rtyper.lltypesystem import lltype, llmemory, rffi
+from rpython.rtyper.lltypesystem import lltype, rffi
from pypy.interpreter.error import OperationError, oefmt
from pypy.module._cffi_backend import cerrno, misc, parse_c_type
@@ -13,6 +13,7 @@
from pypy.module._cffi_backend.ctypefunc import SIZE_OF_FFI_ARG, W_CTypeFunc
from pypy.module._cffi_backend.ctypeprim import W_CTypePrimitiveSigned
from pypy.module._cffi_backend.ctypevoid import W_CTypeVoid
+from pypy.module._cffi_backend.hide_reveal import hide_reveal1
BIG_ENDIAN = sys.byteorder == 'big'
@@ -30,9 +31,7 @@
return cdata
def reveal_callback(raw_ptr):
- addr = rffi.cast(llmemory.Address, raw_ptr)
- gcref = rgc.reveal_gcref(addr)
- return rgc.try_cast_gcref_to_instance(W_ExternPython, gcref)
+ return hide_reveal1().reveal_object(W_ExternPython, raw_ptr)
class Closure(object):
@@ -92,9 +91,7 @@
return ctype
def hide_object(self):
- gcref = rgc.cast_instance_to_gcref(self)
- raw = rgc.hide_nonmovable_gcref(gcref)
- return rffi.cast(rffi.VOIDP, raw)
+ return hide_reveal1().hide_object(rffi.VOIDP, self)
def _repr_extra(self):
space = self.space
diff --git a/pypy/module/_cffi_backend/func.py
b/pypy/module/_cffi_backend/func.py
--- a/pypy/module/_cffi_backend/func.py
+++ b/pypy/module/_cffi_backend/func.py
@@ -168,7 +168,8 @@
rawbytes = cache.wdict.get(w_x)
if rawbytes is None:
data = space.bytes_w(w_x)
- if we_are_translated() and not rgc.can_move(data):
+ if (we_are_translated() and not rgc.can_move(data)
+ and not rgc.must_split_gc_address_space()):
lldata = llstr(data)
data_start = (llmemory.cast_ptr_to_adr(lldata) +
rffi.offsetof(STR, 'chars') +
diff --git a/pypy/module/_cffi_backend/handle.py
b/pypy/module/_cffi_backend/handle.py
--- a/pypy/module/_cffi_backend/handle.py
+++ b/pypy/module/_cffi_backend/handle.py
@@ -3,8 +3,9 @@
from pypy.interpreter.gateway import unwrap_spec
from pypy.interpreter.baseobjspace import W_Root
from pypy.module._cffi_backend import ctypeobj, ctypeptr, cdataobj
+from pypy.module._cffi_backend.hide_reveal import hide_reveal2
from rpython.rtyper.lltypesystem import lltype, llmemory, rffi
-from rpython.rlib import rgc, objectmodel, jit
+from rpython.rlib import objectmodel, jit
# ____________________________________________________________
@@ -15,9 +16,7 @@
# we can cast the CCHARP back to a W_CDataHandle with reveal_gcref().
new_cdataobj = objectmodel.instantiate(cdataobj.W_CDataHandle,
nonmovable=True)
- gcref = rgc.cast_instance_to_gcref(new_cdataobj)
- _cdata = rgc.hide_nonmovable_gcref(gcref)
- _cdata = rffi.cast(rffi.CCHARP, _cdata)
+ _cdata = hide_reveal2().hide_object(rffi.CCHARP, new_cdataobj)
cdataobj.W_CDataHandle.__init__(new_cdataobj, space, _cdata, w_ctype, w_x)
return new_cdataobj
@@ -43,11 +42,10 @@
@jit.dont_look_inside
def _reveal(space, ptr):
addr = rffi.cast(llmemory.Address, ptr)
- gcref = rgc.reveal_gcref(addr)
- if not gcref:
+ if not addr:
raise oefmt(space.w_RuntimeError,
"cannot use from_handle() on NULL pointer")
- cd = rgc.try_cast_gcref_to_instance(cdataobj.W_CDataHandle, gcref)
+ cd = hide_reveal2().reveal_object(cdataobj.W_CDataHandle, addr)
if cd is None:
raise oefmt(space.w_SystemError,
"ffi.from_handle(): dead or bogus object handle")
diff --git a/pypy/module/_cffi_backend/hide_reveal.py
b/pypy/module/_cffi_backend/hide_reveal.py
new file mode 100644
--- /dev/null
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit