crosshair-tool 0.0.88__cp313-cp313-musllinux_1_2_x86_64.whl → 0.0.93__cp313-cp313-musllinux_1_2_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.

Potentially problematic release.


This version of crosshair-tool might be problematic. Click here for more details.

crosshair/__init__.py CHANGED
@@ -15,7 +15,7 @@ from crosshair.statespace import StateSpace
15
15
  from crosshair.tracers import NoTracing, ResumedTracing
16
16
  from crosshair.util import IgnoreAttempt, debug
17
17
 
18
- __version__ = "0.0.88" # Do not forget to update in setup.py!
18
+ __version__ = "0.0.93" # Do not forget to update in setup.py!
19
19
  __author__ = "Phillip Schanely"
20
20
  __license__ = "MIT"
21
21
  __status__ = "Alpha"
crosshair/core.py CHANGED
@@ -329,9 +329,10 @@ def with_realized_args(fn: Callable, deep=False) -> Callable:
329
329
 
330
330
  def realizer(*a, **kw):
331
331
  with NoTracing():
332
- a = map(realize_fn, a)
332
+ a = [realize_fn(arg) for arg in a]
333
333
  kw = {k: realize_fn(v) for (k, v) in kw.items()}
334
- return fn(*a, **kw)
334
+ # You might think we don't need tracing here, but some operations can invoke user-defined behavior:
335
+ return fn(*a, **kw)
335
336
 
336
337
  functools.update_wrapper(realizer, fn)
337
338
  return realizer
@@ -4441,10 +4441,11 @@ def _int(val: Any = 0, base=_MISSING):
4441
4441
  else:
4442
4442
  ret = (ret * base) + ch_num
4443
4443
  return ret
4444
- if base is _MISSING:
4445
- return int(deep_realize(val))
4446
- else:
4447
- return int(deep_realize(val), base=realize(base))
4444
+ elif isinstance(val, CrossHairValue):
4445
+ val = deep_realize(val)
4446
+ base = deep_realize(base)
4447
+
4448
+ return int(val) if base is _MISSING else int(val, base=base)
4448
4449
 
4449
4450
 
