Author: Armin Rigo <ar...@tunes.org> Branch: py3.5 Changeset: r94675:f984fd051c58 Date: 2018-05-23 22:44 +0200 http://bitbucket.org/pypy/pypy/changeset/f984fd051c58/
Log: hg merge py3.5-reverse-debugger diff too long, truncating to 2000 out of 7499 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/doc/whatsnew-head.rst b/pypy/doc/whatsnew-head.rst --- a/pypy/doc/whatsnew-head.rst +++ b/pypy/doc/whatsnew-head.rst @@ -22,3 +22,8 @@ .. branch: gc-more-logging Log additional gc-minor and gc-collect-step info in the PYPYLOG + +.. branch: reverse-debugger + +The reverse-debugger branch has been merged. For more information, see +https://bitbucket.org/pypy/revdb 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,24 @@ run_protected() handles details like forwarding exceptions to sys.excepthook(), catching SystemExit, etc. """ + # don't use try:except: here, otherwise the exception remains + # visible in user code. Make sure revdb_stop is a callable, so + # that we can call it immediately after finally: below. Doing + # so minimizes the number of "blind" lines that we need to go + # back from, with "bstep", after we do "continue" in revdb. + if '__pypy__' in sys.builtin_module_names: + from __pypy__ import revdb_stop + else: + revdb_stop = None + if revdb_stop is None: + revdb_stop = lambda: None + try: # run it try: f(*fargs, **fkwds) finally: + 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), + # XXX is 'softspace' still necessary in Python 3? + 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") + +@gateway.unwrap_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 + """) + +@specialize.memo() +def get_revdb_displayhook(space): + return gateway.interp2app(revdb_displayhook).spacebind(space) + +@specialize.memo() +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 = sorted(locals.keys()) + print('Locals:') + for key in lst: + try: + print(' %s =' % key, end=' ', flush=True) + 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.newbytes(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) _______________________________________________ pypy-commit mailing list pypy-commit@python.org https://mail.python.org/mailman/listinfo/pypy-commit