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.
Files changed (123) hide show
  1. _crosshair_tracers.cpython-39-darwin.so +0 -0
  2. crosshair/__init__.py +1 -1
  3. crosshair/_mark_stacks.h +51 -24
  4. crosshair/_tracers.h +9 -5
  5. crosshair/_tracers_test.py +19 -9
  6. crosshair/auditwall.py +9 -8
  7. crosshair/auditwall_test.py +31 -19
  8. crosshair/codeconfig.py +3 -2
  9. crosshair/condition_parser.py +17 -133
  10. crosshair/condition_parser_test.py +54 -96
  11. crosshair/conftest.py +1 -1
  12. crosshair/copyext.py +91 -22
  13. crosshair/copyext_test.py +33 -0
  14. crosshair/core.py +259 -203
  15. crosshair/core_and_libs.py +20 -0
  16. crosshair/core_regestered_types_test.py +82 -0
  17. crosshair/core_test.py +693 -664
  18. crosshair/diff_behavior.py +76 -21
  19. crosshair/diff_behavior_test.py +132 -23
  20. crosshair/dynamic_typing.py +128 -18
  21. crosshair/dynamic_typing_test.py +91 -4
  22. crosshair/enforce.py +1 -6
  23. crosshair/enforce_test.py +15 -23
  24. crosshair/examples/check_examples_test.py +2 -1
  25. crosshair/fnutil.py +2 -3
  26. crosshair/fnutil_test.py +0 -7
  27. crosshair/fuzz_core_test.py +70 -83
  28. crosshair/libimpl/arraylib.py +10 -7
  29. crosshair/libimpl/binascii_ch_test.py +30 -0
  30. crosshair/libimpl/binascii_test.py +67 -0
  31. crosshair/libimpl/binasciilib.py +150 -0
  32. crosshair/libimpl/bisectlib_test.py +5 -5
  33. crosshair/libimpl/builtinslib.py +1002 -682
  34. crosshair/libimpl/builtinslib_ch_test.py +108 -30
  35. crosshair/libimpl/builtinslib_test.py +431 -143
  36. crosshair/libimpl/codecslib.py +22 -2
  37. crosshair/libimpl/codecslib_test.py +41 -9
  38. crosshair/libimpl/collectionslib.py +44 -8
  39. crosshair/libimpl/collectionslib_test.py +108 -20
  40. crosshair/libimpl/copylib.py +1 -1
  41. crosshair/libimpl/copylib_test.py +18 -0
  42. crosshair/libimpl/datetimelib.py +84 -67
  43. crosshair/libimpl/datetimelib_ch_test.py +12 -7
  44. crosshair/libimpl/datetimelib_test.py +5 -6
  45. crosshair/libimpl/decimallib.py +5257 -0
  46. crosshair/libimpl/decimallib_ch_test.py +78 -0
  47. crosshair/libimpl/decimallib_test.py +76 -0
  48. crosshair/libimpl/encodings/_encutil.py +21 -11
  49. crosshair/libimpl/fractionlib.py +16 -0
  50. crosshair/libimpl/fractionlib_test.py +80 -0
  51. crosshair/libimpl/functoolslib.py +19 -7
  52. crosshair/libimpl/functoolslib_test.py +22 -6
  53. crosshair/libimpl/hashliblib.py +30 -0
  54. crosshair/libimpl/hashliblib_test.py +18 -0
  55. crosshair/libimpl/heapqlib.py +32 -5
  56. crosshair/libimpl/heapqlib_test.py +15 -12
  57. crosshair/libimpl/iolib.py +7 -4
  58. crosshair/libimpl/ipaddresslib.py +8 -0
  59. crosshair/libimpl/itertoolslib_test.py +1 -1
  60. crosshair/libimpl/mathlib.py +165 -2
  61. crosshair/libimpl/mathlib_ch_test.py +44 -0
  62. crosshair/libimpl/mathlib_test.py +59 -16
  63. crosshair/libimpl/oslib.py +7 -0
  64. crosshair/libimpl/pathliblib_test.py +10 -0
  65. crosshair/libimpl/randomlib.py +1 -0
  66. crosshair/libimpl/randomlib_test.py +6 -4
  67. crosshair/libimpl/relib.py +180 -59
  68. crosshair/libimpl/relib_ch_test.py +26 -2
  69. crosshair/libimpl/relib_test.py +77 -14
  70. crosshair/libimpl/timelib.py +35 -13
  71. crosshair/libimpl/timelib_test.py +13 -3
  72. crosshair/libimpl/typeslib.py +15 -0
  73. crosshair/libimpl/typeslib_test.py +36 -0
  74. crosshair/libimpl/unicodedatalib_test.py +3 -3
  75. crosshair/libimpl/weakreflib.py +13 -0
  76. crosshair/libimpl/weakreflib_test.py +69 -0
  77. crosshair/libimpl/zliblib.py +15 -0
  78. crosshair/libimpl/zliblib_test.py +13 -0
  79. crosshair/lsp_server.py +21 -10
  80. crosshair/main.py +48 -28
  81. crosshair/main_test.py +59 -14
  82. crosshair/objectproxy.py +39 -14
  83. crosshair/objectproxy_test.py +27 -13
  84. crosshair/opcode_intercept.py +212 -24
  85. crosshair/opcode_intercept_test.py +172 -18
  86. crosshair/options.py +0 -1
  87. crosshair/patch_equivalence_test.py +5 -21
  88. crosshair/path_cover.py +7 -5
  89. crosshair/path_search.py +6 -4
  90. crosshair/path_search_test.py +1 -2
  91. crosshair/pathing_oracle.py +53 -10
  92. crosshair/pathing_oracle_test.py +21 -0
  93. crosshair/pure_importer_test.py +5 -21
  94. crosshair/register_contract.py +16 -6
  95. crosshair/register_contract_test.py +2 -14
  96. crosshair/simplestructs.py +154 -85
  97. crosshair/simplestructs_test.py +16 -2
  98. crosshair/smtlib.py +24 -0
  99. crosshair/smtlib_test.py +14 -0
  100. crosshair/statespace.py +319 -196
  101. crosshair/statespace_test.py +45 -0
  102. crosshair/stubs_parser.py +0 -2
  103. crosshair/test_util.py +87 -25
  104. crosshair/test_util_test.py +26 -0
  105. crosshair/tools/check_init_and_setup_coincide.py +0 -3
  106. crosshair/tools/generate_demo_table.py +2 -2
  107. crosshair/tracers.py +141 -49
  108. crosshair/type_repo.py +11 -4
  109. crosshair/unicode_categories.py +1 -0
  110. crosshair/util.py +158 -76
  111. crosshair/util_test.py +13 -20
  112. crosshair/watcher.py +4 -4
  113. crosshair/z3util.py +1 -1
  114. {crosshair_tool-0.0.56.dist-info → crosshair_tool-0.0.100.dist-info}/METADATA +45 -36
  115. crosshair_tool-0.0.100.dist-info/RECORD +176 -0
  116. {crosshair_tool-0.0.56.dist-info → crosshair_tool-0.0.100.dist-info}/WHEEL +2 -1
  117. crosshair/examples/hypothesis/__init__.py +0 -2
  118. crosshair/examples/hypothesis/bugs_detected/simple_strategies.py +0 -74
  119. crosshair_tool-0.0.56.dist-info/RECORD +0 -152
  120. /crosshair/{examples/hypothesis/bugs_detected/__init__.py → py.typed} +0 -0
  121. {crosshair_tool-0.0.56.dist-info → crosshair_tool-0.0.100.dist-info}/entry_points.txt +0 -0
  122. {crosshair_tool-0.0.56.dist-info → crosshair_tool-0.0.100.dist-info/licenses}/LICENSE +0 -0
  123. {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
- from collections.abc import ByteString
2
+ import sys
3
3
  from dataclasses import dataclass
4
- from typing import Dict, List, Optional, Tuple, Type, Union
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, ByteString) and isinstance(errors, str)):
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("\uFFFD")
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(self, input: str, errors: str, final: bool) -> bytes:
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: bytes, errors: str, final: bool
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
- import functools as orig_functools
1
+ from functools import _lru_cache_wrapper, partial, reduce, update_wrapper, wraps
2
2
 
