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
crosshair/util.py CHANGED
@@ -10,9 +10,9 @@ import pathlib
10
10
  import re
11
11
  import sys
12
12
  import threading
13
- import time
14
13
  import traceback
15
14
  import types
15
+ from array import array
16
16
  from dataclasses import dataclass
17
17
  from enum import Enum
18
18
  from inspect import (
@@ -21,8 +21,10 @@ from inspect import (
21
21
  getmodulename,
22
22
  getsourcefile,
23
23
  getsourcelines,
24
+ isdatadescriptor,
24
25
  isfunction,
25
26
  )
27
+ from time import monotonic
26
28
  from types import BuiltinFunctionType, FunctionType, MethodDescriptorType, TracebackType
27
29
  from typing import (
28
30
  Any,
@@ -30,12 +32,13 @@ from typing import (
30
32
  Dict,
31
33
  Generator,
32
34
  Generic,
33
- Iterable,
34
35
  List,
35
36
  Mapping,
36
37
  MutableMapping,
37
38
  Optional,
39
+ Sequence,
38
40
  Set,
41
+ TextIO,
39
42
  Tuple,
40
43
  Type,
41
44
  TypeVar,
@@ -43,12 +46,27 @@ from typing import (
43
46
  cast,
44
47
  )
45
48
 
46
- import typing_inspect
49
+ import typing_inspect # type: ignore
47
50
 
48
51
  from crosshair.auditwall import opened_auditwall
49
52
  from crosshair.tracers import COMPOSITE_TRACER, NoTracing, ResumedTracing, is_tracing
50
53
 
51
- _DEBUG = False
54
+ _DEBUG_STREAM: Optional[TextIO] = None
55
+
56
+
57
+ # NOTE: many of these is_* functions should use a TypeGuard in 3.10 (or even TypeIs in 3.13)
58
+
59
+ if sys.version_info >= (3, 12):
60
+ from collections.abc import Buffer
61
+
62
+ def is_bytes_like(obj: object) -> bool:
63
+ return isinstance(obj, Buffer)
64
+
65
+ else:
66
+ from collections.abc import ByteString
67
+
68
+ def is_bytes_like(obj: object) -> bool:
69
+ return isinstance(obj, (ByteString, array))
52
70
 
53
71
 
54
72
  def is_iterable(o: object) -> bool:
@@ -111,8 +129,43 @@ def true_type(obj: object) -> Type:
111
129
  return type(obj)
112
130
 
113
131
 
132
+ CROSSHAIR_EXTRA_ASSERTS = os.environ.get("CROSSHAIR_EXTRA_ASSERTS", "0") == "1"
133
+
134
+ if CROSSHAIR_EXTRA_ASSERTS:
135
+
136
+ def assert_tracing(should_be_tracing):
137
+ def decorator(fn):
138
+ fn_name = fn.__qualname__
139
+
140
+ @functools.wraps(fn)
141
+ def check_tracing(*a, **kw):
142
+ if is_tracing() != should_be_tracing:
143
+ with NoTracing():
144
+ if should_be_tracing:
145
+ raise CrossHairInternal(
146
+ f"should be tracing when calling {fn_name}, but isn't"
147
+ )
148
+ else:
149
+ raise CrossHairInternal(
150
+ f"should not be tracing when calling {fn_name}, but is"
151
+ )
152
+ return fn(*a, **kw)
153
+
154
+ return check_tracing
155
+
156
+ return decorator
157
+
158
+ else:
159
+
160
+ def assert_tracing(should_be_tracing):
161
+ def decorator(fn):
162
+ return fn
163
+
164
+ return decorator
165
+
166
+
114
167
  class IdKeyedDict(collections.abc.MutableMapping):
115
- def __init__(self):
168
+ def __init__(self) -> None:
116
169
  # Confusingly, we hold both the key object and value object in
117
170
  # our inner dict. Holding the key object ensures that we don't
118
171
  # GC the key object, which could lead to reusing the same id()
@@ -129,7 +182,9 @@ class IdKeyedDict(collections.abc.MutableMapping):
129
182
  return self.inner.__delitem__(id(k))
130
183
 
131
184
  def __iter__(self):
132
- return map(id, self.inner.__iter__())
185
+ raise NotImplementedError
186
+ # No use cases for this yet, but we could do something like this:
187
+ # return (actual_key_object for actual_key_object, _ in self.inner.values())
133
188
 
134
189
  def __len__(self):
135
190
  return len(self.inner)
@@ -160,23 +215,30 @@ def sourcelines(thing: object) -> Tuple[str, int, Tuple[str, ...]]:
160
215
 
161
216
  def frame_summary_for_fn(
162
217
  fn: Callable, frames: traceback.StackSummary
163
- ) -> Tuple[str, int]:
218
+ ) -> Tuple[Optional[str], int]:
164
219
  fn_name = fn.__name__
165
- fn_file = cast(str, getsourcefile(fn))
220
+ try:
221
+ fn_file = getsourcefile(fn) # Can return None OR raise TypeError
222
+ except TypeError:
223
+ fn_file = None
224
+ if fn_file is None:
225
+ return (None, 0)
166
226
  for frame in reversed(frames):
167
227
  if frame.name == fn_name and samefile(frame.filename, fn_file):
168
228
  return (frame.filename, frame.lineno or 1)
169
229
  return sourcelines(fn)[:2]
170
230
 
171
231
 
172
- def set_debug(debug: bool):
173
- global _DEBUG
174
- _DEBUG = debug
232
+ def set_debug(new_debug: bool, output: TextIO = sys.stderr):
233
+ global _DEBUG_STREAM
234
+ if new_debug:
235
+ _DEBUG_STREAM = output
236
+ else:
237
+ _DEBUG_STREAM = None
175
238
 
176
239
 
177
240
  def in_debug() -> bool:
178
- global _DEBUG
179
- return _DEBUG
241
+ return bool(_DEBUG_STREAM)
180
242
 
181
243
 
182
244
  def debug(*a):
@@ -190,7 +252,7 @@ def debug(*a):
190
252
  symbolic will change the path exploration that CrossHair normally takes, leading to
191
253
  different outcomes in verbose and non-verbose mode.
192
254
  """
193
- if not _DEBUG:
255
+ if not _DEBUG_STREAM:
194
256
  return
195
257
  with NoTracing():
196
258
  stack = traceback.extract_stack()
@@ -198,9 +260,9 @@ def debug(*a):
198
260
  indent = len(stack) - 3
199
261
  print(
200
262
  "{:06.3f}|{}|{}() {}".format(
201
- time.monotonic(), " " * indent, frame.name, " ".join(map(str, a))
263
+ monotonic(), " " * indent, frame.name, " ".join(map(str, a))
202
264
  ),
203
- file=sys.stderr,
265
+ file=_DEBUG_STREAM,
204
266
  )
205
267
 
206
268
 
@@ -214,43 +276,32 @@ def warn(*a):
214
276
  debug("WARNING:", *a)
215
277
 
216
278
 
217
- TracebackLike = Union[None, TracebackType, Iterable[traceback.FrameSummary]]
218
-
279
+ TracebackLike = Union[None, TracebackType, Sequence[traceback.FrameSummary]]
219
280
 
220
- def test_stack(tb: TracebackLike = None) -> str:
221
- return tiny_stack(tb, ignore=re.compile("^$"))
222
281
 
223
-
224
- def tiny_stack(tb: TracebackLike = None, **kw) -> str:
282
+ def ch_stack(
283
+ tb: TracebackLike = None,
284
+ last_n_frames: int = sys.maxsize,
285
+ currently_handling: Optional[BaseException] = None,
286
+ ) -> str:
225
287
  with NoTracing():
226
- if tb is None:
227
- frames: Iterable[traceback.FrameSummary] = traceback.extract_stack()[:-1]
288
+ if currently_handling:
289
+ if tb is not None:
290
+ raise CrossHairInternal
291
+ lower_frames = traceback.extract_tb(currently_handling.__traceback__)
292
+ higher_frames = traceback.extract_stack()[:-2]
293
+ frames: Sequence[traceback.FrameSummary] = higher_frames + lower_frames
294
+ elif tb is None:
295
+ frames = traceback.extract_stack()[:-1]
228
296
  elif isinstance(tb, TracebackType):
229
297
  frames = traceback.extract_tb(tb)
230
298
  else:
231
299
  frames = tb
232
- return _tiny_stack_frames(frames, **kw)
233
-
234
-
235
- def _tiny_stack_frames(
236
- frames: Iterable[traceback.FrameSummary],
237
- ignore=re.compile(r".*\b(crosshair|z3|typing_inspect|unittest)\b"),
238
- ) -> str:
239
- output: List[str] = []
240
- ignore_ct = 0
241
- for frame in frames:
242
- if ignore.match(frame.filename) and not frame.filename.endswith("_test.py"):
243
- ignore_ct += 1
244
- else:
245
- if ignore_ct > 0:
246
- if output:
247
- output.append(f"(...x{ignore_ct})")
248
- ignore_ct = 0
300
+ output: List[str] = []
301
+ for frame in frames[-last_n_frames:]:
249
302
  filename = os.path.split(frame.filename)[1]
250
303
  output.append(f"({frame.name} {filename}:{frame.lineno})")
251
- if ignore_ct > 0:
252
- output.append(f"(...x{ignore_ct})")
253
- return " ".join(output)
304
+ return " ".join(output)
254
305
 
255
306
 
256
307
  class ErrorDuringImport(Exception):
@@ -300,17 +351,19 @@ def load_file(filename: str) -> types.ModuleType:
300
351
  raise ErrorDuringImport from e
301
352
 
302
353
 
303
- def import_alternative(name: str, suppress: Tuple[str, ...] = ()):
354
+ @contextlib.contextmanager
355
+ def imported_alternative(name: str, suppress: Tuple[str, ...] = ()):
304
356
  """Load an alternative version of a module with some modules suppressed."""
305
357
  modules = sys.modules
306
358
  orig_module = importlib.import_module(name) # Ensure the regular version is loaded
307
- prev = modules.copy()
308
359
  modules.update({k: None for k in suppress}) # type: ignore
360
+ alternative = importlib.reload(orig_module)
309
361
  try:
310
- return importlib.reload(orig_module)
362
+ yield
311
363
  finally:
312
- # sys.modules = prev
313
- pass
364
+ for k in suppress:
365
+ del modules[k]
366
+ importlib.reload(alternative)
314
367
 
315
368
 
316
369
  def format_boundargs_as_dictionary(bound_args: BoundArguments) -> str:
@@ -320,7 +373,7 @@ def format_boundargs_as_dictionary(bound_args: BoundArguments) -> str:
320
373
 
321
374
  def format_boundargs(bound_args: BoundArguments) -> str:
322
375
  arg_strings: List[str] = []
323
- for (name, param) in bound_args.signature.parameters.items():
376
+ for name, param in bound_args.signature.parameters.items():
324
377
  param_kind = param.kind
325
378
  vals = bound_args.arguments.get(name, param.default)
326
379
  if param_kind == Parameter.VAR_POSITIONAL:
@@ -348,12 +401,13 @@ def eval_friendly_repr(obj: object) -> str:
348
401
  assert not is_tracing()
349
402
  with ResumedTracing(), EvalFriendlyReprContext() as ctx:
350
403
  try:
404
+ # TODO: probably only the repr should have tracing enabled
351
405
  return ctx.cleanup(repr(obj))
352
406
  except Exception as e:
353
407
  if isinstance(e, (IgnoreAttempt, UnexploredPath)):
354
408
  raise
355
409
  debug("Repr failed, ", type(e), ":", str(e))
356
- debug("Repr failed at:", test_stack(e.__traceback__))
410
+ debug("Repr failed at:", ch_stack(currently_handling=e))
357
411
  return UNABLE_TO_REPR_TEXT
358
412
 
359
413
 
@@ -438,7 +492,7 @@ class EvalFriendlyReprContext:
438
492
 
439
493
  def __enter__(self):
440
494
  if not is_tracing():
441
- raise CrosshairInternal
495
+ raise CrossHairInternal
442
496
  OVERRIDES: Dict[type, Callable[[Any], Union[str, ReferencedIdentifier]]] = {
443
497
  object: lambda o: "object()",
444
498
  list: lambda o: f"[{', '.join(map(repr, o))}]", # (de-optimize C-level repr)
@@ -454,9 +508,9 @@ class EvalFriendlyReprContext:
454
508
  oid = id(obj)
455
509
  typ = type(obj)
456
510
  if obj in instance_overrides:
457
- repr_fn: Callable[
458
- [Any], Union[str, ReferencedIdentifier]
459
- ] = instance_overrides[obj]
511
+ repr_fn: Callable[[Any], Union[str, ReferencedIdentifier]] = (
512
+ instance_overrides[obj]
513
+ )
460
514
  elif typ == float:
461
515
  if math.isfinite(obj):
462
516
  repr_fn = repr
@@ -494,7 +548,7 @@ class EvalFriendlyReprContext:
494
548
  counts = collections.Counter(re.compile(r"\b_ch_efr_\d+_\b").findall(output))
495
549
  assignment_remaps = {}
496
550
  nextvarnum = 1
497
- for (varname, count) in counts.items():
551
+ for varname, count in counts.items():
498
552
  if count > 1:
499
553
  assignment_remaps[varname + ":="] = f"v{nextvarnum}:="
500
554
  assignment_remaps[varname] = f"v{nextvarnum}"
@@ -576,10 +630,16 @@ class DynamicScopeVar(Generic[_T]):
576
630
 
577
631
  class AttributeHolder:
578
632
  def __init__(self, attrs: Mapping[str, object]):
579
- for (k, v) in attrs.items():
633
+ for k, v in attrs.items():
580
634
  self.__dict__[k] = v
581
635
 
582
636
 
637
+ class CrossHairValue:
638
+ """Base class for values that are pretending to be other values."""
639
+
640
+ pass
641
+
642
+
583
643
  class ControlFlowException(BaseException):
584
644
  # CrossHair sometimes uses exceptions to abort a path mid-execution.
585
645
  # We extend such exceptions from BaseException instead of Exception,
@@ -587,11 +647,15 @@ class ControlFlowException(BaseException):
587
647
  pass
588
648
 
589
649
 
590
- class CrosshairInternal(ControlFlowException):
650
+ class CrossHairInternal(ControlFlowException):
591
651
  def __init__(self, *a):
592
652
  ControlFlowException.__init__(self, *a)
593
- debug("CrosshairInternal", str(self))
594
- debug(" Stack trace:\n" + "".join(traceback.format_stack()))
653
+ if in_debug():
654
+ debug("CrossHairInternal:", str(self))
655
+ debug("CrossHairInternal stack trace:")
656
+ for entry in traceback.format_stack()[:-1]:
657
+ for line in entry.splitlines():
658
+ debug("", line)
595
659
 
596
660
 
597
661
  class UnexploredPath(ControlFlowException):
@@ -604,6 +668,10 @@ class UnknownSatisfiability(UnexploredPath):
604
668
  debug("UnknownSatisfiability", str(self))
605
669
 
606
670
 
671
+ class NotDeterministic(Exception):
672
+ pass
673
+
674
+
607
675
  class PathTimeout(UnexploredPath):
608
676
  pass
609
677
 
@@ -618,28 +686,42 @@ class IgnoreAttempt(ControlFlowException):
618
686
  def __init__(self, *a):
619
687
  if in_debug():
620
688
  debug(f"IgnoreAttempt", *a)
621
- debug("IgnoreAttempt stack:", test_stack())
689
+ debug("IgnoreAttempt stack:", ch_stack())
622
690
 
623
691
 
624
- ExtraUnionType = getattr(types, "UnionType") if sys.version_info >= (3, 10) else None
692
+ if (3, 10) <= sys.version_info < (3, 14):
693
+ # Specialize some definitions for the Python versions where
694
+ # typing.Union != types.UnionType:
625
695
 
696
+ def origin_of(typ: Type) -> Type:
697
+ if hasattr(typ, "__origin__"):
698
+ return typ.__origin__
699
+ elif isinstance(typ, types.UnionType):
700
+ return cast(Type, Union)
701
+ else:
702
+ return typ
626
703
 
627
- def origin_of(typ: Type) -> Type:
628
- if hasattr(typ, "__origin__"):
629
- return typ.__origin__
630
- elif ExtraUnionType and isinstance(typ, ExtraUnionType):
631
- return cast(Type, Union)
632
- else:
633
- return typ
704
+ def type_args_of(typ: Type) -> Tuple[Type, ...]:
705
+ if getattr(typ, "__args__", None):
706
+ if isinstance(typ, types.UnionType):
707
+ return typ.__args__
708
+ return typing_inspect.get_args(typ, evaluate=True)
709
+ else:
710
+ return ()
634
711
 
712
+ else:
635
713
 
636
- def type_args_of(typ: Type) -> Tuple[Type, ...]:
637
- if getattr(typ, "__args__", None):
638
- if ExtraUnionType and isinstance(typ, ExtraUnionType):
639
- return typ.__args__
640
- return typing_inspect.get_args(typ, evaluate=True)
641
- else:
642
- return ()
714
+ def type_args_of(typ: Type) -> Tuple[Type, ...]:
715
+ if getattr(typ, "__args__", None):
716
+ return typing_inspect.get_args(typ, evaluate=True)
717
+ else:
718
+ return ()
719
+
720
+ def origin_of(typ: Type) -> Type:
721
+ origin = getattr(typ, "__origin__", None)
722
+ # 3.14 unifies typing.Union and types.Union, so that's good!
723
+ # But a of 3.14.0a6, types.Union.__origin__ yields a data descriptor, so we need to check that.
724
+ return typ if origin is None or isdatadescriptor(origin) else origin
643
725
 
644
726
 
645
727
  def type_arg_of(typ: Type, index: int) -> Type:
crosshair/util_test.py CHANGED
@@ -1,5 +1,6 @@
1
1
  import sys
2
2
  import traceback
3
+ import types
3
4
  from dataclasses import dataclass
4
5
  from enum import Enum
5
6
  from inspect import signature
@@ -10,11 +11,11 @@ import pytest
10
11
 
11
12
  from crosshair.tracers import PatchingModule
12
13
  from crosshair.util import (
13
- CrosshairInternal,
14
+ CrossHairInternal,
14
15
  DynamicScopeVar,
15
- _tiny_stack_frames,
16
16
  eval_friendly_repr,
17
17
  format_boundargs,
18
+ imported_alternative,
18
19
  is_pure_python,
19
20
  renamed_function,
20
21
  sourcelines,
@@ -77,23 +78,18 @@ def test_dynamic_scope_var_with_exception():
77
78
  assert var.get_if_in_scope() is None
78
79
 
79
80
 
80
- def test_tiny_stack_frames():
81
- FS = traceback.FrameSummary
82
- s = _tiny_stack_frames(
83
- [
84
- FS("a.py", 1, "fooa"),
85
- FS("/crosshair/b.py", 2, "foob"),
86
- FS("/crosshair/c.py", 3, "fooc"),
87
- FS("/other/package/d.py", 4, "food"),
88
- FS("/crosshair/e.py", 5, "fooe"),
89
- ]
90
- )
91
- assert s == "(fooa a.py:1) (...x2) (food d.py:4) (...x1)"
81
+ def test_imported_alternative():
82
+ import heapq
83
+
84
+ assert type(heapq.heapify) == types.BuiltinFunctionType
85
+ with imported_alternative("heapq", ("_heapq",)):
86
+ assert type(heapq.heapify) == types.FunctionType
87
+ assert type(heapq.heapify) == types.BuiltinFunctionType
92
88
 
93
89
 
94
90
  class UnhashableCallable:
95
91
  def __hash__(self):
96
- raise CrosshairInternal("Do not hash")
92
+ raise CrossHairInternal("Do not hash")
97
93
 
98
94
  def __call__(self):
99
95
  return 42
@@ -144,10 +140,8 @@ def test_eval_friendly_repr():
144
140
  # Special float values:
145
141
  assert eval_friendly_repr(float("nan")) == 'float("nan")'
146
142
  # MethodDescriptorType
147
- assert (
148
- eval_friendly_repr(numpy.random.RandomState.randint)
149
- == "RandomState.randint"
150
- )
143
+ assert isinstance(str.join, types.MethodDescriptorType)
144
+ assert eval_friendly_repr(str.join) == "str.join"
151
145
  # enums:
152
146
  assert eval_friendly_repr(Color.RED) == "Color.RED"
153
147
  # basic dataclass
@@ -165,7 +159,6 @@ def test_eval_friendly_repr():
165
159
  assert repr(Color.RED) == "<Color.RED: 0>"
166
160
 
167
161
 
168
- @pytest.mark.skipif(sys.version_info < (3, 8), reason="Python 3.8+ required")
169
162
  def test_renamed_function():
170
163
  def crash_on_seven(x):
171
164
  if x == 7:
crosshair/watcher.py CHANGED
@@ -35,7 +35,7 @@ from crosshair.core_and_libs import (
35
35
  from crosshair.fnutil import NotFound, walk_paths
36
36
  from crosshair.options import AnalysisOptionSet
37
37
  from crosshair.util import (
38
- CrosshairInternal,
38
+ CrossHairInternal,
39
39
  ErrorDuringImport,
40
40
  debug,
41
41
  load_file,
@@ -142,7 +142,7 @@ def pool_worker_main() -> None:
142
142
  output: WorkItemOutput = (filename, stats, messages)
143
143
  print(serialize(output))
144
144
  except BaseException as e:
145
- raise CrosshairInternal("Worker failed while analyzing " + str(filename)) from e
145
+ raise CrossHairInternal("Worker failed while analyzing " + str(filename)) from e
146
146
 
147
147
 
148
148
  class Pool:
@@ -244,8 +244,8 @@ class Watcher:
244
244
  pool = self._pool
245
245
  for filename, _ in sorted(self._modtimes.items(), key=lambda pair: -pair[1]):
246
246
  worker_timeout = max(
247
- 10.0, max_uninteresting_iterations * 100.0
248
- ) # TODO: times 100? is that right?
247
+ 10.0, max_uninteresting_iterations * 1_000.0
248
+ ) # TODO: times 1000? is that right?
249
249
  iter_options = AnalysisOptionSet(
250
250
  max_uninteresting_iterations=max_uninteresting_iterations,
251
251
  )
crosshair/z3util.py CHANGED
@@ -14,7 +14,7 @@ from z3 import (
14
14
  Z3_mk_or,
15
15
  Z3_solver_assert,
16
16
  )
17
- from z3.z3 import _to_ast_array
17
+ from z3.z3 import _to_ast_array # type: ignore
18
18
 
19
19
  ctx = z3.main_ctx()
20
20
  ctx_ref = ctx.ref()
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: crosshair-tool
3
- Version: 0.0.56
3
+ Version: 0.0.100
4
4
  Summary: Analyze Python code for correctness using symbolic execution.
5
5
  Home-page: https://github.com/pschanely/CrossHair
6
6
  Author: Phillip Schanely
@@ -8,64 +8,73 @@ Author-email: pschanely+vE7F@gmail.com
8
8
  License: MIT
9
9
  Classifier: Development Status :: 3 - Alpha
10
10
  Classifier: Intended Audience :: Developers
11
- Classifier: License :: OSI Approved :: MIT License
12
11
  Classifier: Operating System :: OS Independent
13
12
  Classifier: Programming Language :: Python :: 3
14
- Classifier: Programming Language :: Python :: 3.7
15
13
  Classifier: Programming Language :: Python :: 3.8
16
14
  Classifier: Programming Language :: Python :: 3.9
17
15
  Classifier: Programming Language :: Python :: 3.10
18
16
  Classifier: Programming Language :: Python :: 3.11
19
17
  Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
20
19
  Classifier: Programming Language :: Python :: Implementation :: CPython
21
20
  Classifier: Topic :: Software Development :: Quality Assurance
22
21
  Classifier: Topic :: Software Development :: Testing
23
- Requires-Python: >=3.7
22
+ Requires-Python: >=3.8
24
23
  Description-Content-Type: text/markdown
25
24
  License-File: LICENSE
26
25
  Requires-Dist: packaging
27
- Requires-Dist: typing-inspect (>=0.7.1)
28
- Requires-Dist: typing-extensions (>=3.10.0)
29
- Requires-Dist: z3-solver (==4.13.0.0)
30
- Requires-Dist: importlib-metadata (>=4.0.0)
31
- Requires-Dist: pygls (>=1.0.0)
32
- Requires-Dist: typeshed-client (>=2.0.5)
26
+ Requires-Dist: typing-inspect>=0.7.1
27
+ Requires-Dist: typing_extensions>=3.10.0
28
+ Requires-Dist: z3-solver>=4.13.0.0
29
+ Requires-Dist: importlib_metadata>=4.0.0
30
+ Requires-Dist: pygls>=1.0.0
31
+ Requires-Dist: typeshed-client>=2.0.5
33
32
  Provides-Extra: dev
34
- Requires-Dist: autodocsumm (<1,>=0.2.2) ; extra == 'dev'
35
- Requires-Dist: black (==22.3.0) ; extra == 'dev'
36
- Requires-Dist: deal (>=4.13.0) ; extra == 'dev'
37
- Requires-Dist: hypothesis (>=6.0.0) ; extra == 'dev'
38
- Requires-Dist: icontract (>=2.4.0) ; extra == 'dev'
39
- Requires-Dist: isort (==5.11.5) ; extra == 'dev'
40
- Requires-Dist: mypy (==0.990) ; extra == 'dev'
41
- Requires-Dist: pre-commit (~=2.20) ; extra == 'dev'
42
- Requires-Dist: pydantic (<2.0) ; extra == 'dev'
43
- Requires-Dist: pytest ; extra == 'dev'
44
- Requires-Dist: pytest-xdist ; extra == 'dev'
45
- Requires-Dist: sphinx (>=3.4.3) ; extra == 'dev'
46
- Requires-Dist: sphinx-rtd-theme (>=0.5.1) ; extra == 'dev'
47
- Requires-Dist: types-pkg-resources ; extra == 'dev'
48
- Requires-Dist: wheel ; extra == 'dev'
49
- Requires-Dist: numpy (==1.23.4) ; (python_version < "3.12") and extra == 'dev'
50
- Requires-Dist: numpy (==1.26.0) ; (python_version >= "3.12" and python_version < "3.13") and extra == 'dev'
51
- Requires-Dist: numpy (==1.26.2) ; (python_version >= "3.13") and extra == 'dev'
33
+ Requires-Dist: autodocsumm<1,>=0.2.2; extra == "dev"
34
+ Requires-Dist: black==25.9.0; extra == "dev"
35
+ Requires-Dist: deal>=4.13.0; extra == "dev"
36
+ Requires-Dist: icontract>=2.4.0; extra == "dev"
37
+ Requires-Dist: isort==5.11.5; extra == "dev"
38
+ Requires-Dist: mypy==1.18.1; extra == "dev"
39
+ Requires-Dist: numpy==1.24.0; python_version < "3.12" and extra == "dev"
40
+ Requires-Dist: numpy==2.3.3; python_version >= "3.12" and extra == "dev"
41
+ Requires-Dist: pre-commit~=2.20; extra == "dev"
42
+ Requires-Dist: pytest; extra == "dev"
43
+ Requires-Dist: pytest-xdist; extra == "dev"
44
+ Requires-Dist: setuptools; extra == "dev"
45
+ Requires-Dist: sphinx>=3.4.3; extra == "dev"
46
+ Requires-Dist: sphinx-rtd-theme>=0.5.1; extra == "dev"
47
+ Requires-Dist: rst2pdf>=0.102; extra == "dev"
48
+ Requires-Dist: z3-solver==4.15.4.0; extra == "dev"
49
+ Dynamic: author
50
+ Dynamic: author-email
51
+ Dynamic: classifier
52
+ Dynamic: description
53
+ Dynamic: description-content-type
54
+ Dynamic: home-page
55
+ Dynamic: license
56
+ Dynamic: license-file
57
+ Dynamic: provides-extra
58
+ Dynamic: requires-dist
59
+ Dynamic: requires-python
60
+ Dynamic: summary
52
61
 
53
62
  <img src="https://raw.githubusercontent.com/pschanely/CrossHair/main/doc/source/logo-gray.png" width="5%" align="left">
54
63
 
55
64
  # CrossHair
56
65
 
57
- [![Join the chat at https://gitter.im/Cross_Hair/Lobby](https://badges.gitter.im/Cross_Hair/Lobby.svg)](https://gitter.im/Cross_Hair/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
58
- [![Check status](https://github.com/pschanely/CrossHair/workflows/Check/badge.svg)](https://github.com/pschanely/CrossHair/actions?query=workflow%3ACheck)
59
- [![Downloads](https://pepy.tech/badge/crosshair-tool)](https://pepy.tech/project/crosshair-tool)
66
+ [![Join the chat on Discord](https://img.shields.io/discord/1346219754519789719?logo=discord&logoColor=white)](https://discord.gg/rUeTaYTWbb)
67
+ [![Check status](https://github.com/pschanely/CrossHair/actions/workflows/check.yml/badge.svg?branch=main&event=push)](https://github.com/pschanely/CrossHair/actions?query=workflow%3ACheck+event%3Apush)
68
+ [![Downloads](https://static.pepy.tech/badge/crosshair-tool/week)](https://pepy.tech/project/crosshair-tool)
60
69
 
61
70
  An analysis tool for Python that blurs the line between testing and
62
71
  type systems.
63
72
 
64
73
  > **_THE LATEST NEWS:_**
65
- The new CrossHair VSCode extension can work in the background and provide in-line
66
- errors when it finds a counterexample, just like a linter or type-checker.
67
- [Try it out](https://marketplace.visualstudio.com/items?itemName=CrossHair.crosshair)
68
- and tell me what you think!
74
+ Python's most popular property-based testing tool,
75
+ [Hypothesis](https://hypothesis.readthedocs.io/en/latest/),
76
+ now supports running CrossHair as an
77
+ [optional backend](https://hypothesis.readthedocs.io/en/latest/strategies.html#alternative-backends)!
69
78
 
70
79
 
71
80
  If you have a function with