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
|
Binary file
|
crosshair/__init__.py
CHANGED
|
@@ -15,7 +15,7 @@ from crosshair.statespace import StateSpace
|
|
|
15
15
|
from crosshair.tracers import NoTracing, ResumedTracing
|
|
16
16
|
from crosshair.util import IgnoreAttempt, debug
|
|
17
17
|
|
|
18
|
-
__version__ = "0.0.
|
|
18
|
+
__version__ = "0.0.85" # Do not forget to update in setup.py!
|
|
19
19
|
__author__ = "Phillip Schanely"
|
|
20
20
|
__license__ = "MIT"
|
|
21
21
|
__status__ = "Alpha"
|
crosshair/_mark_stacks.h
CHANGED
|
@@ -538,31 +538,6 @@ static const uint8_t _ch_DE_INSTRUMENT[256] = {
|
|
|
538
538
|
#endif
|
|
539
539
|
#endif
|
|
540
540
|
|
|
541
|
-
static const uint8_t _ch_TRACABLE_INSTRUCTIONS[256] = {
|
|
542
|
-
// This must be manually kept in sync the the various
|
|
543
|
-
// instructions that we care about on the python side.
|
|
544
|
-
[MAP_ADD] = 1,
|
|
545
|
-
[BINARY_SUBSCR] = 1,
|
|
546
|
-
[BINARY_SLICE] = 1,
|
|
547
|
-
[CONTAINS_OP] = 1,
|
|
548
|
-
[BUILD_STRING] = 1,
|
|
549
|
-
#if PY_VERSION_HEX < 0x030D0000
|
|
550
|
-
// <= 3.12
|
|
551
|
-
[FORMAT_VALUE] = 1,
|
|
552
|
-
#elif PY_VERSION_HEX < 0x030E0000
|
|
553
|
-
// 3.13
|
|
554
|
-
[CALL_KW] = 1,
|
|
555
|
-
[CONVERT_VALUE] = 1,
|
|
556
|
-
#endif
|
|
557
|
-
[UNARY_NOT] = 1,
|
|
558
|
-
[SET_ADD] = 1,
|
|
559
|
-
[IS_OP] = 1,
|
|
560
|
-
[BINARY_OP] = 1,
|
|
561
|
-
[CALL] = 1,
|
|
562
|
-
[CALL_FUNCTION_EX] = 1,
|
|
563
|
-
};
|
|
564
|
-
|
|
565
|
-
|
|
566
541
|
/* Get the underlying opcode, stripping instrumentation */
|
|
567
542
|
int _ch_Py_GetBaseOpcode(PyCodeObject *code, int i)
|
|
568
543
|
{
|
crosshair/_tracers.h
CHANGED
crosshair/_tracers_test.py
CHANGED
|
@@ -89,7 +89,10 @@ def _log_execution_stacks(fn, *a, **kw):
|
|
|
89
89
|
return stacks
|
|
90
90
|
|
|
91
91
|
|
|
92
|
-
@pytest.mark.skipif(
|
|
92
|
+
@pytest.mark.skipif(
|
|
93
|
+
sys.version_info < (3, 12) or sys.version_info >= (3, 14),
|
|
94
|
+
reason="stack depths only in 3.12 & 3.13",
|
|
95
|
+
)
|
|
93
96
|
def test_one_function_stack_depth():
|
|
94
97
|
_E = (TypeError, KeyboardInterrupt)
|
|
95
98
|
|
|
@@ -100,7 +103,10 @@ def test_one_function_stack_depth():
|
|
|
100
103
|
_log_execution_stacks(a, 4)
|
|
101
104
|
|
|
102
105
|
|
|
103
|
-
@pytest.mark.skipif(
|
|
106
|
+
@pytest.mark.skipif(
|
|
107
|
+
sys.version_info < (3, 12) or sys.version_info >= (3, 14),
|
|
108
|
+
reason="stack depths only in 3.12 & 3.13",
|
|
109
|
+
)
|
|
104
110
|
def test_stack_get():
|
|
105
111
|
def to_be_traced(x):
|
|
106
112
|
r = 8 - x
|
crosshair/auditwall.py
CHANGED
crosshair/auditwall_test.py
CHANGED
|
@@ -42,6 +42,10 @@ def test_popen_disallowed():
|
|
|
42
42
|
assert call([pyexec, __file__, "popen", "withwall"]) == 10
|
|
43
43
|
|
|
44
44
|
|
|
45
|
+
def test_chdir_allowed():
|
|
46
|
+
assert call([pyexec, __file__, "chdir", "withwall"]) == 0
|
|
47
|
+
|
|
48
|
+
|
|
45
49
|
@pytest.mark.skipif(sys.version_info < (3, 9), reason="Python 3.9+ required")
|
|
46
50
|
def test_popen_via_platform_allowed():
|
|
47
51
|
assert call([pyexec, __file__, "popen_via_platform", "withwall"]) == 0
|
|
@@ -58,6 +62,7 @@ _ACTIONS = {
|
|
|
58
62
|
"popen_via_platform": lambda: platform._syscmd_ver( # type: ignore
|
|
59
63
|
supported_platforms=(sys.platform,)
|
|
60
64
|
),
|
|
65
|
+
"chdir": lambda: os.chdir("."),
|
|
61
66
|
}
|
|
62
67
|
|
|
63
68
|
if __name__ == "__main__":
|
crosshair/condition_parser.py
CHANGED
|
@@ -47,6 +47,7 @@ from crosshair.options import AnalysisKind
|
|
|
47
47
|
from crosshair.register_contract import get_contract
|
|
48
48
|
from crosshair.tracers import NoTracing
|
|
49
49
|
from crosshair.util import (
|
|
50
|
+
CrossHairInternal,
|
|
50
51
|
DynamicScopeVar,
|
|
51
52
|
EvalFriendlyReprContext,
|
|
52
53
|
IdKeyedDict,
|
|
@@ -485,6 +486,7 @@ class ConcreteConditionParser(ConditionParser):
|
|
|
485
486
|
method = cls.__dict__.get(method_name, None)
|
|
486
487
|
super_method_conditions = super_methods.get(method_name)
|
|
487
488
|
if super_method_conditions is not None:
|
|
489
|
+
# Re-type the super's `self` argument to be this class:
|
|
488
490
|
revised_sig = set_first_arg_type(super_method_conditions.sig, cls)
|
|
489
491
|
super_method_conditions = replace(
|
|
490
492
|
super_method_conditions, sig=revised_sig
|
|
@@ -511,17 +513,15 @@ class ConcreteConditionParser(ConditionParser):
|
|
|
511
513
|
final_pre = list(conditions.pre)
|
|
512
514
|
final_post = list(conditions.post)
|
|
513
515
|
if method_name in (
|
|
514
|
-
"__new__", #
|
|
516
|
+
"__new__", # a staticmethod, but not isinstance(staticmethod)
|
|
515
517
|
"__repr__", # is itself required for reporting problems with invariants.
|
|
516
518
|
# [set/del]attr can do anything; we can't resonably enforce invariants:
|
|
517
519
|
"__setattr__",
|
|
518
520
|
"__delattr__",
|
|
521
|
+
"__replace__", # Will raise an exception with most arbitrary **kwargs.
|
|
522
|
+
"__annotate__", # a staticmethod, but not isinstance(staticmethod)
|
|
519
523
|
):
|
|
520
524
|
pass
|
|
521
|
-
elif method_name == "__replace__":
|
|
522
|
-
# TODO: remove this case when fixed in 3.13
|
|
523
|
-
# see https://github.com/python/cpython/issues/114198
|
|
524
|
-
pass
|
|
525
525
|
elif method_name == "__del__":
|
|
526
526
|
final_pre.extend(inv)
|
|
527
527
|
elif method_name == "__init__":
|
|
@@ -133,41 +133,32 @@ def test_parse_sphinx_raises() -> None:
|
|
|
133
133
|
assert parse_sphinx_raises(sphinx_raises) == {LocallyDefiendException}
|
|
134
134
|
|
|
135
135
|
|
|
136
|
-
class
|
|
136
|
+
class TestPep316Parser:
|
|
137
137
|
def test_class_parse(self) -> None:
|
|
138
138
|
class_conditions = Pep316Parser().get_class_conditions(Foo)
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
set(class_conditions.methods.keys()), set(["isready", "__init__"])
|
|
145
|
-
)
|
|
139
|
+
assert set([c.expr_source for c in class_conditions.inv]) == {
|
|
140
|
+
"self.x >= 0",
|
|
141
|
+
"self.y >= 0",
|
|
142
|
+
}
|
|
143
|
+
assert {"isready", "__init__"} <= set(class_conditions.methods.keys())
|
|
146
144
|
method = class_conditions.methods["isready"]
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
145
|
+
assert set([c.expr_source for c in method.pre]) == {
|
|
146
|
+
"self.x >= 0",
|
|
147
|
+
"self.y >= 0",
|
|
148
|
+
}
|
|
151
149
|
startlineno = inspect.getsourcelines(Foo)[1]
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
("self.y >= 0", startlineno + 12),
|
|
158
|
-
("__return__ == (self.x == 0)", startlineno + 24),
|
|
159
|
-
]
|
|
160
|
-
),
|
|
161
|
-
)
|
|
150
|
+
assert set([(c.expr_source, c.line) for c in method.post]) == {
|
|
151
|
+
("self.x >= 0", startlineno + 7),
|
|
152
|
+
("self.y >= 0", startlineno + 12),
|
|
153
|
+
("__return__ == (self.x == 0)", startlineno + 24),
|
|
154
|
+
}
|
|
162
155
|
|
|
163
156
|
def test_single_line_condition(self) -> None:
|
|
164
157
|
conditions = Pep316Parser().get_fn_conditions(
|
|
165
158
|
FunctionInfo.from_fn(single_line_condition)
|
|
166
159
|
)
|
|
167
160
|
assert conditions is not None
|
|
168
|
-
|
|
169
|
-
set([c.expr_source for c in conditions.post]), set(["__return__ >= x"])
|
|
170
|
-
)
|
|
161
|
+
assert set([c.expr_source for c in conditions.post]) == {"__return__ >= x"}
|
|
171
162
|
|
|
172
163
|
def test_implies_condition(self):
|
|
173
164
|
conditions = Pep316Parser().get_fn_conditions(
|
|
@@ -182,37 +173,35 @@ class Pep316ParserTest(unittest.TestCase):
|
|
|
182
173
|
FunctionInfo.from_fn(locally_defined_raises_condition)
|
|
183
174
|
)
|
|
184
175
|
assert conditions is not None
|
|
185
|
-
|
|
186
|
-
|
|
176
|
+
assert [] == list(conditions.syntax_messages())
|
|
177
|
+
assert set([LocallyDefiendException]) == conditions.raises
|
|
187
178
|
|
|
188
179
|
def test_tricky_raises_condition(self) -> None:
|
|
189
180
|
conditions = Pep316Parser().get_fn_conditions(
|
|
190
181
|
FunctionInfo.from_fn(tricky_raises_condition)
|
|
191
182
|
)
|
|
192
183
|
assert conditions is not None
|
|
193
|
-
|
|
194
|
-
|
|
184
|
+
assert [] == list(conditions.syntax_messages())
|
|
185
|
+
assert conditions.raises == {KeyError, json.JSONDecodeError}
|
|
195
186
|
|
|
196
187
|
def test_invariant_is_inherited(self) -> None:
|
|
197
188
|
class_conditions = Pep316Parser().get_class_conditions(SubClassExample)
|
|
198
|
-
|
|
189
|
+
assert set(class_conditions.methods.keys()) == {"foo", "__init__"}
|
|
199
190
|
method = class_conditions.methods["foo"]
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
set([c.expr_source for c in method.post]), set(["True", "False"])
|
|
205
|
-
)
|
|
191
|
+
assert len(method.pre) == 1
|
|
192
|
+
assert set([c.expr_source for c in method.pre]) == {"True"}
|
|
193
|
+
assert len(method.post) == 2
|
|
194
|
+
assert set([c.expr_source for c in method.post]) == {"True", "False"}
|
|
206
195
|
|
|
207
196
|
def test_invariant_applies_to_init(self) -> None:
|
|
208
197
|
class_conditions = Pep316Parser().get_class_conditions(BaseClassExample)
|
|
209
|
-
|
|
198
|
+
assert set(class_conditions.methods.keys()) == {"__init__", "foo"}
|
|
210
199
|
|
|
211
200
|
@pytest.mark.skipif(
|
|
212
201
|
sys.version_info >= (3, 13), reason="builtins have signatures in 3.13"
|
|
213
202
|
)
|
|
214
203
|
def test_builtin_conditions_are_null(self) -> None:
|
|
215
|
-
|
|
204
|
+
assert Pep316Parser().get_fn_conditions(FunctionInfo.from_fn(zip)) is None
|
|
216
205
|
|
|
217
206
|
def test_conditions_with_closure_references_and_string_type(self) -> None:
|
|
218
207
|
# This is a function that refers to something in its closure.
|
|
@@ -228,7 +217,7 @@ class Pep316ParserTest(unittest.TestCase):
|
|
|
228
217
|
|
|
229
218
|
|
|
230
219
|
@pytest.mark.skipif(not icontract, reason="icontract is not installed")
|
|
231
|
-
class
|
|
220
|
+
class TestIcontractParser:
|
|
232
221
|
def test_simple_parse(self):
|
|
233
222
|
@icontract.require(lambda ls: len(ls) > 0)
|
|
234
223
|
@icontract.ensure(lambda ls, result: min(ls) <= result <= max(ls))
|
|
@@ -237,17 +226,17 @@ class IcontractParserTest(unittest.TestCase):
|
|
|
237
226
|
|
|
238
227
|
conditions = IcontractParser().get_fn_conditions(FunctionInfo.from_fn(avg))
|
|
239
228
|
assert conditions is not None
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
229
|
+
assert len(conditions.pre) == 1
|
|
230
|
+
assert len(conditions.post) == 1
|
|
231
|
+
assert conditions.pre[0].evaluate({"ls": []}) is False
|
|
243
232
|
post_args = {
|
|
244
233
|
"ls": [42, 43],
|
|
245
234
|
"__old__": AttributeHolder({}),
|
|
246
235
|
"__return__": 40,
|
|
247
236
|
"_": 40,
|
|
248
237
|
}
|
|
249
|
-
|
|
250
|
-
|
|
238
|
+
assert conditions.post[0].evaluate(post_args) is False
|
|
239
|
+
assert len(post_args) == 4 # (check args are unmodified)
|
|
251
240
|
|
|
252
241
|
def test_simple_class_parse(self):
|
|
253
242
|
@icontract.invariant(lambda self: self.i >= 0)
|
|
@@ -268,14 +257,14 @@ class IcontractParserTest(unittest.TestCase):
|
|
|
268
257
|
self.i -= 1
|
|
269
258
|
|
|
270
259
|
conditions = IcontractParser().get_class_conditions(Counter)
|
|
271
|
-
|
|
260
|
+
assert len(conditions.inv) == 1
|
|
272
261
|
|
|
273
262
|
decr_conditions = conditions.methods["decr"]
|
|
274
|
-
|
|
263
|
+
assert len(decr_conditions.pre) == 2
|
|
275
264
|
# decr() precondition: count > 0
|
|
276
|
-
|
|
265
|
+
assert decr_conditions.pre[0].evaluate({"self": Counter()}) is False
|
|
277
266
|
# invariant: count >= 0
|
|
278
|
-
|
|
267
|
+
assert decr_conditions.pre[1].evaluate({"self": Counter()}) is True
|
|
279
268
|
|
|
280
269
|
class TruncatedCounter(Counter):
|
|
281
270
|
@icontract.require(
|
|
@@ -287,21 +276,19 @@ class IcontractParserTest(unittest.TestCase):
|
|
|
287
276
|
|
|
288
277
|
conditions = IcontractParser().get_class_conditions(TruncatedCounter)
|
|
289
278
|
decr_conditions = conditions.methods["decr"]
|
|
290
|
-
|
|
291
|
-
decr_conditions.pre[0].evaluate({"self": TruncatedCounter()}), True
|
|
292
|
-
)
|
|
279
|
+
assert decr_conditions.pre[0].evaluate({"self": TruncatedCounter()}) is True
|
|
293
280
|
|
|
294
281
|
# check the weakened precondition
|
|
295
|
-
|
|
296
|
-
len(decr_conditions.pre)
|
|
282
|
+
assert (
|
|
283
|
+
len(decr_conditions.pre) == 2
|
|
297
284
|
) # one for the invariant, one for the disjunction
|
|
298
285
|
ctr = TruncatedCounter()
|
|
299
286
|
ctr.i = 1
|
|
300
|
-
|
|
301
|
-
|
|
287
|
+
assert decr_conditions.pre[1].evaluate({"self": ctr}) is True
|
|
288
|
+
assert decr_conditions.pre[0].evaluate({"self": ctr}) is True
|
|
302
289
|
ctr.i = 0
|
|
303
|
-
|
|
304
|
-
|
|
290
|
+
assert decr_conditions.pre[1].evaluate({"self": ctr}) is True
|
|
291
|
+
assert decr_conditions.pre[0].evaluate({"self": ctr}) is True
|
|
305
292
|
|
|
306
293
|
|
|
307
294
|
@pytest.mark.skipif(not deal, reason="deal is not installed")
|
|
@@ -395,20 +382,20 @@ def fn_with_docstring_comments_and_assert(numbers: List[int]) -> None:
|
|
|
395
382
|
assert min(numbers) > smallest
|
|
396
383
|
|
|
397
384
|
|
|
398
|
-
class
|
|
385
|
+
class TestAssertsParser:
|
|
399
386
|
def tests_simple_parse(self) -> None:
|
|
400
387
|
conditions = AssertsParser().get_fn_conditions(
|
|
401
388
|
FunctionInfo.from_fn(avg_with_asserts)
|
|
402
389
|
)
|
|
403
390
|
assert conditions is not None
|
|
404
391
|
conditions.fn([])
|
|
405
|
-
|
|
406
|
-
with
|
|
392
|
+
assert conditions.fn([2.2]) == 2.2
|
|
393
|
+
with pytest.raises(AssertionError):
|
|
407
394
|
conditions.fn([9.2, 17.8])
|
|
408
395
|
|
|
409
396
|
def tests_empty_parse(self) -> None:
|
|
410
397
|
conditions = AssertsParser().get_fn_conditions(FunctionInfo.from_fn(debug))
|
|
411
|
-
|
|
398
|
+
assert conditions is None
|
|
412
399
|
|
|
413
400
|
def tests_extra_ast_nodes(self) -> None:
|
|
414
401
|
conditions = AssertsParser().get_fn_conditions(
|
|
@@ -422,10 +409,10 @@ class AssertsParserTest(unittest.TestCase):
|
|
|
422
409
|
# normal, passing case:
|
|
423
410
|
nums = [3, 1, 2]
|
|
424
411
|
conditions.fn(nums)
|
|
425
|
-
|
|
412
|
+
assert nums == [3, 2]
|
|
426
413
|
|
|
427
414
|
# Failing case (duplicate minimum values):
|
|
428
|
-
with
|
|
415
|
+
with pytest.raises(AssertionError):
|
|
429
416
|
nums = [3, 1, 1, 2]
|
|
430
417
|
conditions.fn(nums)
|
|
431
418
|
|
crosshair/copyext.py
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
import sys
|
|
2
|
+
|
|
3
|
+
if sys.version_info >= (3, 14):
|
|
4
|
+
from copy import _atomic_types
|
|
5
|
+
else:
|
|
6
|
+
from copy import _deepcopy_atomic # type: ignore
|
|
2
7
|
from copy import _deepcopy_dict # type: ignore
|
|
3
8
|
from copy import _deepcopy_dispatch # type: ignore
|
|
4
9
|
from copy import _deepcopy_list # type: ignore
|
|
@@ -9,7 +14,7 @@ from copy import Error
|
|
|
9
14
|
from copyreg import dispatch_table # type: ignore
|
|
10
15
|
from enum import Enum
|
|
11
16
|
from types import MappingProxyType
|
|
12
|
-
from typing import Any, Dict, Tuple
|
|
17
|
+
from typing import Any, Callable, Dict, Tuple
|
|
13
18
|
|
|
14
19
|
from crosshair.tracers import ResumedTracing
|
|
15
20
|
from crosshair.util import (
|
|
@@ -85,17 +90,28 @@ def deepcopyext(obj: object, mode: CopyMode, memo: Dict) -> Any:
|
|
|
85
90
|
return cpy
|
|
86
91
|
|
|
87
92
|
|
|
93
|
+
if sys.version_info >= (3, 14):
|
|
94
|
+
|
|
95
|
+
def lookup_dispatch(cls: type) -> Callable:
|
|
96
|
+
if cls in _atomic_types:
|
|
97
|
+
return lambda obj, memo: obj
|
|
98
|
+
return _deepcopy_dispatch.get(cls)
|
|
99
|
+
|
|
100
|
+
else:
|
|
101
|
+
|
|
102
|
+
def lookup_dispatch(cls: type) -> Callable:
|
|
103
|
+
return _deepcopy_dispatch.get(cls)
|
|
104
|
+
|
|
105
|
+
|
|
88
106
|
def _deepconstruct(obj: object, mode: CopyMode, memo: Dict):
|
|
89
107
|
cls = type(obj)
|
|
90
108
|
|
|
91
109
|
def subdeepcopy(obj: object, memo: Dict):
|
|
92
110
|
return deepcopyext(obj, mode, memo)
|
|
93
111
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
if creator
|
|
97
|
-
return obj
|
|
98
|
-
elif creator in (_deepcopy_dict, _deepcopy_list, _deepcopy_tuple):
|
|
112
|
+
creator = lookup_dispatch(cls)
|
|
113
|
+
if creator is not None:
|
|
114
|
+
if creator in (_deepcopy_dict, _deepcopy_list, _deepcopy_tuple):
|
|
99
115
|
return creator(obj, memo, deepcopy=subdeepcopy)
|
|
100
116
|
else:
|
|
101
117
|
# TODO: We loose subdeepcopy in this case - won't
|
crosshair/copyext_test.py
CHANGED
|
@@ -7,7 +7,7 @@ import pytest
|
|
|
7
7
|
from crosshair.copyext import CopyMode, deepcopyext
|
|
8
8
|
from crosshair.core_and_libs import proxy_for_type, standalone_statespace
|
|
9
9
|
from crosshair.libimpl.builtinslib import SymbolicInt
|
|
10
|
-
from crosshair.tracers import NoTracing
|
|
10
|
+
from crosshair.tracers import NoTracing
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
def test_deepcopyext_best_effort():
|
|
@@ -33,6 +33,16 @@ def test_deepcopyext_symbolic_set():
|
|
|
33
33
|
deepcopyext(s, CopyMode.REALIZE, {})
|
|
34
34
|
|
|
35
35
|
|
|
36
|
+
def test_deepcopyext_realize_simple(space):
|
|
37
|
+
x = SymbolicInt("x")
|
|
38
|
+
input = (x,)
|
|
39
|
+
output = deepcopyext(input, CopyMode.REALIZE, {})
|
|
40
|
+
assert input is not output
|
|
41
|
+
assert input[0] is not output[0]
|
|
42
|
+
assert type(input[0]) is SymbolicInt
|
|
43
|
+
assert type(output[0]) is int
|
|
44
|
+
|
|
45
|
+
|
|
36
46
|
def test_deepcopyext_realize(space):
|
|
37
47
|
x = SymbolicInt("x")
|
|
38
48
|
lock = RLock()
|
crosshair/core.py
CHANGED
|
@@ -429,24 +429,30 @@ def get_constructor_signature(cls: Type) -> Optional[inspect.Signature]:
|
|
|
429
429
|
sig = resolve_signature(cls)
|
|
430
430
|
if isinstance(sig, inspect.Signature):
|
|
431
431
|
return sig
|
|
432
|
+
|
|
433
|
+
applicable_sigs: List[Signature] = []
|
|
432
434
|
new_fn = cls.__new__
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
if
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
435
|
+
if new_fn is not object.__new__:
|
|
436
|
+
sig = resolve_signature(new_fn)
|
|
437
|
+
if not isinstance(sig, str):
|
|
438
|
+
applicable_sigs.append(sig)
|
|
439
|
+
init_fn = cls.__init__
|
|
440
|
+
if init_fn is not object.__init__:
|
|
441
|
+
sig = resolve_signature(init_fn)
|
|
442
|
+
if not isinstance(sig, str):
|
|
443
|
+
sig = sig.replace(
|
|
444
|
+
return_annotation=object
|
|
445
|
+
) # make return types compatible (& use __new__'s return)
|
|
446
|
+
applicable_sigs.append(sig)
|
|
447
|
+
if len(applicable_sigs) == 0:
|
|
448
|
+
return inspect.Signature([])
|
|
449
|
+
if len(applicable_sigs) == 2:
|
|
450
|
+
sig = dynamic_typing.intersect_signatures(*applicable_sigs)
|
|
451
|
+
else:
|
|
452
|
+
sig = applicable_sigs[0]
|
|
453
|
+
# strip first argument ("self" or "cls")
|
|
454
|
+
newparams = list(sig.parameters.values())[1:]
|
|
455
|
+
return sig.replace(parameters=newparams)
|
|
450
456
|
|
|
451
457
|
|
|
452
458
|
_TYPE_HINTS = IdKeyedDict()
|