Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions pytest/test_stack_effect.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,5 +175,29 @@ def test_one(xdis_args, dis_args, has_arg: bool) -> None:
return


def test_stack_effect_operand_dependent() -> None:
"""Check stack effects of MAKE_FUNCTION, FORMAT_VALUE, and BUILD_SLICE
for operands whose bits change how many items are popped.
"""
for version in ((3, 6), (3, 10)):
opc = get_opcode(version, False)
for oparg, effect in ((11, -4), (12, -3), (15, -5)):
assert xstack_effect(opc.opmap["MAKE_FUNCTION"], opc, oparg) == effect

for version in ((3, 11), (3, 12)):
opc = get_opcode(version, False)
for oparg, effect in ((3, -2), (4, -1), (8, -1), (9, -2), (10, -2), (11, -3), (15, -4)):
assert xstack_effect(opc.opmap["MAKE_FUNCTION"], opc, oparg) == effect

for version in ((3, 6), (3, 12)):
opc = get_opcode(version, False)
assert xstack_effect(opc.opmap["FORMAT_VALUE"], opc, 0) == 0
assert xstack_effect(opc.opmap["FORMAT_VALUE"], opc, 4) == -1

for version in ((3, 6), (3, 13)):
opc = get_opcode(version, False)
assert xstack_effect(opc.opmap["BUILD_SLICE"], opc, 3) == -2


if __name__ == "__main__":
test_stack_effect_fixed()
18 changes: 12 additions & 6 deletions xdis/cross_dis.py
Original file line number Diff line number Diff line change
Expand Up @@ -508,21 +508,27 @@ def xstack_effect(opcode, opc, oparg: int = 0, jump=None):
"BUILD_TUPLE",
) and version_tuple >= (3, 12):
return 1 - oparg
elif opname in ("BUILD_SLICE") and version_tuple <= (2, 7):
elif opname in ("BUILD_SLICE") and version_tuple < (3, 14):
# this was 2.7 only, but 3.0 through 3.13 also pop the step in a[x:y:z]
# (3.14 changed BUILD_SLICE and its opcode table already handles it)
return -2 if oparg == 3 else -1
elif opname == "FORMAT_VALUE" and version_tuple >= (3, 6):
# f"{x:>10}" also pops the format spec; the table only counted the value
return -1 if oparg & 0x04 else 0
elif opname == "LOAD_ATTR" and version_tuple >= (3, 12):
return 1 if oparg & 1 else 0
elif opname == "MAKE_FUNCTION":
if version_tuple >= (3, 5):
if 0 <= oparg <= 10:
# was 10, but a nested function with defaults, kwdefaults, and a closure is 11 (15 with annotations)
if 0 <= oparg <= (10 if version_tuple == (3, 5) else 15):
if version_tuple == (3, 5):
return [-1, -2, -3, -3, -2, -3, -3, -4, -2, -3, -3, -4][oparg]
elif (3, 6) <= version_tuple < (3, 11):
return [-1, -2, -2, -3, -2, -3, -3, -4, -2, -3, -3, -4][oparg]
elif 0 <= oparg <= 2:
return [0, -1, -1][oparg]
return [-1, -2, -2, -3, -2, -3, -3, -4, -2, -3, -3, -4, -3, -4, -4, -5][oparg]
else:
return None
# only flags 0-2 were handled, so closures and annotations gave None;
# this is the 3.6-3.10 table plus one since 3.11 no longer pops the qualname
return [0, -1, -1, -2, -1, -2, -2, -3, -1, -2, -2, -3, -2, -3, -3, -4][oparg]
else:
return None
elif opname in ("CALL", "INSTRUMENTED_CALL") and version_tuple >= (3, 12):
Expand Down
Loading