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
@@ -1,15 +1,20 @@
1
1
  import time
2
2
 
3
+ import pytest
3
4
  import z3 # type: ignore
4
5
 
6
+ from crosshair.core import Patched, proxy_for_type
5
7
  from crosshair.statespace import (
6
8
  HeapRef,
7
9
  RootNode,
8
10
  SimpleStateSpace,
9
11
  SnapshotRef,
10
12
  StateSpace,
13
+ StateSpaceContext,
11
14
  model_value_to_python,
12
15
  )
16
+ from crosshair.tracers import COMPOSITE_TRACER
17
+ from crosshair.util import UnknownSatisfiability
13
18
 
14
19
  _HEAD_SNAPSHOT = SnapshotRef(-1)
15
20
 
@@ -27,6 +32,21 @@ def test_find_key_in_heap():
27
32
  assert isinstance(dictval, dict)
28
33
 
29
34
 
35
+ def test_timeout() -> None:
36
+ num_ints = 100
37
+ space = StateSpace(time.monotonic() + 60_000, 0.1, RootNode())
38
+ with pytest.raises(UnknownSatisfiability):
39
+ with Patched(), StateSpaceContext(space), COMPOSITE_TRACER:
40
+ ints = [proxy_for_type(int, f"i{i}") for i in range(num_ints)]
41
+ for i in range(num_ints - 2):
42
+ t0 = time.monotonic()
43
+ if ints[i] * ints[i + 1] == ints[i + 2]:
44
+ pass
45
+ ints[i + 1] += ints[i]
46
+ solve_time = time.monotonic() - t0
47
+ assert 0.05 < solve_time < 0.5
48
+
49
+
30
50
  def test_infinite_timeout() -> None:
31
51
  space = StateSpace(time.monotonic() + 1000, float("+inf"), RootNode())
32
52
  assert space.solver.check(True) == z3.sat
@@ -61,3 +81,28 @@ def test_model_value_to_python_AlgebraicNumRef():
61
81
  rt2 = z3.simplify(z3.Sqrt(2))
62
82
  assert type(rt2) == z3.AlgebraicNumRef
63
83
  model_value_to_python(rt2)
