crosshair-tool 0.0.56__cp39-cp39-macosx_11_0_arm64.whl → 0.0.100__cp39-cp39-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.
- _crosshair_tracers.cpython-39-darwin.so +0 -0
- crosshair/__init__.py +1 -1
- crosshair/_mark_stacks.h +51 -24
- crosshair/_tracers.h +9 -5
- crosshair/_tracers_test.py +19 -9
- crosshair/auditwall.py +9 -8
- crosshair/auditwall_test.py +31 -19
- crosshair/codeconfig.py +3 -2
- crosshair/condition_parser.py +17 -133
- crosshair/condition_parser_test.py +54 -96
- crosshair/conftest.py +1 -1
- crosshair/copyext.py +91 -22
- crosshair/copyext_test.py +33 -0
- crosshair/core.py +259 -203
- crosshair/core_and_libs.py +20 -0
- crosshair/core_regestered_types_test.py +82 -0
- crosshair/core_test.py +693 -664
- crosshair/diff_behavior.py +76 -21
- crosshair/diff_behavior_test.py +132 -23
- crosshair/dynamic_typing.py +128 -18
- crosshair/dynamic_typing_test.py +91 -4
- crosshair/enforce.py +1 -6
- crosshair/enforce_test.py +15 -23
- crosshair/examples/check_examples_test.py +2 -1
- crosshair/fnutil.py +2 -3
- crosshair/fnutil_test.py +0 -7
- crosshair/fuzz_core_test.py +70 -83
- crosshair/libimpl/arraylib.py +10 -7
- crosshair/libimpl/binascii_ch_test.py +30 -0
- crosshair/libimpl/binascii_test.py +67 -0
- crosshair/libimpl/binasciilib.py +150 -0
- crosshair/libimpl/bisectlib_test.py +5 -5
- crosshair/libimpl/builtinslib.py +1002 -682
- crosshair/libimpl/builtinslib_ch_test.py +108 -30
- crosshair/libimpl/builtinslib_test.py +431 -143
- crosshair/libimpl/codecslib.py +22 -2
- crosshair/libimpl/codecslib_test.py +41 -9
- crosshair/libimpl/collectionslib.py +44 -8
- crosshair/libimpl/collectionslib_test.py +108 -20
- crosshair/libimpl/copylib.py +1 -1
- crosshair/libimpl/copylib_test.py +18 -0
- crosshair/libimpl/datetimelib.py +84 -67
- crosshair/libimpl/datetimelib_ch_test.py +12 -7
- crosshair/libimpl/datetimelib_test.py +5 -6
- crosshair/libimpl/decimallib.py +5257 -0
- crosshair/libimpl/decimallib_ch_test.py +78 -0
- crosshair/libimpl/decimallib_test.py +76 -0
- crosshair/libimpl/encodings/_encutil.py +21 -11
- crosshair/libimpl/fractionlib.py +16 -0
- crosshair/libimpl/fractionlib_test.py +80 -0
- crosshair/libimpl/functoolslib.py +19 -7
- crosshair/libimpl/functoolslib_test.py +22 -6
- crosshair/libimpl/hashliblib.py +30 -0
- crosshair/libimpl/hashliblib_test.py +18 -0
- crosshair/libimpl/heapqlib.py +32 -5
- crosshair/libimpl/heapqlib_test.py +15 -12
- crosshair/libimpl/iolib.py +7 -4
- crosshair/libimpl/ipaddresslib.py +8 -0
- crosshair/libimpl/itertoolslib_test.py +1 -1
- crosshair/libimpl/mathlib.py +165 -2
- crosshair/libimpl/mathlib_ch_test.py +44 -0
- crosshair/libimpl/mathlib_test.py +59 -16
- crosshair/libimpl/oslib.py +7 -0
- crosshair/libimpl/pathliblib_test.py +10 -0
- crosshair/libimpl/randomlib.py +1 -0
- crosshair/libimpl/randomlib_test.py +6 -4
- crosshair/libimpl/relib.py +180 -59
- crosshair/libimpl/relib_ch_test.py +26 -2
- crosshair/libimpl/relib_test.py +77 -14
- crosshair/libimpl/timelib.py +35 -13
- crosshair/libimpl/timelib_test.py +13 -3
- crosshair/libimpl/typeslib.py +15 -0
- crosshair/libimpl/typeslib_test.py +36 -0
- crosshair/libimpl/unicodedatalib_test.py +3 -3
- crosshair/libimpl/weakreflib.py +13 -0
- crosshair/libimpl/weakreflib_test.py +69 -0
- crosshair/libimpl/zliblib.py +15 -0
- crosshair/libimpl/zliblib_test.py +13 -0
- crosshair/lsp_server.py +21 -10
- crosshair/main.py +48 -28
- crosshair/main_test.py +59 -14
- crosshair/objectproxy.py +39 -14
- crosshair/objectproxy_test.py +27 -13
- crosshair/opcode_intercept.py +212 -24
- crosshair/opcode_intercept_test.py +172 -18
- crosshair/options.py +0 -1
- crosshair/patch_equivalence_test.py +5 -21
- crosshair/path_cover.py +7 -5
- crosshair/path_search.py +6 -4
- crosshair/path_search_test.py +1 -2
- crosshair/pathing_oracle.py +53 -10
- crosshair/pathing_oracle_test.py +21 -0
- crosshair/pure_importer_test.py +5 -21
- crosshair/register_contract.py +16 -6
- crosshair/register_contract_test.py +2 -14
- crosshair/simplestructs.py +154 -85
- crosshair/simplestructs_test.py +16 -2
- crosshair/smtlib.py +24 -0
- crosshair/smtlib_test.py +14 -0
- crosshair/statespace.py +319 -196
- crosshair/statespace_test.py +45 -0
- crosshair/stubs_parser.py +0 -2
- crosshair/test_util.py +87 -25
- crosshair/test_util_test.py +26 -0
- crosshair/tools/check_init_and_setup_coincide.py +0 -3
- crosshair/tools/generate_demo_table.py +2 -2
- crosshair/tracers.py +141 -49
- crosshair/type_repo.py +11 -4
- crosshair/unicode_categories.py +1 -0
- crosshair/util.py +158 -76
- crosshair/util_test.py +13 -20
- crosshair/watcher.py +4 -4
- crosshair/z3util.py +1 -1
- {crosshair_tool-0.0.56.dist-info → crosshair_tool-0.0.100.dist-info}/METADATA +45 -36
- crosshair_tool-0.0.100.dist-info/RECORD +176 -0
- {crosshair_tool-0.0.56.dist-info → crosshair_tool-0.0.100.dist-info}/WHEEL +2 -1
- crosshair/examples/hypothesis/__init__.py +0 -2
- crosshair/examples/hypothesis/bugs_detected/simple_strategies.py +0 -74
- crosshair_tool-0.0.56.dist-info/RECORD +0 -152
- /crosshair/{examples/hypothesis/bugs_detected/__init__.py → py.typed} +0 -0
- {crosshair_tool-0.0.56.dist-info → crosshair_tool-0.0.100.dist-info}/entry_points.txt +0 -0
- {crosshair_tool-0.0.56.dist-info → crosshair_tool-0.0.100.dist-info/licenses}/LICENSE +0 -0
- {crosshair_tool-0.0.56.dist-info → crosshair_tool-0.0.100.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import operator
|
|
2
|
+
import sys
|
|
3
|
+
from decimal import BasicContext, Decimal, ExtendedContext, localcontext
|
|
4
|
+
from typing import Union
|
|
5
|
+
|
|
6
|
+
import pytest # type: ignore
|
|
7
|
+
|
|
8
|
+
from crosshair.core_and_libs import MessageType, analyze_function, run_checkables
|
|
9
|
+
from crosshair.test_util import ResultComparison, compare_results, compare_returns
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _binary_op_under_context(ctx, op):
|
|
13
|
+
def run_op(d1, d2):
|
|
14
|
+
with localcontext(ctx):
|
|
15
|
+
return op(d1, d2)
|
|
16
|
+
|
|
17
|
+
return run_op
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def check_division(
|
|
21
|
+
decimal1: Decimal, decimal2: Union[Decimal, int, float]
|
|
22
|
+
) -> ResultComparison:
|
|
23
|
+
"""post: _"""
|
|
24
|
+
return compare_returns(operator.truediv, decimal1, decimal2)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def check_pow(decimal1: Decimal, decimal2: Decimal) -> ResultComparison:
|
|
28
|
+
"""post: _"""
|
|
29
|
+
return compare_results(operator.pow, decimal1, decimal2)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def check_extended_context(
|
|
33
|
+
decimal1: Decimal, decimal2: Union[Decimal, int, float]
|
|
34
|
+
) -> ResultComparison:
|
|
35
|
+
"""post: _"""
|
|
36
|
+
return compare_results(
|
|
37
|
+
_binary_op_under_context(ExtendedContext, operator.truediv), decimal1, decimal2
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def check_basic_context(
|
|
42
|
+
decimal1: Decimal, decimal2: Union[Decimal, int, float]
|
|
43
|
+
) -> ResultComparison:
|
|
44
|
+
"""post: _"""
|
|
45
|
+
return compare_results(
|
|
46
|
+
_binary_op_under_context(BasicContext, operator.truediv), decimal1, decimal2
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def check_div_using_context_method(
|
|
51
|
+
decimal1: Decimal, decimal2: Union[Decimal, int, float]
|
|
52
|
+
) -> ResultComparison:
|
|
53
|
+
"""post: _"""
|
|
54
|
+
return compare_returns(BasicContext.divide, decimal1, decimal2)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def check_div_using_context_parameter(
|
|
58
|
+
decimal1: Decimal, decimal2: Union[Decimal, int, float]
|
|
59
|
+
) -> ResultComparison:
|
|
60
|
+
"""post: _"""
|
|
61
|
+
return compare_returns(
|
|
62
|
+
lambda d1, d2: d1.divide(d2, context=BasicContext), decimal1, decimal2
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def check_create_decimal_from_float(float_number: float):
|
|
67
|
+
"""post: _"""
|
|
68
|
+
return compare_results(BasicContext.create_decimal_from_float, float_number)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# This is the only real test definition.
|
|
72
|
+
# It runs crosshair on each of the "check" functions defined above.
|
|
73
|
+
@pytest.mark.parametrize("fn_name", [fn for fn in dir() if fn.startswith("check_")])
|
|
74
|
+
def test_builtin(fn_name: str) -> None:
|
|
75
|
+
this_module = sys.modules[__name__]
|
|
76
|
+
messages = run_checkables(analyze_function(getattr(this_module, fn_name)))
|
|
77
|
+
errors = [m for m in messages if m.state > MessageType.PRE_UNSAT]
|
|
78
|
+
assert errors == []
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
from decimal import (
|
|
2
|
+
Decimal,
|
|
3
|
+
DivisionByZero,
|
|
4
|
+
ExtendedContext,
|
|
5
|
+
InvalidOperation,
|
|
6
|
+
localcontext,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
|
|
11
|
+
from crosshair.core import proxy_for_type, standalone_statespace
|
|
12
|
+
from crosshair.libimpl.decimallib import Decimal as PyDecimal
|
|
13
|
+
from crosshair.tracers import NoTracing
|
|
14
|
+
from crosshair.util import debug
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def test_mixed_decimal_addition() -> None:
|
|
18
|
+
d1 = Decimal("1.05")
|
|
19
|
+
with standalone_statespace:
|
|
20
|
+
d2 = proxy_for_type(Decimal, "d2")
|
|
21
|
+
debug("type(d2)", type(d2))
|
|
22
|
+
d1 + d2
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_external_decimal_context() -> None:
|
|
26
|
+
with localcontext(ExtendedContext):
|
|
27
|
+
Decimal("43.4") / 0 # does not raise
|
|
28
|
+
with pytest.raises(DivisionByZero):
|
|
29
|
+
Decimal("43.4") / 0
|
|
30
|
+
with pytest.raises(InvalidOperation):
|
|
31
|
+
Decimal("0") / 0
|
|
32
|
+
with pytest.raises(DivisionByZero):
|
|
33
|
+
PyDecimal("43.4") / 0
|
|
34
|
+
with standalone_statespace as space:
|
|
35
|
+
with NoTracing():
|
|
36
|
+
d1 = proxy_for_type(Decimal, "d1")
|
|
37
|
+
if d1 == 0:
|
|
38
|
+
d1 += 1
|
|
39
|
+
with pytest.raises(DivisionByZero):
|
|
40
|
+
d1 / 0
|
|
41
|
+
with pytest.raises(InvalidOperation):
|
|
42
|
+
(d1 - d1) / 0
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_context_method_on_symbolic():
|
|
46
|
+
with standalone_statespace:
|
|
47
|
+
ExtendedContext.exp(proxy_for_type(Decimal, "d"))
|
|
48
|
+
ExtendedContext.divide_int(Decimal(12), proxy_for_type(Decimal, "d"))
|
|
49
|
+
ExtendedContext.divide_int(Decimal(12), Decimal(2))
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_precision():
|
|
53
|
+
"""post: _"""
|
|
54
|
+
d1, d2 = Decimal("3.4445"), Decimal("1.0023")
|
|
55
|
+
expected = Decimal("4.45")
|
|
56
|
+
assert d1 + d2 != expected
|
|
57
|
+
with standalone_statespace:
|
|
58
|
+
with localcontext() as ctx:
|
|
59
|
+
ctx.prec = 3
|
|
60
|
+
assert d1 + d2 == expected
|
|
61
|
+
with localcontext() as ctx:
|
|
62
|
+
ctx.prec = 1
|
|
63
|
+
assert +expected == Decimal("4")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
# Still working on this! (rn, issue with z3 int exponent vars becoming reals)
|
|
67
|
+
# def test_decimal_end_to_end():
|
|
68
|
+
# def add_tax(price: Decimal):
|
|
69
|
+
# """post: _ != Decimal('1.05')"""
|
|
70
|
+
# ctx = ExtendedContext.copy()
|
|
71
|
+
# ctx.prec = 3
|
|
72
|
+
# with localcontext(ctx):
|
|
73
|
+
# return price + Decimal("0.05")
|
|
74
|
+
# # return price * Decimal("1.05")
|
|
75
|
+
|
|
76
|
+
# check_states(add_tax, POST_FAIL)
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import codecs
|
|
2
|
-
|
|
2
|
+
import sys
|
|
3
3
|
from dataclasses import dataclass
|
|
4
|
-
from typing import
|
|
4
|
+
from typing import List, Optional, Tuple, Type, Union
|
|
5
|
+
|
|
6
|
+
if sys.version_info >= (3, 12):
|
|
7
|
+
from collections.abc import Buffer
|
|
8
|
+
else:
|
|
9
|
+
from collections.abc import ByteString as Buffer
|
|
5
10
|
|
|
6
11
|
from crosshair.core import realize
|
|
7
12
|
from crosshair.libimpl.builtinslib import AnySymbolicStr, SymbolicBytes
|
|
@@ -20,6 +25,7 @@ class UnexpectedEndError(ChunkError):
|
|
|
20
25
|
@dataclass
|
|
21
26
|
class MidChunkError(ChunkError):
|
|
22
27
|
_reason: str
|
|
28
|
+
|
|
23
29
|
# _errlen: int = 1
|
|
24
30
|
def reason(self) -> str:
|
|
25
31
|
return self._reason
|
|
@@ -30,7 +36,7 @@ class _UnicodeDecodeError(UnicodeDecodeError):
|
|
|
30
36
|
UnicodeDecodeError.__init__(self, enc, b"", start, end, reason)
|
|
31
37
|
self.object = byts
|
|
32
38
|
|
|
33
|
-
def __ch_deep_realize__(self) -> object:
|
|
39
|
+
def __ch_deep_realize__(self, memo) -> object:
|
|
34
40
|
enc, obj, reason = self.encoding, self.object, self.reason
|
|
35
41
|
start, end = self.start, self.end
|
|
36
42
|
return UnicodeDecodeError(
|
|
@@ -87,7 +93,7 @@ class StemEncoder:
|
|
|
87
93
|
def decode(
|
|
88
94
|
cls, input: bytes, errors: str = "strict"
|
|
89
95
|
) -> Tuple[Union[str, AnySymbolicStr], int]:
|
|
90
|
-
if not (isinstance(input,
|
|
96
|
+
if not (isinstance(input, Buffer) and isinstance(errors, str)):
|
|
91
97
|
raise TypeError
|
|
92
98
|
parts: List[Union[str, AnySymbolicStr]] = []
|
|
93
99
|
idx = 0
|
|
@@ -107,7 +113,7 @@ class StemEncoder:
|
|
|
107
113
|
continue
|
|
108
114
|
if errors == "replace":
|
|
109
115
|
idx += 1
|
|
110
|
-
parts.append("\
|
|
116
|
+
parts.append("\ufffd")
|
|
111
117
|
continue
|
|
112
118
|
|
|
113
119
|
# 2. Then fall back to native implementations if necessary:
|
|
@@ -127,24 +133,28 @@ class StemEncoder:
|
|
|
127
133
|
|
|
128
134
|
def _getregentry(stem_encoder: Type[StemEncoder]):
|
|
129
135
|
class StemIncrementalEncoder(codecs.BufferedIncrementalEncoder):
|
|
130
|
-
def _buffer_encode(
|
|
136
|
+
def _buffer_encode(
|
|
137
|
+
self, input: str, errors: str, final: bool
|
|
138
|
+
) -> Tuple[bytes, int]:
|
|
131
139
|
enc_name = stem_encoder.encoding_name
|
|
132
140
|
out, idx, err = stem_encoder._encode_chunk(input, 0)
|
|
133
141
|
assert isinstance(out, bytes)
|
|
134
142
|
if not err:
|
|
135
|
-
return out
|
|
143
|
+
return (out, idx)
|
|
136
144
|
if isinstance(err, UnexpectedEndError) or not final:
|
|
137
|
-
return out
|
|
145
|
+
return (out, idx)
|
|
138
146
|
exc = UnicodeEncodeError(enc_name, input, idx, idx + 1, err.reason())
|
|
139
147
|
replacement, idx = codecs.lookup_error(errors)(exc)
|
|
140
148
|
if isinstance(replacement, str):
|
|
141
149
|
replacement = codecs.encode(replacement, enc_name)
|
|
142
|
-
return out + replacement
|
|
150
|
+
return (out + replacement, idx)
|
|
143
151
|
|
|
144
152
|
class StemIncrementalDecoder(codecs.BufferedIncrementalDecoder):
|
|
145
153
|
def _buffer_decode(
|
|
146
|
-
self, input:
|
|
154
|
+
self, input: Buffer, errors: str, final: bool
|
|
147
155
|
) -> Tuple[str, int]:
|
|
156
|
+
if not isinstance(input, bytes):
|
|
157
|
+
input = memoryview(input).tobytes()
|
|
148
158
|
enc_name = stem_encoder.encoding_name
|
|
149
159
|
out, idx, err = stem_encoder._decode_chunk(input, 0)
|
|
150
160
|
assert isinstance(out, str)
|
|
@@ -160,7 +170,7 @@ def _getregentry(stem_encoder: Type[StemEncoder]):
|
|
|
160
170
|
|
|
161
171
|
class StemStreamWriter(codecs.StreamWriter):
|
|
162
172
|
def encode(self, input: str, errors: str = "strict") -> Tuple[bytes, int]:
|
|
163
|
-
raise Exception
|
|
173
|
+
raise Exception # TODO implement
|
|
164
174
|
|
|
165
175
|
class StemStreamReader(codecs.StreamReader):
|
|
166
176
|
def decode(self, input: bytes, errors: str = "strict") -> Tuple[str, int]:
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from fractions import Fraction
|
|
2
|
+
|
|
3
|
+
from crosshair.core import SymbolicFactory, register_type
|
|
4
|
+
from crosshair.statespace import force_true
|
|
5
|
+
from crosshair.tracers import ResumedTracing
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _make_fraction(factory: SymbolicFactory):
|
|
9
|
+
n, d = factory(int, "_numerator"), factory(int, "_denominator")
|
|
10
|
+
with ResumedTracing():
|
|
11
|
+
force_true(d > 0)
|
|
12
|
+
return Fraction(n, d)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def make_registrations() -> None:
|
|
16
|
+
register_type(Fraction, _make_fraction)
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import math
|
|
2
|
+
from fractions import Fraction
|
|
3
|
+
|
|
4
|
+
from crosshair.core import deep_realize
|
|
5
|
+
from crosshair.core_and_libs import proxy_for_type
|
|
6
|
+
from crosshair.statespace import POST_FAIL
|
|
7
|
+
from crosshair.test_util import check_states
|
|
8
|
+
from crosshair.tracers import ResumedTracing, is_tracing
|
|
9
|
+
from crosshair.util import CrossHairInternal
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_fraction_realize(space):
|
|
13
|
+
n = proxy_for_type(int, "n")
|
|
14
|
+
d = proxy_for_type(int, "d")
|
|
15
|
+
with ResumedTracing():
|
|
16
|
+
space.add(d != 0)
|
|
17
|
+
deep_realize(Fraction(n, d))
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class UserFraction(Fraction):
|
|
21
|
+
def __int__(self):
|
|
22
|
+
if not is_tracing():
|
|
23
|
+
raise CrossHairInternal("tracing required while in user code")
|
|
24
|
+
return 1
|
|
25
|
+
|
|
26
|
+
def __round__(self, *a, **kw):
|
|
27
|
+
if not is_tracing():
|
|
28
|
+
raise CrossHairInternal("tracing required while in user code")
|
|
29
|
+
return super().__round__(*a, **kw)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_user_fraction_tracing(space):
|
|
33
|
+
n = proxy_for_type(int, "n")
|
|
34
|
+
d = proxy_for_type(int, "d")
|
|
35
|
+
with ResumedTracing():
|
|
36
|
+
space.add(d != 0)
|
|
37
|
+
fraction = UserFraction(n, d)
|
|
38
|
+
round(fraction) # (works via with_realized_args)
|
|
39
|
+
int(fraction) # (custom interception)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_fraction_copy_doesnt_realize(space):
|
|
43
|
+
n = proxy_for_type(int, "n")
|
|
44
|
+
with ResumedTracing():
|
|
45
|
+
space.add(n >= 0)
|
|
46
|
+
f1 = Fraction(n, 1)
|
|
47
|
+
f2 = f1.__copy__()
|
|
48
|
+
assert space.is_possible(f2.numerator == 2)
|
|
49
|
+
assert space.is_possible(f2.numerator == 3)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_fraction_can_be_one_half() -> None:
|
|
53
|
+
def f(f: Fraction):
|
|
54
|
+
"""post:_"""
|
|
55
|
+
return f != Fraction(1, 2)
|
|
56
|
+
|
|
57
|
+
check_states(f, POST_FAIL)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def test_int_from_fraction(space) -> None:
|
|
61
|
+
n = proxy_for_type(int, "n")
|
|
62
|
+
d = proxy_for_type(int, "d")
|
|
63
|
+
with ResumedTracing():
|
|
64
|
+
space.add(d > 0)
|
|
65
|
+
space.add(n == d * 3)
|
|
66
|
+
f = Fraction(n, d)
|
|
67
|
+
assert space.is_possible(d == 3)
|
|
68
|
+
assert not space.is_possible(f.denominator != 1)
|
|
69
|
+
truncated_fraction = int(f)
|
|
70
|
+
assert space.is_possible(truncated_fraction == 3)
|
|
71
|
+
assert not space.is_possible(truncated_fraction != 3)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def test_fraction_ceil_does_not_explode(space) -> None:
|
|
75
|
+
f = proxy_for_type(Fraction, "f")
|
|
76
|
+
with ResumedTracing():
|
|
77
|
+
math.ceil(f)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# TODO: The math module is patched with deep_realize, but many of the usual operators may not work. Test.
|
|
@@ -1,22 +1,34 @@
|
|
|
1
|
-
|
|
1
|
+
from functools import _lru_cache_wrapper, partial, reduce, update_wrapper, wraps
|
|
2
2
|
|
|
3
|
-
from crosshair.core import
|
|
3
|
+
from crosshair.core import register_patch
|
|
4
4
|
|
|
5
5
|
# TODO: deal with lru_cache (note it needs to be intercepted at import-time)
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
def _partial(func, *a1, **kw1):
|
|
9
9
|
if callable(func):
|
|
10
|
-
|
|
10
|
+
# We make a do-nothing wrapper to ensure that the tracer has a crack
|
|
11
|
+
# at this function when it is called.
|
|
12
|
+
def wrapper(*a2, **kw2):
|
|
13
|
+
return func(*a2, **kw2)
|
|
14
|
+
|
|
15
|
+
update_wrapper(wrapper, func)
|
|
16
|
+
return partial(wrapper, *a1, **kw1)
|
|
11
17
|
else:
|
|
12
18
|
raise TypeError
|
|
13
19
|
|
|
14
20
|
|
|
15
21
|
def _reduce(function, *a, **kw):
|
|
16
|
-
return
|
|
22
|
+
return reduce(lambda x, y: function(x, y), *a, **kw)
|
|
17
23
|
|
|
18
24
|
|
|
19
25
|
def make_registrations():
|
|
20
|
-
register_patch(
|
|
21
|
-
register_patch(
|
|
22
|
-
|
|
26
|
+
register_patch(partial, _partial)
|
|
27
|
+
register_patch(reduce, _reduce)
|
|
28
|
+
|
|
29
|
+
def call_with_skipped_cache(self, *a, **kw):
|
|
30
|
+
if not isinstance(self, _lru_cache_wrapper):
|
|
31
|
+
raise TypeError
|
|
32
|
+
return self.__wrapped__(*a, **kw)
|
|
33
|
+
|
|
34
|
+
register_patch(_lru_cache_wrapper.__call__, call_with_skipped_cache)
|
|
@@ -1,20 +1,36 @@
|
|
|
1
1
|
import functools
|
|
2
|
+
import inspect
|
|
2
3
|
|
|
3
4
|
from crosshair.core import proxy_for_type, standalone_statespace
|
|
4
5
|
from crosshair.libimpl.builtinslib import LazyIntSymbolicStr
|
|
5
|
-
from crosshair.tracers import NoTracing
|
|
6
|
+
from crosshair.tracers import NoTracing, ResumedTracing
|
|
6
7
|
|
|
7
8
|
|
|
8
|
-
def test_partial():
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
xyz = LazyIntSymbolicStr(list(map(ord, "xyz")))
|
|
9
|
+
def test_partial(space):
|
|
10
|
+
abc = LazyIntSymbolicStr(list(map(ord, "abc")))
|
|
11
|
+
xyz = LazyIntSymbolicStr(list(map(ord, "xyz")))
|
|
12
|
+
with ResumedTracing():
|
|
13
13
|
joiner = functools.partial(str.join, ",")
|
|
14
14
|
ret = joiner([abc, xyz])
|
|
15
15
|
assert ret == "abc,xyz"
|
|
16
16
|
|
|
17
17
|
|
|
18
|
+
def test_partial_is_interceptable(space):
|
|
19
|
+
x = proxy_for_type(str, "x")
|
|
20
|
+
y = proxy_for_type(str, "y")
|
|
21
|
+
with ResumedTracing():
|
|
22
|
+
joiner = functools.partial(str.startswith, x)
|
|
23
|
+
# Ensure we don't explode
|
|
24
|
+
list(map(joiner, ["foo", y]))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_partial_arg_is_inspectable(space):
|
|
28
|
+
with ResumedTracing():
|
|
29
|
+
joiner = functools.partial(str.join, ",")
|
|
30
|
+
assert isinstance(joiner, functools.partial)
|
|
31
|
+
assert inspect.getdoc(joiner.func) == inspect.getdoc(str.join)
|
|
32
|
+
|
|
33
|
+
|
|
18
34
|
def test_reduce():
|
|
19
35
|
with standalone_statespace as space:
|
|
20
36
|
with NoTracing():
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
from crosshair.core import register_patch, with_realized_args
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def make_registrations():
|
|
8
|
+
if sys.version_info < (3, 12):
|
|
9
|
+
# As of Python 3.12, SymbolicBytes can implement __buffer__() to be compatible
|
|
10
|
+
# with hash functions. Prior to that, we patch them manually:
|
|
11
|
+
|
|
12
|
+
to_patch = {hashlib.new: None} # we don't use a set so that the patch order
|
|
13
|
+
# is deterministic, which matters for the patch_equivalence_test when
|
|
14
|
+
# run under pytest -n
|
|
15
|
+
for algo_string in sorted(hashlib.algorithms_available):
|
|
16
|
+
hash_constructor = getattr(hashlib, algo_string, None)
|
|
17
|
+
if hash_constructor is not None:
|
|
18
|
+
to_patch[hash_constructor] = None
|
|
19
|
+
try:
|
|
20
|
+
example_instance = hashlib.new(algo_string)
|
|
21
|
+
except ValueError:
|
|
22
|
+
if sys.version_info < (3, 9):
|
|
23
|
+
# in 3.8, some "available" algorithms aren't available
|
|
24
|
+
continue
|
|
25
|
+
else:
|
|
26
|
+
raise
|
|
27
|
+
update_method = getattr((type(example_instance)), "update")
|
|
28
|
+
to_patch[update_method] = None
|
|
29
|
+
for fn in to_patch:
|
|
30
|
+
register_patch(fn, with_realized_args(fn))
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
|
|
3
|
+
from crosshair.core import proxy_for_type, standalone_statespace
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_sha384():
|
|
7
|
+
with standalone_statespace:
|
|
8
|
+
x = proxy_for_type(bytes, "x")
|
|
9
|
+
hashlib.new("sha384", x)
|
|
10
|
+
hashlib.sha384(x)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def test_blake_via_update():
|
|
14
|
+
with standalone_statespace:
|
|
15
|
+
x = proxy_for_type(bytes, "x")
|
|
16
|
+
h = hashlib.blake2b()
|
|
17
|
+
h.update(x)
|
|
18
|
+
h.hexdigest()
|
crosshair/libimpl/heapqlib.py
CHANGED
|
@@ -1,20 +1,47 @@
|
|
|
1
|
+
import functools
|
|
1
2
|
import heapq
|
|
3
|
+
import types
|
|
2
4
|
|
|
3
5
|
import _heapq
|
|
4
6
|
|
|
5
7
|
from crosshair.core import register_patch
|
|
6
|
-
from crosshair.util import
|
|
8
|
+
from crosshair.util import debug, imported_alternative, name_of_type
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _check_first_arg_is_list(fn):
|
|
12
|
+
functools.wraps(fn)
|
|
13
|
+
|
|
14
|
+
def wrapper(heap, *a, **kw):
|
|
15
|
+
if not isinstance(heap, list):
|
|
16
|
+
raise TypeError(
|
|
17
|
+
f"{fn.__name__} argument must be list, not {name_of_type(heap)}"
|
|
18
|
+
)
|
|
19
|
+
return fn(heap, *a, **kw)
|
|
20
|
+
|
|
21
|
+
return wrapper
|
|
7
22
|
|
|
8
23
|
|
|
9
24
|
def make_registrations():
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
25
|
+
native_funcs = [
|
|
26
|
+
"_heapify_max",
|
|
27
|
+
"_heappop_max",
|
|
28
|
+
"_heapreplace_max",
|
|
13
29
|
"heapify",
|
|
14
30
|
"heappop",
|
|
15
31
|
"heappush",
|
|
16
32
|
"heappushpop",
|
|
17
33
|
"heapreplace",
|
|
18
34
|
]
|
|
35
|
+
with imported_alternative("heapq", ("_heapq",)):
|
|
36
|
+
|
|
37
|
+
# The pure python version doesn't always check argument types:
|
|
38
|
+
heapq.heappush = heapq.heappush
|
|
39
|
+
heapq.heappop = _check_first_arg_is_list(heapq.heappop)
|
|
40
|
+
|
|
41
|
+
pure_fns = {name: getattr(heapq, name) for name in native_funcs}
|
|
19
42
|
for name in native_funcs:
|
|
20
|
-
|
|
43
|
+
native_fn = getattr(heapq, name)
|
|
44
|
+
pure_fn = pure_fns[name]
|
|
45
|
+
assert isinstance(native_fn, types.BuiltinFunctionType)
|
|
46
|
+
assert isinstance(pure_fn, types.FunctionType)
|
|
47
|
+
register_patch(native_fn, _check_first_arg_is_list(pure_fn))
|
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
import heapq
|
|
2
|
+
import sys
|
|
2
3
|
from typing import List
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
from crosshair.statespace import CONFIRMED, MessageType
|
|
6
|
-
from crosshair.test_util import check_states
|
|
5
|
+
import pytest
|
|
7
6
|
|
|
7
|
+
from crosshair.core import proxy_for_type
|
|
8
|
+
from crosshair.tracers import ResumedTracing
|
|
8
9
|
|
|
9
|
-
def test_heapify():
|
|
10
|
-
def f(items: List[int]):
|
|
11
|
-
"""
|
|
12
|
-
pre: len(items) == 3
|
|
13
|
-
post: _[0] <= _[1]
|
|
14
|
-
"""
|
|
15
|
-
heapq.heapify(items)
|
|
16
|
-
return items
|
|
17
10
|
|
|
18
|
-
|
|
11
|
+
# TODO https://github.com/pschanely/CrossHair/issues/298
|
|
12
|
+
@pytest.mark.skip(
|
|
13
|
+
reason="heapq get reloaded somehow in parallel ci run, ruining the intercepts",
|
|
14
|
+
)
|
|
15
|
+
def test_heapify(space):
|
|
16
|
+
items = proxy_for_type(List[int], "items")
|
|
17
|
+
|
|
18
|
+
with ResumedTracing():
|
|
19
|
+
space.add(len(items) == 3)
|
|
20
|
+
heapq.heapify(items)
|
|
21
|
+
assert not space.is_possible(items[0] > items[1])
|
crosshair/libimpl/iolib.py
CHANGED
|
@@ -3,9 +3,9 @@ from io import SEEK_CUR, SEEK_END, SEEK_SET, StringIO, TextIOBase
|
|
|
3
3
|
from typing import Optional, Tuple, Union
|
|
4
4
|
|
|
5
5
|
from crosshair import ResumedTracing, SymbolicFactory, register_type
|
|
6
|
-
from crosshair.core import
|
|
6
|
+
from crosshair.core import realize, register_patch
|
|
7
7
|
from crosshair.tracers import NoTracing
|
|
8
|
-
from crosshair.util import IgnoreAttempt
|
|
8
|
+
from crosshair.util import CrossHairValue, IgnoreAttempt
|
|
9
9
|
|
|
10
10
|
_UNIVERSAL_NEWLINE_RE = re.compile(r"(\r\n|\r|\n)")
|
|
11
11
|
|
|
@@ -83,6 +83,8 @@ class BackedStringIO(TextIOBase, CrossHairValue):
|
|
|
83
83
|
elif newline_mode == "":
|
|
84
84
|
self._discovered_newlines.update(_UNIVERSAL_NEWLINE_RE.findall(string))
|
|
85
85
|
return string
|
|
86
|
+
elif newline_mode == "\n":
|
|
87
|
+
return string
|
|
86
88
|
else:
|
|
87
89
|
return string.replace("\n", newline_mode)
|
|
88
90
|
|
|
@@ -148,7 +150,8 @@ class BackedStringIO(TextIOBase, CrossHairValue):
|
|
|
148
150
|
else:
|
|
149
151
|
self._contents = contents[:pos] + writestr + contents[pos + writelen :]
|
|
150
152
|
self._pos = pos + writelen
|
|
151
|
-
return writelen
|
|
153
|
+
# Don't return `writelen` because all the input characters were "written":
|
|
154
|
+
return len(string)
|
|
152
155
|
|
|
153
156
|
def seek(self, amount: int, whence: int = SEEK_SET) -> int:
|
|
154
157
|
if self.closed:
|
|
@@ -210,4 +213,4 @@ def _string_io(initial_value: str = "", newline="\n"):
|
|
|
210
213
|
def make_registrations() -> None:
|
|
211
214
|
register_type(StringIO, make_string_io)
|
|
212
215
|
register_patch(StringIO, _string_io)
|
|
213
|
-
# TODO: register_type
|
|
216
|
+
# TODO: register_type io.TextIO, BytesIO, ...
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import ipaddress
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def make_registrations():
|
|
5
|
+
# ipaddress uses a homegrown internal netmask cache.
|
|
6
|
+
# There aren't many netmasks - load all of them (to avoid nondeterminism):
|
|
7
|
+
[ipaddress.IPv6Network._make_netmask(sz) for sz in range(129)]
|
|
8
|
+
[ipaddress.IPv4Network._make_netmask(sz) for sz in range(33)]
|
|
@@ -13,7 +13,7 @@ def test_keyfn_is_intercepted():
|
|
|
13
13
|
with standalone_statespace as space:
|
|
14
14
|
with NoTracing():
|
|
15
15
|
two = proxy_for_type(int, "two")
|
|
16
|
-
|
|
16
|
+
space.add(two == 2)
|
|
17
17
|
ret = list((k, tuple(v)) for k, v in itertools.groupby([1, two, 3.0], type))
|
|
18
18
|
assert ret == [(int, (1, 2)), (float, (3.0,))]
|
|
19
19
|
|