4450
4451
  _FLOAT_REGEX = re.compile(
@@ -4549,7 +4550,7 @@ def _len(ls):
4549
4550
  def _map(fn, *iters):
4550
4551
  # Wrap the `map` callback in a pure Python lambda.
4551
4552
  # This de-optimization ensures that the callback can be intercepted.
4552
- return map(lambda x: fn(x), *iters)
4553
+ return map(lambda *a: fn(*a), *iters)
4553
4554
 
4554
4555
 
4555
4556
  def _memoryview(source):
@@ -5,7 +5,8 @@ from crosshair.core import deep_realize
5
5
  from crosshair.core_and_libs import proxy_for_type
6
6
  from crosshair.statespace import POST_FAIL
7
7
  from crosshair.test_util import check_states
8
- from crosshair.tracers import ResumedTracing
8
+ from crosshair.tracers import ResumedTracing, is_tracing
9
+ from crosshair.util import CrossHairInternal
9
10
 
10
11
 
11
12
  def test_fraction_realize(space):
@@ -16,6 +17,28 @@ def test_fraction_realize(space):
16
17
  deep_realize(Fraction(n, d))
17
18
 
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
+
19
42
  def test_fraction_copy_doesnt_realize(space):
20
43
  n = proxy_for_type(int, "n")
21
44
  with ResumedTracing():
@@ -1,5 +1,6 @@
1
1
  import copy
2
2
  import itertools
3
+ import operator
3
4
  import re
4
5
  import sys
5
6
  from dataclasses import dataclass
@@ -39,6 +40,7 @@ possible_args = [
39
40
  (42, int), # isinstance
40
41
  (re.compile("(ab|a|b)"), r"\n", ""), # re methods
41
42
  (bool, [1, 1, 0]), # itertools.takewhile and friends
43
+ (operator.add, [1, 0], [1, 1]), # multi-iterable map
42
44
  ([(1, 2), (3, 4)]), # key-value pairs
43
45
  ([(1, 2), ([], 4)]), # key-value pairs w/ unhashable key
44
46
  ]
@@ -6,7 +6,7 @@ from z3 import ExprRef # type: ignore
6
6
 
7
7
  from crosshair.statespace import (
8
8
  AbstractPathingOracle,
9
- DeatchedPathNode,
9
+ DetachedPathNode,
10
10
  ModelValueNode,
11
11
  NodeLike,
12
12
  RootNode,
@@ -117,14 +117,13 @@ class CoveragePathingOracle(AbstractPathingOracle):
117
117
  for step, node in enumerate(path[:-1]):
118
118
  if not isinstance(node, NodeLike):
119
119
  continue
120
- node = node.simplify() # type: ignore
121
120
  if isinstance(node, WorstResultNode):
122
121
  key = node.stacktail
123
122
  if (key not in leading_locs) and (not isinstance(node, ModelValueNode)):
124
123
  self.summarized_positions[key] += Counter(leading_conditions)
125
124
  leading_locs.append(key)
126
- next_node = path[step + 1].simplify()
127
- if isinstance(next_node, DeatchedPathNode):
125
+ next_node = path[step + 1]
126
+ if isinstance(next_node, DetachedPathNode):
128
127
  break
129
128
  if step + 1 < len(path):
130
129
  (is_positive, root_expr) = node.normalized_expr
@@ -133,9 +132,9 @@ class CoveragePathingOracle(AbstractPathingOracle):
133
132
  if is_positive
134
133
  else -self.internalize(root_expr)
135
134
  )
136
- if next_node == node.positive.simplify():
135
+ if next_node == node.positive:
137
136
  leading_conditions.append(expr_signature)
138
- elif next_node == node.negative.simplify():
137
+ elif next_node == node.negative:
139
138
  leading_conditions.append(-expr_signature)
140
139
  else:
141
140
  raise CrossHairInternal(
crosshair/statespace.py CHANGED
@@ -290,49 +290,10 @@ class NodeLike:
290
290
  """
291
291
  raise NotImplementedError
292
292
 
293
- def is_stem(self) -> bool:
294
- return False
295
-
296
- def grow_into(self, node: _N) -> _N:
297
- raise NotImplementedError
298
-
299
- def simplify(self) -> "NodeLike":
300
- return self
301
-
302
293
  def stats(self) -> StateSpaceCounter:
303
294
  raise NotImplementedError
304
295
 
305
296
 
306
- class NodeStem(NodeLike):
307
- evolution: Optional["SearchTreeNode"] = None
308
-
309
- def is_exhausted(self) -> bool:
310
- return False if self.evolution is None else self.evolution.is_exhausted()
311
-
312
- def get_result(self) -> CallAnalysis:
313
- return (
314
- CallAnalysis(VerificationStatus.UNKNOWN)
315
- if self.evolution is None
316
- else self.evolution.get_result()
317
- )
318
-
319
- def is_stem(self) -> bool:
320
- return self.evolution is None or self.evolution.is_stem()
321
-
322
- def grow_into(self, node: _N) -> _N:
323
- self.evolution = node
324
- return node
325
-
326
- def simplify(self):
327
- return self if self.evolution is None else self.evolution
328
-
329
- def stats(self) -> StateSpaceCounter:
330
- return StateSpaceCounter() if self.evolution is None else self.evolution.stats()
331
-
332
- def __repr__(self) -> str:
333
- return "NodeStem()"
334
-
335
-
336
297
  class SearchTreeNode(NodeLike):
337
298
  """A node in the execution path tree."""
338
299
 
@@ -364,6 +325,27 @@ class SearchTreeNode(NodeLike):
364
325
  raise NotImplementedError
365
326
 
366
327
 
328
+ class NodeStem(NodeLike):
329
+ def __init__(self, parent: SearchTreeNode, parent_attr_name: str):
330
+ self.parent = parent
331
+ self.parent_attr_name = parent_attr_name
332
+
333
+ def grow(self, node: SearchTreeNode):
334
+ setattr(self.parent, self.parent_attr_name, node)
335
+
336
+ def is_exhausted(self) -> bool:
337
+ return False
338
+
339
+ def get_result(self) -> CallAnalysis:
340
+ return CallAnalysis(VerificationStatus.UNKNOWN)
341
+
342
+ def stats(self) -> StateSpaceCounter:
343
+ return StateSpaceCounter()
344
+
345
+ def __repr__(self) -> str:
346
+ return "NodeStem()"
347
+
348
+
367
349
  def solver_is_sat(solver, *exprs) -> bool:
368
350
  ret = solver.check(*exprs)
369
351
  if ret == z3.unknown:
@@ -411,7 +393,7 @@ class SinglePathNode(SearchTreeNode):
411
393
 
412
394
  def __init__(self, decision: bool):
413
395
  self.decision = decision
414
- self.child = NodeStem()
396
+ self.child = NodeStem(self, "child")
415
397
  self._random = newrandom()
416
398
 
417
399
  def choose(
@@ -420,7 +402,7 @@ class SinglePathNode(SearchTreeNode):
420
402
  return (self.decision, 1.0, self.child)
421
403
 
422
404
  def compute_result(self, leaf_analysis: CallAnalysis) -> Tuple[CallAnalysis, bool]:
423
- self.child = self.child.simplify()
405
+ assert isinstance(self.child, SearchTreeNode)
424
406
  return (self.child.get_result(), self.child.is_exhausted())
425
407
 
426
408
  def stats(self) -> StateSpaceCounter:
@@ -449,7 +431,7 @@ class RootNode(SinglePathNode):
449
431
  self.iteration = 0
450
432
 
451
433
 
452
- class DeatchedPathNode(SinglePathNode):
434
+ class DetachedPathNode(SinglePathNode):
453
435
  def __init__(self):
454
436
  super().__init__(True)
455
437
  # Seems like `exhausted` should be True, but we set to False until we can
@@ -459,7 +441,6 @@ class DeatchedPathNode(SinglePathNode):
459
441
  self._stats = None
460
442
 
461
443
  def compute_result(self, leaf_analysis: CallAnalysis) -> Tuple[CallAnalysis, bool]:
462
- self.child = self.child.simplify()
463
444
  return (leaf_analysis, True)
464
445
 
465
446
  def stats(self) -> StateSpaceCounter:
@@ -494,8 +475,8 @@ class RandomizedBinaryPathNode(BinaryPathNode):
494
475
  def __init__(self, rand: random.Random):
495
476
  super().__init__()
496
477
  self._random = rand
497
- self.positive = NodeStem()
498
- self.negative = NodeStem()
478
+ self.positive = NodeStem(self, "positive")
479
+ self.negative = NodeStem(self, "negative")
499
480
 
500
481
  def probability_true(
501
482
  self, space: "StateSpace", requested_probability: Optional[float] = None
@@ -520,10 +501,6 @@ class RandomizedBinaryPathNode(BinaryPathNode):
520
501
  else:
521
502
  return (positive_ok, 1.0, self.positive if positive_ok else self.negative)
522
503
 
523
- def _simplify(self) -> None:
524
- self.positive = self.positive.simplify()
525
- self.negative = self.negative.simplify()
526
-
527
504
 
528
505
  class ParallelNode(RandomizedBinaryPathNode):
529
506
  """Choose either path; the first complete result will be used."""
@@ -537,7 +514,6 @@ class ParallelNode(RandomizedBinaryPathNode):
537
514
  return f"ParallelNode(false_pct={self._false_probability}, {self._desc})"
538
515
 
539
516
  def compute_result(self, leaf_analysis: CallAnalysis) -> Tuple[CallAnalysis, bool]:
540
- self._simplify()
541
517
  positive, negative = self.positive, self.negative
542
518
  pos_exhausted = positive.is_exhausted()
543
519
  neg_exhausted = negative.is_exhausted()
@@ -655,7 +631,6 @@ class WorstResultNode(RandomizedBinaryPathNode):
655
631
  )
656
632
 
657
633
  def compute_result(self, leaf_analysis: CallAnalysis) -> Tuple[CallAnalysis, bool]:
658
- self._simplify()
659
634
  positive, negative = self.positive, self.negative
660
635
  exhausted = self._is_exhausted()
661
636
  if node_status(positive) == VerificationStatus.REFUTED or (
@@ -697,7 +672,6 @@ class ModelValueNode(WorstResultNode):
697
672
 
698
673
  def debug_path_tree(node, highlights, prefix="") -> List[str]:
699
674
  highlighted = node in highlights
700
- node = node.simplify()
701
675
  highlighted |= node in highlights
702
676
  if isinstance(node, BinaryPathNode):
703
677
  if isinstance(node, WorstResultNode) and node.forced_path is not None:
@@ -806,23 +780,27 @@ class StateSpace:
806
780
  return value # type: ignore
807
781
 
808
782
  def stats_lookahead(self) -> Tuple[StateSpaceCounter, StateSpaceCounter]:
809
- node = self._search_position.simplify()
810
- if node.is_stem():
783
+ node = self._search_position
784
+ if isinstance(node, NodeStem):
811
785
  return (StateSpaceCounter(), StateSpaceCounter())
812
- assert isinstance(
813
- node, BinaryPathNode
814
- ), f"node {node} {node.is_stem()} is not a binarypathnode"
786
+ assert isinstance(node, BinaryPathNode), f"node {node} is not a binarypathnode"
815
787
  return node.stats_lookahead()
816
788
 
789
+ def grow_into(self, node: _N) -> _N:
790
+ assert isinstance(self._search_position, NodeStem)
791
+ self._search_position.grow(node)
792
+ node.iteration = self._root.iteration
793
+ self._search_position = node
794
+ return node
795
+
817
796
  def fork_parallel(self, false_probability: float, desc: str = "") -> bool:
818
- if self._search_position.is_stem():
819
- node: NodeLike = self._search_position.grow_into(
820
- ParallelNode(self._random, false_probability, desc)
821
- )
797
+ node = self._search_position
798
+ if isinstance(node, NodeStem):
799
+ node = self.grow_into(ParallelNode(self._random, false_probability, desc))
800
+ node.stacktail = self.gen_stack_descriptions()
822
801
  assert isinstance(node, ParallelNode)
823
802
  self._search_position = node
824
803
  else:
825
- node = self._search_position.simplify()
826
804
  if not isinstance(node, ParallelNode):
827
805
  self.raise_not_deterministic(
828
806
  node, "Wrong node type (expected ParallelNode)"
@@ -878,33 +856,34 @@ class StateSpace:
878
856
  # NOTE: format_stack() is more human readable, but it pulls source file contents,
879
857
  # so it is (1) slow, and (2) unstable when source code changes while we are checking.
880
858
  stacktail = self.gen_stack_descriptions()
881
- if self._search_position.is_stem():
882
- # We only allow time outs at stems - that's because we don't want
883
- # to think about how mutating an existing path branch would work:
884
- self.check_timeout()
885
- node = self._search_position.grow_into(
886
- WorstResultNode(self._random, expr, self.solver)
887
- )
888
- node.iteration = self._root.iteration
889
- node.stacktail = stacktail
890
- else:
891
- node = self._search_position.simplify() # type: ignore
859
+ if isinstance(self._search_position, SearchTreeNode):
860
+ node = self._search_position
892
861
  not_deterministic_reason = (
893
862
  (
894
863
  (not isinstance(node, WorstResultNode))
895
- and "Wrong node type (expected WorstResultNode)"
864
+ and f"Wrong node type (is {name_of_type(type(node))}, expected WorstResultNode)"
896
865
  )
897
866
  # TODO: Not clear whether we want this stack trace check.
898
867
  # A stack change usually indicates a serious problem, but not 100% of the time.
899
868
  # Keeping it would mean that we fail earlier.
900
869
  # But also see https://github.com/HypothesisWorks/hypothesis/pull/4034#issuecomment-2606415404
901
870
  # or (node.stacktail != stacktail and "Stack trace changed")
902
- or ((not z3.eq(node.expr, expr)) and "SMT expression changed")
871
+ or (
872
+ (hasattr(node, "expr") and (not z3.eq(node.expr, expr)))
873
+ and "SMT expression changed"
874
+ )
903
875
  )
904
876
  if not_deterministic_reason:
905
877
  self.raise_not_deterministic(
906
878
  node, not_deterministic_reason, expr=expr, stacktail=stacktail
907
879
  )
880
+ else:
881
+ # We only allow time outs at stems - that's because we don't want
882
+ # to think about how mutating an existing path branch would work:
883
+ self.check_timeout()
884
+ node = self.grow_into(WorstResultNode(self._random, expr, self.solver))
885
+ node.stacktail = stacktail
886
+
908
887
  self._search_position = node
909
888
  choose_true, chosen_probability, stem = node.choose(
910
889
  self, probability_true=probability_true
@@ -937,10 +916,10 @@ class StateSpace:
937
916
  currently_handling: Optional[BaseException] = None,
938
917
  ) -> NoReturn:
939
918
  lines = ["*** Begin Not Deterministic Debug ***"]
940
- if hasattr(node, "iteration"):
941
- lines.append(f"Previous iteration: {node.iteration}")
919
+ if getattr(node, "iteration", None) is not None:
920
+ lines.append(f"Previous iteration: {node.iteration}") # type: ignore
942
921
  if hasattr(node, "expr"):
943
- lines.append(f"Previous SMT expression: {node.expr}")
922
+ lines.append(f"Previous SMT expression: {node.expr}") # type: ignore
944
923
  if expr is not None:
945
924
  lines.append(f"Current SMT expression: {expr}")
946
925
  if not stacktail:
@@ -966,11 +945,11 @@ class StateSpace:
966
945
  def find_model_value(self, expr: z3.ExprRef) -> Any:
967
946
  with NoTracing():
968
947
  while True:
969
- if self._search_position.is_stem():
970
- self._search_position = self._search_position.grow_into(
948
+ if isinstance(self._search_position, NodeStem):
949
+ self._search_position = self.grow_into(
971
950
  ModelValueNode(self._random, expr, self.solver)
972
951
  )
973
- node = self._search_position.simplify()
952
+ node = self._search_position
974
953
  if isinstance(node, SearchLeaf):
975
954
  raise CrossHairInternal(
976
955
  f"Cannot use symbolics; path is already terminated"
@@ -1109,14 +1088,14 @@ class StateSpace:
1109
1088
  if not prefer_true(check_ret):
1110
1089
  raise IgnoreAttempt("deferred assumption failed: " + description)
1111
1090
  self.is_detached = True
1112
- if not self._search_position.is_stem():
1091
+ if not isinstance(self._search_position, NodeStem):
1113
1092
  self.raise_not_deterministic(
1114
1093
  self._search_position,
1115
- f"Expect to detach path at a stem node, not at this node: {self._search_position.simplify()}",
1094
+ f"Expect to detach path at a stem node, not at this node: {self._search_position}",
1116
1095
  currently_handling=currently_handling,
1117
1096
  )
1118
- node = self._search_position.grow_into(DeatchedPathNode())
1119
- assert node.child.is_stem()
1097
+ node = self.grow_into(DetachedPathNode())
1098
+ assert isinstance(node.child, NodeStem)
1120
1099
  self.choices_made.append(node)
1121
1100
  self._search_position = node.child
1122
1101
  debug("Detached from search tree")
@@ -1130,12 +1109,9 @@ class StateSpace:
1130
1109
  self, analysis: CallAnalysis
1131
1110
  ) -> Tuple[Optional[CallAnalysis], bool]:
1132
1111
  # In some cases, we might ignore an attempt while not at a leaf.
1133
- if self._search_position.is_stem():
1134
- self._search_position = self._search_position.grow_into(
1135
- SearchLeaf(analysis)
1136
- )
1112
+ if isinstance(self._search_position, NodeStem):
1113
+ self._search_position = self.grow_into(SearchLeaf(analysis))
1137
1114
  else:
1138
- self._search_position = self._search_position.simplify()
1139
1115
  assert isinstance(self._search_position, SearchTreeNode)
1140
1116
  self._search_position.exhausted = True
1141
1117
  self._search_position.result = analysis
crosshair/test_util.py CHANGED
@@ -1,9 +1,21 @@
1
1
  import pathlib
2
2
  import sys
3
- from collections.abc import Container
4
3
  from copy import deepcopy
5
4
  from dataclasses import dataclass, replace
6
- from typing import Callable, Iterable, List, Mapping, Optional, Sequence, 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
+ )
7
19
 
8
20
  from crosshair.core import (
9
21
  AnalysisMessage,
@@ -29,6 +41,13 @@ from crosshair.util import (
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
@@ -123,34 +142,39 @@ def check_messages(checkables: Iterable[Checkable], **kw) -> ComparableLists:
123
142
  return (msgs, [AnalysisMessage(**kw)])
124
143
 
125
144
 
126
- def flexible_equal(a, b):
145
+ _NAN_ABLE = (Decimal, Real)
146
+
147
+
148
+ def flexible_equal(a: object, b: object) -> bool:
127
149
  if a is b:
128
150
  return True
129
151
  if type(a) is type(b) and type(a).__eq__ is object.__eq__:
130
152
  # If types match and it uses identity-equals, we can't do much. Assume equal.
131
153
  return True
132
- if a != a and b != b: # handle float('nan')
154
+ if isinstance(a, _NAN_ABLE) and isinstance(b, _NAN_ABLE) and isnan(a) and isnan(b):
133
155
  return True
134
156
  if (
135
157
  is_iterable(a)
136
- and not isinstance(a, Container)
158
+ and not isinstance(a, Collection)
137
159
  and is_iterable(b)
138
- and not isinstance(b, Container)
160
+ and not isinstance(b, Collection)
139
161
  ): # unsized iterables compare by contents
140
- a, b = list(a), list(b)
141
- if type(a) == type(b):
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
+ ):
142
168
  # Recursively apply flexible_equal for most containers:
169
+ if len(a) != len(b): # type: ignore
170
+ return False
143
171
  if isinstance(a, Mapping):
144
- if len(a) != len(b):
145
- return False
146
172
  for k, v in a.items():
147
- if not flexible_equal(v, b[k]):
173
+ if not flexible_equal(v, b.get(k, _MISSING)): # type: ignore
148
174
  return False
149
175
  return True
150
- if isinstance(a, Container) and not isinstance(a, (str, bytes)):
151
- if len(a) != len(b):
152
- return False
153
- return all(flexible_equal(ai, bi) for ai, bi in zip(a, b))
176
+ else:
177
+ return all(flexible_equal(ai, bi) for ai, bi in zip(a, b)) # type: ignore
154
178
 
155
179
  return a == b
156
180
 
@@ -14,3 +14,13 @@ def test_flexible_equal():
14
14
  assert flexible_equal(gen(), iter([11, 22]))
15
15
  assert not flexible_equal(gen(), iter([11, 22, 33]))
16
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)
crosshair/type_repo.py CHANGED
@@ -20,6 +20,8 @@ _IGNORED_MODULE_ROOTS = {
20
20
  "pkg_resources",
21
21
  "pytest",
22
22
  "py", # (part of pytest)
23
+ # Disabled because the import attempt can cause problems:
24
+ "numpy", # importing numpy/testing/_private/utils.py attempts a subprocess call
23
25
  }
24
26
 
25
27
 
@@ -59,8 +61,6 @@ def get_subclass_map() -> Dict[type, List[type]]:
59
61
  if module_name.split(".", 1)[0] in _IGNORED_MODULE_ROOTS:
60
62
  continue
61
63
  if module is None:
62
- # We set the internal _datetime module to None, ensuring that
63
- # we don't load the C implementation.
64
64
  continue
65
65
  try:
66
66
  module_classes = inspect.getmembers(module, inspect.isclass)
crosshair/util.py CHANGED
@@ -54,6 +54,8 @@ from crosshair.tracers import COMPOSITE_TRACER, NoTracing, ResumedTracing, is_tr
54
54
  _DEBUG_STREAM: Optional[TextIO] = None
55
55
 
56
56
 
57
+ # NOTE: many of these is_* functions should use a TypeGuard in 3.10 (or even TypeIs in 3.13)
58
+
57
59
  if sys.version_info >= (3, 12):
58
60
  from collections.abc import Buffer
59
61
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: crosshair-tool
3
- Version: 0.0.88
3
+ Version: 0.0.93
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
@@ -47,6 +47,7 @@ Requires-Dist: setuptools; extra == "dev"
47
47
  Requires-Dist: sphinx>=3.4.3; extra == "dev"
48
48
  Requires-Dist: sphinx-rtd-theme>=0.5.1; extra == "dev"
49
49
  Requires-Dist: rst2pdf>=0.102; extra == "dev"
50
+ Requires-Dist: z3-solver==4.14.1.0; extra == "dev"
50
51
  Dynamic: author
51
52
  Dynamic: author-email
52
53
  Dynamic: classifier
@@ -1,5 +1,5 @@
1
- _crosshair_tracers.cpython-313-x86_64-linux-musl.so,sha256=Z2SgScHtOg2z92ZHPdmaV5EYqiTFsEOVcOGGI8g95AQ,121680
2
- crosshair/__init__.py,sha256=SSP_9GXeo_0ypAR-f-Zt32yVGvXY3tEY9SWKDgy_m_A,936
1
+ _crosshair_tracers.cpython-313-x86_64-linux-musl.so,sha256=3LUEPD-dVqiKIuAp-hqgfrasmI2ai2dbn1IODJAK8Lc,121688
2
+ crosshair/__init__.py,sha256=LfHa3bjHbl9WlLnVFSn-2R3qe0yEptc8-0Edze71rmo,936
3
3
  crosshair/__main__.py,sha256=zw9Ylf8v2fGocE57o4FqvD0lc7U4Ld2GbeCGxRWrpqo,252
4
4
  crosshair/_mark_stacks.h,sha256=j86qubOUvVhoR19d74iQ084RrTEq8M6oT4wJsGQUySY,28678
5
5
  crosshair/_preliminaries_test.py,sha256=r2PohNNMfIkDqsnvI6gKlJTbwBaZA9NQJueQfJMN2Eo,504
@@ -16,7 +16,7 @@ crosshair/condition_parser_test.py,sha256=eUQYnVkHewn8qg-XbzcElb0mHkPxPJAX548dPV
16
16
  crosshair/conftest.py,sha256=BkLszApkdy6FrvzaHO7xh8_BJrG9AfytFTse-HuQVvg,653
17
17
  crosshair/copyext.py,sha256=GBGQP9YAHoezLXwb_M59Hh1VXSou5EQt4ZmmUA0T_og,4899
18
18
  crosshair/copyext_test.py,sha256=uJzdC9m2FqMjqQ-ITFoP0MZg3OCiO8paU-d533KocD8,2108
19
- crosshair/core.py,sha256=K7147xrvcvtrP1T9oIRywq2uHMMsIu8ZwFZWBQXPozg,63634
19
+ crosshair/core.py,sha256=YegFbe3RMJwxHCadhJ42gUJjL9mICaLAE_Dmb1-lsK4,63750
20
20
  crosshair/core_and_libs.py,sha256=8FGL62GnyX6WHOqKh0rqJ0aJ_He5pwZm_zwPXTaPqhI,3963
21
21
  crosshair/core_regestered_types_test.py,sha256=er3ianvu-l0RS-WrS46jmOWr4Jq06Cec9htAXGXJSNg,2099
22
22
  crosshair/core_test.py,sha256=H6qZFBFuUhgh9qWgxxc1Cs7xuC8s6FYvewmTYUh0Dpg,32200
@@ -39,12 +39,12 @@ crosshair/opcode_intercept.py,sha256=z4Yb9prYE2UK21AxhjAeXyXAk5IriDuCSSCeNhbDu2A
39
39
  crosshair/opcode_intercept_test.py,sha256=Si3rJQR5cs5d4uB8uwE2K8MjP8rE1a4yHkjXzhfS10A,9241
40
40
  crosshair/options.py,sha256=htQNgnrpoRjSNq6rfLBAF8nos-NNIwmP6tQYyI8ugsM,6775
41
41
  crosshair/options_test.py,sha256=lzA-XtwEwQPa4wV1wwhCRKhyLOvIhThU9WK5QRaRbxQ,379
42
- crosshair/patch_equivalence_test.py,sha256=mvYpsbHZS2AiQra-jK8T8QywAG1gNNCUNQPPWI9k5t8,2506
42
+ crosshair/patch_equivalence_test.py,sha256=eoLaGRvrR9nGUO_ybZ9XsWhs5ejC4IEPd0k-ihG3Nsg,2580
43
43
  crosshair/path_cover.py,sha256=wV0Vy8IPDzqXQ2VI8a94FxltS9p-Y1oF17OKePjvpgs,6710
44
44
  crosshair/path_cover_test.py,sha256=U46zw4-m7yAXhu8-3Xnhvf-_9Ov5ivfCAm5euGwpRFA,4089
45
45
  crosshair/path_search.py,sha256=wwZjp-3E4dENnJa6BlnSq8FARkIx0PyUYc7kvH32A2k,5588
46
46
  crosshair/path_search_test.py,sha256=7cqzAMXUYAtA00mq9XR5AaZChqeQyXyCfuuv53_51pk,1692
47
- crosshair/pathing_oracle.py,sha256=4mvZuXb-JXIxojidk5-43PXnPfIfAEFa-1LvAvVlCqk,9303
47
+ crosshair/pathing_oracle.py,sha256=3M93zXMorWr8m5c1KM2zGU5X1M3lcV-AIw0lX74eN00,9219
48
48
  crosshair/pure_importer.py,sha256=-t4eowrZOQmfqK1N2tjI5POoaxRGavytwMmbRivelFg,878
49
49
  crosshair/pure_importer_test.py,sha256=Xjtlwn1mj7g-6VA87lrvzfUADCjlmn1wgHtbrnc0uuY,421
50
50
  crosshair/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -54,18 +54,18 @@ crosshair/simplestructs.py,sha256=CiZSuHH_j_bYitaW-n7vWd_42xSyV6Jh8es3BQLlcHk,34
54
54
  crosshair/simplestructs_test.py,sha256=6uDdrSISHLhwnFuESkR8mUGw7m1llM6vCNDFChkfSs8,8639
55
55
  crosshair/smtlib.py,sha256=hh-P32KHoH9BCq3oDYGp2PfOeOb8CwDj8tTkgqroLD8,689
56
56
  crosshair/smtlib_test.py,sha256=edzEn19u2YYHxSzG9RrMiu2HTiEexAuehC3IlG9LuJM,511
57
- crosshair/statespace.py,sha256=KeIIBwBKxDgQO0ML2LvYXhS5DgmelaYF-RRXopR9lqA,41794
57
+ crosshair/statespace.py,sha256=TVjUhFZU0x72oLPN7-eRu7PoMh1OiQOy0uMASsFb_Qw,41236
58
58
  crosshair/statespace_test.py,sha256=LOblIarBbcB9oD_gVR5kK_4P2PWQymVGgJr3wNqP3Fs,2621
59
59
  crosshair/stubs_parser.py,sha256=rlBTQus5BlZ3Ygg6Xzk5dbQbDtRpv6w9i2HQmGrPVmc,14240
60
60
  crosshair/stubs_parser_test.py,sha256=0itTT0Udul_51RJXNv6KB97z44gYze6NZfKJL7yIDzA,1228
61
- crosshair/test_util.py,sha256=HQ1nd5KZJfrZ8S5iP0EkltCtGu0PGELB3hCcPXk78ow,10073
62
- crosshair/test_util_test.py,sha256=FIOWVBMiF-zyq0pGsDQ8W6lB6_E9sGpD80dioHHETyQ,512
61
+ crosshair/test_util.py,sha256=D9-f-DdzJemfAUkQL0cwKxPL8RZ-5gkVmghyRcKlBJI,10367
62
+ crosshair/test_util_test.py,sha256=_r8DtAI5b1Yn1ruv9o51FWHmARII3-WDkWWnnY1iaAw,943
63
63
  crosshair/tracers.py,sha256=_jaSDgZ_pYdqacWE_msXn7W7CoSdQ_-7hlrxa891oHo,17139
64
64
  crosshair/tracers_test.py,sha256=EBK_ZCy2MsxqmEaGjo0uw9zAztW9O6fhCW_0PJxyTS8,3270
65
- crosshair/type_repo.py,sha256=dqMnDNgBqchRwsbXJcZmPyPHfiQC9kqFAB7z-zoA_0I,4649
65
+ crosshair/type_repo.py,sha256=x_eK-YlcHv_dxDKy6m7ty0zNO6y058o3r6QJ55RcG3s,4664
66
66
  crosshair/unicode_categories.py,sha256=g4pnUPanx8KkpwI06ZUGx8GR8Myruf_EpTjyti_V4z8,333519
67
67
  crosshair/unicode_categories_test.py,sha256=ZAU37IDGm9PDvwy_CGFcrF9Waa8JuUNdI4aq74wkB6c,739
68
- crosshair/util.py,sha256=eddHHvkn9NnsfYahpuIF4Xp03kIiQ8EJUaliFEO4XG4,22124
68
+ crosshair/util.py,sha256=sIfaKFvNHR5w5VCno2d15d_LyfwoewKqRzJ_3ZNV-Vc,22218
69
69
  crosshair/util_test.py,sha256=_KTQ0O4cLhF1pAeB8Y8Cyqbd0UyZf5KxJUaiA-ew-tE,4676
70
70
  crosshair/watcher.py,sha256=kCCMlLe2KhW5MbEbMmixNRjRAvu5CypIAGd1V_YZ9QM,10048
71
71
  crosshair/watcher_test.py,sha256=Ef1YSwy68wWPR5nPjwvEKPqxltI9pE9lTbnesmDy3Bk,2764
@@ -100,7 +100,7 @@ crosshair/libimpl/binascii_ch_test.py,sha256=hFqSfF1Q8jl2LNBIWaQ6vBJIIshPOmSwrR0
100
100
  crosshair/libimpl/binascii_test.py,sha256=LOBqLAJ77Kx8vorjVTaT3X0Z93zw4P5BvwUapMCiSLg,1970
101
101
  crosshair/libimpl/binasciilib.py,sha256=9w4C37uxRNOmz9EUuhJduHlMKn0f7baY5fwwdvx1uto,5070
102
102
  crosshair/libimpl/bisectlib_test.py,sha256=ZQtYmBYD0Pb1IiFelsgdvqyeUMKaqaDb1BRb87LTSbI,753
103
- crosshair/libimpl/builtinslib.py,sha256=0NRP8l_weL0kh050cOKMrqK8y-glh3NySYLJYjdHVz8,171622
103
+ crosshair/libimpl/builtinslib.py,sha256=UU2WDnQbPQMIETjvKfJlDXAFYyAXcRVvGt4SFYs-Jgk,171663
104
104
  crosshair/libimpl/builtinslib_ch_test.py,sha256=W4wWapqlxSjsC5XgREfgxS9e_iwKxgNQhbFE3umUfNI,30504
105
105
  crosshair/libimpl/builtinslib_test.py,sha256=Y_jRe5J6pPaJ_Nuk1lJ1biP5yczsfCj--NgNhwcbAfQ,90654
106
106
  crosshair/libimpl/codecslib.py,sha256=lB87T1EYSBh4JXaqzjSpQG9CMfKtgckwA7f6OIR0S-Q,2668
@@ -118,7 +118,7 @@ crosshair/libimpl/decimallib_ch_test.py,sha256=FVCY4KB8lJWeRKnz8txNkEc1te4QRKQYJ
118
118
  crosshair/libimpl/decimallib_test.py,sha256=393MkVB9-LPcA7JJK6wGAbDyd-YejkjwrXRaEDaVhjM,2238
119
119
  crosshair/libimpl/encodings_ch_test.py,sha256=0qLsioOuFUZkOjP4J9Wct4CGBaBY8BnHx9paZHnIofI,2513
120
120
  crosshair/libimpl/fractionlib.py,sha256=qdbiAHHC480YdKq3wYK_piZ3UD7oT64YfuNclypMUfQ,458
121
- crosshair/libimpl/fractionlib_test.py,sha256=lLMbGvzP5E_tnZ2Yi5_MawRxwqsSJZRW1A7vtgSSBvM,1638
121
+ crosshair/libimpl/fractionlib_test.py,sha256=g7uNHTfzDebyc-SgH_4ziUAz7rJLZlHGZmPpny2P6hs,2357
122
122
  crosshair/libimpl/functoolslib.py,sha256=LnMtmq2Sawde6XtPd6Yg_YOc6S5ai-pMpBWPQAkR3X4,783
123
123
  crosshair/libimpl/functoolslib_test.py,sha256=DswrS51n93EaxPvDGB-d3tZSLawEp38zQ5sNdYlbn50,1114
124
124
  crosshair/libimpl/hashliblib.py,sha256=Ki_cw28OnhZExgKbSoh5GaDbBfNRIOqH7O2aYQJGS3M,1234
@@ -167,9 +167,9 @@ crosshair/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
167
167
  crosshair/tools/check_help_in_doc.py,sha256=P21AH3mYrTVuBgWD6v65YXqBqmqpQDUTQeoZ10rB6TU,8235
168
168
  crosshair/tools/check_init_and_setup_coincide.py,sha256=kv61bXqKSKF_5J-kLNEhCrCPyszg7iZQWDu_Scnec98,3502
169
169
  crosshair/tools/generate_demo_table.py,sha256=0SeO0xQdiT-mbLNHt4rYL0wcc2DMh0v3qtzBdoQonDk,3831
170
- crosshair_tool-0.0.88.dist-info/METADATA,sha256=fb65F1neoaCfY1YaRda8cocX3gOjjCWW5it7Xn2DXto,6726
171
- crosshair_tool-0.0.88.dist-info/WHEEL,sha256=4VbEOkf4fdBUBHdV24POjoH-zuik_eIDLSImZZCAQpQ,112
172
- crosshair_tool-0.0.88.dist-info/entry_points.txt,sha256=u5FIPVn1jqn4Kzg5K_iNnbP6L4hQw5FWjQ0UMezG2VE,96
173
- crosshair_tool-0.0.88.dist-info/top_level.txt,sha256=2jLWtM-BWg_ZYNbNfrcds0HFZD62a6J7ZIbcgcQrRk4,29
174
- crosshair_tool-0.0.88.dist-info/RECORD,,
175
- crosshair_tool-0.0.88.dist-info/licenses/LICENSE,sha256=NVyMvNqn1pH6RSHs6RWRcJyJvORnpgGFBlF73buqYJ0,4459
170
+ crosshair_tool-0.0.93.dist-info/METADATA,sha256=EYRE-NAWbA23Od5TfsbHDQ_R70UNonJPNCdNawPlT-c,6777
171
+ crosshair_tool-0.0.93.dist-info/WHEEL,sha256=4VbEOkf4fdBUBHdV24POjoH-zuik_eIDLSImZZCAQpQ,112
172
+ crosshair_tool-0.0.93.dist-info/entry_points.txt,sha256=u5FIPVn1jqn4Kzg5K_iNnbP6L4hQw5FWjQ0UMezG2VE,96
173
+ crosshair_tool-0.0.93.dist-info/top_level.txt,sha256=2jLWtM-BWg_ZYNbNfrcds0HFZD62a6J7ZIbcgcQrRk4,29
174
+ crosshair_tool-0.0.93.dist-info/RECORD,,
175
+ crosshair_tool-0.0.93.dist-info/licenses/LICENSE,sha256=NVyMvNqn1pH6RSHs6RWRcJyJvORnpgGFBlF73buqYJ0,4459