84
+
85
+
86
+ def test_model_value_to_python_ArithRef():
87
+ # Tests that a plain z3.ArithRef can be exported as Python
88
+ # See https://github.com/pschanely/CrossHair/issues/381
89
+ rt2 = z3.ToInt(2 ** z3.Int("x"))
90
+ print("type(rt2)", type(rt2))
91
+ assert type(rt2) == z3.ArithRef
92
+ model_value_to_python(rt2)
93
+
94
+
95
+ def test_smt_fanout(space: SimpleStateSpace):
96
+ option1 = z3.Bool("option1")
97
+ option2 = z3.Bool("option2")
98
+ space.add(z3.Xor(option1, option2)) # Ensure exactly one option can be set
99
+ exprs_and_results = [(option1, "result1"), (option2, "result2")]
100
+
101
+ result = space.smt_fanout(exprs_and_results, desc="choose_one")
102
+ assert result in ("result1", "result2")
103
+ if result == "result1":
104
+ assert space.is_possible(option1)
105
+ assert not space.is_possible(option2)
106
+ else:
107
+ assert not space.is_possible(option1)
108
+ assert space.is_possible(option2)
crosshair/stubs_parser.py CHANGED
@@ -22,8 +22,6 @@ def signature_from_stubs(fn: Callable) -> Tuple[List[Signature], bool]:
22
22
  """
23
23
  Try to find signature(s) for the given function in the stubs.
24
24
 
25
- Note: this feature is only available for Python >= 3.8.
26
-
27
25
  For overloaded functions, all signatures found will be returned.
28
26
 
29
27
  :param fn: The function to lookup a signature for.
crosshair/test_util.py CHANGED
@@ -2,7 +2,20 @@ import pathlib
2
2
  import sys
3
3
  from copy import deepcopy
4
4
  from dataclasses import dataclass, replace
5
- from typing import Callable, Iterable, List, Mapping, Optional, Sequence, Set, Tuple
5
+ from decimal import Decimal
6
+ from math import isnan
7
+ from numbers import Real
8
+ from typing import (
9
+ Callable,
10
+ Collection,
11
+ Iterable,
12
+ List,
13
+ Mapping,
14
+ Optional,
15
+ Sequence,
16
+ Set,
17
+ Tuple,
18
+ )
6
19
 
7
20
  from crosshair.core import (
8
21
  AnalysisMessage,
@@ -14,21 +27,27 @@ from crosshair.core import (
14
27
  )
15
28
  from crosshair.options import AnalysisOptionSet
16
29
  from crosshair.statespace import context_statespace
17
- from crosshair.tracers import NoTracing, is_tracing
30
+ from crosshair.tracers import NoTracing, ResumedTracing
18
31
  from crosshair.util import (
19
- IgnoreAttempt,
20
- UnexploredPath,
32
+ assert_tracing,
33
+ ch_stack,
21
34
  debug,
22
35
  in_debug,
36
+ is_iterable,
23
37
  is_pure_python,
24
38
  name_of_type,
25
- test_stack,
26
- true_type,
27
39
  )
28
40
 
29
41
  ComparableLists = Tuple[List, List]
30
42
 
31
43
 
44
+ class _Missing:
45
+ pass
46
+
47
+
48
+ _MISSING = _Missing()
49
+
50
+
32
51
  def simplefs(path: pathlib.Path, files: dict) -> None:
33
52
  for name, contents in files.items():
34
53
  subpath = path / name
@@ -54,8 +73,8 @@ def check_states(
54
73
  )
55
74
  elif expected == MessageType.CONFIRMED:
56
75
  local_opts = AnalysisOptionSet(
57
- per_condition_timeout=10,
58
- per_path_timeout=5,
76
+ per_condition_timeout=60,
77
+ per_path_timeout=20,
59
78
  max_uninteresting_iterations=sys.maxsize,
60
79
  )
61
80
  elif expected == MessageType.POST_ERR:
@@ -123,9 +142,40 @@ def check_messages(checkables: Iterable[Checkable], **kw) -> ComparableLists:
123
142
  return (msgs, [AnalysisMessage(**kw)])
124
143
 
125
144
 
126
- def nan_equal(a, b):
127
- if a != a and b != b: # handle float('nan')
145
+ _NAN_ABLE = (Decimal, Real)
146
+
147
+
148
+ def flexible_equal(a: object, b: object) -> bool:
149
+ if a is b:
128
150
  return True
151
+ if type(a) is type(b) and type(a).__eq__ is object.__eq__:
152
+ # If types match and it uses identity-equals, we can't do much. Assume equal.
153
+ return True
154
+ if isinstance(a, _NAN_ABLE) and isinstance(b, _NAN_ABLE) and isnan(a) and isnan(b):
155
+ return True
156
+ if (
157
+ is_iterable(a)
158
+ and not isinstance(a, Collection)
159
+ and is_iterable(b)
160
+ and not isinstance(b, Collection)
161
+ ): # unsized iterables compare by contents
162
+ a, b = list(a), list(b) # type: ignore
163
+ if (
164
+ type(a) == type(b)
165
+ and isinstance(a, Collection)
166
+ and not isinstance(a, (str, bytes, Set))
167
+ ):
168
+ # Recursively apply flexible_equal for most containers:
169
+ if len(a) != len(b): # type: ignore
170
+ return False
171
+ if isinstance(a, Mapping):
172
+ for k, v in a.items():
173
+ if not flexible_equal(v, b.get(k, _MISSING)): # type: ignore
174
+ return False
175
+ return True
176
+ else:
177
+ return all(flexible_equal(ai, bi) for ai, bi in zip(a, b)) # type: ignore
178
+
129
179
  return a == b
130
180
 
131
181
 
@@ -133,6 +183,7 @@ def nan_equal(a, b):
133
183
  class ExecutionResult:
134
184
  ret: object # return value
135
185
  exc: Optional[BaseException] # exception raised, if any
186
+ tb: Optional[str]
136
187
  # args after the function terminates:
137
188
  post_args: Sequence
138
189
  post_kwargs: Mapping[str, object]
@@ -141,7 +192,7 @@ class ExecutionResult:
141
192
  if not isinstance(other, ExecutionResult):
142
193
  return False
143
194
  return (
144
- nan_equal(self.ret, other.ret)
195
+ flexible_equal(self.ret, other.ret)
145
196
  and type(self.exc) == type(other.exc)
146
197
  and self.post_args == other.post_args
147
198
  and self.post_kwargs == other.post_kwargs
@@ -152,7 +203,7 @@ class ExecutionResult:
152
203
  if self.exc:
153
204
  exc = self.exc
154
205
  exc_type = name_of_type(type(exc))
155
- tb = test_stack(exc.__traceback__)
206
+ tb = self.tb or "(missing traceback)"
156
207
  ret = f"exc={exc_type}: {str(exc)} {tb}"
157
208
  else:
158
209
  ret = f"ret={self.ret!r}"
@@ -178,7 +229,8 @@ def summarize_execution(
178
229
  if not kwargs:
179
230
  kwargs = {}
180
231
  ret: object = None
181
- exc = None
232
+ exc: Optional[Exception] = None
233
+ tbstr: Optional[str] = None
182
234
  try:
183
235
  possibly_symbolic_ret = fn(*args, **kwargs)
184
236
  if detach_path:
@@ -199,13 +251,14 @@ def summarize_execution(
199
251
  except Exception as e:
200
252
  exc = e
201
253
  if detach_path:
202
- context_statespace().detach_path()
254
+ context_statespace().detach_path(e)
203
255
  exc = deep_realize(exc)
204
256
  # NOTE: deep_realize somehow empties the __traceback__ member; re-assign it:
205
257
  exc.__traceback__ = e.__traceback__
258
+ tbstr = ch_stack(currently_handling=exc)
206
259
  if in_debug():
207
- debug("hit exception:", type(exc), exc, test_stack(exc.__traceback__))
208
- return ExecutionResult(ret, exc, args, kwargs)
260
+ debug("hit exception:", type(exc), exc, tbstr)
261
+ return ExecutionResult(ret, exc, tbstr, args, kwargs)
209
262
 
210
263
 
211
264
  @dataclass
@@ -235,8 +288,8 @@ def compare_returns(fn: Callable, *a: object, **kw: object) -> ResultComparison:
235
288
  return comparison
236
289
 
237
290
 
291
+ @assert_tracing(True)
238
292
  def compare_results(fn: Callable, *a: object, **kw: object) -> ResultComparison:
239
- assert is_tracing()
240
293
  original_a = deepcopy(a)
241
294
  original_kw = deepcopy(kw)
242
295
  symbolic_result = summarize_execution(fn, a, kw)
@@ -245,16 +298,25 @@ def compare_results(fn: Callable, *a: object, **kw: object) -> ResultComparison:
245
298
  concrete_kw = deep_realize(original_kw)
246
299
 
247
300
  # Check that realization worked, too:
248
- for idx, arg in enumerate(concrete_a):
249
- if true_type(arg) != type(arg):
301
+ with NoTracing():
302
+ labels_and_args = [
303
+ *(
304
+ (f"Argument {idx + 1}", a[idx], arg)
305
+ for idx, arg in enumerate(concrete_a)
306
+ ),
307
+ *((f"Keyword argument '{k}'", kw[k], v) for k, v in concrete_kw.items()),
308
+ ]
309
+ for label, symbolic_arg, concrete_arg in labels_and_args:
310
+ with ResumedTracing():
311
+ symbolic_type = type(symbolic_arg)
312
+ concrete_type = type(concrete_arg)
313
+ true_concrete_type = type(concrete_arg)
250
314
  assert (
251
- False
252
- ), f"Argument {idx + 1} was {true_type(arg)} afer realization; expected {type(arg)}"
253
- for k, v in concrete_kw.items():
254
- if true_type(v) != type(v):
315
+ true_concrete_type == concrete_type
316
+ ), f"{label} did not realize. It is {true_concrete_type} instead of {concrete_type}."
255
317
  assert (
256
- False
257
- ), f"Keyword argument '{k}' was {true_type(v)} afer realization; expected {type(arg)}"
318
+ true_concrete_type == symbolic_type
319
+ ), f"{label} should realize to {symbolic_type}; it is {true_concrete_type} instead."
258
320
 
259
321
  with NoTracing():
260
322
  concrete_result = summarize_execution(
@@ -0,0 +1,26 @@
1
+ from crosshair.test_util import flexible_equal
2
+
3
+
4
+ def test_flexible_equal():
5
+ assert float("nan") != float("nan")
6
+ assert flexible_equal(float("nan"), float("nan"))
7
+ assert flexible_equal((42, float("nan")), (42, float("nan")))
8
+ assert not flexible_equal([float("nan"), 11], [float("nan"), 22])
9
+
10
+ def gen():
11
+ yield 11
12
+ yield 22
13
+
14
+ assert flexible_equal(gen(), iter([11, 22]))
15
+ assert not flexible_equal(gen(), iter([11, 22, 33]))
16
+ assert not flexible_equal(gen(), iter([11]))
17
+
18
+ ordered_set_1 = {10_000, 20_000} | {30_000}
19
+ ordered_set_2 = {30_000, 20_000} | {10_000}
20
+ assert list(ordered_set_1) != list(ordered_set_2) # (different orderings)
21
+ assert flexible_equal(ordered_set_1, ordered_set_2)
22
+
23
+ ordered_dict_1 = {1: 2, 3: 4}
24
+ ordered_dict_2 = {3: 4, 1: 2}
25
+ assert list(ordered_dict_1.items()) != list(ordered_dict_2.items())
26
+ assert flexible_equal(ordered_dict_1, ordered_dict_2)
@@ -1,13 +1,10 @@
1
1
  #!/usr/bin/env python3
2
2
 
3
3
  """Check that the distribution and crosshair/__init__.py are in sync."""
4
- import email
5
4
  import subprocess
6
5
  import sys
7
6
  from typing import Dict, Optional
8
7
 
9
- import pkg_resources
10
-
11
8
  import crosshair
12
9
 
13
10
 
@@ -107,7 +107,7 @@ def divide_stdlib_module(
107
107
  modulename: str, items: list[tuple[str, str, str]]
108
108
  ) -> dict[str, list[tuple[str, str, str]]]:
109
109
  ret = defaultdict(list)
110
- for (name, color, src) in items:
110
+ for name, color, src in items:
111
111
  if name.endswith("_method"):
112
112
  name = name.removesuffix("_method")
113
113
  (classname, methodname) = name.split("_", 1)
@@ -120,7 +120,7 @@ def divide_stdlib_module(
120
120
 
121
121
 
122
122
  stdlib = {}
123
- for (modulename, items) in stdlib_demos().items():
123
+ for modulename, items in stdlib_demos().items():
124
124
  stdlib[modulename] = divide_stdlib_module(modulename, items)
125
125
 
126
126