Skip to content

Commit 04676b6

Browse files
authored
bpo-45017: move opcode-related logic from modulefinder to dis (GH-28246)
1 parent 49acac0 commit 04676b6

File tree

3 files changed

+76
-26
lines changed

3 files changed

+76
-26
lines changed

Lib/dis.py

+36
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,42 @@ def findlinestarts(code):
535535
yield start, line
536536
return
537537

538+
def _find_imports(co):
539+
"""Find import statements in the code
540+
541+
Generate triplets (name, level, fromlist) where
542+
name is the imported module and level, fromlist are
543+
the corresponding args to __import__.
544+
"""
545+
IMPORT_NAME = opmap['IMPORT_NAME']
546+
LOAD_CONST = opmap['LOAD_CONST']
547+
548+
consts = co.co_consts
549+
names = co.co_names
550+
opargs = [(op, arg) for _, op, arg in _unpack_opargs(co.co_code)
551+
if op != EXTENDED_ARG]
552+
for i, (op, oparg) in enumerate(opargs):
553+
if (op == IMPORT_NAME and i >= 2
554+
and opargs[i-1][0] == opargs[i-2][0] == LOAD_CONST):
555+
level = consts[opargs[i-2][1]]
556+
fromlist = consts[opargs[i-1][1]]
557+
yield (names[oparg], level, fromlist)
558+
559+
def _find_store_names(co):
560+
"""Find names of variables which are written in the code
561+
562+
Generate sequence of strings
563+
"""
564+
STORE_OPS = {
565+
opmap['STORE_NAME'],
566+
opmap['STORE_GLOBAL']
567+
}
568+
569+
names = co.co_names
570+
for _, op, arg in _unpack_opargs(co.co_code):
571+
if op in STORE_OPS:
572+
yield names[arg]
573+
538574

539575
class Bytecode:
540576
"""The bytecode operations of a piece of code

Lib/modulefinder.py

+7-26
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,6 @@
88
import io
99
import sys
1010

11-
12-
LOAD_CONST = dis.opmap['LOAD_CONST']
13-
IMPORT_NAME = dis.opmap['IMPORT_NAME']
14-
STORE_NAME = dis.opmap['STORE_NAME']
15-
STORE_GLOBAL = dis.opmap['STORE_GLOBAL']
16-
STORE_OPS = STORE_NAME, STORE_GLOBAL
17-
EXTENDED_ARG = dis.EXTENDED_ARG
18-
1911
# Old imp constants:
2012

2113
_SEARCH_ERROR = 0
@@ -394,24 +386,13 @@ def _safe_import_hook(self, name, caller, fromlist, level=-1):
394386

395387
def scan_opcodes(self, co):
396388
# Scan the code, and yield 'interesting' opcode combinations
397-
code = co.co_code
398-
names = co.co_names
399-
consts = co.co_consts
400-
opargs = [(op, arg) for _, op, arg in dis._unpack_opargs(code)
401-
if op != EXTENDED_ARG]
402-
for i, (op, oparg) in enumerate(opargs):
403-
if op in STORE_OPS:
404-
yield "store", (names[oparg],)
405-
continue
406-
if (op == IMPORT_NAME and i >= 2
407-
and opargs[i-1][0] == opargs[i-2][0] == LOAD_CONST):
408-
level = consts[opargs[i-2][1]]
409-
fromlist = consts[opargs[i-1][1]]
410-
if level == 0: # absolute import
411-
yield "absolute_import", (fromlist, names[oparg])
412-
else: # relative import
413-
yield "relative_import", (level, fromlist, names[oparg])
414-
continue
389+
for name in dis._find_store_names(co):
390+
yield "store", (name,)
391+
for name, level, fromlist in dis._find_imports(co):
392+
if level == 0: # absolute import
393+
yield "absolute_import", (fromlist, name)
394+
else: # relative import
395+
yield "relative_import", (level, fromlist, name)
415396

416397
def scan_code(self, co, m):
417398
code = co.co_code

Lib/test/test_dis.py

+33
Original file line numberDiff line numberDiff line change
@@ -1326,5 +1326,38 @@ def test_assert_not_in_with_arg_in_bytecode(self):
13261326
with self.assertRaises(AssertionError):
13271327
self.assertNotInBytecode(code, "LOAD_CONST", 1)
13281328

1329+
class TestFinderMethods(unittest.TestCase):
1330+
def test__find_imports(self):
1331+
cases = [
1332+
("import a.b.c", ('a.b.c', 0, None)),
1333+
("from a.b import c", ('a.b', 0, ('c',))),
1334+
("from a.b import c as d", ('a.b', 0, ('c',))),
1335+
("from a.b import *", ('a.b', 0, ('*',))),
1336+
("from ...a.b import c as d", ('a.b', 3, ('c',))),
1337+
("from ..a.b import c as d, e as f", ('a.b', 2, ('c', 'e'))),
1338+
("from ..a.b import *", ('a.b', 2, ('*',))),
1339+
]
1340+
for src, expected in cases:
1341+
with self.subTest(src=src):
1342+
code = compile(src, "<string>", "exec")
1343+
res = tuple(dis._find_imports(code))
1344+
self.assertEqual(len(res), 1)
1345+
self.assertEqual(res[0], expected)
1346+
1347+
def test__find_store_names(self):
1348+
cases = [
1349+
("x+y", ()),
1350+
("x=y=1", ('x', 'y')),
1351+
("x+=y", ('x',)),
1352+
("global x\nx=y=1", ('x', 'y')),
1353+
("global x\nz=x", ('z',)),
1354+
]
1355+
for src, expected in cases:
1356+
with self.subTest(src=src):
1357+
code = compile(src, "<string>", "exec")
1358+
res = tuple(dis._find_store_names(code))
1359+
self.assertEqual(res, expected)
1360+
1361+
13291362
if __name__ == "__main__":
13301363
unittest.main()

0 commit comments

Comments
 (0)