crosshair-tool 0.0.83__cp313-cp313-win_amd64.whl → 0.0.85__cp313-cp313-win_amd64.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.cp313-win_amd64.pyd +0 -0
- crosshair/__init__.py +1 -1
- crosshair/_mark_stacks.h +0 -25
- crosshair/_tracers.h +2 -0
- crosshair/_tracers_test.py +8 -2
- crosshair/auditwall.py +0 -1
- crosshair/auditwall_test.py +5 -0
- crosshair/condition_parser.py +5 -5
- crosshair/condition_parser_test.py +50 -63
- crosshair/copyext.py +23 -7
- crosshair/copyext_test.py +11 -1
- crosshair/core.py +23 -17
- crosshair/core_test.py +625 -584
- crosshair/diff_behavior_test.py +14 -21
- crosshair/dynamic_typing.py +90 -1
- crosshair/dynamic_typing_test.py +73 -1
- crosshair/enforce_test.py +15 -22
- crosshair/fnutil_test.py +4 -8
- crosshair/libimpl/arraylib.py +2 -5
- crosshair/libimpl/binasciilib.py +2 -3
- crosshair/libimpl/builtinslib.py +28 -21
- crosshair/libimpl/builtinslib_test.py +1 -8
- crosshair/libimpl/collectionslib.py +18 -3
- crosshair/libimpl/collectionslib_test.py +89 -15
- crosshair/libimpl/encodings/_encutil.py +8 -3
- crosshair/libimpl/mathlib_test.py +0 -7
- crosshair/libimpl/relib_ch_test.py +2 -2
- crosshair/libimpl/timelib.py +34 -15
- crosshair/libimpl/timelib_test.py +12 -2
- crosshair/lsp_server.py +1 -1
- crosshair/main.py +3 -1
- crosshair/objectproxy_test.py +7 -11
- crosshair/opcode_intercept.py +24 -8
- crosshair/opcode_intercept_test.py +13 -2
- crosshair/py.typed +0 -0
- crosshair/tracers.py +27 -9
- crosshair/type_repo.py +2 -2
- crosshair/unicode_categories.py +1 -0
- crosshair/util.py +45 -16
- crosshair/watcher.py +2 -2
- {crosshair_tool-0.0.83.dist-info → crosshair_tool-0.0.85.dist-info}/METADATA +4 -3
- {crosshair_tool-0.0.83.dist-info → crosshair_tool-0.0.85.dist-info}/RECORD +46 -45
- {crosshair_tool-0.0.83.dist-info → crosshair_tool-0.0.85.dist-info}/WHEEL +1 -1
- {crosshair_tool-0.0.83.dist-info → crosshair_tool-0.0.85.dist-info}/entry_points.txt +0 -0
- {crosshair_tool-0.0.83.dist-info → crosshair_tool-0.0.85.dist-info/licenses}/LICENSE +0 -0
- {crosshair_tool-0.0.83.dist-info → crosshair_tool-0.0.85.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,9 +1,13 @@
|
|
|
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
7
|
import typing_inspect # type: ignore
|
|
6
8
|
|
|
9
|
+
from crosshair.util import debug # type: ignore
|
|
10
|
+
|
|
7
11
|
_EMPTYSET: frozenset = frozenset()
|
|
8
12
|
|
|
9
13
|
|
|
@@ -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
|
|
|
@@ -27,7 +26,10 @@ def test_fn_globals_on_builtin() -> None:
|
|
|
27
26
|
|
|
28
27
|
def test_resolve_signature_invalid_annotations() -> None:
|
|
29
28
|
sig = resolve_signature(with_invalid_type_annotation)
|
|
30
|
-
|
|
29
|
+
if sys.version_info >= (3, 14):
|
|
30
|
+
assert sig == "TypeThatIsNotDefined"
|
|
31
|
+
else:
|
|
32
|
+
assert sig == "name 'TypeThatIsNotDefined' is not defined"
|
|
31
33
|
|
|
32
34
|
|
|
33
35
|
@pytest.mark.skipif(
|
|
@@ -74,9 +76,3 @@ def test_load_function_at_line():
|
|
|
74
76
|
|
|
75
77
|
def test_FunctionInfo_get_callable_on_generic():
|
|
76
78
|
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,10 +30,6 @@ 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
|
-
def is_bytes_like(obj: object) -> bool:
|
|
33
|
-
return isinstance(obj, (ByteString, array))
|
|
34
|
-
|
|
35
|
-
|
|
36
33
|
def pick_code(space: StateSpace) -> Tuple[str, int, int]:
|
|
37
34
|
last_idx = len(INT_TYPE_BOUNDS) - 1
|
|
38
35
|
for (idx, (code, rng)) in enumerate(INT_TYPE_BOUNDS.items()):
|
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
|
@@ -17,11 +17,10 @@ from dataclasses import dataclass
|
|
|
17
17
|
from itertools import zip_longest
|
|
18
18
|
from math import inf, isfinite, isinf, isnan, nan
|
|
19
19
|
from numbers import Integral, Number, Real
|
|
20
|
-
from sys import maxunicode
|
|
20
|
+
from sys import maxunicode, version_info
|
|
21
21
|
from typing import (
|
|
22
22
|
Any,
|
|
23
23
|
BinaryIO,
|
|
24
|
-
ByteString,
|
|
25
24
|
Callable,
|
|
26
25
|
Dict,
|
|
27
26
|
FrozenSet,
|
|
@@ -59,6 +58,8 @@ try:
|
|
|
59
58
|
except ImportError:
|
|
60
59
|
from z3 import FfpEQ as fpEQ
|
|
61
60
|
|
|
61
|
+
import sys
|
|
62
|
+
|
|
62
63
|
from crosshair.abcstring import AbcString
|
|
63
64
|
from crosshair.core import (
|
|
64
65
|
SymbolicFactory,
|
|
@@ -117,6 +118,7 @@ from crosshair.util import (
|
|
|
117
118
|
assert_tracing,
|
|
118
119
|
ch_stack,
|
|
119
120
|
debug,
|
|
121
|
+
is_bytes_like,
|
|
120
122
|
is_hashable,
|
|
121
123
|
is_iterable,
|
|
122
124
|
memo,
|
|
@@ -127,9 +129,9 @@ from crosshair.util import (
|
|
|
127
129
|
from crosshair.z3util import z3And, z3Eq, z3Ge, z3Gt, z3IntVal, z3Not, z3Or
|
|
128
130
|
|
|
129
131
|
if sys.version_info >= (3, 12):
|
|
130
|
-
from collections.abc import Buffer
|
|
132
|
+
from collections.abc import Buffer
|
|
131
133
|
else:
|
|
132
|
-
from collections.abc import ByteString as
|
|
134
|
+
from collections.abc import ByteString as Buffer
|
|
133
135
|
|
|
134
136
|
|
|
135
137
|
_T = TypeVar("_T")
|
|
@@ -1214,10 +1216,11 @@ class SymbolicInt(SymbolicIntable, AtomicSymbolicValue):
|
|
|
1214
1216
|
cur_divisor = 10
|
|
1215
1217
|
while True:
|
|
1216
1218
|
leftover = self // cur_divisor
|
|
1217
|
-
if leftover
|
|
1219
|
+
if leftover != 0:
|
|
1220
|
+
codepoints.append(48 + (leftover % 10))
|
|
1221
|
+
cur_divisor *= 10
|
|
1222
|
+
else:
|
|
1218
1223
|
break
|
|
1219
|
-
codepoints.append(48 + (leftover % 10))
|
|
1220
|
-
cur_divisor *= 10
|
|
1221
1224
|
with NoTracing():
|
|
1222
1225
|
codepoints.reverse()
|
|
1223
1226
|
return LazyIntSymbolicStr(codepoints)
|
|
@@ -1317,7 +1320,7 @@ class SymbolicInt(SymbolicIntable, AtomicSymbolicValue):
|
|
|
1317
1320
|
z3.If(val < 128, 7, 8)))))))))
|
|
1318
1321
|
# fmt: on
|
|
1319
1322
|
|
|
1320
|
-
if
|
|
1323
|
+
if version_info >= (3, 12):
|
|
1321
1324
|
|
|
1322
1325
|
def is_integer(self):
|
|
1323
1326
|
return True
|
|
@@ -2935,7 +2938,7 @@ class AnySymbolicStr(AbcString):
|
|
|
2935
2938
|
def capitalize(self):
|
|
2936
2939
|
if self.__len__() == 0:
|
|
2937
2940
|
return ""
|
|
2938
|
-
if
|
|
2941
|
+
if version_info >= (3, 8):
|
|
2939
2942
|
firstchar = self[0].title()
|
|
2940
2943
|
else:
|
|
2941
2944
|
firstchar = self[0].upper()
|
|
@@ -3173,7 +3176,7 @@ class AnySymbolicStr(AbcString):
|
|
|
3173
3176
|
return ""
|
|
3174
3177
|
|
|
3175
3178
|
def splitlines(self, keepends=False):
|
|
3176
|
-
if
|
|
3179
|
+
if version_info < (3, 12):
|
|
3177
3180
|
if not isinstance(keepends, int):
|
|
3178
3181
|
raise TypeError
|
|
3179
3182
|
mylen = self.__len__()
|
|
@@ -3439,6 +3442,7 @@ class LazyIntSymbolicStr(AnySymbolicStr, CrossHairValue):
|
|
|
3439
3442
|
SliceView,
|
|
3440
3443
|
SequenceConcatenation,
|
|
3441
3444
|
list, # TODO: are we sharing mutable state here?
|
|
3445
|
+
tuple,
|
|
3442
3446
|
),
|
|
3443
3447
|
):
|
|
3444
3448
|
self._codepoints = smtvar
|
|
@@ -3968,7 +3972,7 @@ def is_ascii_space_ord(char_ord: int):
|
|
|
3968
3972
|
)
|
|
3969
3973
|
|
|
3970
3974
|
|
|
3971
|
-
class BytesLike(
|
|
3975
|
+
class BytesLike(Buffer, AbcString, CrossHairValue):
|
|
3972
3976
|
def __eq__(self, other) -> bool:
|
|
3973
3977
|
if not isinstance(other, _ALL_BYTES_TYPES):
|
|
3974
3978
|
return False
|
|
@@ -3976,7 +3980,7 @@ class BytesLike(BufferAbc, AbcString, CrossHairValue):
|
|
|
3976
3980
|
return False
|
|
3977
3981
|
return list(self) == list(other)
|
|
3978
3982
|
|
|
3979
|
-
if
|
|
3983
|
+
if version_info >= (3, 12):
|
|
3980
3984
|
|
|
3981
3985
|
def __buffer__(self, flags: int):
|
|
3982
3986
|
with NoTracing():
|
|
@@ -4136,7 +4140,10 @@ class SymbolicBytes(BytesLike):
|
|
|
4136
4140
|
accumulated = []
|
|
4137
4141
|
high = None
|
|
4138
4142
|
if not isinstance(hexstr, str):
|
|
4139
|
-
|
|
4143
|
+
if is_bytes_like(hexstr) and version_info >= (3, 14):
|
|
4144
|
+
hexstr = LazyIntSymbolicStr(tuple(hexstr))
|
|
4145
|
+
else:
|
|
4146
|
+
raise TypeError
|
|
4140
4147
|
for idx, ch in enumerate(hexstr):
|
|
4141
4148
|
if not ch.isascii():
|
|
4142
4149
|
raise ValueError(
|
|
@@ -4895,7 +4902,7 @@ def _int_from_bytes(
|
|
|
4895
4902
|
) -> int:
|
|
4896
4903
|
if byteorder is _MISSING:
|
|
4897
4904
|
# byteorder defaults to "big" as of 3.11
|
|
4898
|
-
if
|
|
4905
|
+
if version_info >= (3, 11):
|
|
4899
4906
|
byteorder = "big"
|
|
4900
4907
|
else:
|
|
4901
4908
|
raise TypeError
|
|
@@ -5038,11 +5045,11 @@ def _str_percent_format(self, other):
|
|
|
5038
5045
|
|
|
5039
5046
|
|
|
5040
5047
|
def _bytes_join(self, itr) -> str:
|
|
5041
|
-
return _join(self, itr, self_type=bytes, item_type=
|
|
5048
|
+
return _join(self, itr, self_type=bytes, item_type=Buffer)
|
|
5042
5049
|
|
|
5043
5050
|
|
|
5044
5051
|
def _bytearray_join(self, itr) -> str:
|
|
5045
|
-
return _join(self, itr, self_type=bytearray, item_type=
|
|
5052
|
+
return _join(self, itr, self_type=bytearray, item_type=Buffer)
|
|
5046
5053
|
|
|
5047
5054
|
|
|
5048
5055
|
def _str_format(self, *a, **kw) -> Union[AnySymbolicStr, str]:
|
|
@@ -5109,7 +5116,7 @@ def make_registrations():
|
|
|
5109
5116
|
|
|
5110
5117
|
register_type(Union, make_union_choice)
|
|
5111
5118
|
|
|
5112
|
-
if
|
|
5119
|
+
if version_info >= (3, 8):
|
|
5113
5120
|
from typing import Final
|
|
5114
5121
|
|
|
5115
5122
|
register_type(Final, lambda p, t: p(t))
|
|
@@ -5178,7 +5185,7 @@ def make_registrations():
|
|
|
5178
5185
|
register_type(SupportsFloat, lambda p: p(float))
|
|
5179
5186
|
register_type(SupportsInt, lambda p: p(int))
|
|
5180
5187
|
register_type(SupportsRound, lambda p: p(float))
|
|
5181
|
-
register_type(SupportsBytes, lambda p: p(
|
|
5188
|
+
register_type(SupportsBytes, lambda p: p(Buffer))
|
|
5182
5189
|
register_type(SupportsComplex, lambda p: p(complex))
|
|
5183
5190
|
|
|
5184
5191
|
# Patches
|
|
@@ -5267,7 +5274,7 @@ def make_registrations():
|
|
|
5267
5274
|
"upper",
|
|
5268
5275
|
"zfill",
|
|
5269
5276
|
]
|
|
5270
|
-
if
|
|
5277
|
+
if version_info >= (3, 9):
|
|
5271
5278
|
names_to_str_patch.append("removeprefix")
|
|
5272
5279
|
names_to_str_patch.append("removesuffix")
|
|
5273
5280
|
for name in names_to_str_patch:
|
|
@@ -5323,12 +5330,12 @@ def make_registrations():
|
|
|
5323
5330
|
# Patches on int
|
|
5324
5331
|
register_patch(int.__repr__, with_checked_self(int, "__repr__"))
|
|
5325
5332
|
register_patch(int.as_integer_ratio, with_checked_self(int, "as_integer_ratio"))
|
|
5326
|
-
if
|
|
5333
|
+
if version_info >= (3, 10):
|
|
5327
5334
|
register_patch(int.bit_count, with_checked_self(int, "bit_count"))
|
|
5328
5335
|
register_patch(int.bit_length, with_checked_self(int, "bit_length"))
|
|
5329
5336
|
register_patch(int.conjugate, with_checked_self(int, "conjugate"))
|
|
5330
5337
|
register_patch(int.from_bytes, _int_from_bytes)
|
|
5331
|
-
if
|
|
5338
|
+
if version_info >= (3, 12):
|
|
5332
5339
|
register_patch(int.is_integer, with_checked_self(int, "is_integer"))
|
|
5333
5340
|
register_patch(int.to_bytes, with_checked_self(int, "to_bytes"))
|
|
5334
5341
|
|
|
@@ -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()
|