crosshair-tool 0.0.99__cp312-cp312-macosx_10_13_x86_64.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 (176) hide show
  1. _crosshair_tracers.cpython-312-darwin.so +0 -0
  2. crosshair/__init__.py +42 -0
  3. crosshair/__main__.py +8 -0
  4. crosshair/_mark_stacks.h +790 -0
  5. crosshair/_preliminaries_test.py +18 -0
  6. crosshair/_tracers.h +94 -0
  7. crosshair/_tracers_pycompat.h +522 -0
  8. crosshair/_tracers_test.py +138 -0
  9. crosshair/abcstring.py +245 -0
  10. crosshair/auditwall.py +190 -0
  11. crosshair/auditwall_test.py +77 -0
  12. crosshair/codeconfig.py +113 -0
  13. crosshair/codeconfig_test.py +117 -0
  14. crosshair/condition_parser.py +1237 -0
  15. crosshair/condition_parser_test.py +497 -0
  16. crosshair/conftest.py +30 -0
  17. crosshair/copyext.py +155 -0
  18. crosshair/copyext_test.py +84 -0
  19. crosshair/core.py +1763 -0
  20. crosshair/core_and_libs.py +149 -0
  21. crosshair/core_regestered_types_test.py +82 -0
  22. crosshair/core_test.py +1316 -0
  23. crosshair/diff_behavior.py +314 -0
  24. crosshair/diff_behavior_test.py +261 -0
  25. crosshair/dynamic_typing.py +346 -0
  26. crosshair/dynamic_typing_test.py +210 -0
  27. crosshair/enforce.py +282 -0
  28. crosshair/enforce_test.py +182 -0
  29. crosshair/examples/PEP316/__init__.py +1 -0
  30. crosshair/examples/PEP316/bugs_detected/__init__.py +0 -0
  31. crosshair/examples/PEP316/bugs_detected/getattr_magic.py +16 -0
  32. crosshair/examples/PEP316/bugs_detected/hash_consistent_with_equals.py +31 -0
  33. crosshair/examples/PEP316/bugs_detected/shopping_cart.py +24 -0
  34. crosshair/examples/PEP316/bugs_detected/showcase.py +39 -0
  35. crosshair/examples/PEP316/correct_code/__init__.py +0 -0
  36. crosshair/examples/PEP316/correct_code/arith.py +60 -0
  37. crosshair/examples/PEP316/correct_code/chess.py +77 -0
  38. crosshair/examples/PEP316/correct_code/nesting_inference.py +17 -0
  39. crosshair/examples/PEP316/correct_code/numpy_examples.py +132 -0
  40. crosshair/examples/PEP316/correct_code/rolling_average.py +35 -0
  41. crosshair/examples/PEP316/correct_code/showcase.py +104 -0
  42. crosshair/examples/__init__.py +0 -0
  43. crosshair/examples/check_examples_test.py +146 -0
  44. crosshair/examples/deal/__init__.py +1 -0
  45. crosshair/examples/icontract/__init__.py +1 -0
  46. crosshair/examples/icontract/bugs_detected/__init__.py +0 -0
  47. crosshair/examples/icontract/bugs_detected/showcase.py +41 -0
  48. crosshair/examples/icontract/bugs_detected/wrong_sign.py +8 -0
  49. crosshair/examples/icontract/correct_code/__init__.py +0 -0
  50. crosshair/examples/icontract/correct_code/arith.py +51 -0
  51. crosshair/examples/icontract/correct_code/showcase.py +94 -0
  52. crosshair/fnutil.py +391 -0
  53. crosshair/fnutil_test.py +75 -0
  54. crosshair/fuzz_core_test.py +516 -0
  55. crosshair/libimpl/__init__.py +0 -0
  56. crosshair/libimpl/arraylib.py +161 -0
  57. crosshair/libimpl/binascii_ch_test.py +30 -0
  58. crosshair/libimpl/binascii_test.py +67 -0
  59. crosshair/libimpl/binasciilib.py +150 -0
  60. crosshair/libimpl/bisectlib_test.py +23 -0
  61. crosshair/libimpl/builtinslib.py +5228 -0
  62. crosshair/libimpl/builtinslib_ch_test.py +1191 -0
  63. crosshair/libimpl/builtinslib_test.py +3735 -0
  64. crosshair/libimpl/codecslib.py +86 -0
  65. crosshair/libimpl/codecslib_test.py +86 -0
  66. crosshair/libimpl/collectionslib.py +264 -0
  67. crosshair/libimpl/collectionslib_ch_test.py +252 -0
  68. crosshair/libimpl/collectionslib_test.py +332 -0
  69. crosshair/libimpl/copylib.py +23 -0
  70. crosshair/libimpl/copylib_test.py +18 -0
  71. crosshair/libimpl/datetimelib.py +2559 -0
  72. crosshair/libimpl/datetimelib_ch_test.py +354 -0
  73. crosshair/libimpl/datetimelib_test.py +112 -0
  74. crosshair/libimpl/decimallib.py +5257 -0
  75. crosshair/libimpl/decimallib_ch_test.py +78 -0
  76. crosshair/libimpl/decimallib_test.py +76 -0
  77. crosshair/libimpl/encodings/__init__.py +23 -0
  78. crosshair/libimpl/encodings/_encutil.py +187 -0
  79. crosshair/libimpl/encodings/ascii.py +44 -0
  80. crosshair/libimpl/encodings/latin_1.py +40 -0
  81. crosshair/libimpl/encodings/utf_8.py +93 -0
  82. crosshair/libimpl/encodings_ch_test.py +83 -0
  83. crosshair/libimpl/fractionlib.py +16 -0
  84. crosshair/libimpl/fractionlib_test.py +80 -0
  85. crosshair/libimpl/functoolslib.py +34 -0
  86. crosshair/libimpl/functoolslib_test.py +56 -0
  87. crosshair/libimpl/hashliblib.py +30 -0
  88. crosshair/libimpl/hashliblib_test.py +18 -0
  89. crosshair/libimpl/heapqlib.py +47 -0
  90. crosshair/libimpl/heapqlib_test.py +21 -0
  91. crosshair/libimpl/importliblib.py +18 -0
  92. crosshair/libimpl/importliblib_test.py +38 -0
  93. crosshair/libimpl/iolib.py +216 -0
  94. crosshair/libimpl/iolib_ch_test.py +128 -0
  95. crosshair/libimpl/iolib_test.py +19 -0
  96. crosshair/libimpl/ipaddresslib.py +8 -0
  97. crosshair/libimpl/itertoolslib.py +44 -0
  98. crosshair/libimpl/itertoolslib_test.py +44 -0
  99. crosshair/libimpl/jsonlib.py +984 -0
  100. crosshair/libimpl/jsonlib_ch_test.py +42 -0
  101. crosshair/libimpl/jsonlib_test.py +51 -0
  102. crosshair/libimpl/mathlib.py +179 -0
  103. crosshair/libimpl/mathlib_ch_test.py +44 -0
  104. crosshair/libimpl/mathlib_test.py +67 -0
  105. crosshair/libimpl/oslib.py +7 -0
  106. crosshair/libimpl/pathliblib_test.py +10 -0
  107. crosshair/libimpl/randomlib.py +178 -0
  108. crosshair/libimpl/randomlib_test.py +120 -0
  109. crosshair/libimpl/relib.py +846 -0
  110. crosshair/libimpl/relib_ch_test.py +169 -0
  111. crosshair/libimpl/relib_test.py +493 -0
  112. crosshair/libimpl/timelib.py +72 -0
  113. crosshair/libimpl/timelib_test.py +82 -0
  114. crosshair/libimpl/typeslib.py +15 -0
  115. crosshair/libimpl/typeslib_test.py +36 -0
  116. crosshair/libimpl/unicodedatalib.py +75 -0
  117. crosshair/libimpl/unicodedatalib_test.py +42 -0
  118. crosshair/libimpl/urlliblib.py +23 -0
  119. crosshair/libimpl/urlliblib_test.py +19 -0
  120. crosshair/libimpl/weakreflib.py +13 -0
  121. crosshair/libimpl/weakreflib_test.py +69 -0
  122. crosshair/libimpl/zliblib.py +15 -0
  123. crosshair/libimpl/zliblib_test.py +13 -0
  124. crosshair/lsp_server.py +261 -0
  125. crosshair/lsp_server_test.py +30 -0
  126. crosshair/main.py +973 -0
  127. crosshair/main_test.py +543 -0
  128. crosshair/objectproxy.py +376 -0
  129. crosshair/objectproxy_test.py +41 -0
  130. crosshair/opcode_intercept.py +601 -0
  131. crosshair/opcode_intercept_test.py +304 -0
  132. crosshair/options.py +218 -0
  133. crosshair/options_test.py +10 -0
  134. crosshair/patch_equivalence_test.py +75 -0
  135. crosshair/path_cover.py +209 -0
  136. crosshair/path_cover_test.py +138 -0
  137. crosshair/path_search.py +161 -0
  138. crosshair/path_search_test.py +52 -0
  139. crosshair/pathing_oracle.py +271 -0
  140. crosshair/pathing_oracle_test.py +21 -0
  141. crosshair/pure_importer.py +27 -0
  142. crosshair/pure_importer_test.py +16 -0
  143. crosshair/py.typed +0 -0
  144. crosshair/register_contract.py +273 -0
  145. crosshair/register_contract_test.py +190 -0
  146. crosshair/simplestructs.py +1165 -0
  147. crosshair/simplestructs_test.py +283 -0
  148. crosshair/smtlib.py +24 -0
  149. crosshair/smtlib_test.py +14 -0
  150. crosshair/statespace.py +1199 -0
  151. crosshair/statespace_test.py +108 -0
  152. crosshair/stubs_parser.py +352 -0
  153. crosshair/stubs_parser_test.py +43 -0
  154. crosshair/test_util.py +329 -0
  155. crosshair/test_util_test.py +26 -0
  156. crosshair/tools/__init__.py +0 -0
  157. crosshair/tools/check_help_in_doc.py +264 -0
  158. crosshair/tools/check_init_and_setup_coincide.py +119 -0
  159. crosshair/tools/generate_demo_table.py +127 -0
  160. crosshair/tracers.py +544 -0
  161. crosshair/tracers_test.py +154 -0
  162. crosshair/type_repo.py +151 -0
  163. crosshair/unicode_categories.py +589 -0
  164. crosshair/unicode_categories_test.py +27 -0
  165. crosshair/util.py +741 -0
  166. crosshair/util_test.py +173 -0
  167. crosshair/watcher.py +307 -0
  168. crosshair/watcher_test.py +107 -0
  169. crosshair/z3util.py +76 -0
  170. crosshair/z3util_test.py +11 -0
  171. crosshair_tool-0.0.99.dist-info/METADATA +144 -0
  172. crosshair_tool-0.0.99.dist-info/RECORD +176 -0
  173. crosshair_tool-0.0.99.dist-info/WHEEL +6 -0
  174. crosshair_tool-0.0.99.dist-info/entry_points.txt +3 -0
  175. crosshair_tool-0.0.99.dist-info/licenses/LICENSE +93 -0
  176. crosshair_tool-0.0.99.dist-info/top_level.txt +2 -0
