Skip to content

Commit 29061a2

Browse files
authored
Use Slots on all Expression objects (#2394)
* XSlotted metaclass to force all Expressions to use slots * Add test for slots. Also small refactor for abstract classes and operands arguments * Remove pickle data file belonging to deleted test
1 parent dd57db8 commit 29061a2

File tree

8 files changed

+298
-77
lines changed

8 files changed

+298
-77
lines changed

manticore/core/smtlib/expression.py

Lines changed: 107 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
import re
66
import copy
7-
from typing import Union, Optional, Dict, List
7+
from typing import Union, Optional, Dict, Tuple
88

99

1010
class ExpressionException(SmtlibError):
@@ -15,10 +15,59 @@ class ExpressionException(SmtlibError):
1515
pass
1616

1717

18-
class Expression:
18+
class XSlotted(type):
19+
"""
20+
Metaclass that will propagate slots on multi-inheritance classes
21+
Every class should define __xslots__ (instead of __slots__)
22+
23+
class Base(object, metaclass=XSlotted, abstract=True):
24+
pass
25+
26+
class A(Base, abstract=True):
27+
__xslots__ = ('a',)
28+
pass
29+
30+
class B(Base, abstract=True):
31+
__xslots__ = ('b',)
32+
pass
33+
34+
class C(A, B):
35+
pass
36+
37+
# Normal case / baseline
38+
class X(object):
39+
__slots__ = ('a', 'b')
40+
41+
c = C()
42+
c.a = 1
43+
c.b = 2
44+
45+
x = X()
46+
x.a = 1
47+
x.b = 2
48+
49+
import sys
50+
print (sys.getsizeof(c),sys.getsizeof(x)) #same value
51+
"""
52+
53+
def __new__(cls, clsname, bases, attrs, abstract=False):
54+
xslots = frozenset(attrs.get("__xslots__", ()))
55+
# merge the xslots of all the bases with the one defined here
56+
for base in bases:
57+
xslots = xslots.union(getattr(base, "__xslots__", ()))
58+
attrs["__xslots__"] = tuple(xslots)
59+
if abstract:
60+
attrs["__slots__"] = tuple()
61+
else:
62+
attrs["__slots__"] = attrs["__xslots__"]
63+
attrs["__hash__"] = object.__hash__
64+
return super().__new__(cls, clsname, bases, attrs)
65+
66+
67+
class Expression(object, metaclass=XSlotted, abstract=True):
1968
""" Abstract taintable Expression. """
2069

21-
__slots__ = ["_taint"]
70+
__xslots__: Tuple[str, ...] = ("_taint",)
2271

2372
def __init__(self, taint: Union[tuple, frozenset] = ()):
2473
if self.__class__ is Expression:
@@ -114,8 +163,8 @@ def taint_with(arg, *taints, value_bits=256, index_bits=256):
114163

115164
###############################################################################
116165
# Booleans
117-
class Bool(Expression):
118-
__slots__: List[str] = []
166+
class Bool(Expression, abstract=True):
167+
"""Bool expressions represent symbolic value of truth"""
119168

120169
def __init__(self, *operands, **kwargs):
121170
super().__init__(*operands, **kwargs)
@@ -169,7 +218,7 @@ def __bool__(self):
169218

170219

171220
class BoolVariable(Bool):
172-
__slots__ = ["_name"]
221+
__xslots__: Tuple[str, ...] = ("_name",)
173222

174223
def __init__(self, name: str, *args, **kwargs):
175224
assert " " not in name
@@ -195,11 +244,11 @@ def declaration(self):
195244

196245

197246
class BoolConstant(Bool):
198-
__slots__ = ["_value"]
247+
__xslots__: Tuple[str, ...] = ("_value",)
199248

200249
def __init__(self, value: bool, *args, **kwargs):
201-
self._value = value
202250
super().__init__(*args, **kwargs)
251+
self._value = value
203252

204253
def __bool__(self):
205254
return self.value
@@ -209,8 +258,10 @@ def value(self):
209258
return self._value
210259

211260

212-
class BoolOperation(Bool):
213-
__slots__ = ["_operands"]
261+
class BoolOperation(Bool, abstract=True):
262+
""" An operation that results in a Bool """
263+
264+
__xslots__: Tuple[str, ...] = ("_operands",)
214265

215266
def __init__(self, *operands, **kwargs):
216267
self._operands = operands
@@ -250,10 +301,10 @@ def __init__(self, cond: "Bool", true: "Bool", false: "Bool", **kwargs):
250301
super().__init__(cond, true, false, **kwargs)
251302

252303

253-
class BitVec(Expression):
254-
""" This adds a bitsize to the Expression class """
304+
class BitVec(Expression, abstract=True):
305+
""" BitVector expressions have a fixed bit size """
255306

256-
__slots__ = ["size"]
307+
__xslots__: Tuple[str, ...] = ("size",)
257308

258309
def __init__(self, size, *operands, **kwargs):
259310
super().__init__(*operands, **kwargs)
@@ -456,7 +507,7 @@ def Bool(self):
456507

457508

458509
class BitVecVariable(BitVec):
459-
__slots__ = ["_name"]
510+
__xslots__: Tuple[str, ...] = ("_name",)
460511

461512
def __init__(self, size: int, name: str, *args, **kwargs):
462513
assert " " not in name
@@ -482,7 +533,7 @@ def declaration(self):
482533

483534

484535
class BitVecConstant(BitVec):
485-
__slots__ = ["_value"]
536+
__xslots__: Tuple[str, ...] = ("_value",)
486537

487538
def __init__(self, size: int, value: int, *args, **kwargs):
488539
MASK = (1 << size) - 1
@@ -512,8 +563,10 @@ def signed_value(self):
512563
return self._value
513564

514565

515-
class BitVecOperation(BitVec):
516-
__slots__ = ["_operands"]
566+
class BitVecOperation(BitVec, abstract=True):
567+
""" An operation that results in a BitVec """
568+
569+
__xslots__: Tuple[str, ...] = ("_operands",)
517570

518571
def __init__(self, size, *operands, **kwargs):
519572
self._operands = operands
@@ -670,8 +723,15 @@ def __init__(self, a, b, *args, **kwargs):
670723

671724
###############################################################################
672725
# Array BV32 -> BV8 or BV64 -> BV8
673-
class Array(Expression):
674-
__slots__ = ["_index_bits", "_index_max", "_value_bits"]
726+
class Array(Expression, abstract=True):
727+
"""An Array expression is an unmutable mapping from bitvector to bitvector
728+
729+
array.index_bits is the number of bits used for addressing a value
730+
array.value_bits is the number of bits used in the values
731+
array.index_max counts the valid indexes starting at 0. Accessing outside the bound is undefined
732+
"""
733+
734+
__xslots__: Tuple[str, ...] = ("_index_bits", "_index_max", "_value_bits")
675735

676736
def __init__(
677737
self, index_bits: int, index_max: Optional[int], value_bits: int, *operands, **kwargs
@@ -764,7 +824,7 @@ def store(self, index, value):
764824

765825
def write(self, offset, buf):
766826
if not isinstance(buf, (Array, bytearray)):
767-
raise TypeError("Array or bytearray expected got {:s}".format(type(buf)))
827+
raise TypeError(f"Array or bytearray expected got {type(buf)}")
768828
arr = self
769829
for i, val in enumerate(buf):
770830
arr = arr.store(offset + i, val)
@@ -820,18 +880,18 @@ def read_BE(self, address, size):
820880
bytes = []
821881
for offset in range(size):
822882
bytes.append(self.get(address + offset, 0))
823-
return BitVecConcat(size * self.value_bits, *bytes)
883+
return BitVecConcat(size * self.value_bits, tuple(bytes))
824884

825885
def read_LE(self, address, size):
826886
address = self.cast_index(address)
827887
bytes = []
828888
for offset in range(size):
829889
bytes.append(self.get(address + offset, 0))
830-
return BitVecConcat(size * self.value_bits, *reversed(bytes))
890+
return BitVecConcat(size * self.value_bits, tuple(reversed(bytes)))
831891

832892
def write_BE(self, address, value, size):
833893
address = self.cast_index(address)
834-
value = BitVec(size * self.value_bits).cast(value)
894+
value = BitVecConstant(size * self.value_bits, value=0).cast(value)
835895
array = self
836896
for offset in range(size):
837897
array = array.store(
@@ -842,7 +902,7 @@ def write_BE(self, address, value, size):
842902

843903
def write_LE(self, address, value, size):
844904
address = self.cast_index(address)
845-
value = BitVec(size * self.value_bits).cast(value)
905+
value = BitVecConstant(size * self.value_bits, value=0).cast(value)
846906
array = self
847907
for offset in reversed(range(size)):
848908
array = array.store(
@@ -903,7 +963,7 @@ def __radd__(self, other):
903963

904964

905965
class ArrayVariable(Array):
906-
__slots__ = ["_name"]
966+
__xslots__: Tuple[str, ...] = ("_name",)
907967

908968
def __init__(self, index_bits, index_max, value_bits, name, *args, **kwargs):
909969
assert " " not in name
@@ -929,7 +989,9 @@ def declaration(self):
929989

930990

931991
class ArrayOperation(Array):
932-
__slots__ = ["_operands"]
992+
"""An operation that result in an Array"""
993+
994+
__xslots__: Tuple[str, ...] = ("_operands",)
933995

934996
def __init__(self, array: Array, *operands, **kwargs):
935997
self._operands = (array, *operands)
@@ -989,6 +1051,8 @@ def __setstate__(self, state):
9891051

9901052

9911053
class ArraySlice(ArrayOperation):
1054+
__xslots__: Tuple[str, ...] = ("_slice_offset", "_slice_size")
1055+
9921056
def __init__(
9931057
self, array: Union["Array", "ArrayProxy"], offset: int, size: int, *args, **kwargs
9941058
):
@@ -1033,6 +1097,15 @@ def store(self, index, value):
10331097

10341098

10351099
class ArrayProxy(Array):
1100+
__xslots__: Tuple[str, ...] = (
1101+
"constraints",
1102+
"_default",
1103+
"_concrete_cache",
1104+
"_written",
1105+
"_array",
1106+
"_name",
1107+
)
1108+
10361109
def __init__(self, array: Array, default: Optional[int] = None):
10371110
self._default = default
10381111
self._concrete_cache: Dict[int, int] = {}
@@ -1229,7 +1302,7 @@ def get(self, index, default=None):
12291302

12301303

12311304
class ArraySelect(BitVec):
1232-
__slots__ = ["_operands"]
1305+
__xslots__: Tuple[str, ...] = ("_operands",)
12331306

12341307
def __init__(self, array: "Array", index: "BitVec", *operands, **kwargs):
12351308
assert index.size == array.index_bits
@@ -1257,20 +1330,26 @@ def __repr__(self):
12571330

12581331

12591332
class BitVecSignExtend(BitVecOperation):
1333+
__xslots__: Tuple[str, ...] = ("extend",)
1334+
12601335
def __init__(self, operand: "BitVec", size_dest: int, *args, **kwargs):
12611336
assert size_dest >= operand.size
12621337
super().__init__(size_dest, operand, *args, **kwargs)
12631338
self.extend = size_dest - operand.size
12641339

12651340

12661341
class BitVecZeroExtend(BitVecOperation):
1342+
__xslots__: Tuple[str, ...] = ("extend",)
1343+
12671344
def __init__(self, size_dest: int, operand: "BitVec", *args, **kwargs):
12681345
assert size_dest >= operand.size
12691346
super().__init__(size_dest, operand, *args, **kwargs)
12701347
self.extend = size_dest - operand.size
12711348

12721349

12731350
class BitVecExtract(BitVecOperation):
1351+
__xslots__: Tuple[str, ...] = ("_begining", "_end")
1352+
12741353
def __init__(self, operand: "BitVec", offset: int, size: int, *args, **kwargs):
12751354
assert offset >= 0 and offset + size <= operand.size
12761355
super().__init__(size, operand, *args, **kwargs)
@@ -1291,7 +1370,7 @@ def end(self):
12911370

12921371

12931372
class BitVecConcat(BitVecOperation):
1294-
def __init__(self, size_dest: int, *operands, **kwargs):
1373+
def __init__(self, size_dest: int, operands: Tuple, **kwargs):
12951374
assert all(isinstance(x, BitVec) for x in operands)
12961375
assert size_dest == sum(x.size for x in operands)
12971376
super().__init__(size_dest, *operands, **kwargs)

manticore/core/smtlib/operators.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ def cast(x):
163163
return BitVecConstant(arg_size, x)
164164
return x
165165

166-
return BitVecConcat(total_size, *list(map(cast, args)))
166+
return BitVecConcat(total_size, tuple(map(cast, args)))
167167
else:
168168
return args[0]
169169
else:

manticore/core/smtlib/visitors.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -594,7 +594,7 @@ def visit_BitVecConcat(self, expression, *operands):
594594
if last_o is not None:
595595
new_operands.append(last_o)
596596
if changed:
597-
return BitVecConcat(expression.size, *new_operands)
597+
return BitVecConcat(expression.size, tuple(new_operands))
598598

599599
op = operands[0]
600600
value = None
@@ -648,7 +648,7 @@ def visit_BitVecExtract(self, expression, *operands):
648648
if size == 0:
649649
assert expression.size == sum([x.size for x in new_operands])
650650
return BitVecConcat(
651-
expression.size, *reversed(new_operands), taint=expression.taint
651+
expression.size, tuple(reversed(new_operands)), taint=expression.taint
652652
)
653653

654654
if begining >= item.size:

tests/ethereum/test_general.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from manticore.core.plugin import Plugin
1717
from manticore.core.smtlib import ConstraintSet, operators
1818
from manticore.core.smtlib import Z3Solver
19-
from manticore.core.smtlib.expression import BitVec
19+
from manticore.core.smtlib.expression import BitVec, BitVecVariable
2020
from manticore.core.smtlib.visitors import to_constant
2121
from manticore.core.state import TerminateState
2222
from manticore.ethereum import (
@@ -1382,7 +1382,7 @@ def will_evm_execute_instruction_callback(self, state, i, *args, **kwargs):
13821382

13831383
class EthHelpersTest(unittest.TestCase):
13841384
def setUp(self):
1385-
self.bv = BitVec(256)
1385+
self.bv = BitVecVariable(256, name="BVV")
13861386

13871387
def test_concretizer(self):
13881388
policy = "SOME_NONSTANDARD_POLICY"

tests/native/test_register.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import unittest
22

3-
from manticore.core.smtlib import Bool, BitVecConstant
3+
from manticore.core.smtlib import Bool, BoolVariable, BitVecConstant
44
from manticore.native.cpu.register import Register
55

66

@@ -47,7 +47,7 @@ def test_bool_write_nonflag(self):
4747

4848
def test_Bool(self):
4949
r = Register(32)
50-
b = Bool()
50+
b = BoolVariable(name="B")
5151
r.write(b)
5252
self.assertIs(r.read(), b)
5353

tests/other/data/ErrRelated.pkl.gz

-17.2 KB
Binary file not shown.

0 commit comments

Comments
 (0)