crosshair-tool 0.0.85__cp313-cp313-macosx_10_13_universal2.whl → 0.0.87__cp313-cp313-macosx_10_13_universal2.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.

Binary file
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.85" # Do not forget to update in setup.py!
18
+ __version__ = "0.0.87" # Do not forget to update in setup.py!
19
19
  __author__ = "Phillip Schanely"
20
20
  __license__ = "MIT"
21
21
  __status__ = "Alpha"
crosshair/_mark_stacks.h CHANGED
@@ -538,23 +538,6 @@ static const uint8_t _ch_DE_INSTRUMENT[256] = {
538
538
  #endif
539
539
  #endif
540
540
 
541
- /* Get the underlying opcode, stripping instrumentation */
542
- int _ch_Py_GetBaseOpcode(PyCodeObject *code, int i)
543
- {
544
- int opcode = _PyCode_CODE(code)[i].op.code;
545
- if (opcode == INSTRUMENTED_LINE) {
546
- opcode = code->_co_monitoring->lines[i].original_opcode;
547
- }
548
- if (opcode == INSTRUMENTED_INSTRUCTION) {
549
- opcode = code->_co_monitoring->per_instruction_opcodes[i];
550
- }
551
- int deinstrumented = _ch_DE_INSTRUMENT[opcode];
552
- if (deinstrumented) {
553
- return deinstrumented;
554
- }
555
- return _ch_PyOpcode_Deopt[opcode];
556
- }
557
-
558
541
  static int64_t *
559
542
  _ch_mark_stacks(PyCodeObject *code_obj, int len)
560
543
  {
@@ -602,14 +585,14 @@ _ch_mark_stacks(PyCodeObject *code_obj, int len)
602
585
  /* Scan instructions */
603
586
  for (i = 0; i < len;) {
604
587
  int64_t next_stack = stacks[i];
605
- opcode = _ch_Py_GetBaseOpcode(code_obj, i);
588
+ opcode = code[i].op.code;
606
589
  uint8_t trace_enabled_here = _ch_TRACABLE_INSTRUCTIONS[opcode];
607
590
  enabled_tracing[i] |= trace_enabled_here;
608
591
  int oparg = 0;
609
592
  while (opcode == EXTENDED_ARG) {
610
593
  oparg = (oparg << 8) | code[i].op.arg;
611
594
  i++;
612
- opcode = _ch_Py_GetBaseOpcode(code_obj, i);
595
+ opcode = code[i].op.code;
613
596
  stacks[i] = next_stack;
614
597
  }
615
598
  int next_i = i + _ch_PyOpcode_Caches[opcode] + 1;
crosshair/auditwall.py CHANGED
@@ -1,4 +1,5 @@
1
1
  import importlib
2
+ import inspect
2
3
  import os
3
4
  import sys
4
5
  import traceback
@@ -28,9 +29,10 @@ def reject(event: str, args: Tuple) -> None:
28
29
 
29
30
 
30
31
  def inside_module(modules: Iterable[ModuleType]) -> bool:
31
- files = {m.__file__ for m in modules}
32
- for frame, lineno in traceback.walk_stack(None):
33
- if frame.f_code.co_filename in files:
32
+ """Checks whether the current call stack is inside one of the given modules."""
33
+ for frame, _lineno in traceback.walk_stack(None):
34
+ frame_module = inspect.getmodule(frame)
35
+ if frame_module and frame_module in modules:
34
36
  return True
35
37
  return False
36
38
 
@@ -60,7 +62,7 @@ def check_msvcrt_open(event: str, args: Tuple) -> None:
60
62
  _MODULES_THAT_CAN_POPEN: Optional[Set[ModuleType]] = None
61
63
 
62
64
 
63
- def modules_with_allowed_popen():
65
+ def modules_with_allowed_subprocess():
64
66
  global _MODULES_THAT_CAN_POPEN
65
67
  if _MODULES_THAT_CAN_POPEN is None:
66
68
  allowed_module_names = ("_aix_support", "ctypes", "platform", "uuid")
@@ -74,13 +76,14 @@ def modules_with_allowed_popen():
74
76
 
75
77
 
76
78
  def check_subprocess(event: str, args: Tuple) -> None:
77
- if not inside_module(modules_with_allowed_popen()):
79
+ if not inside_module(modules_with_allowed_subprocess()):
78
80
  reject(event, args)
79
81
 
80
82
 
81
83
  _SPECIAL_HANDLERS = {
82
84
  "open": check_open,
83
85
  "subprocess.Popen": check_subprocess,
86
+ "os.posix_spawn": check_subprocess,
84
87
  "msvcrt.open_osfhandle": check_msvcrt_open,
85
88
  }
86
89
 
@@ -267,7 +267,7 @@ class SymbolicValue(CrossHairValue):
267
267
  self.snapshot = SnapshotRef(-1)
268
268
  self.python_type = typ
269
269
  if type(smtvar) is str:
270
- self.var = self.__init_var__(typ, smtvar)
270
+ self.var: Any = self.__init_var__(typ, smtvar)
271
271
  else:
272
272
  self.var = smtvar
273
273
  # TODO test that smtvar's sort matches expected?
@@ -402,6 +402,10 @@ def crosshair_types_for_python_type(
402
402
  return _PYTYPE_TO_WRAPPER_TYPE.get(origin, ())
403
403
 
404
404
 
405
+ def python_types_using_atomic_symbolics() -> Iterable[Type[AtomicSymbolicValue]]:
406
+ return _PYTYPE_TO_WRAPPER_TYPE.keys()
407
+
408
+
405
409
  class ModelingDirector:
406
410
  def __init__(self, *a) -> None:
407
411
  # Maps python type to the symbolic type we've chosen to represent it (on this iteration)
@@ -834,19 +838,6 @@ def setup_binops():
834
838
 
835
839
  setup_binop(_, {ops.lshift, ops.rshift})
836
840
 
837
- _AND_MASKS_TO_MOD = {
838
- # It's common to use & to mask low bits. We can avoid realization by converting
839
- # these situations into mod operations.
840
- 0x01: 2,
841
- 0x03: 4,
842
- 0x07: 8,
843
- 0x0F: 16,
844
- 0x1F: 32,
845
- 0x3F: 64,
846
- 0x7F: 128,
847
- 0xFF: 256,
848
- }
849
-
850
841
  def _(op: BinFn, a: Integral, b: Integral):
851
842
  with NoTracing():
852
843
  if isinstance(b, SymbolicInt):
@@ -857,9 +848,12 @@ def setup_binops():
857
848
  b = realize(b)
858
849
  if b == 0:
859
850
  return 0
860
- mask_mod = _AND_MASKS_TO_MOD.get(b)
861
- if mask_mod and isinstance(a, SymbolicInt):
862
- if context_statespace().smt_fork(a.var >= 0, probability_true=0.75):
851
+ # It's common to use & to mask low bits. We can avoid realization by converting
852
+ # these situations into mod operations.
853
+ mask_mod = b + 1
854
+ if b > 0 and mask_mod & b == 0 and isinstance(a, SymbolicInt): # type: ignore
855
+ space = context_statespace()
856
+ if space.smt_fork(a.var >= 0, probability_true=0.75):
863
857
  return SymbolicInt(a.var % mask_mod)
864
858
  else:
865
859
  return SymbolicInt(b - ((-a.var - 1) % mask_mod))
@@ -3488,10 +3482,6 @@ class LazyIntSymbolicStr(AnySymbolicStr, CrossHairValue):
3488
3482
  otherpoints = [ord(ch) for ch in other]
3489
3483
  with ResumedTracing():
3490
3484
  return mypoints.__eq__(otherpoints)
3491
- elif isinstance(other, SeqBasedSymbolicStr):
3492
- with ResumedTracing():
3493
- otherpoints = [ord(ch) for ch in other]
3494
- return mypoints.__eq__(otherpoints)
3495
3485
  else:
3496
3486
  return NotImplemented
3497
3487
 
@@ -3660,257 +3650,6 @@ class LazyIntSymbolicStr(AnySymbolicStr, CrossHairValue):
3660
3650
  return self._find(substr, start, end, from_right=True)
3661
3651
 
3662
3652
 
3663
- class SeqBasedSymbolicStr(AtomicSymbolicValue, SymbolicSequence, AnySymbolicStr):
3664
- def __init__(self, smtvar: Union[str, z3.ExprRef], typ: Type = str):
3665
- assert typ == str
3666
- SymbolicValue.__init__(self, smtvar, typ)
3667
- self.item_pytype = str
3668
- if isinstance(smtvar, str):
3669
- # Constrain fresh strings to valid codepoints
3670
- space = context_statespace()
3671
- idxvar = z3.Int("idxvar" + space.uniq())
3672
- z3seq = self.var
3673
- space.add(
3674
- z3.ForAll(
3675
- [idxvar], z3.And(0 <= z3seq[idxvar], z3seq[idxvar] <= maxunicode)
3676
- )
3677
- )
3678
-
3679
- @classmethod
3680
- def _ch_smt_sort(cls) -> z3.SortRef:
3681
- return z3.SeqSort(z3.IntSort())
3682
-
3683
- @classmethod
3684
- def _pytype(cls) -> Type:
3685
- return str
3686
-
3687
- @classmethod
3688
- def _smt_promote_literal(cls, literal) -> Optional[z3.SortRef]:
3689
- if isinstance(literal, str):
3690
- if len(literal) <= 1:
3691
- if len(literal) == 0:
3692
- return z3.Empty(z3.SeqSort(z3.IntSort()))
3693
- return z3.Unit(z3IntVal(ord(literal)))
3694
- return z3.Concat([z3.Unit(z3IntVal(ord(ch))) for ch in literal])
3695
- return None
3696
-
3697
- def __ch_realize__(self) -> object:
3698
- codepoints = context_statespace().find_model_value(self.var)
3699
- return "".join(chr(x) for x in codepoints)
3700
-
3701
- def __copy__(self):
3702
- return SeqBasedSymbolicStr(self.var)
3703
-
3704
- def __hash__(self):
3705
- return hash(self.__str__())
3706
-
3707
- @staticmethod
3708
- def _concat_strings(
3709
- a: Union[str, "SeqBasedSymbolicStr"], b: Union[str, "SeqBasedSymbolicStr"]
3710
- ) -> Union[str, "SeqBasedSymbolicStr"]:
3711
- assert not is_tracing()
3712
- # Assumes at least one argument is symbolic and not tracing
3713
- if isinstance(a, SeqBasedSymbolicStr) and isinstance(b, SeqBasedSymbolicStr):
3714
- return SeqBasedSymbolicStr(a.var + b.var)
3715
- elif isinstance(a, str) and isinstance(b, SeqBasedSymbolicStr):
3716
- return SeqBasedSymbolicStr(
3717
- SeqBasedSymbolicStr._coerce_to_smt_sort(a) + b.var
3718
- )
3719
- else:
3720
- assert isinstance(a, SeqBasedSymbolicStr)
3721
- assert isinstance(b, str)
3722
- return SeqBasedSymbolicStr(
3723
- a.var + SeqBasedSymbolicStr._coerce_to_smt_sort(b)
3724
- )
3725
-
3726
- def __add__(self, other):
3727
- with NoTracing():
3728
- if isinstance(other, (SeqBasedSymbolicStr, str)):
3729
- return SeqBasedSymbolicStr._concat_strings(self, other)
3730
- if isinstance(other, AnySymbolicStr):
3731
- return NotImplemented
3732
- raise TypeError
3733
-
3734
- def __radd__(self, other):
3735
- with NoTracing():
3736
- if isinstance(other, (SeqBasedSymbolicStr, str)):
3737
- return SeqBasedSymbolicStr._concat_strings(other, self)
3738
- if isinstance(other, AnySymbolicStr):
3739
- return NotImplemented
3740
- raise TypeError
3741
-
3742
- def __mul__(self, other):
3743
- if isinstance(other, Integral):
3744
- if other <= 1:
3745
- return self if other == 1 else ""
3746
- # Note that in SymbolicInt, we attempt string multiplication via regex.
3747
- # Z3 cannot do much with a symbolic regex, so we case-split on
3748
- # the repetition count.
3749
- return SeqBasedSymbolicStr(z3.Concat(*[self.var for _ in range(other)]))
3750
- return NotImplemented
3751
-
3752
- __rmul__ = __mul__
3753
-
3754
- def __mod__(self, other):
3755
- return self.__str__() % realize(other)
3756
-
3757
- def __contains__(self, other):
3758
- with NoTracing():
3759
- forced = force_to_smt_sort(other, SeqBasedSymbolicStr)
3760
- return SymbolicBool(z3.Contains(self.var, forced))
3761
-
3762
- def __getitem__(self, i: Union[int, slice]):
3763
- with NoTracing():
3764
- idx_or_pair = process_slice_vs_symbolic_len(
3765
- context_statespace(), i, z3.Length(self.var)
3766
- )
3767
- if isinstance(idx_or_pair, tuple):
3768
- (start, stop) = idx_or_pair
3769
- smt_result = z3.Extract(self.var, start, stop - start)
3770
- else:
3771
- smt_result = z3.Unit(self.var[idx_or_pair])
3772
- return SeqBasedSymbolicStr(smt_result)
3773
-
3774
- def endswith(self, substr):
3775
- with NoTracing():
3776
- smt_substr = force_to_smt_sort(substr, SeqBasedSymbolicStr)
3777
- return SymbolicBool(z3.SuffixOf(smt_substr, self.var))
3778
-
3779
- def find(self, substr, start=None, end=None):
3780
- if not isinstance(substr, str):
3781
- raise TypeError
3782
- with NoTracing():
3783
- space = context_statespace()
3784
- smt_my_len = z3.Length(self.var)
3785
- if start is None and end is None:
3786
- smt_start = z3IntVal(0)
3787
- smt_end = smt_my_len
3788
- smt_str = self.var
3789
- if len(substr) == 0:
3790
- return 0
3791
- else:
3792
- (smt_start, smt_end) = flip_slice_vs_symbolic_len(
3793
- space, slice(start, end, None), smt_my_len
3794
- )
3795
- if len(substr) == 0:
3796
- # Add oddity of CPython. We can find the empty string when over-slicing
3797
- # off the left side of the string, but not off the right:
3798
- # ''.find('', 3, 4) == -1
3799
- # ''.find('', -4, -3) == 0
3800
- if space.smt_fork(smt_start > smt_my_len):
3801
- return -1
3802
- elif space.smt_fork(smt_start > 0):
3803
- return SymbolicInt(smt_start)
3804
- else:
3805
- return 0
3806
- (smt_start, smt_end) = clip_range_to_symbolic_len(
3807
- space, smt_start, smt_end, smt_my_len
3808
- )
3809
- smt_str = z3.SubString(self.var, smt_start, smt_end - smt_start)
3810
-
3811
- smt_sub = force_to_smt_sort(substr, SeqBasedSymbolicStr)
3812
- if space.smt_fork(z3.Contains(smt_str, smt_sub)):
3813
- return SymbolicInt(z3.IndexOf(smt_str, smt_sub, 0) + smt_start)
3814
- else:
3815
- return -1
3816
-
3817
- def partition(self, sep: str):
3818
- if not isinstance(sep, str):
3819
- raise TypeError
3820
- if len(sep) == 0:
3821
- raise ValueError
3822
- with NoTracing():
3823
- space = context_statespace()
3824
- smt_str = self.var
3825
- smt_sep = force_to_smt_sort(sep, SeqBasedSymbolicStr)
3826
- if space.smt_fork(z3.Contains(smt_str, smt_sep)):
3827
- uniq = space.uniq()
3828
- # Divide my contents into 4 concatenated parts:
3829
- prefix = SeqBasedSymbolicStr(f"prefix{uniq}")
3830
- match1 = SeqBasedSymbolicStr(
3831
- f"match1{uniq}"
3832
- ) # the first character of the match
3833
- match_tail = SeqBasedSymbolicStr(f"match_tail{uniq}")
3834
- suffix = SeqBasedSymbolicStr(f"suffix{uniq}")
3835
- space.add(z3.Length(match1.var) == 1)
3836
- space.add(smt_sep == z3.Concat(match1.var, match_tail.var))
3837
- space.add(smt_str == z3.Concat(prefix.var, smt_sep, suffix.var))
3838
- space.add(
3839
- z3.Not(z3.Contains(z3.Concat(match_tail.var, suffix.var), smt_sep))
3840
- )
3841
- return (prefix, sep, suffix)
3842
- else:
3843
- return (self, "", "")
3844
-
3845
- def rfind(self, substr, start=None, end=None) -> Union[int, SymbolicInt]:
3846
- if not isinstance(substr, str):
3847
- raise TypeError
3848
- with NoTracing():
3849
- space = context_statespace()
3850
- smt_my_len = z3.Length(self.var)
3851
- if start is None and end is None:
3852
- smt_start = z3IntVal(0)
3853
- smt_end = smt_my_len
3854
- smt_str = self.var
3855
- if len(substr) == 0:
3856
- return SymbolicInt(smt_my_len)
3857
- else:
3858
- (smt_start, smt_end) = flip_slice_vs_symbolic_len(
3859
- space, slice(start, end, None), smt_my_len
3860
- )
3861
- if len(substr) == 0:
3862
- # Add oddity of CPython. We can find the empty string when over-slicing
3863
- # off the left side of the string, but not off the right:
3864
- # ''.find('', 3, 4) == -1
3865
- # ''.find('', -4, -3) == 0
3866
- if space.smt_fork(smt_start > smt_my_len):
3867
- return -1
3868
- elif space.smt_fork(smt_end < 0):
3869
- return 0
3870
- elif space.smt_fork(smt_end < smt_my_len):
3871
- return SymbolicInt(smt_end)
3872
- else:
3873
- return SymbolicInt(smt_my_len)
3874
- (smt_start, smt_end) = clip_range_to_symbolic_len(
3875
- space, smt_start, smt_end, smt_my_len
3876
- )
3877
- smt_str = z3.SubString(self.var, smt_start, smt_end - smt_start)
3878
- smt_sub = force_to_smt_sort(substr, SeqBasedSymbolicStr)
3879
- if space.smt_fork(z3.Contains(smt_str, smt_sub)):
3880
- uniq = space.uniq()
3881
- # Divide my contents into 4 concatenated parts:
3882
- prefix = SeqBasedSymbolicStr(f"prefix{uniq}")
3883
- match1 = SeqBasedSymbolicStr(f"match1{uniq}")
3884
- match_tail = SeqBasedSymbolicStr(f"match_tail{uniq}")
3885
- suffix = SeqBasedSymbolicStr(f"suffix{uniq}")
3886
- space.add(z3.Length(match1.var) == 1)
3887
- space.add(smt_sub == z3.Concat(match1.var, match_tail.var))
3888
- space.add(smt_str == z3.Concat(prefix.var, smt_sub, suffix.var))
3889
- space.add(
3890
- z3.Not(z3.Contains(z3.Concat(match_tail.var, suffix.var), smt_sub))
3891
- )
3892
- return SymbolicInt(smt_start + z3.Length(prefix.var))
3893
- else:
3894
- return -1
3895
-
3896
- def rpartition(self, sep: str):
3897
- result = self.rsplit(sep, maxsplit=1)
3898
- if len(result) == 1:
3899
- return ("", "", self)
3900
- elif len(result) == 2:
3901
- return (result[0], sep, result[1])
3902
-
3903
- def startswith(self, substr, start=None, end=None):
3904
- if isinstance(substr, tuple):
3905
- return any(self.startswith(s, start, end) for s in substr)
3906
- smt_substr = force_to_smt_sort(substr, SeqBasedSymbolicStr)
3907
- if start is not None or end is not None:
3908
- # TODO: "".startswith("", 1) should be False, not True
3909
- return self[start:end].startswith(substr)
3910
- with NoTracing():
3911
- return SymbolicBool(z3.PrefixOf(smt_substr, self.var))
3912
-
3913
-
3914
3653
  def buffer_to_byte_seq(obj: object) -> Optional[Sequence[int]]:
3915
3654
  if isinstance(obj, (bytes, bytearray)):
3916
3655
  return list(obj)
@@ -4822,11 +4561,6 @@ def _ord(c: str) -> int:
4822
4561
  with NoTracing():
4823
4562
  if isinstance(c, LazyIntSymbolicStr):
4824
4563
  return c._codepoints[0]
4825
- elif isinstance(c, SeqBasedSymbolicStr):
4826
- space = context_statespace()
4827
- ret = SymbolicInt("ord" + space.uniq())
4828
- space.add(c.var == z3.Unit(ret.var))
4829
- return ret
4830
4564
  return ord(realize(c))
4831
4565
 
4832
4566
 
@@ -2117,7 +2117,7 @@ def test_dict___bool___ok() -> None:
2117
2117
  check_states(f, CONFIRMED)
2118
2118
 
2119
2119
 
2120
- def test_dict___iter__() -> None:
2120
+ def test_dict___iter___fail() -> None:
2121
2121
  def f(a: Dict[int, str]) -> List[int]:
2122
2122
  """
2123
2123
  post[a]: 5 in _
@@ -30,6 +30,7 @@ def test_mappingproxy_deep_realize(space):
30
30
  assert type(copy) is MappingProxyType
31
31
  with ResumedTracing():
32
32
  val_from_orig = orig[key]
33
- val_from_copy = copy[key]
33
+ realized_key = deep_realize(key)
34
+ val_from_copy = copy[realized_key]
34
35
  assert type(val_from_orig) is SymbolicInt
35
36
  assert type(val_from_copy) is int
@@ -1,10 +1,11 @@
1
1
  import dis
2
2
  import sys
3
3
  import weakref
4
+ from collections import defaultdict
4
5
  from collections.abc import MutableMapping, Set
5
6
  from sys import version_info
6
7
  from types import CodeType, FrameType
7
- from typing import Callable
8
+ from typing import Any, Callable, Iterable, Mapping, Tuple, Union
8
9
 
9
10
  from crosshair.core import (
10
11
  ATOMIC_IMMUTABLE_TYPES,
@@ -13,20 +14,29 @@ from crosshair.core import (
13
14
  )
14
15
  from crosshair.libimpl.builtinslib import (
15
16
  AnySymbolicStr,
17
+ AtomicSymbolicValue,
18
+ ModelingDirector,
16
19
  SymbolicBool,
17
20
  SymbolicInt,
18
21
  SymbolicList,
22
+ python_types_using_atomic_symbolics,
19
23
  )
20
24
  from crosshair.simplestructs import LinearSet, ShellMutableSet, SimpleDict, SliceView
21
25
  from crosshair.statespace import context_statespace
22
26
  from crosshair.tracers import (
23
27
  COMPOSITE_TRACER,
24
28
  NoTracing,
29
+ ResumedTracing,
25
30
  TracingModule,
26
31
  frame_stack_read,
27
32
  frame_stack_write,
28
33
  )
29
- from crosshair.util import CROSSHAIR_EXTRA_ASSERTS, CrossHairInternal, CrossHairValue
34
+ from crosshair.util import (
35
+ CROSSHAIR_EXTRA_ASSERTS,
36
+ CrossHairInternal,
37
+ CrossHairValue,
38
+ debug,
39
+ )
30
40
  from crosshair.z3util import z3Not, z3Or
31
41
 
32
42
  BINARY_SUBSCR = dis.opmap.get("BINARY_SUBSCR", 256)
@@ -60,13 +70,100 @@ _DEEPLY_CONCRETE_KEY_TYPES = (
60
70
  )
61
71
 
62
72
 
73
+ class MultiSubscriptableContainer:
74
+ """Used for indexing a symbolic (non-slice) key into a concrete container"""
75
+
76
+ def __init__(self, container: Union[list, tuple, dict]):
77
+ self.container = container
78
+
79
+ def __getitem__(self, key: AtomicSymbolicValue) -> object:
80
+ with NoTracing():
81
+ space = context_statespace()
82
+ container = self.container
83
+ if isinstance(container, Mapping):
84
+ kv_pairs: Iterable[Tuple[Any, Any]] = container.items()
85
+ else:
86
+ in_bounds = space.smt_fork(
87
+ z3Or(-len(container) <= key.var, key.var < len(container)),
88
+ desc=f"index_in_bounds",
89
+ probability_true=0.9,
90
+ )
91
+ if not in_bounds:
92
+ raise IndexError
93
+ kv_pairs = enumerate(container)
94
+
95
+ values_by_type = defaultdict(list)
96
+ values_by_id = {}
97
+ keys_by_value_id = defaultdict(list)
98
+ symbolic_for_pytype = space.extra(ModelingDirector).choose
99
+ for cur_key, cur_value in kv_pairs:
100
+ if (
101
+ isinstance(cur_value, AtomicSymbolicValue)
102
+ or type(cur_value) in python_types_using_atomic_symbolics()
103
+ ):
104
+ pytype = (
105
+ cur_value._pytype()
106
+ if isinstance(cur_value, AtomicSymbolicValue)
107
+ else type(cur_value)
108
+ )
109
+ # Some types like real-based float and symbolic types don't cover all values:
110
+ if (
111
+ symbolic_for_pytype(pytype)._smt_promote_literal(cur_value)
112
+ is not None
113
+ ):
114
+ values_by_type[pytype].append((cur_key, cur_value))
115
+ continue
116
+ # No symbolics cover this value, but we might still find repeated values:
117
+ values_by_id[id(cur_value)] = cur_value
118
+ keys_by_value_id[id(cur_value)].append(cur_key)
119
+ for value_type, cur_pairs in values_by_type.items():
120
+ hypothetical_result = symbolic_for_pytype(value_type)(
121
+ "item_at_" + space.uniq(), value_type
122
+ )
123
+ with ResumedTracing():
124
+ condition_pairs = []
125
+ for cur_key, cur_val in cur_pairs:
126
+ keys_equal = key == cur_key
127
+ values_equal = hypothetical_result == cur_val
128
+ with NoTracing():
129
+ if isinstance(keys_equal, SymbolicBool):
130
+ condition_pairs.append((keys_equal, values_equal))
131
+ elif keys_equal is False:
132
+ pass
133
+ else:
134
+ # (because the key must be symbolic, we don't ever expect raw True)
135
+ raise CrossHairInternal(
136
+ f"key comparison type: {type(keys_equal)} {keys_equal}"
137
+ )
138
+ if any(keys_equal for keys_equal, _ in condition_pairs):
139
+ space.add(any([all(pair) for pair in condition_pairs]))
140
+ return hypothetical_result
141
+
142
+ for (value_id, value), probability_true in with_uniform_probabilities(
143
+ values_by_id.items()
144
+ ):
145
+ keys_for_value = keys_by_value_id[value_id]
146
+ with ResumedTracing():
147
+ is_match = any([key == k for k in keys_for_value])
148
+ if isinstance(is_match, SymbolicBool):
149
+ if space.smt_fork(
150
+ is_match.var,
151
+ probability_true=probability_true,
152
+ ):
153
+ return value
154
+ elif is_match:
155
+ return value
156
+
157
+ if type(container) is dict:
158
+ raise KeyError # ( f"Key {key} not found in dict")
159
+ else:
160
+ raise IndexError # (f"Index {key} out of range for list/tuple of length {len(container)}")
161
+
162
+
63
163
  class SymbolicSubscriptInterceptor(TracingModule):
64
164
  opcodes_wanted = frozenset([BINARY_SUBSCR, BINARY_OP])
65
165
 
66
166
  def trace_op(self, frame, codeobj, codenum):
67
- # Note that because this is called from inside a Python trace handler, tracing
68
- # is automatically disabled, so there's no need for a `with NoTracing():` guard.
69
-
70
167
  if codenum == BINARY_OP:
71
168
  oparg = frame_op_arg(frame)
72
169
  if oparg != 26: # subscript operator, NB_SUBSCR
@@ -78,7 +175,14 @@ class SymbolicSubscriptInterceptor(TracingModule):
78
175
  # If we got this far, the index is likely symbolic (or perhaps a slice object)
79
176
  container = frame_stack_read(frame, -2)
80
177
  container_type = type(container)
81
- if container_type is dict:
178
+ if isinstance(key, AtomicSymbolicValue) and type(container) in (
179
+ tuple,
180
+ list,
181
+ dict,
182
+ ):
183
+ wrapped_container = MultiSubscriptableContainer(container)
184
+ frame_stack_write(frame, -2, wrapped_container)
185
+ elif container_type is dict:
82
186
  # SimpleDict won't hash the keys it's given!
83
187
  wrapped_dict = SimpleDict(list(container.items()))
84
188
  frame_stack_write(frame, -2, wrapped_dict)
@@ -90,41 +194,6 @@ class SymbolicSubscriptInterceptor(TracingModule):
90
194
  if isinstance(start, SymbolicInt) or isinstance(stop, SymbolicInt):
91
195
  view_wrapper = SliceView(container, 0, len(container))
92
196
  frame_stack_write(frame, -2, SymbolicList(view_wrapper))
93
- elif container_type is list or container_type is tuple:
94
- if not isinstance(key, SymbolicInt):
95
- return
96
- # We can't stay symbolic with a concrete list and symbolic numeric index.
97
- # But we can make the choice evenly and combine duplicate values, if any.
98
-
99
- space = context_statespace()
100
- in_bounds = space.smt_fork(
101
- z3Or(-len(container) <= key.var, key.var < len(container)),
102
- desc=f"index_in_bounds",
103
- probability_true=0.9,
104
- )
105
- if not in_bounds:
106
- return
107
- # TODO: `container` should be the same (per path node) on every run;
108
- # it would be great to cache this computation somehow.
109
- indices = {}
110
- for idx, value in enumerate(container):
111
- value_id = id(value)
112
- if value_id in indices:
113
- indices[value_id].append(idx)
114
- else:
115
- indices[value_id] = [idx]
116
- for value_id, probability_true in with_uniform_probabilities(
117
- indices.keys()
118
- ):
119
- indices_with_value = indices[value_id]
120
- if space.smt_fork(
121
- z3Or(*[key.var == i for i in indices_with_value]),
122
- desc=f"index_to_{'_or_'.join(map(str, indices_with_value))}",
123
- probability_true=probability_true,
124
- ):
125
- # avoids realization of `key` in case `container` has duplicates
126
- frame_stack_write(frame, -1, indices_with_value[0])
127
- break
128
197
 
129
198
 
130
199
  class SymbolicSliceInterceptor(TracingModule):
@@ -1,10 +1,19 @@
1
+ import math
1
2
  import sys
3
+ from abc import ABCMeta
2
4
  from typing import List, Set
3
5
 
4
6
  import pytest
5
7
 
6
8
  from crosshair.core_and_libs import NoTracing, proxy_for_type, standalone_statespace
7
- from crosshair.statespace import POST_FAIL, MessageType
9
+ from crosshair.libimpl.builtinslib import (
10
+ ModelingDirector,
11
+ RealBasedSymbolicFloat,
12
+ SymbolicBool,
13
+ SymbolicInt,
14
+ SymbolicType,
15
+ )
16
+ from crosshair.statespace import POST_FAIL
8
17
  from crosshair.test_util import check_states
9
18
  from crosshair.tracers import ResumedTracing
10
19
  from crosshair.z3util import z3And
@@ -23,7 +32,73 @@ def test_dict_index():
23
32
  check_states(numstr, POST_FAIL)
24
33
 
25
34
 
26
- def test_concrete_list_with_symbolic_index_deduplicates_values(space):
35
+ def test_dict_index_without_realization(space):
36
+ class WithMeta(metaclass=ABCMeta):
37
+ pass
38
+
39
+ space.extra(ModelingDirector).global_representations[float] = RealBasedSymbolicFloat
40
+ a = {
41
+ -1: WithMeta,
42
+ # ^ tests regression: isinstance(WithMeta(), type) but type(WithMeta) != type
43
+ 0: list,
44
+ 1.0: 10.0,
45
+ 2: 20,
46
+ 3: 30,
47
+ 4: 40,
48
+ ("complex", "key"): 50,
49
+ 6: math.inf,
50
+ 7: math.inf,
51
+ }
52
+ int_key = proxy_for_type(int, "int_key")
53
+ int_key2 = proxy_for_type(int, "int_key2")
54
+ int_key3 = proxy_for_type(int, "int_key3")
55
+ float_key = RealBasedSymbolicFloat("float_key")
56
+ float_key2 = RealBasedSymbolicFloat("float_key2")
57
+ with ResumedTracing():
58
+ # Try some concrete values out first:
59
+ assert a[("complex", "key")] == 50
60
+ assert a[6] == float("inf")
61
+ try:
62
+ a[42]
63
+ assert False, "Expected KeyError for missing key 42"
64
+ except KeyError:
65
+ pass
66
+
67
+ space.add(2 <= int_key)
68
+ space.add(int_key <= 4)
69
+ int_result = a[int_key]
70
+ assert space.is_possible(int_result == 20)
71
+ assert space.is_possible(int_result == 40)
72
+ assert not space.is_possible(int_result == 10)
73
+ space.add(float_key == 1.0)
74
+ float_result = a[float_key]
75
+ assert space.is_possible(float_result == 10.0)
76
+ assert not space.is_possible(float_result == 42.0)
77
+ space.add(float_key2 == 2.0)
78
+ float_result2 = a[float_key2]
79
+ assert space.is_possible(float_result2 == 20)
80
+ space.add(int_key2 == 0)
81
+ int_result2 = a[int_key2]
82
+ assert int_result2 == list
83
+ space.add(any([int_key3 == 6, int_key3 == 7]))
84
+ inf_result = a[int_key3]
85
+ assert inf_result is math.inf
86
+ assert isinstance(int_result, SymbolicInt)
87
+ assert isinstance(float_result, RealBasedSymbolicFloat)
88
+ assert isinstance(float_result2, SymbolicInt)
89
+ assert isinstance(int_result2, SymbolicType)
90
+
91
+
92
+ def test_dict_symbolic_index_miss(space):
93
+ a = {6: 60, 7: 70}
94
+ x = proxy_for_type(int, "x")
95
+ with ResumedTracing():
96
+ space.add(x <= 4)
97
+ with pytest.raises(KeyError):
98
+ result = a[x]
99
+
100
+
101
+ def test_concrete_list_with_symbolic_index_simple(space):
27
102
  haystack = [False] * 13 + [True] + [False] * 11
28
103
 
29
104
  idx = proxy_for_type(int, "idx")
@@ -31,8 +106,12 @@ def test_concrete_list_with_symbolic_index_deduplicates_values(space):
31
106
  space.add(0 <= idx)
32
107
  space.add(idx < len(haystack))
33
108
  ret = haystack[idx]
34
- assert ret
35
- assert not space.is_possible(idx != 13)
109
+ assert isinstance(ret, SymbolicBool)
110
+ with ResumedTracing():
111
+ assert space.is_possible(idx == 13)
112
+ assert space.is_possible(idx == 12)
113
+ space.add(ret)
114
+ assert not space.is_possible(idx == 12)
36
115
 
37
116
 
38
117
  def test_concrete_list_with_symbolic_index_unhashable_values(space):
@@ -204,5 +283,6 @@ def test_identity_operator_does_not_realize_on_differing_types():
204
283
  b1 = proxy_for_type(bool, "b1")
205
284
  choices_made_at_start = len(space.choices_made)
206
285
  space.add(b1)
207
- _ = b1 is 42 # noqa: F632
286
+ fourty_two = 42 # assignment just to avoid lint errors
287
+ b1 is fourty_two
208
288
  assert len(space.choices_made) == choices_made_at_start
crosshair/statespace.py CHANGED
@@ -743,11 +743,13 @@ class StateSpace:
743
743
  model_check_timeout: float,
744
744
  search_root: RootNode,
745
745
  ):
746
- smt_timeout = model_check_timeout * 1000 + 1
747
746
  smt_tactic = z3.Tactic("smt")
748
- if smt_timeout < 1 << 63:
749
- smt_tactic = z3.TryFor(smt_tactic, int(smt_timeout))
750
747
  self.solver = smt_tactic.solver()
748
+ if model_check_timeout < 1 << 63:
749
+ self.smt_timeout: Optional[int] = int(model_check_timeout * 1000 + 1)
750
+ self.solver.set(timeout=self.smt_timeout)
751
+ else:
752
+ self.smt_timeout = None
751
753
  self.solver.set(mbqi=True)
752
754
  # turn off every randomization thing we can think of:
753
755
  self.solver.set("random-seed", 42)
@@ -1097,7 +1099,10 @@ class StateSpace:
1097
1099
  else:
1098
1100
  # Give ourselves a time extension for deferred assumptions and
1099
1101
  # (likely) counterexample generation to follow.
1100
- self.execution_deadline += 2.0
1102
+ self.execution_deadline += 4.0
1103
+ if self.smt_timeout is not None:
1104
+ self.smt_timeout = self.smt_timeout * 2
1105
+ self.solver.set(timeout=self.smt_timeout)
1101
1106
  for description, checker in self._deferred_assumptions:
1102
1107
  with ResumedTracing():
1103
1108
  check_ret = checker()
crosshair/util.py CHANGED
@@ -180,7 +180,9 @@ class IdKeyedDict(collections.abc.MutableMapping):
180
180
  return self.inner.__delitem__(id(k))
181
181
 
182
182
  def __iter__(self):
183
- return map(id, self.inner.__iter__())
183
+ raise NotImplementedError
184
+ # No use cases for this yet, but we could do something like this:
185
+ # return (actual_key_object for actual_key_object, _ in self.inner.values())
184
186
 
185
187
  def __len__(self):
186
188
  return len(self.inner)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: crosshair-tool
3
- Version: 0.0.85
3
+ Version: 0.0.87
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
@@ -46,7 +46,7 @@ Requires-Dist: pytest-xdist; extra == "dev"
46
46
  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
- Requires-Dist: wheel; extra == "dev"
49
+ Requires-Dist: rst2pdf>=0.102; extra == "dev"
50
50
  Dynamic: author
51
51
  Dynamic: author-email
52
52
  Dynamic: classifier
@@ -1,4 +1,4 @@
1
- _crosshair_tracers.cpython-313-darwin.so,sha256=EFLWw4Ipkp0hpxUDAdxESPrp53fySTPJibsXhylirg4,140688
1
+ _crosshair_tracers.cpython-313-darwin.so,sha256=UROMO1abqw-cEi3ZoTdYccYz5i0mvZSONbdoDVLUgHE,140512
2
2
  crosshair/_tracers_pycompat.h,sha256=6IYnbQxrYkhBsLDAHSX25DPOwo1oYHCZUVWZ8c7YCnQ,14356
3
3
  crosshair/pure_importer.py,sha256=-t4eowrZOQmfqK1N2tjI5POoaxRGavytwMmbRivelFg,878
4
4
  crosshair/options.py,sha256=htQNgnrpoRjSNq6rfLBAF8nos-NNIwmP6tQYyI8ugsM,6775
@@ -13,7 +13,7 @@ crosshair/dynamic_typing_test.py,sha256=oyg94OXjF_2jNFy33UJjkfWnDXKnM4or2TXbxrOq
13
13
  crosshair/enforce.py,sha256=FsZx3D-KtGrhb8xdAZbPUtwvVmEu8IAn7rwf7tmkrRY,10010
14
14
  crosshair/path_search_test.py,sha256=7cqzAMXUYAtA00mq9XR5AaZChqeQyXyCfuuv53_51pk,1692
15
15
  crosshair/condition_parser.py,sha256=oquaht026eZUigh2lyaFLXYDbmENdBKjddszx0a-B3w,42647
16
- crosshair/util.py,sha256=OtkhDSdggqt-A7sTfp4sHa69_M7HNyD8w4gHhxcEUDo,21977
16
+ crosshair/util.py,sha256=eddHHvkn9NnsfYahpuIF4Xp03kIiQ8EJUaliFEO4XG4,22124
17
17
  crosshair/dynamic_typing.py,sha256=jbI9FXv5-WXREQjeDtlDQladv-xCW21TUOM3qErJaJ4,11998
18
18
  crosshair/register_contract.py,sha256=EnDAxngJhKvJLFdw5kVgqaYDQ5hAZXKwAGBdXpot-AQ,10386
19
19
  crosshair/tracers.py,sha256=_jaSDgZ_pYdqacWE_msXn7W7CoSdQ_-7hlrxa891oHo,17139
@@ -23,7 +23,7 @@ crosshair/fnutil.py,sha256=X80bD2Lh4QAh-rF561r3JRxjxcuZepF3hJaxaj1GG9s,13123
23
23
  crosshair/unicode_categories.py,sha256=g4pnUPanx8KkpwI06ZUGx8GR8Myruf_EpTjyti_V4z8,333519
24
24
  crosshair/copyext.py,sha256=GBGQP9YAHoezLXwb_M59Hh1VXSou5EQt4ZmmUA0T_og,4899
25
25
  crosshair/_tracers_test.py,sha256=KpCGspjOUeZuhwTYgn_RxI4M4wMbT5rldusFDgneQ6M,3596
26
- crosshair/__init__.py,sha256=vw6g0DHh0Clj2rgk1Iu4a-sWjwIf5SwN6IR04XBP47s,936
26
+ crosshair/__init__.py,sha256=pCHAq8T8tcr3KLNCJY6xCK4MAWSn-XIiJTIrjTaMpPE,936
27
27
  crosshair/core.py,sha256=K7147xrvcvtrP1T9oIRywq2uHMMsIu8ZwFZWBQXPozg,63634
28
28
  crosshair/path_cover.py,sha256=wV0Vy8IPDzqXQ2VI8a94FxltS9p-Y1oF17OKePjvpgs,6710
29
29
  crosshair/enforce_test.py,sha256=C6CQ4P1FjkdIJeJg3aJynp1iLDCE6BFCEVtSqXbvmQk,4665
@@ -32,7 +32,7 @@ crosshair/core_test.py,sha256=H6qZFBFuUhgh9qWgxxc1Cs7xuC8s6FYvewmTYUh0Dpg,32200
32
32
  crosshair/codeconfig.py,sha256=GgF-ND8Ha3FysSTQ-JuezHjlhGVBbo5aCJov1Ps3VSE,3959
33
33
  crosshair/util_test.py,sha256=_KTQ0O4cLhF1pAeB8Y8Cyqbd0UyZf5KxJUaiA-ew-tE,4676
34
34
  crosshair/watcher_test.py,sha256=Ef1YSwy68wWPR5nPjwvEKPqxltI9pE9lTbnesmDy3Bk,2764
35
- crosshair/auditwall.py,sha256=FjpeqM6HxsKYEfVJ20CRzkQu1I2zt1dpzwejrDp-W9U,4975
35
+ crosshair/auditwall.py,sha256=sqOmfXQLgmGfWS7b8SmVv66eFM2owaGn-4Ppq453VLI,5138
36
36
  crosshair/simplestructs_test.py,sha256=6uDdrSISHLhwnFuESkR8mUGw7m1llM6vCNDFChkfSs8,8639
37
37
  crosshair/z3util_test.py,sha256=CZovn4S9mYcG_yQegcxm80VHrvUdvNei0gvGTF9TOrk,173
38
38
  crosshair/diff_behavior.py,sha256=_5X_pTN0_-rSPrh8dfpODJG_phFMn7fWc-_zLgO3UTk,11253
@@ -43,7 +43,7 @@ crosshair/stubs_parser_test.py,sha256=0itTT0Udul_51RJXNv6KB97z44gYze6NZfKJL7yIDz
43
43
  crosshair/options_test.py,sha256=lzA-XtwEwQPa4wV1wwhCRKhyLOvIhThU9WK5QRaRbxQ,379
44
44
  crosshair/patch_equivalence_test.py,sha256=mvYpsbHZS2AiQra-jK8T8QywAG1gNNCUNQPPWI9k5t8,2506
45
45
  crosshair/abcstring.py,sha256=ROU8LzS7kfEU2L_D3QfhVxIjrYr1VctwUWfylC7KlCc,6549
46
- crosshair/_mark_stacks.h,sha256=rvIET5OGjKMcUZxEtIb9RhxRDveAfPXb3HLZZWfM3Bs,29261
46
+ crosshair/_mark_stacks.h,sha256=j86qubOUvVhoR19d74iQ084RrTEq8M6oT4wJsGQUySY,28678
47
47
  crosshair/fuzz_core_test.py,sha256=q7WsZt6bj5OJrXaVsT3JaRYWWnL8X_1flSfty4Z7CcA,16903
48
48
  crosshair/unicode_categories_test.py,sha256=ZAU37IDGm9PDvwy_CGFcrF9Waa8JuUNdI4aq74wkB6c,739
49
49
  crosshair/statespace_test.py,sha256=LOblIarBbcB9oD_gVR5kK_4P2PWQymVGgJr3wNqP3Fs,2621
@@ -53,13 +53,13 @@ crosshair/path_search.py,sha256=wwZjp-3E4dENnJa6BlnSq8FARkIx0PyUYc7kvH32A2k,5588
53
53
  crosshair/auditwall_test.py,sha256=VPcw_OW3nl3BkOZY4wEEtVDyTamdgqD4IjRccI2p5vI,2030
54
54
  crosshair/smtlib_test.py,sha256=edzEn19u2YYHxSzG9RrMiu2HTiEexAuehC3IlG9LuJM,511
55
55
  crosshair/register_contract_test.py,sha256=DhvKIcF3LgQfHfUSCccoA11ctCdFaQR263Pc4YUuxyk,4970
56
- crosshair/statespace.py,sha256=PIT5ajZZcur88_5Cs0QkPkUVa-fqKwsqgNrRNPKRoSo,41547
57
- crosshair/opcode_intercept_test.py,sha256=rWO84XLQF8YEQLIxvrlnJWLqMfKXgah_6AW3u_NADlQ,6189
56
+ crosshair/statespace.py,sha256=KeIIBwBKxDgQO0ML2LvYXhS5DgmelaYF-RRXopR9lqA,41794
57
+ crosshair/opcode_intercept_test.py,sha256=hBf04VwhaIzPdoAaqQmQJfx36yaznD9sm9x1Lb9VDm8,8790
58
58
  crosshair/main_test.py,sha256=DFXtE--616vCF1WAS576SHXO19K0LsDPzJIWNT5vuXM,14383
59
59
  crosshair/codeconfig_test.py,sha256=RnC-RnNpr6If4eHmOepDZ33MCmfyhup08dzHKCm5xWA,3350
60
60
  crosshair/watcher.py,sha256=kCCMlLe2KhW5MbEbMmixNRjRAvu5CypIAGd1V_YZ9QM,10048
61
61
  crosshair/test_util_test.py,sha256=FIOWVBMiF-zyq0pGsDQ8W6lB6_E9sGpD80dioHHETyQ,512
62
- crosshair/opcode_intercept.py,sha256=yNixjTRjc8R9PXO1xGF4BoQ-IAHXup7CWgUwbubRzZc,18996
62
+ crosshair/opcode_intercept.py,sha256=z4Yb9prYE2UK21AxhjAeXyXAk5IriDuCSSCeNhbDu2A,21880
63
63
  crosshair/simplestructs.py,sha256=CiZSuHH_j_bYitaW-n7vWd_42xSyV6Jh8es3BQLlcHk,34221
64
64
  crosshair/tracers_test.py,sha256=EBK_ZCy2MsxqmEaGjo0uw9zAztW9O6fhCW_0PJxyTS8,3270
65
65
  crosshair/smtlib.py,sha256=hh-P32KHoH9BCq3oDYGp2PfOeOb8CwDj8tTkgqroLD8,689
@@ -107,13 +107,13 @@ crosshair/libimpl/binascii_test.py,sha256=LOBqLAJ77Kx8vorjVTaT3X0Z93zw4P5BvwUapM
107
107
  crosshair/libimpl/collectionslib_test.py,sha256=3h7XTToKWauMhjrEwLXVI0jT8FZKBlkvlw0oiPMmuKM,9164
108
108
  crosshair/libimpl/timelib.py,sha256=MXEFOZjFGa1-yLvmB3l3DFTLF9PSluOlmRK-ZJaA_oI,2409
109
109
  crosshair/libimpl/jsonlib_test.py,sha256=U40WJf-69dtflz75sIsl5zA3IV5R6Ltc4Z9jv_Fh-Fw,1382
110
- crosshair/libimpl/builtinslib.py,sha256=lMYlTJsirb-rx8RO3EU3-LwEivmWtdQ2VHfuubbsIKs,182505
110
+ crosshair/libimpl/builtinslib.py,sha256=NjTvFTdPwQcBWC4PWmnD73rK37eXUp3pdwRoz2-IBEI,171512
111
111
  crosshair/libimpl/mathlib_test.py,sha256=QShLCXHdv3tx5PQxcSoR0MHeZ1huaiV6d3u7C2mGOn4,1861
112
112
  crosshair/libimpl/fractionlib.py,sha256=qdbiAHHC480YdKq3wYK_piZ3UD7oT64YfuNclypMUfQ,458
113
113
  crosshair/libimpl/binascii_ch_test.py,sha256=hFqSfF1Q8jl2LNBIWaQ6vBJIIshPOmSwrR0T1Ko4Leo,1009
114
114
  crosshair/libimpl/jsonlib.py,sha256=xFTvqGKzQcCgPme1WIpNMjBPfNHVZBMNuNx0uKMYXj0,28805
115
- crosshair/libimpl/typeslib_test.py,sha256=Gf-EymanxMty2-B53BiT81w1CSTzoHVXvELwqiGOqxc,1083
116
- crosshair/libimpl/builtinslib_test.py,sha256=GmnsgjSKjkE-3cb-aQwSV5BYbNHvD-O1E8sB-hZv8f4,90270
115
+ crosshair/libimpl/typeslib_test.py,sha256=qCeUU_c-zmuvfwHEsaYqic9wdGzs9XbDZNr6bF2Xp58,1129
116
+ crosshair/libimpl/builtinslib_test.py,sha256=Q0DvH7Q_Ja0Tg83enhB_y_Pdau_S-bA6ztorjvgl4Mw,90275
117
117
  crosshair/libimpl/zliblib.py,sha256=XymJTKYbplpYJZ-P7GKVSY3V_8HPy5lqRFsCH1zezIk,357
118
118
  crosshair/libimpl/decimallib.py,sha256=zBKDrDZcg45oCKUkf6SIDuVpjA1Web7tD1MEQo5cjxI,177348
119
119
  crosshair/libimpl/relib_ch_test.py,sha256=zvSBF82mNQR5yEOMwwcaBOh8OpJkQeiVl85pgYVvJRA,5235
@@ -166,9 +166,9 @@ crosshair/libimpl/encodings/ascii.py,sha256=Cz1xraTkXdQ5aBKDkorX4rAvrmf877_EqzC9
166
166
  crosshair/libimpl/encodings/__init__.py,sha256=5LTEj1M-S00eZ4rfQWczAixg57vyh_9vZ5m5EKB5Ksc,680
167
167
  crosshair/libimpl/encodings/latin_1.py,sha256=ftUsPjUb9L7UKXKi9P7OAqOl9FkNP98M9jMAvseXBCQ,1242
168
168
  crosshair/libimpl/encodings/_encutil.py,sha256=nwVWqcGM1f7-hAC3Z46KnfrLzAjhfy4zaTa11uVBk6M,6828
169
- crosshair_tool-0.0.85.dist-info/RECORD,,
170
- crosshair_tool-0.0.85.dist-info/WHEEL,sha256=A6iggJuFsuu67bHdjxJADhwSEJmqwgO3xFoNCIwjOxc,115
171
- crosshair_tool-0.0.85.dist-info/entry_points.txt,sha256=u5FIPVn1jqn4Kzg5K_iNnbP6L4hQw5FWjQ0UMezG2VE,96
172
- crosshair_tool-0.0.85.dist-info/top_level.txt,sha256=2jLWtM-BWg_ZYNbNfrcds0HFZD62a6J7ZIbcgcQrRk4,29
173
- crosshair_tool-0.0.85.dist-info/METADATA,sha256=ceFH89wcuwsmpx0R5LKTkwTiag76wifHPC545rEm4dQ,6717
174
- crosshair_tool-0.0.85.dist-info/licenses/LICENSE,sha256=NVyMvNqn1pH6RSHs6RWRcJyJvORnpgGFBlF73buqYJ0,4459
169
+ crosshair_tool-0.0.87.dist-info/RECORD,,
170
+ crosshair_tool-0.0.87.dist-info/WHEEL,sha256=YOKbiIEZep2WTRrpKV0dvEfsSeIOVH2NqduVxoKQt6I,142
171
+ crosshair_tool-0.0.87.dist-info/entry_points.txt,sha256=u5FIPVn1jqn4Kzg5K_iNnbP6L4hQw5FWjQ0UMezG2VE,96
172
+ crosshair_tool-0.0.87.dist-info/top_level.txt,sha256=2jLWtM-BWg_ZYNbNfrcds0HFZD62a6J7ZIbcgcQrRk4,29
173
+ crosshair_tool-0.0.87.dist-info/METADATA,sha256=5fceVgbFzOQhEoI4B8xJdNGOUd2aAftv63dxpz6Dp0E,6726
174
+ crosshair_tool-0.0.87.dist-info/licenses/LICENSE,sha256=NVyMvNqn1pH6RSHs6RWRcJyJvORnpgGFBlF73buqYJ0,4459
@@ -1,5 +1,6 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (78.1.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: false
4
4
  Tag: cp313-cp313-macosx_10_13_universal2
5
+ Generator: delocate 0.13.0
5
6