@@ -0,0 +1,42 @@
1
+ import json
2
+ import sys
3
+ from typing import Dict, List, Tuple, Union
4
+
5
+ import pytest # type: ignore
6
+
7
+ from crosshair.core_and_libs import MessageType, analyze_function, run_checkables
8
+ from crosshair.options import AnalysisOptionSet
9
+ from crosshair.test_util import compare_results
10
+
11
+
12
+ def check_decode(s: str):
13
+ """post: _"""
14
+ return compare_results(json.loads, s)
15
+
16
+
17
+ def check_encode_atomics(obj: Union[bool, float, str, int]):
18
+ """post: _"""
19
+ return compare_results(json.dumps, obj)
20
+
21
+
22
+ def check_encode_containers(obj: Union[Dict[float, bool], Tuple[int, bool], List[str]]):
23
+ """post: _"""
24
+ return compare_results(json.dumps, obj)
25
+
26
+
27
+ def check_encode_decode_roundtrip(obj: Union[bool, int, str]):
28
+ """post: _"""
29
+ return compare_results(lambda o: json.loads(json.dumps(o)), obj)
30
+
31
+
32
+ # TODO: Test customized encoding stuff
33
+
34
+
35
+ # This is the only real test definition.
36
+ # It runs crosshair on each of the "check" functions defined above.
37
+ @pytest.mark.parametrize("fn_name", [fn for fn in dir() if fn.startswith("check_")])
38
+ def test_builtin(fn_name: str) -> None:
39
+ fn = getattr(sys.modules[__name__], fn_name)
40
+ messages = run_checkables(analyze_function(fn))
41
+ errors = [m for m in messages if m.state > MessageType.PRE_UNSAT]
42
+ assert errors == []
@@ -0,0 +1,51 @@
1
+ import json
2
+ from typing import List
3
+
4
+ import pytest
5
+
6
+ from crosshair.core_and_libs import standalone_statespace
7
+ from crosshair.statespace import POST_FAIL
8
+ from crosshair.test_util import check_states
9
+
10
+
11
+ def test_disallow_unicode_digits():
12
+ with standalone_statespace:
13
+ float("0E٠") # This is a valid float!
14
+ with pytest.raises(json.JSONDecodeError):
15
+ json.loads("0E٠") # But not a valid JSON float.
16
+
17
+
18
+ @pytest.mark.demo("yellow")
19
+ def test_dumps():
20
+ def f(lst: List[int]):
21
+ """
22
+ Can a JSON-serialized list be larger than 10 characters?
23
+
24
+ NOTE: Although this example is fast, most JSON serialization
25
+ tasks require at least a few minutes of analysis, and many may
26
+ not be solvable in any reasonable time frame.
27
+
28
+ post: len(_) <= 10
29
+ """
30
+ return json.dumps(lst)
31
+
32
+ check_states(f, POST_FAIL)
33
+
34
+
35
+ @pytest.mark.demo("yellow")
36
+ def test_loads():
37
+ def f(s: str):
38
+ """
39
+ Can we parse an empty JSON array out of a 3 character string?
40
+
41
+ NOTE: Although this example is fast, most JSON deserialization
42
+ tasks require at least a few minutes of analysis, and many may
43
+ not be solvable in any reasonable time frame.
44
+
45
+ pre: len(s) == 3
46
+ raises: json.JSONDecodeError
47
+ post: _ != []
48
+ """
49
+ return json.loads(s)
50
+
51
+ check_states(f, POST_FAIL)
@@ -0,0 +1,179 @@
1
+ import math
2
+ import sys
3
+ from numbers import Real
4
+
5
+ import z3 # type: ignore
6
+
7
+ from crosshair import NoTracing, register_patch
8
+ from crosshair.core import with_realized_args
9
+ from crosshair.libimpl.builtinslib import (
10
+ PreciseIeeeSymbolicFloat,
11
+ RealBasedSymbolicFloat,
12
+ SymbolicBool,
13
+ SymbolicIntable,
14
+ SymbolicValue,
15
+ smt_xor,
16
+ )
17
+ from crosshair.tracers import ResumedTracing
18
+ from crosshair.util import name_of_type
19
+ from crosshair.z3util import z3Not, z3Or
20
+
21
+
22
+ def _is_positive(x):
23
+ if isinstance(x, SymbolicValue):
24
+ if isinstance(x, PreciseIeeeSymbolicFloat):
25
+ return SymbolicBool(z3Not(z3.fpIsNegative(x.var)))
26
+ elif isinstance(x, RealBasedSymbolicFloat):
27
+ return SymbolicBool(x.var >= 0)
28
+ else:
29
+ with ResumedTracing():
30
+ return x >= 0
31
+ else:
32
+ return math.copysign(1, x) == 1
33
+
34
+
35
+ def _copysign(x, y):
36
+ if not isinstance(x, Real):
37
+ raise TypeError(f"must be real number, not {name_of_type(type(x))}")
38
+ if not isinstance(y, Real):
39
+ raise TypeError(f"must be real number, not {name_of_type(type(y))}")
40
+ with NoTracing():
41
+ x_is_positive = _is_positive(x)
42
+ y_is_positive = _is_positive(y)
43
+ # then invert as needed:
44
+ invert = smt_xor(x_is_positive, y_is_positive)
45
+ with NoTracing():
46
+ if isinstance(invert, SymbolicBool) and isinstance(
47
+ x, (PreciseIeeeSymbolicFloat, RealBasedSymbolicFloat)
48
+ ):
49
+ return type(x)(z3.If(invert.var, -x.var, x.var))
50
+ with ResumedTracing():
51
+ return -x if invert else x
52
+
53
+
54
+ if sys.version_info >= (3, 9):
55
+
56
+ def _gcd(a=0, b=0):
57
+ while b:
58
+ a, b = b, a % b
59
+ return abs(a)
60
+
61
+ else: # (arguments were required in Python <= 3.8)
62
+
63
+ def _gcd(a, b):
64
+ while b:
65
+ a, b = b, a % b
66
+ return abs(a)
67
+
68
+
69
+ def _isfinite(x):
70
+ with NoTracing():
71
+ if isinstance(x, (SymbolicIntable, RealBasedSymbolicFloat)):
72
+ return True
73
+ elif isinstance(x, PreciseIeeeSymbolicFloat):
74
+ return SymbolicBool(z3Not(z3Or(z3.fpIsNaN(x.var), z3.fpIsInf(x.var))))
75
+ else:
76
+ return math.isfinite(x)
77
+
78
+
79
+ def _isnan(x):
80
+ with NoTracing():
81
+ if isinstance(x, (SymbolicIntable, RealBasedSymbolicFloat)):
82
+ return False
83
+ elif isinstance(x, PreciseIeeeSymbolicFloat):
84
+ return SymbolicBool(z3.fpIsNaN(x.var))
85
+ else:
86
+ return math.isnan(x)
87
+
88
+
89
+ def _isinf(x):
90
+ with NoTracing():
91
+ if isinstance(x, (SymbolicIntable, RealBasedSymbolicFloat)):
92
+ return False
93
+ elif isinstance(x, PreciseIeeeSymbolicFloat):
94
+ return SymbolicBool(z3.fpIsInf(x.var))
95
+ else:
96
+ return math.isinf(x)
97
+
98
+
99
+ _FUNCTIONS_WITH_REALIZATION = [
100
+ # TODO: we could attempt to implement some of these in the SMT solver
101
+ "acos",
102
+ "acosh",
103
+ "asin",
104
+ "asinh",
105
+ "atan",
106
+ "atan2",
107
+ "atanh",
108
+ "ceil",
109
+ "comb",
110
+ "cos",
111
+ "cosh",
112
+ "degrees",
113
+ "dist",
114
+ "erf",
115
+ "erfc",
116
+ "exp",
117
+ "expm1",
118
+ "fabs",
119
+ "factorial",
120
+ "floor",
121
+ "fmod",
122
+ "frexp",
123
+ "fsum",
124
+ "gamma",
125
+ "hypot",
126
+ "isclose",
127
+ "isqrt",
128
+ "ldexp",
129
+ "lgamma",
130
+ "log",
131
+ "log10",
132
+ "log1p",
133
+ "log2",
134
+ "modf",
135
+ "perm",
136
+ "pow",
137
+ "prod",
138
+ "radians",
139
+ "remainder",
140
+ "sin",
141
+ "sinh",
142
+ "sqrt",
143
+ "tan",
144
+ "tanh",
145
+ "trunc",
146
+ ]
147
+
148
+ if sys.version_info >= (3, 9):
149
+ _FUNCTIONS_WITH_REALIZATION.extend(
150
+ [
151
+ "lcm",
152
+ "nextafter",
153
+ "ulp",
154
+ ]
155
+ )
156
+
157
+ if sys.version_info >= (3, 11):
158
+ _FUNCTIONS_WITH_REALIZATION.extend(
159
+ [
160
+ "cbrt",
161
+ "exp2",
162
+ ]
163
+ )
164
+
165
+ if sys.version_info >= (3, 12):
166
+ _FUNCTIONS_WITH_REALIZATION.append("sumprod")
167
+
168
+
169
+ def make_registrations():
170
+ register_patch(math.copysign, _copysign)
171
+ register_patch(math.gcd, _gcd)
172
+ register_patch(math.isfinite, _isfinite)
173
+ register_patch(math.isnan, _isnan)
174
+ register_patch(math.isinf, _isinf)
175
+ for fn_name in _FUNCTIONS_WITH_REALIZATION:
176
+ fn = getattr(math, fn_name)
177
+ register_patch(
178
+ fn, with_realized_args(fn, deep=True)
179
+ ) # deep realization needed for Fraction instances
@@ -0,0 +1,44 @@
1
+ import math
2
+ import sys
3
+ from typing import Union
4
+
5
+ import pytest # type: ignore
6
+
7
+ from crosshair.core import realize
8
+ from crosshair.core_and_libs import MessageType, analyze_function, run_checkables
9
+ from crosshair.options import AnalysisOptionSet
10
+ from crosshair.test_util import compare_results
11
+
12
+
13
+ def check_copysign(
14
+ a: Union[int, float], b: Union[int, float], realize_a: bool, realize_b: bool
15
+ ):
16
+ """post: _"""
17
+ if realize_a:
18
+ a = realize(a)
19
+ if realize_b:
20
+ b = realize(b)
21
+ return compare_results(math.copysign, a, b)
22
+
23
+
24
+ def check_gcd(a: int, b: int):
25
+ """
26
+ pre: all([-10 < a, a < 10, -10 < b, b < 10]) # for performance
27
+ post: _
28
+ """
29
+ return compare_results(math.gcd, a, b)
30
+
31
+
32
+ # This is the only real test definition.
33
+ # It runs crosshair on each of the "check" functions defined above.
34
+ @pytest.mark.parametrize("fn_name", [fn for fn in dir() if fn.startswith("check_")])
35
+ def test_builtin(fn_name: str) -> None:
36
+ this_module = sys.modules[__name__]
37
+ messages = run_checkables(
38
+ analyze_function(
39
+ getattr(this_module, fn_name),
40
+ AnalysisOptionSet(max_uninteresting_iterations=50),
41
+ )
42
+ )
43
+ errors = [m for m in messages if m.state > MessageType.PRE_UNSAT]
44
+ assert errors == []
@@ -0,0 +1,67 @@
1
+ import math
2
+ import sys
3
+
4
+ import pytest
5
+
6
+ from crosshair.core import proxy_for_type, standalone_statespace
7
+ from crosshair.libimpl.builtinslib import (
8
+ ModelingDirector,
9
+ PreciseIeeeSymbolicFloat,
10
+ RealBasedSymbolicFloat,
11
+ )
12
+ from crosshair.statespace import POST_FAIL
13
+ from crosshair.test_util import check_states
14
+ from crosshair.tracers import NoTracing, ResumedTracing
15
+ from crosshair.util import set_debug
16
+
17
+
18
+ def test_copysign_e2e():
19
+ def can_find_minus_zero(x: float):
20
+ """post: math.copysign(1, _) == 1"""
21
+ if x == 0:
22
+ return x
23
+ return 1
24
+
25
+ check_states(can_find_minus_zero, POST_FAIL)
26
+
27
+
28
+ @pytest.mark.parametrize(
29
+ "FloatType", [PreciseIeeeSymbolicFloat, RealBasedSymbolicFloat]
30
+ )
31
+ def test_copysign_symbolics(FloatType, space):
32
+ space.extra(ModelingDirector).global_representations[float] = FloatType
33
+ x = FloatType("x")
34
+ y = FloatType("y")
35
+ with ResumedTracing():
36
+ assert not space.is_possible(math.copysign(x, -0.0) > 0.0)
37
+ assert space.is_possible(math.copysign(x, y) > 0.0)
38
+ assert space.is_possible(math.copysign(x, y) < 0.0)
39
+
40
+
41
+ def test_isfinite():
42
+ with standalone_statespace:
43
+ with NoTracing():
44
+ x = RealBasedSymbolicFloat("symfloat")
45
+ assert math.isfinite(x)
46
+ assert math.isfinite(2.3)
47
+ assert not math.isfinite(float("nan"))
48
+
49
+
50
+ def test_isinf():
51
+ with standalone_statespace:
52
+ with NoTracing():
53
+ x = RealBasedSymbolicFloat("symfloat")
54
+ assert not math.isinf(x)
55
+ assert not math.isinf(float("nan"))
56
+ assert math.isinf(float("-inf"))
57
+
58
+
59
+ def test_log():
60
+ with standalone_statespace as space:
61
+ with NoTracing():
62
+ i = proxy_for_type(int, "i")
63
+ f = proxy_for_type(float, "f")
64
+ space.add(i > 0)
65
+ space.add(f > 0)
66
+ math.log(i)
67
+ math.log(f)
@@ -0,0 +1,7 @@
1
+ import os
2
+
3
+ from crosshair.core import register_patch
4
+
5
+
6
+ def make_registrations():
7
+ register_patch(os.fspath, os._fspath)
@@ -0,0 +1,10 @@
1
+ from pathlib import PurePath
2
+
3
+ from crosshair.core import proxy_for_type
4
+ from crosshair.tracers import ResumedTracing
5
+
6
+
7
+ def test_PurePath___init__(space):
8
+ pathstr = proxy_for_type(str, "pathstr")
9
+ with ResumedTracing():
10
+ PurePath(pathstr)
@@ -0,0 +1,178 @@
1
+ import random
2
+ from functools import partial
3
+ from inspect import Parameter, Signature
4
+ from typing import Any, Callable, List, Optional, Union
5
+
6
+ from crosshair import NoTracing, SymbolicFactory, register_type
7
+ from crosshair.libimpl.builtinslib import SymbolicInt
8
+ from crosshair.register_contract import register_contract
9
+
10
+
11
+ class ExplicitRandom(random.Random):
12
+ def __new__(cls, *a):
13
+ return super().__new__(ExplicitRandom, 0)
14
+
15
+ def __init__(
16
+ self,
17
+ future_values: Optional[List[Union[int, float]]] = None,
18
+ idx: int = 0,
19
+ intgen: Callable[[int], int] = lambda c: 0,
20
+ floatgen: Callable[[], float] = lambda: 0.0,
21
+ ):
22
+ self._future_values = future_values if future_values else []
23
+ self._idx = idx
24
+ self._intgen = intgen
25
+ self._floatgen = floatgen
26
+ super().__init__()
27
+
28
+ def __copy__(self):
29
+ # Just a regular copy. (otherwise, we'd be deferring to getstate/setstate)
30
+ cls = self.__class__
31
+ result = cls.__new__(cls)
32
+ result.__dict__.update(self.__dict__)
33
+ return result
34
+
35
+ def __ch_deepcopy__(self, memo):
36
+ # We pretend this is a deepcopy, but it isn't.
37
+ # That way, the values lazily added to _future_values will be the same in each
38
+ # instance, even though we don't know what they are at the time of the deep
39
+ # copy.
40
+ return self.__copy__()
41
+
42
+ def __repr__(self) -> str:
43
+ return f"crosshair.libimpl.randomlib.ExplicitRandom({self._future_values!r})"
44
+
45
+ def __reduce__(self):
46
+ return (ExplicitRandom, (self._future_values, self._idx))
47
+
48
+ def random(self) -> float:
49
+ idx = self._idx
50
+ future_values = self._future_values
51
+ if idx >= len(future_values):
52
+ future_values.append(self._floatgen())
53
+ intorfloat = self._future_values[idx]
54
+ if isinstance(intorfloat, int):
55
+ ret = 1.0 / abs(intorfloat) if intorfloat != 0 else 0.0
56
+ else:
57
+ ret = intorfloat
58
+ self._idx += 1
59
+ return ret
60
+
61
+ def _randbelow(self, cap: int) -> int:
62
+ idx = self._idx
63
+ future_values = self._future_values
64
+ if idx >= len(future_values):
65
+ future_values.append(self._intgen(cap))
66
+ intorfloat = future_values[idx]
67
+ if isinstance(intorfloat, float):
68
+ ret = abs(hash(intorfloat)) % cap
69
+ else:
70
+ ret = intorfloat
71
+ self._idx += 1
72
+ return ret
73
+
74
+ def getrandbits(self, k: int) -> int:
75
+ return self._randbelow(2**k)
76
+
77
+
78
+ def genint(factory: SymbolicFactory, cap: int):
79
+ # TODO: conditionally use SymbolicBoundedInt here and below
80
+ with NoTracing():
81
+ symbolic_int = SymbolicInt(factory.varname + factory.space.uniq(), int)
82
+ factory.space.add(0 <= symbolic_int.var)
83
+ factory.space.add(symbolic_int.var < SymbolicInt._coerce_to_smt_sort(cap))
84
+ return symbolic_int
85
+
86
+
87
+ def genfloat(factory: SymbolicFactory):
88
+ with NoTracing():
89
+ symbolic_float: Any = factory(float)
90
+ factory.space.add(0.0 <= symbolic_float.var)
91
+ factory.space.add(symbolic_float.var < 1.0)
92
+ return symbolic_float
93
+
94
+
95
+ def make_registrations() -> None:
96
+ register_type(
97
+ random.Random,
98
+ lambda p: ExplicitRandom([], 0, partial(genint, p), partial(genfloat, p)),
99
+ )
100
+
101
+ register_contract(
102
+ random.Random.random,
103
+ post=lambda __return__: 0.0 <= __return__ < 1.0,
104
+ sig=Signature(
105
+ parameters=[
106
+ Parameter("self", Parameter.POSITIONAL_OR_KEYWORD),
107
+ ],
108
+ return_annotation=float,
109
+ ),
110
+ )
111
+ register_contract(
112
+ random.Random.randint,
113
+ pre=lambda a, b: a <= b,
114
+ post=lambda __return__, a, b: a <= __return__ <= b,
115
+ sig=Signature(
116
+ parameters=[
117
+ Parameter("self", Parameter.POSITIONAL_OR_KEYWORD),
118
+ Parameter("a", Parameter.POSITIONAL_OR_KEYWORD, annotation=int),
119
+ Parameter("b", Parameter.POSITIONAL_OR_KEYWORD, annotation=int),
120
+ ],
121
+ return_annotation=int,
122
+ ),
123
+ )
124
+ register_contract(
125
+ random.Random.randrange,
126
+ pre=lambda start, stop, step: (
127
+ (step == 1 and start >= 1)
128
+ if stop is None
129
+ else (start != stop and step != 0 and (stop - start > 0) == (step > 0))
130
+ ),
131
+ post=lambda __return__, start, stop, step, _int=int: (
132
+ (0 <= __return__ < start if stop is None else start <= __return__ < stop)
133
+ and (step == 1 or (__return__ - start) % step == 0)
134
+ ),
135
+ sig=Signature(
136
+ parameters=[
137
+ Parameter("self", Parameter.POSITIONAL_OR_KEYWORD),
138
+ Parameter("start", Parameter.POSITIONAL_OR_KEYWORD, annotation=int),
139
+ Parameter(
140
+ "stop",
141
+ Parameter.POSITIONAL_OR_KEYWORD,
142
+ default=None,
143
+ annotation=Optional[int],
144
+ ),
145
+ Parameter(
146
+ "step", Parameter.POSITIONAL_OR_KEYWORD, default=1, annotation=int
147
+ ),
148
+ Parameter(
149
+ "_int", Parameter.POSITIONAL_OR_KEYWORD, default=int, annotation=int
150
+ ),
151
+ ],
152
+ return_annotation=int,
153
+ ),
154
+ )
155
+ register_contract(
156
+ random.Random.uniform,
157
+ post=lambda __return__, a, b: min(a, b) <= __return__ <= max(a, b),
158
+ sig=Signature(
159
+ parameters=[
160
+ Parameter("self", Parameter.POSITIONAL_OR_KEYWORD),
161
+ Parameter("a", Parameter.POSITIONAL_OR_KEYWORD, annotation=float),
162
+ Parameter("b", Parameter.POSITIONAL_OR_KEYWORD, annotation=float),
163
+ ],
164
+ return_annotation=float,
165
+ ),
166
+ )
167
+ register_contract(
168
+ random.Random.getrandbits,
169
+ pre=lambda k: k >= 0,
170
+ post=lambda __return__, k: 0 <= __return__ < 2**k,
171
+ sig=Signature(
172
+ parameters=[
173
+ Parameter("self", Parameter.POSITIONAL_OR_KEYWORD),
174
+ Parameter("k", Parameter.POSITIONAL_OR_KEYWORD, annotation=int),
175
+ ],
176
+ return_annotation=int,
177
+ ),
178
+ )
@@ -0,0 +1,120 @@
1
+ import copy
2
+ import random
3
+
4
+ import pytest
5
+
6
+ from crosshair.core_and_libs import proxy_for_type, standalone_statespace
7
+ from crosshair.libimpl.randomlib import ExplicitRandom
8
+ from crosshair.statespace import CANNOT_CONFIRM, CONFIRMED, POST_FAIL, MessageType
9
+ from crosshair.test_util import check_states
10
+ from crosshair.tracers import ResumedTracing
11
+
12
+
13
+ def test_ExplicitRandom():
14
+ rng = ExplicitRandom([1, 2])
15
+ assert rng.randrange(0, 10) == 1
16
+ assert rng.choice(["a", "b", "c"]) == "c"
17
+ assert rng.choice(["a", "b", "c"]) == "a"
18
+ assert rng.random() == 0.0
19
+ assert repr(rng) == "crosshair.libimpl.randomlib.ExplicitRandom([1, 2, 0, 0.0])"
20
+
21
+
22
+ def test_ExplicitRandom_copy():
23
+ rng = ExplicitRandom([1, 2])
24
+ (rng2,) = copy.deepcopy([rng])
25
+ assert rng.randint(0, 5) == 1
26
+ assert rng2.randint(0, 5) == 1
27
+ assert rng.randint(0, 5) == 2
28
+ assert rng2.randint(0, 5) == 2
29
+
30
+
31
+ def test_proxy_random():
32
+ with standalone_statespace as space:
33
+ rng = proxy_for_type(random.Random, "rng")
34
+ i = rng.randrange(5, 10)
35
+ with ResumedTracing():
36
+ assert space.is_possible(i == 5)
37
+ assert space.is_possible(i == 9)
38
+ assert not space.is_possible(i == 4)
39
+
40
+
41
+ def test_global_randrange():
42
+ assert random.randrange(10, 20, 5) in (10, 15) # confirm we've got the args right
43
+
44
+ def f():
45
+ """post: _ in (10, 15)"""
46
+ return random.randrange(10, 20, 5)
47
+
48
+ check_states(f, CONFIRMED)
49
+
50
+
51
+ def test_global_randrange_only_upperbound():
52
+ assert random.randrange(2) in (0, 1) # confirm we've got the args right
53
+
54
+ def f():
55
+ """post: _ in (0, 1)"""
56
+ return random.randrange(2)
57
+
58
+ check_states(f, CONFIRMED)
59
+
60
+
61
+ @pytest.mark.demo
62
+ def test_randrange():
63
+ def f():
64
+ """
65
+ Can random.randrange() produce the value at low end of the range?
66
+
67
+ NOTE: CrossHair's random generator can produce any possible valid
68
+ value.
69
+ The counterexample includes a monkey-patching context
70
+ manager that lets you reproduce the issue, e.g.:
71
+ with crosshair.patch_to_return({random.Random.randrange: [20]}):
72
+ f()
73
+
74
+ post: _ != 10
75
+ """
76
+ return random.randrange(10, 20)
77
+
78
+ check_states(f, POST_FAIL)
79
+
80
+
81
+ @pytest.mark.demo
82
+ def test_uniform():
83
+ assert 10.0 <= random.uniform(10, 20) <= 20.0 # confirm we've got the args right
84
+
85
+ def f():
86
+ """
87
+ Can random.uniform() produce the value at high end of the range?
88
+
89
+ NOTE: CrossHair's random generator can produce any possible valid
90
+ value.
91
+ The counterexample includes a monkey-patching context
92
+ manager that lets you reproduce the issue, e.g.:
93
+ with crosshair.patch_to_return({random.Random.uniform: [20.0]}):
94
+ f()
95
+
96
+ post: _ != 20.0
97
+ """
98
+ return random.uniform(10, 20)
99
+
100
+ check_states(f, POST_FAIL)
101
+
102
+
103
+ def test_global_uniform_inverted_args():
104
+ assert -2.0 <= random.uniform(10, -2) <= 10.0 # confirm we've got the args right
105
+
106
+ def f():
107
+ """post: -2.0 <= _ <= 10.0"""
108
+ return random.uniform(10, -2)
109
+
110
+ check_states(f, CONFIRMED)
111
+
112
+
113
+ def test_global_getrandbits():
114
+ assert 0 <= random.getrandbits(3) < 8
115
+
116
+ def f():
117
+ """post: 0<= _ < 8"""
118
+ return random.getrandbits(3)
119
+
120
+ check_states(f, CONFIRMED)