From 1cd2d1e91565a9ac5ea57a9a19bf347518f4a883 Mon Sep 17 00:00:00 2001 From: rocky Date: Tue, 3 Apr 2018 10:35:02 -0400 Subject: [PATCH] DRY scanner code more... Expand 2.6 testing --- Makefile | 2 +- pytest/test_fjt.py | 21 +++------------------ pytest/test_function_call.py | 15 ++++++++++++--- pytest/test_grammar.py | 12 +++++++++--- pytest/test_single_compile.py | 4 +++- uncompyle6/scanner.py | 12 ++++++++---- uncompyle6/scanners/scanner2.py | 11 ++--------- uncompyle6/scanners/scanner26.py | 9 +-------- uncompyle6/scanners/scanner3.py | 5 ----- 9 files changed, 39 insertions(+), 52 deletions(-) diff --git a/Makefile b/Makefile index eb24ba59..e860e38c 100644 --- a/Makefile +++ b/Makefile @@ -28,7 +28,7 @@ check-short: pytest $(MAKE) -C test check-short #: Tests for Python 2.7, 3.3 and 3.4 -check-2.7 check-3.3 check-3.4: pytest +check-2.6 check-2.7 check-3.3 check-3.4: pytest $(MAKE) -C test $@ #: Tests for Python 3.2 and 3.5 - pytest doesn't work here diff --git a/pytest/test_fjt.py b/pytest/test_fjt.py index bad2ff2f..9987a9cf 100644 --- a/pytest/test_fjt.py +++ b/pytest/test_fjt.py @@ -23,12 +23,7 @@ def test_if_in_for(): code = bug.__code__ scan = get_scanner(PYTHON_VERSION) if 2.7 <= PYTHON_VERSION <= 3.0 and not IS_PYPY: - bytecode = scan.build_instructions(code) - scan.lines = scan.build_lines_data(code) - scan.insts = list(bytecode) - scan.offset2inst_index = {} - for i, inst in enumerate(scan.insts): - scan.offset2inst_index[inst.offset] = i + scan.build_instructions(code) fjt = scan.find_jump_targets(False) ## FIXME: the data below is wrong. @@ -43,12 +38,7 @@ def test_if_in_for(): # {'start': 62, 'end': 63, 'type': 'for-else'}] code = bug_loop.__code__ - bytecode = scan.build_instructions(code) - scan.lines = scan.build_lines_data(code) - scan.insts = list(bytecode) - scan.offset2inst_index = {} - for i, inst in enumerate(scan.insts): - scan.offset2inst_index[inst.offset] = i + scan.build_instructions(code) fjt = scan.find_jump_targets(False) assert{64: [42], 67: [42, 42], 42: [16, 41], 19: [6]} == fjt assert scan.structs == [ @@ -62,12 +52,7 @@ def test_if_in_for(): {'start': 48, 'end': 67, 'type': 'while-loop'}] elif 3.2 < PYTHON_VERSION <= 3.4: - bytecode = scan.build_instructions(code) - scan.lines = scan.build_lines_data(code) - scan.insts = list(bytecode) - scan.offset2inst_index = {} - for i, inst in enumerate(scan.insts): - scan.offset2inst_index[inst.offset] = i + scan.build_instructions(code) fjt = scan.find_jump_targets(False) assert {69: [66], 63: [18]} == fjt assert scan.structs == \ diff --git a/pytest/test_function_call.py b/pytest/test_function_call.py index 936c31ce..4adb06a7 100644 --- a/pytest/test_function_call.py +++ b/pytest/test_function_call.py @@ -6,7 +6,7 @@ import pytest # uncompyle from validate import validate_uncompyle from test_fstring import expressions - +from uncompyle6 import PYTHON_VERSION alpha = st.sampled_from(string.ascii_lowercase) numbers = st.sampled_from(string.digits) @@ -81,10 +81,11 @@ def function_calls(draw, def test_function_no_args(): validate_uncompyle("fn()") - +@pytest.mark.skipif(PYTHON_VERSION < 2.7, + reason="need at least Python 2.7") def isolated_function_calls(which): """ - Returns a strategy for generating function calls, but isolated to + Returns a strategy for generating function calls, but isolated to particular types of arguments, for example only positional arguments. This can help reason about debugging errors in specific types of function @@ -108,21 +109,29 @@ def isolated_function_calls(which): with settings(max_examples=25): + @pytest.mark.skipif(PYTHON_VERSION < 2.7, + reason="need at least Python 2.7") @given(isolated_function_calls('positional')) @example("fn(0)") def test_function_positional_only(expr): validate_uncompyle(expr) + @pytest.mark.skipif(PYTHON_VERSION < 2.7, + reason="need at least Python 2.7") @given(isolated_function_calls('keyword')) @example("fn(a=0)") def test_function_call_keyword_only(expr): validate_uncompyle(expr) + @pytest.mark.skipif(PYTHON_VERSION < 2.7, + reason="need at least Python 2.7") @given(isolated_function_calls('star')) @example("fn(*items)") def test_function_call_star_only(expr): validate_uncompyle(expr) + @pytest.mark.skipif(PYTHON_VERSION < 2.7, + reason="need at least Python 2.7") @given(isolated_function_calls('double_star')) @example("fn(**{})") def test_function_call_double_star_only(expr): diff --git a/pytest/test_grammar.py b/pytest/test_grammar.py index cd486ea9..aebbd463 100644 --- a/pytest/test_grammar.py +++ b/pytest/test_grammar.py @@ -20,13 +20,17 @@ def test_grammar(): # We have custom rules that create the below expect_lhs = set(['pos_arg', 'get_iter', 'attribute']) - unused_rhs = set(['list', 'mkfunc', 'dict', + unused_rhs = set(['list', 'mkfunc', 'mklambda', 'unpack',]) expect_right_recursive = set([('designList', ('store', 'DUP_TOP', 'designList'))]) - expect_lhs.add('kvlist') - expect_lhs.add('kv3') + + if PYTHON_VERSION > 2.6: + expect_lhs.add('kvlist') + expect_lhs.add('kv3') + unused_rhs.add('dict') + if PYTHON3: expect_lhs.add('load_genexpr') @@ -85,6 +89,8 @@ def test_grammar(): """.split()) if 2.6 <= PYTHON_VERSION <= 2.7: opcode_set = set(s.opc.opname).union(ignore_set) + if PYTHON_VERSION == 2.6: + opcode_set.add("THEN") check_tokens(tokens, opcode_set) elif PYTHON_VERSION == 3.4: ignore_set.add('LOAD_CLASSNAME') diff --git a/pytest/test_single_compile.py b/pytest/test_single_compile.py index 2e381aa5..34f1ae31 100644 --- a/pytest/test_single_compile.py +++ b/pytest/test_single_compile.py @@ -1,6 +1,8 @@ import pytest -from uncompyle6 import PYTHON_VERSION, PYTHON3, deparse_code +from uncompyle6 import PYTHON_VERSION, deparse_code +@pytest.mark.skip(PYTHON_VERSION < 2.7, + reason="need at least Python 2.7") def test_single_mode(): single_expressions = ( 'i = 1', diff --git a/uncompyle6/scanner.py b/uncompyle6/scanner.py index 8f3aa230..410bda00 100755 --- a/uncompyle6/scanner.py +++ b/uncompyle6/scanner.py @@ -105,6 +105,11 @@ class Scanner(object): bytecode = Bytecode(co, self.opc) self.build_prev_op() self.insts = self.remove_extended_args(list(bytecode)) + self.lines = self.build_lines_data(co) + self.offset2inst_index = {} + for i, inst in enumerate(self.insts): + self.offset2inst_index[inst.offset] = i + return bytecode def build_lines_data(self, code_obj): @@ -117,10 +122,6 @@ class Scanner(object): linestarts = list(self.opc.findlinestarts(code_obj)) self.linestarts = dict(linestarts) - # Plain set with offsets of first ops on line. - # FIXME: we probably could do without - self.linestart_offsets = set(a for (a, _) in linestarts) - # 'List-map' which shows line number of current op and offset of # first op on following line, given offset of op as index lines = [] @@ -452,6 +453,9 @@ class Scanner(object): Go through passed offsets, filtering ifs located somewhere mid-line. """ + + # FIXME: this doesn't work for Python 3.6+ + filtered = [] for i in ifs: # For each offset, if line number of current and next op diff --git a/uncompyle6/scanners/scanner2.py b/uncompyle6/scanners/scanner2.py index d6875e73..8a5bd8c3 100644 --- a/uncompyle6/scanners/scanner2.py +++ b/uncompyle6/scanners/scanner2.py @@ -171,11 +171,6 @@ class Scanner2(Scanner): customize['PyPy'] = 0 codelen = len(self.code) - self.lines = self.build_lines_data(co) - - self.offset2inst_index = {} - for i, inst in enumerate(self.insts): - self.offset2inst_index[inst.offset] = i free, names, varnames = self.unmangle_code_names(co, classname) self.names = names @@ -186,8 +181,6 @@ class Scanner2(Scanner): self.load_asserts = set() for i in self.op_range(0, codelen): - self.offset2inst_index[inst.offset] = i - # We need to detect the difference between: # raise AssertionError # and @@ -358,7 +351,7 @@ class Scanner2(Scanner): if (offset in self.stmts and self.code[offset+3] not in (self.opc.END_FINALLY, self.opc.POP_BLOCK)): - if ((offset in self.linestart_offsets and + if ((offset in self.linestarts and self.code[self.prev[offset]] == self.opc.JUMP_ABSOLUTE) or self.code[target] == self.opc.FOR_ITER or offset not in self.not_continue): @@ -956,7 +949,7 @@ class Scanner2(Scanner): 'end': pre_rtarget}) # FIXME: this is yet another case were we need dominators. - if (pre_rtarget not in self.linestart_offsets + if (pre_rtarget not in self.linestarts or self.version < 2.7): self.not_continue.add(pre_rtarget) diff --git a/uncompyle6/scanners/scanner26.py b/uncompyle6/scanners/scanner26.py index 90af54e8..210fab82 100755 --- a/uncompyle6/scanners/scanner26.py +++ b/uncompyle6/scanners/scanner26.py @@ -84,13 +84,6 @@ class Scanner26(scan.Scanner2): codelen = len(self.code) - self.lines = self.build_lines_data(co) - - self.insts = list(bytecode) - self.offset2inst_index = {} - for i, inst in enumerate(self.insts): - self.offset2inst_index[inst.offset] = i - free, names, varnames = self.unmangle_code_names(co, classname) self.names = names @@ -248,7 +241,7 @@ class Scanner26(scan.Scanner2): if (offset in self.stmts and self.code[offset+3] not in (self.opc.END_FINALLY, self.opc.POP_BLOCK)): - if ((offset in self.linestart_offsets and + if ((offset in self.linestarts and tokens[-1].kind == 'JUMP_BACK') or offset not in self.not_continue): op_name = 'CONTINUE' diff --git a/uncompyle6/scanners/scanner3.py b/uncompyle6/scanners/scanner3.py index d291ef4b..82753646 100644 --- a/uncompyle6/scanners/scanner3.py +++ b/uncompyle6/scanners/scanner3.py @@ -187,19 +187,14 @@ class Scanner3(Scanner): if self.is_pypy: customize['PyPy'] = 0 - self.lines = self.build_lines_data(co) - # Scan for assertions. Later we will # turn 'LOAD_GLOBAL' to 'LOAD_ASSERT'. # 'LOAD_ASSERT' is used in assert statements. self.load_asserts = set() - self.offset2inst_index = {} n = len(self.insts) for i, inst in enumerate(self.insts): - self.offset2inst_index[inst.offset] = i - # We need to detect the difference between: # raise AssertionError # and