crosshair-tool 0.0.84__cp38-cp38-macosx_11_0_arm64.whl → 0.0.85__cp38-cp38-macosx_11_0_arm64.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-38-darwin.so +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/condition_parser.py +5 -5
- crosshair/condition_parser_test.py +1 -1
- crosshair/copyext.py +23 -7
- crosshair/copyext_test.py +11 -1
- crosshair/dynamic_typing.py +1 -1
- crosshair/fnutil_test.py +4 -1
- crosshair/libimpl/arraylib.py +0 -13
- crosshair/libimpl/builtinslib.py +22 -14
- crosshair/libimpl/collectionslib.py +13 -2
- crosshair/libimpl/collectionslib_test.py +10 -2
- crosshair/libimpl/timelib.py +34 -15
- crosshair/libimpl/timelib_test.py +12 -2
- crosshair/lsp_server.py +1 -1
- crosshair/opcode_intercept.py +23 -8
- crosshair/opcode_intercept_test.py +13 -2
- crosshair/tracers.py +27 -9
- crosshair/type_repo.py +2 -2
- crosshair/unicode_categories.py +1 -0
- crosshair/util.py +45 -16
- {crosshair_tool-0.0.84.dist-info → crosshair_tool-0.0.85.dist-info}/METADATA +1 -1
- {crosshair_tool-0.0.84.dist-info → crosshair_tool-0.0.85.dist-info}/RECORD +30 -30
- {crosshair_tool-0.0.84.dist-info → crosshair_tool-0.0.85.dist-info}/LICENSE +0 -0
- {crosshair_tool-0.0.84.dist-info → crosshair_tool-0.0.85.dist-info}/WHEEL +0 -0
- {crosshair_tool-0.0.84.dist-info → crosshair_tool-0.0.85.dist-info}/entry_points.txt +0 -0
- {crosshair_tool-0.0.84.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/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__":
|
|
@@ -140,7 +140,7 @@ class TestPep316Parser:
|
|
|
140
140
|
"self.x >= 0",
|
|
141
141
|
"self.y >= 0",
|
|
142
142
|
}
|
|
143
|
-
assert set(class_conditions.methods.keys())
|
|
143
|
+
assert {"isready", "__init__"} <= set(class_conditions.methods.keys())
|
|
144
144
|
method = class_conditions.methods["isready"]
|
|
145
145
|
assert set([c.expr_source for c in method.pre]) == {
|
|
146
146
|
"self.x >= 0",
|
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/dynamic_typing.py
CHANGED
|
@@ -4,7 +4,7 @@ from inspect import Parameter, Signature
|
|
|
4
4
|
from itertools import zip_longest
|
|
5
5
|
from typing import Any, Callable, Dict, List, Mapping, Optional, Sequence, Tuple, Type
|
|
6
6
|
|
|
7
|
-
import typing_inspect
|
|
7
|
+
import typing_inspect # type: ignore
|
|
8
8
|
|
|
9
9
|
from crosshair.util import debug # type: ignore
|
|
10
10
|
|
crosshair/fnutil_test.py
CHANGED
|
@@ -26,7 +26,10 @@ def test_fn_globals_on_builtin() -> None:
|
|
|
26
26
|
|
|
27
27
|
def test_resolve_signature_invalid_annotations() -> None:
|
|
28
28
|
sig = resolve_signature(with_invalid_type_annotation)
|
|
29
|
-
|
|
29
|
+
if sys.version_info >= (3, 14):
|
|
30
|
+
assert sig == "TypeThatIsNotDefined"
|
|
31
|
+
else:
|
|
32
|
+
assert sig == "name 'TypeThatIsNotDefined' is not defined"
|
|
30
33
|
|
|
31
34
|
|
|
32
35
|
@pytest.mark.skipif(
|
crosshair/libimpl/arraylib.py
CHANGED
|
@@ -30,19 +30,6 @@ INT_TYPE_BOUNDS: Dict[str, Tuple[int, int]] = {
|
|
|
30
30
|
INT_TYPE_SIZE = {c: array(c).itemsize for c in INT_TYPE_BOUNDS.keys()}
|
|
31
31
|
|
|
32
32
|
|
|
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))
|
|
44
|
-
|
|
45
|
-
|
|
46
33
|
def pick_code(space: StateSpace) -> Tuple[str, int, int]:
|
|
47
34
|
last_idx = len(INT_TYPE_BOUNDS) - 1
|
|
48
35
|
for (idx, (code, rng)) in enumerate(INT_TYPE_BOUNDS.items()):
|
crosshair/libimpl/builtinslib.py
CHANGED
|
@@ -17,7 +17,7 @@ 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,
|
|
@@ -58,6 +58,8 @@ try:
|
|
|
58
58
|
except ImportError:
|
|
59
59
|
from z3 import FfpEQ as fpEQ
|
|
60
60
|
|
|
61
|
+
import sys
|
|
62
|
+
|
|
61
63
|
from crosshair.abcstring import AbcString
|
|
62
64
|
from crosshair.core import (
|
|
63
65
|
SymbolicFactory,
|
|
@@ -116,6 +118,7 @@ from crosshair.util import (
|
|
|
116
118
|
assert_tracing,
|
|
117
119
|
ch_stack,
|
|
118
120
|
debug,
|
|
121
|
+
is_bytes_like,
|
|
119
122
|
is_hashable,
|
|
120
123
|
is_iterable,
|
|
121
124
|
memo,
|
|
@@ -1213,10 +1216,11 @@ class SymbolicInt(SymbolicIntable, AtomicSymbolicValue):
|
|
|
1213
1216
|
cur_divisor = 10
|
|
1214
1217
|
while True:
|
|
1215
1218
|
leftover = self // cur_divisor
|
|
1216
|
-
if leftover
|
|
1219
|
+
if leftover != 0:
|
|
1220
|
+
codepoints.append(48 + (leftover % 10))
|
|
1221
|
+
cur_divisor *= 10
|
|
1222
|
+
else:
|
|
1217
1223
|
break
|
|
1218
|
-
codepoints.append(48 + (leftover % 10))
|
|
1219
|
-
cur_divisor *= 10
|
|
1220
1224
|
with NoTracing():
|
|
1221
1225
|
codepoints.reverse()
|
|
1222
1226
|
return LazyIntSymbolicStr(codepoints)
|
|
@@ -1316,7 +1320,7 @@ class SymbolicInt(SymbolicIntable, AtomicSymbolicValue):
|
|
|
1316
1320
|
z3.If(val < 128, 7, 8)))))))))
|
|
1317
1321
|
# fmt: on
|
|
1318
1322
|
|
|
1319
|
-
if
|
|
1323
|
+
if version_info >= (3, 12):
|
|
1320
1324
|
|
|
1321
1325
|
def is_integer(self):
|
|
1322
1326
|
return True
|
|
@@ -2934,7 +2938,7 @@ class AnySymbolicStr(AbcString):
|
|
|
2934
2938
|
def capitalize(self):
|
|
2935
2939
|
if self.__len__() == 0:
|
|
2936
2940
|
return ""
|
|
2937
|
-
if
|
|
2941
|
+
if version_info >= (3, 8):
|
|
2938
2942
|
firstchar = self[0].title()
|
|
2939
2943
|
else:
|
|
2940
2944
|
firstchar = self[0].upper()
|
|
@@ -3172,7 +3176,7 @@ class AnySymbolicStr(AbcString):
|
|
|
3172
3176
|
return ""
|
|
3173
3177
|
|
|
3174
3178
|
def splitlines(self, keepends=False):
|
|
3175
|
-
if
|
|
3179
|
+
if version_info < (3, 12):
|
|
3176
3180
|
if not isinstance(keepends, int):
|
|
3177
3181
|
raise TypeError
|
|
3178
3182
|
mylen = self.__len__()
|
|
@@ -3438,6 +3442,7 @@ class LazyIntSymbolicStr(AnySymbolicStr, CrossHairValue):
|
|
|
3438
3442
|
SliceView,
|
|
3439
3443
|
SequenceConcatenation,
|
|
3440
3444
|
list, # TODO: are we sharing mutable state here?
|
|
3445
|
+
tuple,
|
|
3441
3446
|
),
|
|
3442
3447
|
):
|
|
3443
3448
|
self._codepoints = smtvar
|
|
@@ -3975,7 +3980,7 @@ class BytesLike(Buffer, AbcString, CrossHairValue):
|
|
|
3975
3980
|
return False
|
|
3976
3981
|
return list(self) == list(other)
|
|
3977
3982
|
|
|
3978
|
-
if
|
|
3983
|
+
if version_info >= (3, 12):
|
|
3979
3984
|
|
|
3980
3985
|
def __buffer__(self, flags: int):
|
|
3981
3986
|
with NoTracing():
|
|
@@ -4135,7 +4140,10 @@ class SymbolicBytes(BytesLike):
|
|
|
4135
4140
|
accumulated = []
|
|
4136
4141
|
high = None
|
|
4137
4142
|
if not isinstance(hexstr, str):
|
|
4138
|
-
|
|
4143
|
+
if is_bytes_like(hexstr) and version_info >= (3, 14):
|
|
4144
|
+
hexstr = LazyIntSymbolicStr(tuple(hexstr))
|
|
4145
|
+
else:
|
|
4146
|
+
raise TypeError
|
|
4139
4147
|
for idx, ch in enumerate(hexstr):
|
|
4140
4148
|
if not ch.isascii():
|
|
4141
4149
|
raise ValueError(
|
|
@@ -4894,7 +4902,7 @@ def _int_from_bytes(
|
|
|
4894
4902
|
) -> int:
|
|
4895
4903
|
if byteorder is _MISSING:
|
|
4896
4904
|
# byteorder defaults to "big" as of 3.11
|
|
4897
|
-
if
|
|
4905
|
+
if version_info >= (3, 11):
|
|
4898
4906
|
byteorder = "big"
|
|
4899
4907
|
else:
|
|
4900
4908
|
raise TypeError
|
|
@@ -5108,7 +5116,7 @@ def make_registrations():
|
|
|
5108
5116
|
|
|
5109
5117
|
register_type(Union, make_union_choice)
|
|
5110
5118
|
|
|
5111
|
-
if
|
|
5119
|
+
if version_info >= (3, 8):
|
|
5112
5120
|
from typing import Final
|
|
5113
5121
|
|
|
5114
5122
|
register_type(Final, lambda p, t: p(t))
|
|
@@ -5266,7 +5274,7 @@ def make_registrations():
|
|
|
5266
5274
|
"upper",
|
|
5267
5275
|
"zfill",
|
|
5268
5276
|
]
|
|
5269
|
-
if
|
|
5277
|
+
if version_info >= (3, 9):
|
|
5270
5278
|
names_to_str_patch.append("removeprefix")
|
|
5271
5279
|
names_to_str_patch.append("removesuffix")
|
|
5272
5280
|
for name in names_to_str_patch:
|
|
@@ -5322,12 +5330,12 @@ def make_registrations():
|
|
|
5322
5330
|
# Patches on int
|
|
5323
5331
|
register_patch(int.__repr__, with_checked_self(int, "__repr__"))
|
|
5324
5332
|
register_patch(int.as_integer_ratio, with_checked_self(int, "as_integer_ratio"))
|
|
5325
|
-
if
|
|
5333
|
+
if version_info >= (3, 10):
|
|
5326
5334
|
register_patch(int.bit_count, with_checked_self(int, "bit_count"))
|
|
5327
5335
|
register_patch(int.bit_length, with_checked_self(int, "bit_length"))
|
|
5328
5336
|
register_patch(int.conjugate, with_checked_self(int, "conjugate"))
|
|
5329
5337
|
register_patch(int.from_bytes, _int_from_bytes)
|
|
5330
|
-
if
|
|
5338
|
+
if version_info >= (3, 12):
|
|
5331
5339
|
register_patch(int.is_integer, with_checked_self(int, "is_integer"))
|
|
5332
5340
|
register_patch(int.to_bytes, with_checked_self(int, "to_bytes"))
|
|
5333
5341
|
|
|
@@ -126,8 +126,19 @@ class ListBasedDeque(collections.abc.MutableSequence, CrossHairValue, Generic[T]
|
|
|
126
126
|
prefix.reverse()
|
|
127
127
|
self._contents = prefix + self._contents
|
|
128
128
|
|
|
129
|
-
|
|
130
|
-
|
|
129
|
+
if sys.version_info >= (3, 14):
|
|
130
|
+
|
|
131
|
+
def index(self, item: T, *bounds) -> int:
|
|
132
|
+
try:
|
|
133
|
+
return self._contents.index(item, *bounds)
|
|
134
|
+
except ValueError as exc:
|
|
135
|
+
exc.args = ("deque.index(x): x not in deque",)
|
|
136
|
+
raise
|
|
137
|
+
|
|
138
|
+
else:
|
|
139
|
+
|
|
140
|
+
def index(self, item: T, *bounds) -> int:
|
|
141
|
+
return self._contents.index(item, *bounds)
|
|
131
142
|
|
|
132
143
|
def insert(self, index: int, item: T) -> None:
|
|
133
144
|
self._contents.insert(index, item)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import re
|
|
1
2
|
import sys
|
|
2
3
|
from collections import Counter, defaultdict, deque, namedtuple
|
|
3
4
|
from copy import deepcopy
|
|
@@ -98,7 +99,11 @@ def test_deque_index_with_start_index_throws_correct_exception(test_list) -> Non
|
|
|
98
99
|
with pytest.raises(ValueError) as context:
|
|
99
100
|
test_list.index(1, 2)
|
|
100
101
|
|
|
101
|
-
|
|
102
|
+
if sys.version_info >= (3, 14):
|
|
103
|
+
# assert context.match(re.escape("list.index(x): x not in list"))
|
|
104
|
+
assert context.match(re.escape("deque.index(x): x not in deque"))
|
|
105
|
+
else:
|
|
106
|
+
assert context.match("1 is not in list")
|
|
102
107
|
|
|
103
108
|
|
|
104
109
|
def test_deque_index_with_start_and_end_index(test_list) -> None:
|
|
@@ -112,7 +117,10 @@ def test_deque_index_with_start_and_end_index_throws_correct_exception(
|
|
|
112
117
|
with pytest.raises(ValueError) as context:
|
|
113
118
|
test_list.index(6, 0, 1)
|
|
114
119
|
|
|
115
|
-
|
|
120
|
+
if sys.version_info >= (3, 14):
|
|
121
|
+
assert context.match(re.escape("deque.index(x): x not in deque"))
|
|
122
|
+
else:
|
|
123
|
+
assert context.match("6 is not in list")
|
|
116
124
|
|
|
117
125
|
|
|
118
126
|
def test_deque_insert(test_list) -> None:
|
crosshair/libimpl/timelib.py
CHANGED
|
@@ -1,27 +1,45 @@
|
|
|
1
1
|
import time as real_time
|
|
2
2
|
from inspect import Signature
|
|
3
3
|
from math import isfinite
|
|
4
|
-
from typing import Any,
|
|
4
|
+
from typing import Any, Literal
|
|
5
5
|
|
|
6
|
-
from crosshair.core import
|
|
6
|
+
from crosshair.core import register_patch
|
|
7
7
|
from crosshair.register_contract import register_contract
|
|
8
8
|
from crosshair.statespace import context_statespace
|
|
9
9
|
from crosshair.tracers import NoTracing
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
class EarliestPossibleTime:
|
|
13
|
+
monotonic: float = 0.0
|
|
14
|
+
process_time: float = 0.0
|
|
15
|
+
|
|
16
|
+
def __init__(self, *a):
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# Imprecision at high values becomes a sort of artificial problem
|
|
21
|
+
_UNREALISTICALLY_LARGE_TIME_FLOAT = float(60 * 60 * 24 * 365 * 100_000)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _gte_last(kind: Literal["monotonic", "process_time"], value: Any) -> bool:
|
|
25
|
+
with NoTracing():
|
|
26
|
+
earliest_times = context_statespace().extra(EarliestPossibleTime)
|
|
27
|
+
threshold = getattr(earliest_times, kind)
|
|
28
|
+
setattr(earliest_times, kind, value)
|
|
29
|
+
return all([threshold <= value, value < _UNREALISTICALLY_LARGE_TIME_FLOAT])
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _sleep(value: float) -> None:
|
|
13
33
|
with NoTracing():
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
return True
|
|
18
|
-
return value >= previous[-2]
|
|
34
|
+
earliest_times = context_statespace().extra(EarliestPossibleTime)
|
|
35
|
+
earliest_times.monotonic += value
|
|
36
|
+
return None
|
|
19
37
|
|
|
20
38
|
|
|
21
39
|
def make_registrations():
|
|
22
40
|
register_contract(
|
|
23
41
|
real_time.time,
|
|
24
|
-
post=lambda __return__: __return__ > 0.0,
|
|
42
|
+
post=lambda __return__: __return__ > 0.0 and isfinite(__return__),
|
|
25
43
|
sig=Signature(parameters=[], return_annotation=float),
|
|
26
44
|
)
|
|
27
45
|
register_contract(
|
|
@@ -31,23 +49,24 @@ def make_registrations():
|
|
|
31
49
|
)
|
|
32
50
|
register_contract(
|
|
33
51
|
real_time.monotonic,
|
|
34
|
-
post=lambda __return__:
|
|
35
|
-
and
|
|
52
|
+
post=lambda __return__: _gte_last("monotonic", __return__)
|
|
53
|
+
and isfinite(__return__),
|
|
36
54
|
sig=Signature(parameters=[], return_annotation=float),
|
|
37
55
|
)
|
|
38
56
|
register_contract(
|
|
39
57
|
real_time.monotonic_ns,
|
|
40
|
-
post=lambda __return__:
|
|
41
|
-
and _gte_last(real_time.monotonic_ns, __return__),
|
|
58
|
+
post=lambda __return__: _gte_last("monotonic", __return__ / 1_000_000_000),
|
|
42
59
|
sig=Signature(parameters=[], return_annotation=int),
|
|
43
60
|
)
|
|
44
61
|
register_contract(
|
|
45
62
|
real_time.process_time,
|
|
46
|
-
post=lambda __return__: _gte_last(
|
|
63
|
+
post=lambda __return__: _gte_last("process_time", __return__)
|
|
64
|
+
and isfinite(__return__),
|
|
47
65
|
sig=Signature(parameters=[], return_annotation=float),
|
|
48
66
|
)
|
|
49
67
|
register_contract(
|
|
50
68
|
real_time.process_time_ns,
|
|
51
|
-
post=lambda __return__: _gte_last(
|
|
69
|
+
post=lambda __return__: _gte_last("process_time", __return__ / 1_000_000_000),
|
|
52
70
|
sig=Signature(parameters=[], return_annotation=int),
|
|
53
71
|
)
|
|
72
|
+
register_patch(real_time.sleep, _sleep)
|
|
@@ -2,7 +2,7 @@ import time
|
|
|
2
2
|
|
|
3
3
|
import pytest
|
|
4
4
|
|
|
5
|
-
from crosshair.statespace import CONFIRMED, POST_FAIL
|
|
5
|
+
from crosshair.statespace import CANNOT_CONFIRM, CONFIRMED, POST_FAIL
|
|
6
6
|
from crosshair.test_util import check_states
|
|
7
7
|
|
|
8
8
|
|
|
@@ -69,4 +69,14 @@ def test_monotonic_ns():
|
|
|
69
69
|
start = time.monotonic_ns()
|
|
70
70
|
return time.monotonic_ns() - start
|
|
71
71
|
|
|
72
|
-
check_states(f,
|
|
72
|
+
check_states(f, CANNOT_CONFIRM)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_sleep():
|
|
76
|
+
def f():
|
|
77
|
+
"""post: _ >= 60.0"""
|
|
78
|
+
start = time.monotonic()
|
|
79
|
+
time.sleep(60.01)
|
|
80
|
+
return time.monotonic() - start
|
|
81
|
+
|
|
82
|
+
check_states(f, CANNOT_CONFIRM)
|
crosshair/lsp_server.py
CHANGED
|
@@ -86,7 +86,7 @@ def publish_messages(
|
|
|
86
86
|
if message.state < MessageType.PRE_UNSAT:
|
|
87
87
|
continue
|
|
88
88
|
# TODO: consider server.show_message_log()ing the long description
|
|
89
|
-
diagnostics.append(get_diagnostic(message, doc.lines if doc else
|
|
89
|
+
diagnostics.append(get_diagnostic(message, doc.lines if doc else []))
|
|
90
90
|
server.publish_diagnostics(uri, diagnostics)
|
|
91
91
|
if not diagnostics:
|
|
92
92
|
# After we publish an empty set, it's safe to forget about the file:
|
crosshair/opcode_intercept.py
CHANGED
|
@@ -26,10 +26,10 @@ from crosshair.tracers import (
|
|
|
26
26
|
frame_stack_read,
|
|
27
27
|
frame_stack_write,
|
|
28
28
|
)
|
|
29
|
-
from crosshair.util import CrossHairInternal, CrossHairValue
|
|
29
|
+
from crosshair.util import CROSSHAIR_EXTRA_ASSERTS, CrossHairInternal, CrossHairValue
|
|
30
30
|
from crosshair.z3util import z3Not, z3Or
|
|
31
31
|
|
|
32
|
-
BINARY_SUBSCR = dis.opmap
|
|
32
|
+
BINARY_SUBSCR = dis.opmap.get("BINARY_SUBSCR", 256)
|
|
33
33
|
BINARY_SLICE = dis.opmap.get("BINARY_SLICE", 256)
|
|
34
34
|
BUILD_STRING = dis.opmap["BUILD_STRING"]
|
|
35
35
|
COMPARE_OP = dis.opmap["COMPARE_OP"]
|
|
@@ -61,11 +61,17 @@ _DEEPLY_CONCRETE_KEY_TYPES = (
|
|
|
61
61
|
|
|
62
62
|
|
|
63
63
|
class SymbolicSubscriptInterceptor(TracingModule):
|
|
64
|
-
opcodes_wanted = frozenset([BINARY_SUBSCR])
|
|
64
|
+
opcodes_wanted = frozenset([BINARY_SUBSCR, BINARY_OP])
|
|
65
65
|
|
|
66
66
|
def trace_op(self, frame, codeobj, codenum):
|
|
67
67
|
# Note that because this is called from inside a Python trace handler, tracing
|
|
68
68
|
# is automatically disabled, so there's no need for a `with NoTracing():` guard.
|
|
69
|
+
|
|
70
|
+
if codenum == BINARY_OP:
|
|
71
|
+
oparg = frame_op_arg(frame)
|
|
72
|
+
if oparg != 26: # subscript operator, NB_SUBSCR
|
|
73
|
+
return
|
|
74
|
+
|
|
69
75
|
key = frame_stack_read(frame, -1)
|
|
70
76
|
if isinstance(key, _DEEPLY_CONCRETE_KEY_TYPES):
|
|
71
77
|
return
|
|
@@ -345,7 +351,9 @@ class MapAddInterceptor(TracingModule):
|
|
|
345
351
|
# Afterwards, overwrite the interpreter's resulting dict with ours:
|
|
346
352
|
def post_op():
|
|
347
353
|
old_dict_obj = frame_stack_read(frame, dict_offset + 2)
|
|
348
|
-
if not isinstance(
|
|
354
|
+
if CROSSHAIR_EXTRA_ASSERTS and not isinstance(
|
|
355
|
+
old_dict_obj, (dict, MutableMapping)
|
|
356
|
+
):
|
|
349
357
|
raise CrossHairInternal("interpreter stack corruption detected")
|
|
350
358
|
frame_stack_write(frame, dict_offset + 2, dict_obj)
|
|
351
359
|
|
|
@@ -427,7 +435,8 @@ class SetAddInterceptor(TracingModule):
|
|
|
427
435
|
# Set and value are concrete; continue as normal.
|
|
428
436
|
return
|
|
429
437
|
# Have the interpreter do a fake addition, namely `set().add(1)`
|
|
430
|
-
|
|
438
|
+
dummy_set: Set = set()
|
|
439
|
+
frame_stack_write(frame, set_offset, dummy_set)
|
|
431
440
|
frame_stack_write(frame, -1, 1)
|
|
432
441
|
|
|
433
442
|
# And do our own addition separately:
|
|
@@ -435,6 +444,12 @@ class SetAddInterceptor(TracingModule):
|
|
|
435
444
|
|
|
436
445
|
# Later, overwrite the interpreter's result with ours:
|
|
437
446
|
def post_op():
|
|
447
|
+
if CROSSHAIR_EXTRA_ASSERTS:
|
|
448
|
+
to_replace = frame_stack_read(frame, set_offset + 1)
|
|
449
|
+
if to_replace is not dummy_set:
|
|
450
|
+
raise CrossHairInternal(
|
|
451
|
+
f"Found an instance of {type(to_replace)} where dummy set should be."
|
|
452
|
+
)
|
|
438
453
|
frame_stack_write(frame, set_offset + 1, set_obj)
|
|
439
454
|
|
|
440
455
|
COMPOSITE_TRACER.set_postop_callback(post_op, frame)
|
|
@@ -450,9 +465,9 @@ class IdentityInterceptor(TracingModule):
|
|
|
450
465
|
def trace_op(self, frame: FrameType, codeobj: CodeType, codenum: int) -> None:
|
|
451
466
|
arg1 = frame_stack_read(frame, -1)
|
|
452
467
|
arg2 = frame_stack_read(frame, -2)
|
|
453
|
-
if isinstance(arg1, SymbolicBool):
|
|
468
|
+
if isinstance(arg1, SymbolicBool) and isinstance(arg2, (bool, SymbolicBool)):
|
|
454
469
|
frame_stack_write(frame, -1, arg1.__ch_realize__())
|
|
455
|
-
if isinstance(arg2, SymbolicBool):
|
|
470
|
+
if isinstance(arg2, SymbolicBool) and isinstance(arg1, (bool, SymbolicBool)):
|
|
456
471
|
frame_stack_write(frame, -2, arg2.__ch_realize__())
|
|
457
472
|
|
|
458
473
|
|
|
@@ -467,7 +482,7 @@ class ModuloInterceptor(TracingModule):
|
|
|
467
482
|
if isinstance(left, str):
|
|
468
483
|
if codenum == BINARY_OP:
|
|
469
484
|
oparg = frame_op_arg(frame)
|
|
470
|
-
if oparg != 6: # modulo operator
|
|
485
|
+
if oparg != 6: # modulo operator, NB_REMAINDER
|
|
471
486
|
return
|
|
472
487
|
frame_stack_write(frame, -2, DeoptimizedPercentFormattingStr(left))
|
|
473
488
|
|