3
- from crosshair.core import register_fn_type_patch, register_patch
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
- return orig_functools.partial(lambda *a2, **kw2: func(*a2, **kw2), *a1, **kw1)
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 orig_functools.reduce(lambda x, y: function(x, y), *a, **kw)
22
+ return reduce(lambda x, y: function(x, y), *a, **kw)
17
23
 
18
24
 
19
25
  def make_registrations():
20
- register_patch(orig_functools.partial, _partial)
21
- register_patch(orig_functools.reduce, _reduce)
22
- register_fn_type_patch(orig_functools._lru_cache_wrapper, lambda w: w.__wrapped__)
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
- with standalone_statespace as space:
10
- with NoTracing():
11
- abc = LazyIntSymbolicStr(list(map(ord, "abc")))
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()
@@ -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 import_alternative
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
- pureheapq = import_alternative("heapq", ("_heapq",))
11
- native_funcs = [name for name in dir(_heapq) if not name.startswith("_")]
12
- assert native_funcs == [
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
- register_patch(getattr(heapq, name), getattr(pureheapq, name))
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
- from crosshair.options import AnalysisOptionSet
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
- check_states(f, CONFIRMED)
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])
@@ -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 CrossHairValue, realize, register_patch
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(io.TextIO, )
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
- space.add(two.var == 2)
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