Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplifying and enhancing const_key #54

Merged
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
59 changes: 7 additions & 52 deletions bytecode/instr.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import enum
import dis
import math
import opcode as _opcode
import sys
import types
from marshal import dumps as _dumps

import bytecode as _bytecode

Expand All @@ -27,56 +26,12 @@ class Compare(enum.IntEnum):


def const_key(obj):
# Python implmentation of the C function _PyCode_ConstantKey()
# of Python 3.6

obj_type = type(obj)
# Note: check obj_type == test_type rather than isinstance(obj, test_type)
# to not merge instance of subtypes

if (obj is None
or obj is Ellipsis
or obj_type in {int, bool, bytes, str, types.CodeType}):
return (obj_type, obj)

if obj_type == float:
# all we need is to make the tuple different in either the 0.0
# or -0.0 case from all others, just to avoid the "coercion".
if obj == 0.0 and math.copysign(1.0, obj) < 0:
return (obj_type, obj, None)
else:
return (obj_type, obj)

if obj_type == complex:
# For the complex case we must make complex(x, 0.)
# different from complex(x, -0.) and complex(0., y)
# different from complex(-0., y), for any x and y.
# All four complex zeros must be distinguished.
real_negzero = (obj.real == 0.0 and math.copysign(1.0, obj.real) < 0.0)
imag_negzero = (obj.imag == 0.0 and math.copysign(1.0, obj.imag) < 0.0)

# use True, False and None singleton as tags for the real and imag
# sign, to make tuples different
if real_negzero and imag_negzero:
return (obj_type, obj, True)
elif imag_negzero:
return (obj_type, obj, False)
elif real_negzero:
return (obj_type, obj, None)
else:
return (obj_type, obj)

if type(obj) == tuple:
key = tuple(const_key(item) for item in obj)
return (obj_type, obj, key)

if type(obj) == frozenset:
key = frozenset(const_key(item) for item in obj)
return (obj_type, obj, key)

# For other types, we use the object identifier as an unique identifier
# to ensure that they are seen as unequal.
return (obj_type, id(obj))
try:
return _dumps(obj)
except ValueError:
# For other types, we use the object identifier as an unique identifier
# to ensure that they are seen as unequal.
return (type(obj), id(obj))


def _check_lineno(lineno):
Expand Down
29 changes: 29 additions & 0 deletions bytecode/tests/test_instr.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,35 @@ def check(instr):
for arg in 2**31, 2**32, 2**63, 2**64, -1:
self.assertEqual(Instr('LOAD_CONST', arg).stack_effect(), 1)

def test_code_object_containing_mutable_data(self):
from bytecode import Bytecode, Instr
from types import CodeType

def f():
def g():
return "value"
return g

f_code = Bytecode.from_code(f.__code__)
instr_load_code = None
mutable_datum = [4, 2]

for each in f_code:
if (isinstance(each, Instr)
and each.name == 'LOAD_CONST'
and isinstance(each.arg, CodeType)):
instr_load_code = each
break

self.assertIsNotNone(instr_load_code)

g_code = Bytecode.from_code(instr_load_code.arg)
g_code[0].arg = mutable_datum
instr_load_code.arg = g_code.to_code()
f.__code__ = f_code.to_code()

self.assertIs(f()(), mutable_datum)


if __name__ == "__main__":
unittest.main()
4 changes: 4 additions & 0 deletions doc/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ New features:
- :class:`Bytecode`, :class:`ConcreteBytecode`, :class:`BasicBlock` and
:class:`ControlFlowGraph` have a new :meth:`legalize` method validating
their content and removing SetLineno. PR #52
- Modify the implementation of :code:`const_key` to avoid manual
synchronizations with :code:`_PyCode_ConstantKey` in CPython codebase and
allow the use of arbitrary Python objects as constants of nested code
objects. #54

API changes:

Expand Down