crosshair-tool 0.0.83__cp39-cp39-macosx_10_9_universal2.whl → 0.0.84__cp39-cp39-macosx_10_9_universal2.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of crosshair-tool might be problematic. Click here for more details.
- _crosshair_tracers.cpython-39-darwin.so +0 -0
- crosshair/__init__.py +1 -1
- crosshair/auditwall.py +0 -1
- crosshair/auditwall_test.py +5 -0
- crosshair/condition_parser_test.py +50 -63
- crosshair/core.py +23 -17
- crosshair/core_test.py +625 -584
- crosshair/diff_behavior_test.py +14 -21
- crosshair/dynamic_typing.py +91 -2
- crosshair/dynamic_typing_test.py +73 -1
- crosshair/enforce_test.py +15 -22
- crosshair/fnutil_test.py +0 -7
- crosshair/libimpl/arraylib.py +13 -3
- crosshair/libimpl/binasciilib.py +2 -3
- crosshair/libimpl/builtinslib.py +6 -7
- crosshair/libimpl/builtinslib_test.py +1 -8
- crosshair/libimpl/collectionslib.py +5 -1
- crosshair/libimpl/collectionslib_test.py +79 -13
- crosshair/libimpl/encodings/_encutil.py +8 -3
- crosshair/libimpl/mathlib_test.py +0 -7
- crosshair/libimpl/relib_ch_test.py +2 -2
- crosshair/main.py +3 -1
- crosshair/objectproxy_test.py +7 -11
- crosshair/opcode_intercept.py +1 -0
- crosshair/py.typed +0 -0
- crosshair/watcher.py +2 -2
- {crosshair_tool-0.0.83.dist-info → crosshair_tool-0.0.84.dist-info}/METADATA +4 -3
- {crosshair_tool-0.0.83.dist-info → crosshair_tool-0.0.84.dist-info}/RECORD +32 -31
- {crosshair_tool-0.0.83.dist-info → crosshair_tool-0.0.84.dist-info}/WHEEL +1 -1
- {crosshair_tool-0.0.83.dist-info → crosshair_tool-0.0.84.dist-info}/entry_points.txt +0 -0
- {crosshair_tool-0.0.83.dist-info → crosshair_tool-0.0.84.dist-info/licenses}/LICENSE +0 -0
- {crosshair_tool-0.0.83.dist-info → crosshair_tool-0.0.84.dist-info}/top_level.txt +0 -0
crosshair/diff_behavior_test.py
CHANGED
|
@@ -81,7 +81,7 @@ def _sum_list_rewrite_2(int_list):
|
|
|
81
81
|
return count
|
|
82
82
|
|
|
83
83
|
|
|
84
|
-
class
|
|
84
|
+
class TestBehaviorDiff:
|
|
85
85
|
def test_diff_method(self):
|
|
86
86
|
diffs = diff_behavior(
|
|
87
87
|
walk_qualname(Base, "foo"),
|
|
@@ -89,10 +89,9 @@ class BehaviorDiffTest(unittest.TestCase):
|
|
|
89
89
|
DEFAULT_OPTIONS.overlay(max_iterations=10),
|
|
90
90
|
)
|
|
91
91
|
assert isinstance(diffs, list)
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
)
|
|
92
|
+
assert [(d.result1.return_repr, d.result2.return_repr) for d in diffs] == [
|
|
93
|
+
("10", "11")
|
|
94
|
+
]
|
|
96
95
|
|
|
97
96
|
def test_diff_staticmethod(self):
|
|
98
97
|
diffs = diff_behavior(
|
|
@@ -100,20 +99,20 @@ class BehaviorDiffTest(unittest.TestCase):
|
|
|
100
99
|
foo2,
|
|
101
100
|
DEFAULT_OPTIONS.overlay(max_iterations=10),
|
|
102
101
|
)
|
|
103
|
-
|
|
102
|
+
assert diffs == []
|
|
104
103
|
|
|
105
104
|
def test_diff_behavior_same(self) -> None:
|
|
106
105
|
diffs = diff_behavior(foo1, foo2, DEFAULT_OPTIONS.overlay(max_iterations=10))
|
|
107
|
-
|
|
106
|
+
assert diffs == []
|
|
108
107
|
|
|
109
108
|
def test_diff_behavior_different(self) -> None:
|
|
110
109
|
diffs = diff_behavior(foo1, foo3, DEFAULT_OPTIONS.overlay(max_iterations=10))
|
|
111
|
-
|
|
110
|
+
assert len(diffs) == 1
|
|
112
111
|
diff = diffs[0]
|
|
113
112
|
assert isinstance(diff, BehaviorDiff)
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
113
|
+
assert int(diff.args["x"]) > 1000
|
|
114
|
+
assert diff.result1.return_repr == "100"
|
|
115
|
+
assert diff.result2.return_repr == "1000"
|
|
117
116
|
|
|
118
117
|
def test_diff_behavior_mutation(self) -> None:
|
|
119
118
|
def cut_out_item1(a: List[int], i: int):
|
|
@@ -130,10 +129,10 @@ class BehaviorDiffTest(unittest.TestCase):
|
|
|
130
129
|
opts,
|
|
131
130
|
)
|
|
132
131
|
assert not isinstance(diffs, str)
|
|
133
|
-
|
|
132
|
+
assert len(diffs) == 1
|
|
134
133
|
diff = diffs[0]
|
|
135
|
-
|
|
136
|
-
|
|
134
|
+
assert len(diff.args["a"]) > 1
|
|
135
|
+
assert diff.args["i"] == "-1"
|
|
137
136
|
|
|
138
137
|
def test_example_coverage(self) -> None:
|
|
139
138
|
# Try to get examples that highlist the differences in the code.
|
|
@@ -159,7 +158,7 @@ class BehaviorDiffTest(unittest.TestCase):
|
|
|
159
158
|
debug("diffs=", diffs)
|
|
160
159
|
assert not isinstance(diffs, str)
|
|
161
160
|
return_vals = set((d.result1.return_repr, d.result2.return_repr) for d in diffs)
|
|
162
|
-
|
|
161
|
+
assert return_vals == {("False", "None"), ("False", "True")}
|
|
163
162
|
|
|
164
163
|
|
|
165
164
|
def test_diff_behavior_lambda() -> None:
|
|
@@ -262,9 +261,3 @@ def test_diff_behavior_nan() -> None:
|
|
|
262
261
|
DEFAULT_OPTIONS,
|
|
263
262
|
)
|
|
264
263
|
assert diffs == []
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
if __name__ == "__main__":
|
|
268
|
-
if ("-v" in sys.argv) or ("--verbose" in sys.argv):
|
|
269
|
-
set_debug(True)
|
|
270
|
-
unittest.main()
|
crosshair/dynamic_typing.py
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import collections.abc
|
|
2
2
|
import typing
|
|
3
|
-
from
|
|
3
|
+
from inspect import Parameter, Signature
|
|
4
|
+
from itertools import zip_longest
|
|
5
|
+
from typing import Any, Callable, Dict, List, Mapping, Optional, Sequence, Tuple, Type
|
|
4
6
|
|
|
5
|
-
import typing_inspect
|
|
7
|
+
import typing_inspect
|
|
8
|
+
|
|
9
|
+
from crosshair.util import debug # type: ignore
|
|
6
10
|
|
|
7
11
|
_EMPTYSET: frozenset = frozenset()
|
|
8
12
|
|
|
@@ -234,3 +238,88 @@ def realize(pytype: Type, bindings: Mapping[object, type]) -> object:
|
|
|
234
238
|
if pytype_origin is Callable: # Callable args get flattened
|
|
235
239
|
newargs = [newargs[:-1], newargs[-1]]
|
|
236
240
|
return pytype_origin.__getitem__(tuple(newargs))
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def isolate_var_params(
|
|
244
|
+
sig: Signature,
|
|
245
|
+
) -> Tuple[
|
|
246
|
+
List[Parameter], Dict[str, Parameter], Optional[Parameter], Optional[Parameter]
|
|
247
|
+
]:
|
|
248
|
+
pos_only_params: List[Parameter] = []
|
|
249
|
+
keyword_params: Dict[str, Parameter] = {}
|
|
250
|
+
var_positional: Optional[Parameter] = None
|
|
251
|
+
var_keyword: Optional[Parameter] = None
|
|
252
|
+
for name, param in sig.parameters.items():
|
|
253
|
+
if param.kind == Parameter.VAR_POSITIONAL:
|
|
254
|
+
var_positional = param
|
|
255
|
+
elif param.kind == Parameter.VAR_KEYWORD:
|
|
256
|
+
var_keyword = param
|
|
257
|
+
elif param.kind == Parameter.POSITIONAL_ONLY:
|
|
258
|
+
pos_only_params.append(param)
|
|
259
|
+
else:
|
|
260
|
+
keyword_params[name] = param
|
|
261
|
+
return pos_only_params, keyword_params, var_positional, var_keyword
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def intersect_signatures(
|
|
265
|
+
sig1: Signature,
|
|
266
|
+
sig2: Signature,
|
|
267
|
+
) -> Signature:
|
|
268
|
+
"""
|
|
269
|
+
Approximate the intersection of two signatures.
|
|
270
|
+
The resulting signature may be overly loose
|
|
271
|
+
(matching some inputs that neither of the original signatures would match),
|
|
272
|
+
but it should cover all the inputs for each original signature.
|
|
273
|
+
|
|
274
|
+
One minor exception: All arguments that are allowed to be called as
|
|
275
|
+
keyword arguments will be converted to keyword-only arguments.
|
|
276
|
+
We do this to resolve the abiguity when position-or-keyword arguments
|
|
277
|
+
appear in the same position but with different names.
|
|
278
|
+
"""
|
|
279
|
+
pos1, key1, var_pos1, var_key1 = isolate_var_params(sig1)
|
|
280
|
+
pos2, key2, var_pos2, var_key2 = isolate_var_params(sig2)
|
|
281
|
+
is_squishy1 = var_pos1 is not None or var_key1 is not None
|
|
282
|
+
is_squishy2 = var_pos2 is not None or var_key2 is not None
|
|
283
|
+
out_params: Dict[str, Parameter] = {}
|
|
284
|
+
for (p1, p2) in zip_longest(pos1, pos2):
|
|
285
|
+
if p1 is None:
|
|
286
|
+
if is_squishy1:
|
|
287
|
+
out_params[p2.name] = p2
|
|
288
|
+
elif p2 is None:
|
|
289
|
+
if is_squishy2:
|
|
290
|
+
out_params[p1.name] = p1
|
|
291
|
+
elif unify(p1.annotation, p2.annotation):
|
|
292
|
+
out_params[p1.name] = p1
|
|
293
|
+
else:
|
|
294
|
+
out_params[p2.name] = p2
|
|
295
|
+
for key in [
|
|
296
|
+
k
|
|
297
|
+
for pair in zip_longest(key1.keys(), key2.keys())
|
|
298
|
+
for k in pair
|
|
299
|
+
if k is not None
|
|
300
|
+
]:
|
|
301
|
+
if key not in key2:
|
|
302
|
+
if is_squishy2:
|
|
303
|
+
out_params[key] = key1[key].replace(kind=Parameter.KEYWORD_ONLY)
|
|
304
|
+
continue
|
|
305
|
+
if key not in key1:
|
|
306
|
+
if is_squishy1:
|
|
307
|
+
out_params[key] = key2[key].replace(kind=Parameter.KEYWORD_ONLY)
|
|
308
|
+
continue
|
|
309
|
+
if unify(key1[key].annotation, key2[key].annotation):
|
|
310
|
+
out_params[key] = key1[key].replace(kind=Parameter.KEYWORD_ONLY)
|
|
311
|
+
else:
|
|
312
|
+
out_params[key] = key2[key].replace(kind=Parameter.KEYWORD_ONLY)
|
|
313
|
+
if var_pos1 and var_pos2:
|
|
314
|
+
out_params[var_pos1.name] = var_pos1
|
|
315
|
+
if var_key1 and var_key2:
|
|
316
|
+
out_params[var_key1.name] = var_key1
|
|
317
|
+
if unify(sig1.return_annotation, sig2.return_annotation):
|
|
318
|
+
out_return_annotation = sig1.return_annotation
|
|
319
|
+
else:
|
|
320
|
+
out_return_annotation = sig2.return_annotation
|
|
321
|
+
result = Signature(
|
|
322
|
+
parameters=list(out_params.values()), return_annotation=out_return_annotation
|
|
323
|
+
)
|
|
324
|
+
debug("Combined __init__ and __new__ signatures", sig1, "and", sig2, "into", result)
|
|
325
|
+
return result
|
crosshair/dynamic_typing_test.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import collections
|
|
2
|
+
from inspect import Parameter, Signature, signature
|
|
2
3
|
from typing import (
|
|
3
4
|
Callable,
|
|
4
5
|
Dict,
|
|
@@ -6,15 +7,25 @@ from typing import (
|
|
|
6
7
|
Iterable,
|
|
7
8
|
List,
|
|
8
9
|
Mapping,
|
|
10
|
+
Optional,
|
|
9
11
|
Sequence,
|
|
10
12
|
Tuple,
|
|
11
13
|
TypeVar,
|
|
12
14
|
Union,
|
|
13
15
|
)
|
|
14
16
|
|
|
17
|
+
import pytest
|
|
15
18
|
from typing_extensions import TypedDict
|
|
16
19
|
|
|
17
|
-
from crosshair.dynamic_typing import
|
|
20
|
+
from crosshair.dynamic_typing import (
|
|
21
|
+
get_bindings_from_type_arguments,
|
|
22
|
+
intersect_signatures,
|
|
23
|
+
realize,
|
|
24
|
+
unify,
|
|
25
|
+
)
|
|
26
|
+
from crosshair.options import AnalysisOptionSet
|
|
27
|
+
from crosshair.statespace import CANNOT_CONFIRM
|
|
28
|
+
from crosshair.test_util import check_states
|
|
18
29
|
|
|
19
30
|
_T = TypeVar("_T")
|
|
20
31
|
_U = TypeVar("_U")
|
|
@@ -121,3 +132,64 @@ def test_bindings_from_type_arguments():
|
|
|
121
132
|
var_mapping = get_bindings_from_type_arguments(Pair[int, str])
|
|
122
133
|
assert var_mapping == {_U: int, _T: str}
|
|
123
134
|
assert realize(List[_U], var_mapping) == List[int]
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def test_intersect_signatures_basic():
|
|
138
|
+
def f1(x: int, y: str, **kw) -> List[bool]:
|
|
139
|
+
return []
|
|
140
|
+
|
|
141
|
+
def f2(x: bool, *extra: str, **kw) -> List[int]:
|
|
142
|
+
return []
|
|
143
|
+
|
|
144
|
+
intersection = intersect_signatures(signature(f1), signature(f2))
|
|
145
|
+
assert intersection is not None
|
|
146
|
+
assert intersection.parameters == {
|
|
147
|
+
"x": Parameter("x", kind=Parameter.KEYWORD_ONLY, annotation=bool),
|
|
148
|
+
"y": Parameter("y", kind=Parameter.KEYWORD_ONLY, annotation=str),
|
|
149
|
+
"kw": Parameter("kw", kind=Parameter.VAR_KEYWORD),
|
|
150
|
+
}
|
|
151
|
+
assert intersection.return_annotation == List[bool]
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def test_intersect_signatures_typevars():
|
|
155
|
+
_T = TypeVar("_T")
|
|
156
|
+
|
|
157
|
+
def f1(cc, *args, **kwds):
|
|
158
|
+
pass
|
|
159
|
+
|
|
160
|
+
def f2(dd, left: Optional[_T], right: Optional[_T]):
|
|
161
|
+
pass
|
|
162
|
+
|
|
163
|
+
intersection = intersect_signatures(signature(f1), signature(f2))
|
|
164
|
+
assert intersection is not None
|
|
165
|
+
expected = {
|
|
166
|
+
"dd": Parameter("dd", kind=Parameter.KEYWORD_ONLY),
|
|
167
|
+
"left": Parameter("left", kind=Parameter.KEYWORD_ONLY, annotation=Optional[_T]),
|
|
168
|
+
"right": Parameter(
|
|
169
|
+
"right", kind=Parameter.KEYWORD_ONLY, annotation=Optional[_T]
|
|
170
|
+
),
|
|
171
|
+
}
|
|
172
|
+
assert intersection.parameters == expected
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
@pytest.mark.skip(
|
|
176
|
+
reason="The inspect module doesn't expose runtime type information yet"
|
|
177
|
+
)
|
|
178
|
+
def test_intersect_signature_with_crosshair():
|
|
179
|
+
def check_intersect_signatures(
|
|
180
|
+
sig1: Signature, sig2: Signature, pos_args: List, kw_args: Mapping[str, object]
|
|
181
|
+
) -> None:
|
|
182
|
+
"""post: True"""
|
|
183
|
+
|
|
184
|
+
def _sig_bindable(sig: Signature) -> bool:
|
|
185
|
+
try:
|
|
186
|
+
sig.bind(*pos_args, **kw_args)
|
|
187
|
+
return True
|
|
188
|
+
except TypeError:
|
|
189
|
+
return False
|
|
190
|
+
|
|
191
|
+
if _sig_bindable(sig1) or _sig_bindable(sig2):
|
|
192
|
+
intersection = intersect_signatures(sig1, sig2)
|
|
193
|
+
assert _sig_bindable(intersection)
|
|
194
|
+
|
|
195
|
+
check_states(check_intersect_signatures, CANNOT_CONFIRM)
|
crosshair/enforce_test.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import abc
|
|
2
2
|
import sys
|
|
3
|
-
import unittest
|
|
4
3
|
from contextlib import ExitStack
|
|
5
4
|
|
|
6
5
|
import pytest
|
|
@@ -57,21 +56,21 @@ class Enforcement(ExitStack):
|
|
|
57
56
|
COMPOSITE_TRACER.trace_caller()
|
|
58
57
|
|
|
59
58
|
|
|
60
|
-
class
|
|
59
|
+
class TestCore:
|
|
61
60
|
def test_enforce_conditions(self) -> None:
|
|
62
|
-
|
|
61
|
+
assert foo(-1) == -2 # unchecked
|
|
63
62
|
with Enforcement():
|
|
64
|
-
|
|
65
|
-
with
|
|
63
|
+
assert foo(50) == 100
|
|
64
|
+
with pytest.raises(PreconditionFailed):
|
|
66
65
|
foo(-1)
|
|
67
|
-
with
|
|
66
|
+
with pytest.raises(PostconditionFailed):
|
|
68
67
|
foo(0)
|
|
69
68
|
|
|
70
69
|
def test_class_enforce(self) -> None:
|
|
71
70
|
Pokeable().pokeby(-1) # no exception (yet!)
|
|
72
71
|
with Enforcement():
|
|
73
72
|
Pokeable().poke()
|
|
74
|
-
with
|
|
73
|
+
with pytest.raises(PreconditionFailed):
|
|
75
74
|
Pokeable().pokeby(-1)
|
|
76
75
|
|
|
77
76
|
def test_enforce_on_uncopyable_value(self) -> None:
|
|
@@ -81,7 +80,7 @@ class CoreTest(unittest.TestCase):
|
|
|
81
80
|
|
|
82
81
|
not_copyable = NotCopyable()
|
|
83
82
|
with Enforcement():
|
|
84
|
-
with
|
|
83
|
+
with pytest.raises(AttributeError):
|
|
85
84
|
same_thing(not_copyable)
|
|
86
85
|
|
|
87
86
|
|
|
@@ -114,19 +113,19 @@ class DerivedFooable(BaseFooable):
|
|
|
114
113
|
"""pre: x > 0"""
|
|
115
114
|
|
|
116
115
|
|
|
117
|
-
class
|
|
116
|
+
class TestTrickyCases:
|
|
118
117
|
def test_attrs_restored_properly(self) -> None:
|
|
119
118
|
orig_class_dict = DerivedFooable.__dict__.copy()
|
|
120
119
|
with Enforcement():
|
|
121
120
|
pass
|
|
122
121
|
for k, v in orig_class_dict.items():
|
|
123
|
-
|
|
124
|
-
DerivedFooable.__dict__[k]
|
|
125
|
-
)
|
|
122
|
+
assert (
|
|
123
|
+
DerivedFooable.__dict__[k] is v
|
|
124
|
+
), f'member "{k}" changed afer encforcement'
|
|
126
125
|
|
|
127
126
|
def test_enforcement_of_class_methods(self) -> None:
|
|
128
127
|
with Enforcement():
|
|
129
|
-
with
|
|
128
|
+
with pytest.raises(PreconditionFailed):
|
|
130
129
|
BaseFooable.class_foo(50)
|
|
131
130
|
with Enforcement():
|
|
132
131
|
DerivedFooable.class_foo(50)
|
|
@@ -134,14 +133,14 @@ class TrickyCasesTest(unittest.TestCase):
|
|
|
134
133
|
def test_enforcement_of_static_methods(self) -> None:
|
|
135
134
|
with Enforcement():
|
|
136
135
|
DerivedFooable.static_foo(50)
|
|
137
|
-
with
|
|
136
|
+
with pytest.raises(PreconditionFailed):
|
|
138
137
|
BaseFooable.static_foo(50)
|
|
139
138
|
|
|
140
139
|
def test_super_method_enforced(self) -> None:
|
|
141
140
|
with Enforcement():
|
|
142
|
-
with
|
|
141
|
+
with pytest.raises(PreconditionFailed):
|
|
143
142
|
DerivedFooable().foo_only_in_super(50)
|
|
144
|
-
with
|
|
143
|
+
with pytest.raises(PreconditionFailed):
|
|
145
144
|
DerivedFooable().foo(-1)
|
|
146
145
|
# Derived class has a weaker precondition, so this is OK:
|
|
147
146
|
DerivedFooable().foo(50)
|
|
@@ -181,9 +180,3 @@ def test_enforcement_init_on_abcmeta() -> None:
|
|
|
181
180
|
with pytest.raises(PostconditionFailed):
|
|
182
181
|
WithMetaclass(55)
|
|
183
182
|
WithMetaclass(99)
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
if __name__ == "__main__":
|
|
187
|
-
if ("-v" in sys.argv) or ("--verbose" in sys.argv):
|
|
188
|
-
set_debug(True)
|
|
189
|
-
unittest.main()
|
crosshair/fnutil_test.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import builtins
|
|
2
2
|
import inspect
|
|
3
3
|
import sys
|
|
4
|
-
import unittest
|
|
5
4
|
from dataclasses import dataclass
|
|
6
5
|
from typing import Generic
|
|
7
6
|
|
|
@@ -74,9 +73,3 @@ def test_load_function_at_line():
|
|
|
74
73
|
|
|
75
74
|
def test_FunctionInfo_get_callable_on_generic():
|
|
76
75
|
assert FunctionInfo.from_class(Generic, "__class_getitem__").get_callable() is None
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if __name__ == "__main__":
|
|
80
|
-
if ("-v" in sys.argv) or ("--verbose" in sys.argv):
|
|
81
|
-
set_debug(True)
|
|
82
|
-
unittest.main()
|
crosshair/libimpl/arraylib.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import sys
|
|
1
2
|
from array import array
|
|
2
|
-
from typing import BinaryIO,
|
|
3
|
+
from typing import BinaryIO, Dict, Iterable, List, Sequence, Tuple
|
|
3
4
|
|
|
4
5
|
import z3 # type: ignore
|
|
5
6
|
|
|
@@ -29,8 +30,17 @@ INT_TYPE_BOUNDS: Dict[str, Tuple[int, int]] = {
|
|
|
29
30
|
INT_TYPE_SIZE = {c: array(c).itemsize for c in INT_TYPE_BOUNDS.keys()}
|
|
30
31
|
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
if sys.version_info >= (3, 12):
|
|
34
|
+
from collections.abc import Buffer
|
|
35
|
+
|
|
36
|
+
def is_bytes_like(obj: object) -> bool:
|
|
37
|
+
return isinstance(obj, Buffer)
|
|
38
|
+
|
|
39
|
+
else:
|
|
40
|
+
from collections.abc import ByteString
|
|
41
|
+
|
|
42
|
+
def is_bytes_like(obj: object) -> bool:
|
|
43
|
+
return isinstance(obj, (ByteString, array))
|
|
34
44
|
|
|
35
45
|
|
|
36
46
|
def pick_code(space: StateSpace) -> Tuple[str, int, int]:
|
crosshair/libimpl/binasciilib.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import binascii
|
|
2
|
-
from collections.abc import ByteString
|
|
3
2
|
from functools import partial
|
|
4
|
-
from typing import Dict, Iterable, Tuple
|
|
3
|
+
from typing import Dict, Iterable, Tuple, Union
|
|
5
4
|
|
|
6
5
|
from crosshair.core import register_patch
|
|
7
6
|
from crosshair.libimpl.builtinslib import _ALL_BYTES_TYPES, SymbolicBytes
|
|
@@ -89,7 +88,7 @@ _DECODE_MAPPER_BASE64_STRICT = partial(
|
|
|
89
88
|
_ENCODE_MAPPER_BASE64 = partial(_remap, _ENCODE_BASE64_MAP)
|
|
90
89
|
|
|
91
90
|
|
|
92
|
-
def make_bytes(arg: object) ->
|
|
91
|
+
def make_bytes(arg: object) -> Union[bytes, bytearray, memoryview]:
|
|
93
92
|
if isinstance(arg, (bytes, bytearray, memoryview)):
|
|
94
93
|
return arg
|
|
95
94
|
if isinstance(arg, str):
|
crosshair/libimpl/builtinslib.py
CHANGED
|
@@ -21,7 +21,6 @@ from sys import maxunicode
|
|
|
21
21
|
from typing import (
|
|
22
22
|
Any,
|
|
23
23
|
BinaryIO,
|
|
24
|
-
ByteString,
|
|
25
24
|
Callable,
|
|
26
25
|
Dict,
|
|
27
26
|
FrozenSet,
|
|
@@ -127,9 +126,9 @@ from crosshair.util import (
|
|
|
127
126
|
from crosshair.z3util import z3And, z3Eq, z3Ge, z3Gt, z3IntVal, z3Not, z3Or
|
|
128
127
|
|
|
129
128
|
if sys.version_info >= (3, 12):
|
|
130
|
-
from collections.abc import Buffer
|
|
129
|
+
from collections.abc import Buffer
|
|
131
130
|
else:
|
|
132
|
-
from collections.abc import ByteString as
|
|
131
|
+
from collections.abc import ByteString as Buffer
|
|
133
132
|
|
|
134
133
|
|
|
135
134
|
_T = TypeVar("_T")
|
|
@@ -3968,7 +3967,7 @@ def is_ascii_space_ord(char_ord: int):
|
|
|
3968
3967
|
)
|
|
3969
3968
|
|
|
3970
3969
|
|
|
3971
|
-
class BytesLike(
|
|
3970
|
+
class BytesLike(Buffer, AbcString, CrossHairValue):
|
|
3972
3971
|
def __eq__(self, other) -> bool:
|
|
3973
3972
|
if not isinstance(other, _ALL_BYTES_TYPES):
|
|
3974
3973
|
return False
|
|
@@ -5038,11 +5037,11 @@ def _str_percent_format(self, other):
|
|
|
5038
5037
|
|
|
5039
5038
|
|
|
5040
5039
|
def _bytes_join(self, itr) -> str:
|
|
5041
|
-
return _join(self, itr, self_type=bytes, item_type=
|
|
5040
|
+
return _join(self, itr, self_type=bytes, item_type=Buffer)
|
|
5042
5041
|
|
|
5043
5042
|
|
|
5044
5043
|
def _bytearray_join(self, itr) -> str:
|
|
5045
|
-
return _join(self, itr, self_type=bytearray, item_type=
|
|
5044
|
+
return _join(self, itr, self_type=bytearray, item_type=Buffer)
|
|
5046
5045
|
|
|
5047
5046
|
|
|
5048
5047
|
def _str_format(self, *a, **kw) -> Union[AnySymbolicStr, str]:
|
|
@@ -5178,7 +5177,7 @@ def make_registrations():
|
|
|
5178
5177
|
register_type(SupportsFloat, lambda p: p(float))
|
|
5179
5178
|
register_type(SupportsInt, lambda p: p(int))
|
|
5180
5179
|
register_type(SupportsRound, lambda p: p(float))
|
|
5181
|
-
register_type(SupportsBytes, lambda p: p(
|
|
5180
|
+
register_type(SupportsBytes, lambda p: p(Buffer))
|
|
5182
5181
|
register_type(SupportsComplex, lambda p: p(complex))
|
|
5183
5182
|
|
|
5184
5183
|
# Patches
|
|
@@ -8,7 +8,6 @@ import math
|
|
|
8
8
|
import operator
|
|
9
9
|
import re
|
|
10
10
|
import sys
|
|
11
|
-
import unittest
|
|
12
11
|
from abc import ABC, abstractmethod
|
|
13
12
|
from array import array
|
|
14
13
|
from numbers import Integral
|
|
@@ -2882,7 +2881,7 @@ def test_frozenset___or__(space):
|
|
|
2882
2881
|
assert len(s1 | s2) == 2
|
|
2883
2882
|
|
|
2884
2883
|
|
|
2885
|
-
class
|
|
2884
|
+
class TestProtocols:
|
|
2886
2885
|
# TODO: move most of this into a collectionslib_test.py file
|
|
2887
2886
|
def test_hashable_values_fail(self) -> None:
|
|
2888
2887
|
def f(b: bool, i: int, t: Tuple[str, ...]) -> int:
|
|
@@ -3657,9 +3656,3 @@ def TODO_test_deepcopy_independence():
|
|
|
3657
3656
|
with NoTracing():
|
|
3658
3657
|
assert ls[0] is not lscopy[0]
|
|
3659
3658
|
# Next try mutation on one and test the other...
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
if __name__ == "__main__":
|
|
3663
|
-
if ("-v" in sys.argv) or ("--verbose" in sys.argv):
|
|
3664
|
-
set_debug(True)
|
|
3665
|
-
unittest.main()
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import collections
|
|
2
|
+
import sys
|
|
2
3
|
from typing import (
|
|
3
4
|
Any,
|
|
4
5
|
Callable,
|
|
@@ -245,5 +246,8 @@ def make_registrations():
|
|
|
245
246
|
|
|
246
247
|
register_type(collections.abc.MutableSet, lambda p, t=Any: p(Set[t])) # type: ignore
|
|
247
248
|
|
|
248
|
-
|
|
249
|
+
if sys.version_info < (3, 14):
|
|
250
|
+
register_type(collections.abc.ByteString, lambda p: p(bytes))
|
|
251
|
+
if sys.version_info >= (3, 12):
|
|
252
|
+
register_type(collections.abc.Buffer, lambda p: p(bytes))
|
|
249
253
|
register_type(collections.abc.Hashable, lambda p: p(int))
|
|
@@ -1,14 +1,23 @@
|
|
|
1
|
-
import
|
|
1
|
+
import sys
|
|
2
|
+
from collections import Counter, defaultdict, deque, namedtuple
|
|
2
3
|
from copy import deepcopy
|
|
3
|
-
from
|
|
4
|
+
from inspect import Parameter, Signature
|
|
5
|
+
from typing import Counter, DefaultDict, Deque, NamedTuple, Tuple
|
|
4
6
|
|
|
5
7
|
import pytest
|
|
6
8
|
|
|
7
|
-
from crosshair.core import
|
|
9
|
+
from crosshair.core import (
|
|
10
|
+
deep_realize,
|
|
11
|
+
get_constructor_signature,
|
|
12
|
+
proxy_for_type,
|
|
13
|
+
realize,
|
|
14
|
+
standalone_statespace,
|
|
15
|
+
)
|
|
8
16
|
from crosshair.libimpl.collectionslib import ListBasedDeque
|
|
9
17
|
from crosshair.statespace import CANNOT_CONFIRM, CONFIRMED, POST_FAIL, MessageType
|
|
10
18
|
from crosshair.test_util import check_states
|
|
11
19
|
from crosshair.tracers import NoTracing, ResumedTracing
|
|
20
|
+
from crosshair.util import CrossHairValue
|
|
12
21
|
|
|
13
22
|
|
|
14
23
|
@pytest.fixture
|
|
@@ -24,7 +33,7 @@ def test_counter_symbolic_deep(space):
|
|
|
24
33
|
|
|
25
34
|
|
|
26
35
|
def test_counter_deep(space):
|
|
27
|
-
d =
|
|
36
|
+
d = Counter()
|
|
28
37
|
with ResumedTracing():
|
|
29
38
|
deep_realize(d)
|
|
30
39
|
deepcopy(d)
|
|
@@ -176,7 +185,7 @@ def test_deque_extendleft_method() -> None:
|
|
|
176
185
|
"""
|
|
177
186
|
Can any deque be extended by itself and form this palindrome?
|
|
178
187
|
|
|
179
|
-
post[ls]: ls !=
|
|
188
|
+
post[ls]: ls != deque([1, 2, 3, 3, 2, 1])
|
|
180
189
|
"""
|
|
181
190
|
ls.extendleft(ls)
|
|
182
191
|
|
|
@@ -185,22 +194,22 @@ def test_deque_extendleft_method() -> None:
|
|
|
185
194
|
|
|
186
195
|
def test_deque_add_symbolic_to_concrete():
|
|
187
196
|
with standalone_statespace as space:
|
|
188
|
-
d = ListBasedDeque([1, 2]) +
|
|
197
|
+
d = ListBasedDeque([1, 2]) + deque([3, 4])
|
|
189
198
|
assert list(d) == [1, 2, 3, 4]
|
|
190
199
|
|
|
191
200
|
|
|
192
201
|
def test_deque_eq():
|
|
193
202
|
with standalone_statespace as space:
|
|
194
203
|
assert ListBasedDeque([1, 2]) == ListBasedDeque([1, 2])
|
|
195
|
-
assert
|
|
204
|
+
assert deque([1, 2]) == ListBasedDeque([1, 2])
|
|
196
205
|
assert ListBasedDeque([1, 2]) != ListBasedDeque([1, 55])
|
|
197
|
-
assert
|
|
206
|
+
assert deque([1, 2]) != ListBasedDeque([1, 55])
|
|
198
207
|
|
|
199
208
|
|
|
200
209
|
def test_defaultdict_repr_equiv(test_list) -> None:
|
|
201
210
|
def f(symbolic: DefaultDict[int, int]) -> Tuple[dict, dict]:
|
|
202
211
|
"""post: _[0] == _[1]"""
|
|
203
|
-
concrete =
|
|
212
|
+
concrete = defaultdict(symbolic.default_factory, symbolic.items())
|
|
204
213
|
return (symbolic, concrete)
|
|
205
214
|
|
|
206
215
|
check_states(f, CANNOT_CONFIRM)
|
|
@@ -243,16 +252,73 @@ def test_defaultdict_realize():
|
|
|
243
252
|
with standalone_statespace:
|
|
244
253
|
with NoTracing():
|
|
245
254
|
d = proxy_for_type(DefaultDict[int, int], "d")
|
|
246
|
-
assert type(realize(d)) is
|
|
255
|
+
assert type(realize(d)) is defaultdict
|
|
247
256
|
|
|
248
257
|
|
|
249
258
|
#
|
|
250
|
-
# We don't patch namedtuple, but namedtuple performs magic
|
|
259
|
+
# We don't patch namedtuple, but namedtuple performs magic dynamic type
|
|
251
260
|
# generation, which can interfere with CrossHair.
|
|
252
261
|
#
|
|
253
262
|
|
|
254
263
|
|
|
255
264
|
def test_namedtuple_creation():
|
|
256
265
|
with standalone_statespace:
|
|
257
|
-
# Ensure type creation doesn't raise exception:
|
|
258
|
-
Color =
|
|
266
|
+
# Ensure type creation under trace doesn't raise exception:
|
|
267
|
+
Color = namedtuple("Color", ("name", "hex"))
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def test_namedtuple_argument_detection_untyped():
|
|
271
|
+
UntypedColor = namedtuple("UntypedColor", ("name", "hex"))
|
|
272
|
+
expected_signature = Signature(
|
|
273
|
+
parameters=[
|
|
274
|
+
Parameter("name", Parameter.POSITIONAL_OR_KEYWORD),
|
|
275
|
+
Parameter("hex", Parameter.POSITIONAL_OR_KEYWORD),
|
|
276
|
+
],
|
|
277
|
+
return_annotation=Signature.empty,
|
|
278
|
+
)
|
|
279
|
+
assert get_constructor_signature(UntypedColor) == expected_signature
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def test_namedtuple_argument_detection_typed_with_subclass():
|
|
283
|
+
class ClassTypedColor(NamedTuple):
|
|
284
|
+
name: str
|
|
285
|
+
hex: int
|
|
286
|
+
|
|
287
|
+
expected_parameters = {
|
|
288
|
+
"name": Parameter("name", Parameter.POSITIONAL_OR_KEYWORD, annotation=str),
|
|
289
|
+
"hex": Parameter("hex", Parameter.POSITIONAL_OR_KEYWORD, annotation=int),
|
|
290
|
+
}
|
|
291
|
+
assert get_constructor_signature(ClassTypedColor).parameters == expected_parameters
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
@pytest.mark.skipif(
|
|
295
|
+
sys.version_info < (3, 9),
|
|
296
|
+
reason="Functional namedtuple field types supported on Python >= 3.9",
|
|
297
|
+
)
|
|
298
|
+
def test_namedtuple_argument_detection_typed_functionally():
|
|
299
|
+
FunctionallyTypedColor = NamedTuple(
|
|
300
|
+
"FunctionallyTypedColor", [("name", str), ("hex", int)]
|
|
301
|
+
)
|
|
302
|
+
expected_parameters = {
|
|
303
|
+
"name": Parameter("name", Parameter.POSITIONAL_OR_KEYWORD, annotation=str),
|
|
304
|
+
"hex": Parameter("hex", Parameter.POSITIONAL_OR_KEYWORD, annotation=int),
|
|
305
|
+
}
|
|
306
|
+
assert (
|
|
307
|
+
get_constructor_signature(FunctionallyTypedColor).parameters
|
|
308
|
+
== expected_parameters
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
@pytest.mark.skipif(
|
|
313
|
+
sys.version_info < (3, 9),
|
|
314
|
+
reason="Functional namedtuple field types supported on Python >= 3.9",
|
|
315
|
+
)
|
|
316
|
+
def test_namedtuple_symbolic_creation(space):
|
|
317
|
+
UntypedColor = namedtuple("Color", "name hex")
|
|
318
|
+
Color = NamedTuple("Color", [("name", str), ("hex", int)])
|
|
319
|
+
untyped_color = proxy_for_type(UntypedColor, "color")
|
|
320
|
+
assert isinstance(untyped_color.hex, CrossHairValue)
|
|
321
|
+
color = proxy_for_type(Color, "color")
|
|
322
|
+
with ResumedTracing():
|
|
323
|
+
assert space.is_possible(color.hex == 5)
|
|
324
|
+
assert space.is_possible(color.hex == 10)
|