crosshair-tool 0.0.94__cp38-cp38-musllinux_1_2_x86_64.whl → 0.0.96__cp38-cp38-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.94" # Do not forget to update in setup.py!
18
+ __version__ = "0.0.96" # Do not forget to update in setup.py!
19
19
  __author__ = "Phillip Schanely"
20
20
  __license__ = "MIT"
21
21
  __status__ = "Alpha"
@@ -1,8 +1,6 @@
1
1
  import inspect
2
2
  import json
3
3
  import sys
4
- import textwrap
5
- import unittest
6
4
  from typing import List
7
5
 
8
6
  import pytest
crosshair/core.py CHANGED
@@ -377,7 +377,8 @@ def with_symbolic_self(symbolic_cls: Type, fn: Callable):
377
377
  elif any(isinstance(a, CrossHairValue) for a in args) or (
378
378
  kwargs and any(isinstance(a, CrossHairValue) for a in kwargs.values())
379
379
  ):
380
- self = symbolic_cls._smt_promote_literal(self)
380
+ # NOTE: _ch_create_from_literal is suppoerted for very few types right now
381
+ self = symbolic_cls._ch_create_from_literal(self)
381
382
  target_fn = getattr(symbolic_cls, fn.__name__)
382
383
  else:
383
384
  args = map(realize, args)
crosshair/core_test.py CHANGED
@@ -5,7 +5,6 @@ import re
5
5
  import sys
6
6
  import time
7
7
  from typing import *
8
- from unittest import skipIf
9
8
 
10
9
  import pytest # type: ignore
11
10
 
@@ -736,7 +735,7 @@ def test_newtype() -> None:
736
735
  assert isinstance(x, SymbolicInt)
737
736
 
738
737
 
739
- @skipIf(sys.version_info < (3, 12), "type statements added in 3.12")
738
+ @pytest.mark.skipif(sys.version_info < (3, 12), reason="type statements added in 3.12")
740
739
  def test_type_statement() -> None:
741
740
  env: dict[str, Any] = {}
742
741
  exec("type MyIntNew = int\n", env)
@@ -747,7 +746,7 @@ def test_type_statement() -> None:
747
746
  assert isinstance(x, SymbolicInt)
748
747
 
749
748
 
750
- @skipIf(sys.version_info < (3, 12), "type statements added in 3.12")
749
+ @pytest.mark.skipif(sys.version_info < (3, 12), reason="type statements added in 3.12")
751
750
  def test_parameterized_type_statement() -> None:
752
751
  env: dict[str, Any] = {}
753
752
  exec("type Pair[A, B] = tuple[B, A]\n", env)
@@ -1,5 +1,3 @@
1
- import sys
2
- import unittest
3
1
  from typing import Callable, List, Optional
4
2
 
5
3
  from crosshair.diff_behavior import (
@@ -346,7 +346,7 @@ class AtomicSymbolicValue(SymbolicValue):
346
346
  raise CrossHairInternal(f"_pytype not implemented in {cls}")
347
347
 
348
348
  @classmethod
349
- def _smt_promote_literal(cls, val: object) -> Optional[z3.SortRef]:
349
+ def _smt_promote_literal(cls, literal: object) -> Optional[z3.ExprRef]:
350
350
  raise CrossHairInternal(f"_smt_promote_literal not implemented in {cls}")
351
351
 
352
352
  @classmethod
@@ -1120,7 +1120,7 @@ class SymbolicBool(SymbolicIntable, AtomicSymbolicValue):
1120
1120
  return bool
1121
1121
 
1122
1122
  @classmethod
1123
- def _smt_promote_literal(cls, literal) -> Optional[z3.SortRef]:
1123
+ def _smt_promote_literal(cls, literal) -> Optional[z3.ExprRef]:
1124
1124
  if isinstance(literal, bool):
1125
1125
  return z3.BoolVal(literal)
1126
1126
  return None
@@ -1189,7 +1189,7 @@ class SymbolicInt(SymbolicIntable, AtomicSymbolicValue):
1189
1189
  return int
1190
1190
 
1191
1191
  @classmethod
1192
- def _smt_promote_literal(cls, literal) -> Optional[z3.SortRef]:
1192
+ def _smt_promote_literal(cls, literal) -> Optional[z3.ExprRef]:
1193
1193
  if isinstance(literal, int):
1194
1194
  return z3IntVal(literal)
1195
1195
  return None
@@ -1410,7 +1410,7 @@ class PreciseIeeeSymbolicFloat(SymbolicFloat):
1410
1410
  return _PRECISE_IEEE_FLOAT_SORT
1411
1411
 
1412
1412
  @classmethod
1413
- def _smt_promote_literal(cls, literal) -> Optional[z3.SortRef]:
1413
+ def _smt_promote_literal(cls, literal) -> Optional[z3.ExprRef]:
1414
1414
  if isinstance(literal, float):
1415
1415
  return z3.FPVal(literal, cls._ch_smt_sort())
1416
1416
  return None
@@ -1533,7 +1533,7 @@ class RealBasedSymbolicFloat(SymbolicFloat):
1533
1533
  return z3.RealSort()
1534
1534
 
1535
1535
  @classmethod
1536
- def _smt_promote_literal(cls, literal) -> Optional[z3.SortRef]:
1536
+ def _smt_promote_literal(cls, literal) -> Optional[z3.ExprRef]:
1537
1537
  if isinstance(literal, float) and isfinite(literal):
1538
1538
  return z3.RealVal(literal)
1539
1539
  return None
@@ -2447,7 +2447,7 @@ class SymbolicType(AtomicSymbolicValue, SymbolicValue, Untracable):
2447
2447
  return type
2448
2448
 
2449
2449
  @classmethod
2450
- def _smt_promote_literal(cls, literal) -> Optional[z3.SortRef]:
2450
+ def _smt_promote_literal(cls, literal) -> Optional[z3.ExprRef]:
2451
2451
  if isinstance(literal, type):
2452
2452
  return context_statespace().extra(SymbolicTypeRepository).get_type(literal)
2453
2453
  return None
@@ -2611,18 +2611,6 @@ class SymbolicObject(ObjectProxy, CrossHairValue, Untracable):
2611
2611
  return object()
2612
2612
  return proxy_for_type(pytype, varname, allow_subtypes=False)
2613
2613
 
2614
- def _wrapped(self):
2615
- with NoTracing():
2616
- inner = _MISSING
2617
- try:
2618
- inner = object.__getattribute__(self, "_inner")
2619
- except AttributeError:
2620
- pass
2621
- if inner is _MISSING:
2622
- inner = self._realize()
2623
- object.__setattr__(self, "_inner", inner)
2624
- return inner
2625
-
2626
2614
  def __ch_realize__(self):
2627
2615
  return realize(self._wrapped())
2628
2616
 
@@ -2680,10 +2668,25 @@ class SymbolicCallable:
2680
2668
  __annotations__: dict = {}
2681
2669
 
2682
2670
  def __init__(self, values: list):
2671
+ """
2672
+ A function that will ignore its arguments and produce return values
2673
+ from the list given.
2674
+ If the given list is exhausted, the function will just repeatedly
2675
+ return the final value in the list.
2676
+
2677
+ If `values` is concrete, it must be non-mepty.
2678
+ If `values` is a symbolic list, it will be forced to be non-empty
2679
+ (the caller must enure that's possible).
2680
+ """
2683
2681
  assert not is_tracing()
2684
2682
  with ResumedTracing():
2685
- if not values:
2686
- raise IgnoreAttempt
2683
+ has_values = len(values) > 0
2684
+ if isinstance(values, CrossHairValue):
2685
+ space = context_statespace()
2686
+ assert space.is_possible(has_values)
2687
+ space.add(has_values)
2688
+ else:
2689
+ assert has_values
2687
2690
  self.values = values
2688
2691
  self.idx = 0
2689
2692
 
@@ -2707,6 +2710,7 @@ class SymbolicCallable:
2707
2710
  if idx >= len(values):
2708
2711
  return values[-1]
2709
2712
  else:
2713
+ self.idx += 1
2710
2714
  return values[idx]
2711
2715
 
2712
2716
  def __bool__(self):
@@ -3418,6 +3422,26 @@ class AnySymbolicStr(AbcString):
3418
3422
  return "0" * fill_length + self
3419
3423
 
3420
3424
 
3425
+ def _unfindable_range(start: Optional[int], end: Optional[int], mylen: int) -> bool:
3426
+ """
3427
+ Emulates some preliminary checks that CPython makes before searching
3428
+ for substrings within some bounds. (in e.g. str.find, str.startswith, etc)
3429
+ """
3430
+ if start is None or start == 0 or start <= -mylen:
3431
+ return False
3432
+
3433
+ # At this point, we know that `start` is defined and points to an index after 0
3434
+ if end is None or end >= mylen:
3435
+ return start > mylen
3436
+
3437
+ # At this point, we know that `end` is defined and points to an index before the end of the string
3438
+ if start < 0:
3439
+ start += mylen
3440
+ if end < 0:
3441
+ end += mylen
3442
+ return end < start
3443
+
3444
+
3421
3445
  class LazyIntSymbolicStr(AnySymbolicStr, CrossHairValue):
3422
3446
  """
3423
3447
  A symbolic string that lazily generates SymbolicInt-based characters as needed.
@@ -3456,10 +3480,8 @@ class LazyIntSymbolicStr(AnySymbolicStr, CrossHairValue):
3456
3480
  codepoints = tuple(self._codepoints)
3457
3481
  return "".join(chr(realize(x)) for x in codepoints)
3458
3482
 
3459
- # This is normally an AtomicSymbolicValue method, but sometimes it's used in a
3460
- # duck-typing way.
3461
3483
  @classmethod
3462
- def _smt_promote_literal(cls, val: object) -> Optional[z3.SortRef]:
3484
+ def _ch_create_from_literal(cls, val: object) -> Optional[CrossHairValue]:
3463
3485
  if isinstance(val, str):
3464
3486
  return LazyIntSymbolicStr(list(map(ord, val)))
3465
3487
  return None
@@ -3493,6 +3515,10 @@ class LazyIntSymbolicStr(AnySymbolicStr, CrossHairValue):
3493
3515
  with NoTracing():
3494
3516
  if not isinstance(i, (Integral, slice)):
3495
3517
  raise TypeError(type(i))
3518
+ # This could/should? be symbolic by naming all the possibilities.
3519
+ # Note the slice case still must realize the return length.
3520
+ # Especially because we no longer explore realization trees except
3521
+ # as a last resort.
3496
3522
  i = deep_realize(i)
3497
3523
  with ResumedTracing():
3498
3524
  newcontents = self._codepoints[i]
@@ -3573,11 +3599,15 @@ class LazyIntSymbolicStr(AnySymbolicStr, CrossHairValue):
3573
3599
  return any(self.endswith(s, start, end) for s in substr)
3574
3600
  if not isinstance(substr, str):
3575
3601
  raise TypeError
3602
+ substrlen = len(substr)
3576
3603
  if start is None and end is None:
3577
3604
  matchable = self
3578
3605
  else:
3579
3606
  matchable = self[start:end]
3580
- return matchable[-len(substr) :] == substr
3607
+ if substrlen == 0:
3608
+ return not _unfindable_range(start, end, len(self))
3609
+ else:
3610
+ return matchable[-substrlen:] == substr
3581
3611
 
3582
3612
  def startswith(self, substr, start=None, end=None):
3583
3613
  if isinstance(substr, tuple):
@@ -3587,6 +3617,10 @@ class LazyIntSymbolicStr(AnySymbolicStr, CrossHairValue):
3587
3617
  if start is None and end is None:
3588
3618
  matchable = self
3589
3619
  else:
3620
+ # Wacky special case: the empty string is findable off the left
3621
+ # side but not the right!
3622
+ if _unfindable_range(start, end, len(self)):
3623
+ return False
3590
3624
  matchable = self[start:end]
3591
3625
  return matchable[: len(substr)] == substr
3592
3626
 
@@ -3627,7 +3661,7 @@ class LazyIntSymbolicStr(AnySymbolicStr, CrossHairValue):
3627
3661
  end += mylen
3628
3662
  matchstr = self[start:end] if start != 0 or end is not mylen else self
3629
3663
  if len(substr) == 0:
3630
- # Add oddity of CPython. We can find the empty string when over-slicing
3664
+ # An oddity of CPython. We can find the empty string when over-slicing
3631
3665
  # off the left side of the string, but not off the right:
3632
3666
  # ''.find('', 3, 4) == -1
3633
3667
  # ''.find('', -4, -3) == 0
@@ -4802,14 +4836,17 @@ def _str_format_map(self, map) -> Union[AnySymbolicStr, str]:
4802
4836
 
4803
4837
 
4804
4838
  def _str_startswith(self, substr, start=None, end=None) -> bool:
4805
- if not isinstance(self, str):
4806
- raise TypeError
4807
4839
  with NoTracing():
4840
+ if isinstance(self, LazyIntSymbolicStr):
4841
+ with ResumedTracing():
4842
+ return self.startswith(substr, start, end)
4843
+ elif not isinstance(self, str):
4844
+ raise TypeError
4808
4845
  # Handle native values with native implementation:
4809
4846
  if type(substr) is str:
4810
4847
  return self.startswith(substr, start, end)
4811
4848
  if type(substr) is tuple:
4812
- if all(type(i) is str for i in substr):
4849
+ if all(type(s) is str for s in substr):
4813
4850
  return self.startswith(substr, start, end)
4814
4851
  symbolic_self = LazyIntSymbolicStr([ord(c) for c in self])
4815
4852
  return symbolic_self.startswith(substr, start, end)
@@ -606,9 +606,14 @@ def check_str_endswith(
606
606
  string: str, suffix: str, start: Optional[int], end: Optional[int]
607
607
  ) -> ResultComparison:
608
608
  """post: _"""
609
+ # crosshair: max_uninteresting_iterations=100
610
+
611
+ for i in (len(string), len(suffix), start, end):
612
+ if i is not None and abs(i) >= 1:
613
+ pass
614
+
609
615
  return compare_results(
610
- lambda s, *a: s.endswith(*a),
611
- string,
616
+ lambda s, *a, **kw: s.endswith(*a, **kw), string, suffix, start, end
612
617
  )
613
618
 
614
619
 
@@ -825,6 +830,11 @@ def check_str_startswith(
825
830
  end: Optional[int],
826
831
  ) -> ResultComparison:
827
832
  """post: _"""
833
+ # crosshair: max_uninteresting_iterations=100
834
+
835
+ for i in (len(string), len(prefix), start, end):
836
+ if i is not None and abs(i) >= 1:
837
+ pass
828
838
  return compare_results(
829
839
  lambda s, *a, **kw: s.startswith(*a, **kw), string, prefix, start, end
830
840
  )
@@ -939,6 +939,31 @@ def test_str_replace_method() -> None:
939
939
  check_states(f, POST_FAIL)
940
940
 
941
941
 
942
+ def test_str_startswith(space) -> None:
943
+ symbolic_char = proxy_for_type(str, "x")
944
+ symbolic_empty = proxy_for_type(str, "y")
945
+ with ResumedTracing():
946
+ space.add(len(symbolic_char) == 1)
947
+ space.add(len(symbolic_empty) == 0)
948
+ assert symbolic_char.startswith(symbolic_empty)
949
+ assert symbolic_char.startswith(symbolic_char)
950
+ assert symbolic_char.startswith(("foo", symbolic_empty))
951
+ assert not symbolic_char.startswith(("foo", "bar"))
952
+ assert symbolic_char.startswith(("", "bar"))
953
+ assert symbolic_char.startswith("")
954
+ assert symbolic_char.startswith(symbolic_empty, 1)
955
+ assert symbolic_char.startswith(symbolic_empty, 1, 1)
956
+ assert str.startswith(symbolic_char, symbolic_empty)
957
+ assert "foo".startswith(symbolic_empty)
958
+ assert not "".startswith(symbolic_char)
959
+
960
+ # Yes, the empty string is findable off the left side but not the right
961
+ assert "x".startswith("", -10, -9)
962
+ assert symbolic_char.startswith(symbolic_empty, -10, -9)
963
+ assert not "x".startswith("", 9, 10)
964
+ assert not symbolic_char.startswith(symbolic_empty, 9, 10)
965
+
966
+
942
967
  @pytest.mark.demo
943
968
  def test_str_index_method() -> None:
944
969
  def f(a: str) -> int:
@@ -3134,6 +3159,17 @@ def test_callable_as_bool() -> None:
3134
3159
  check_states(f, CONFIRMED)
3135
3160
 
3136
3161
 
3162
+ def test_callable_can_return_different_values(space) -> None:
3163
+ fn = proxy_for_type(Callable[[], int], "fn")
3164
+ with ResumedTracing():
3165
+ first_return = fn()
3166
+ second_return = fn()
3167
+ returns_are_equal = first_return == second_return
3168
+ returns_are_not_equal = first_return != second_return
3169
+ assert space.is_possible(returns_are_equal)
3170
+ assert space.is_possible(returns_are_not_equal)
3171
+
3172
+
3137
3173
  @pytest.mark.smoke
3138
3174
  def test_callable_repr() -> None:
3139
3175
  def f(f1: Callable[[int], int]) -> int:
@@ -3,7 +3,7 @@ import sys
3
3
  from collections import Counter, defaultdict, deque, namedtuple
4
4
  from copy import deepcopy
5
5
  from inspect import Parameter, Signature
6
- from typing import Counter, DefaultDict, Deque, NamedTuple, Tuple
6
+ from typing import Callable, Counter, DefaultDict, Deque, Dict, NamedTuple, Tuple
7
7
 
8
8
  import pytest
9
9
 
@@ -14,7 +14,7 @@ from crosshair.core import (
14
14
  realize,
15
15
  standalone_statespace,
16
16
  )
17
- from crosshair.libimpl.collectionslib import ListBasedDeque
17
+ from crosshair.libimpl.collectionslib import ListBasedDeque, PureDefaultDict
18
18
  from crosshair.statespace import CANNOT_CONFIRM, CONFIRMED, POST_FAIL, MessageType
19
19
  from crosshair.test_util import check_states
20
20
  from crosshair.tracers import NoTracing, ResumedTracing
@@ -246,10 +246,10 @@ def test_defaultdict_default_fail(test_list) -> None:
246
246
 
247
247
 
248
248
  def test_defaultdict_default_ok(test_list) -> None:
249
- def f(a: DefaultDict[int, int], k1: int, k2: int) -> DefaultDict[int, int]:
249
+ def f(a: DefaultDict[int, int], k: int) -> DefaultDict[int, int]:
250
250
  """
251
251
  pre: len(a) == 0 and a.default_factory is not None
252
- post: _[k1] == _[k2]
252
+ post: _[k] == _[k]
253
253
  """
254
254
  return a
255
255
 
@@ -1,4 +1,4 @@
1
- from functools import _lru_cache_wrapper, partial, reduce
1
+ from functools import _lru_cache_wrapper, partial, reduce, update_wrapper, wraps
2
2
 
3
3
  from crosshair.core import register_patch
4
4
 
@@ -7,7 +7,13 @@ from crosshair.core import register_patch
7
7
 
8
8
  def _partial(func, *a1, **kw1):
9
9
  if callable(func):
10
- return partial(lambda *a2, **kw2: func(*a2, **kw2), *a1, **kw1)
10
+ # We make a do-nothing wrapper to ensure that the tracer has a crack
11
+ # at this function when it is called.
12
+ def wrapper(*a2, **kw2):
13
+ return func(*a2, **kw2)
14
+
15
+ update_wrapper(wrapper, func)
16
+ return partial(wrapper, *a1, **kw1)
11
17
  else:
12
18
  raise TypeError
13
19
 
@@ -1,20 +1,36 @@
1
1
  import functools
2
+ import inspect
2
3
 
3
4
  from crosshair.core import proxy_for_type, standalone_statespace
4
5
  from crosshair.libimpl.builtinslib import LazyIntSymbolicStr
5
- from crosshair.tracers import NoTracing
6
+ from crosshair.tracers import NoTracing, ResumedTracing
6
7
 
7
8
 
8
- def test_partial():
9
- with standalone_statespace as space:
10
- with NoTracing():
11
- abc = LazyIntSymbolicStr(list(map(ord, "abc")))
12
- xyz = LazyIntSymbolicStr(list(map(ord, "xyz")))
9
+ def test_partial(space):
10
+ abc = LazyIntSymbolicStr(list(map(ord, "abc")))
11
+ xyz = LazyIntSymbolicStr(list(map(ord, "xyz")))
12
+ with ResumedTracing():
13
13
  joiner = functools.partial(str.join, ",")
14
14
  ret = joiner([abc, xyz])
15
15
  assert ret == "abc,xyz"
16
16
 
17
17
 
18
+ def test_partial_is_interceptable(space):
19
+ x = proxy_for_type(str, "x")
20
+ y = proxy_for_type(str, "y")
21
+ with ResumedTracing():
22
+ joiner = functools.partial(str.startswith, x)
23
+ # Ensure we don't explode
24
+ list(map(joiner, ["foo", y]))
25
+
26
+
27
+ def test_partial_arg_is_inspectable(space):
28
+ with ResumedTracing():
29
+ joiner = functools.partial(str.join, ",")
30
+ assert isinstance(joiner, functools.partial)
31
+ assert inspect.getdoc(joiner.func) == inspect.getdoc(str.join)
32
+
33
+
18
34
  def test_reduce():
19
35
  with standalone_statespace as space:
20
36
  with NoTracing():
crosshair/objectproxy.py CHANGED
@@ -10,11 +10,36 @@ from crosshair.tracers import NoTracing
10
10
  # (which is BSD licenced)
11
11
  #
12
12
 
13
+ _MISSING = object()
14
+
15
+
16
+ def proxy_inplace_op(proxy, op, *args):
17
+ my_original_value = proxy._wrapped()
18
+ my_new_value = op(my_original_value, *args)
19
+ # We need to return our own identity if (and only if!) the underlying value does.
20
+ if my_new_value is my_original_value:
21
+ return proxy
22
+ else:
23
+ object.__setattr__(proxy, "_inner", my_new_value)
24
+ return my_new_value
25
+
13
26
 
14
27
  class ObjectProxy:
15
- def _wrapped(self):
28
+ def _realize(self):
16
29
  raise NotImplementedError
17
30
 
31
+ def _wrapped(self):
32
+ with NoTracing():
33
+ inner = _MISSING
34
+ try:
35
+ inner = object.__getattribute__(self, "_inner")
36
+ except AttributeError:
37
+ pass
38
+ if inner is _MISSING:
39
+ inner = self._realize()
40
+ object.__setattr__(self, "_inner", inner)
41
+ return inner
42
+
18
43
  def __get_module__(self) -> str:
19
44
  return self._wrapped().__module__
20
45
 
@@ -233,40 +258,40 @@ class ObjectProxy:
233
258
  return other | self._wrapped()
234
259
 
235
260
  def __iadd__(self, other):
236
- return operator.iadd(self._wrapped(), other)
261
+ return proxy_inplace_op(self, operator.iadd, other)
237
262
 
238
263
  def __isub__(self, other):
239
- return operator.isub(self._wrapped(), other)
264
+ return proxy_inplace_op(self, operator.isub, other)
240
265
 
241
266
  def __imul__(self, other):
242
- return operator.imul(self._wrapped(), other)
267
+ return proxy_inplace_op(self, operator.imul, other)
243
268
 
244
269
  def __itruediv__(self, other):
245
- return operator.itruediv(self._wrapped(), other)
270
+ return proxy_inplace_op(self, operator.itruediv, other)
246
271
 
247
272
  def __ifloordiv__(self, other):
248
- return operator.iflootdiv(self._wrapped(), other)
273
+ return proxy_inplace_op(self, operator.ifloordiv, other)
249
274
 
250
275
  def __imod__(self, other):
251
- return operator.imod(self._wrapped(), other)
276
+ return proxy_inplace_op(self, operator.imod, other)
252
277
 
253
278
  def __ipow__(self, other, *args):
254
- return operator.ipow(self._wrapped(), other, *args)
279
+ return proxy_inplace_op(self, operator.ipow, other, *args)
255
280
 
256
281
  def __ilshift__(self, other):
257
- return operator.ilshift(self._wrapped(), other)
282
+ return proxy_inplace_op(self, operator.ilshift, other)
258
283
 
259
284
  def __irshift__(self, other):
260
- return operator.irshift(self._wrapped(), other)
285
+ return proxy_inplace_op(self, operator.irshift, other)
261
286
 
262
287
  def __iand__(self, other):
263
- return operator.iand(self._wrapped(), other)
288
+ return proxy_inplace_op(self, operator.iand, other)
264
289
 
265
290
  def __ixor__(self, other):
266
- return operator.ixor(self._wrapped(), other)
291
+ return proxy_inplace_op(self, operator.ixor, other)
267
292
 
268
293
  def __ior__(self, other):
269
- return operator.ior(self._wrapped(), other)
294
+ return proxy_inplace_op(self, operator.ior, other)
270
295
 
271
296
  def __neg__(self):
272
297
  return -self._wrapped()
@@ -7,12 +7,12 @@ class ObjectWrap(ObjectProxy):
7
7
  def __init__(self, obj):
8
8
  object.__setattr__(self, "_o", obj)
9
9
 
10
- def _wrapped(self):
10
+ def _realize(self):
11
11
  return object.__getattribute__(self, "_o")
12
12
 
13
13
 
14
14
  class TestObjectProxy:
15
- def test_object_proxy(self) -> None:
15
+ def test_object_proxy_over_list(self) -> None:
16
16
  i = [1, 2, 3]
17
17
  proxy = ObjectWrap(i)
18
18
  assert i == proxy
@@ -21,3 +21,21 @@ class TestObjectProxy:
21
21
  assert [1, 2, 3, 4, 5] == proxy + [5]
22
22
  assert [2, 3] == proxy[1:3]
23
23
  assert [1, 2, 3, 4] == proxy
24
+
25
+ def test_inplace_identities(self) -> None:
26
+ proxy = ObjectWrap(3.0)
27
+ orig_proxy = proxy
28
+ proxy += 1.0
29
+ assert proxy is not orig_proxy
30
+ proxy = ObjectWrap([1, 2])
31
+ orig_proxy = proxy
32
+ proxy += [3, 4]
33
+ assert proxy is orig_proxy
34
+
35
+ def test_object_proxy_over_float(self) -> None:
36
+ proxy = ObjectWrap(4.5)
37
+ proxy //= 2.0
38
+ assert 2.0 == proxy
39
+ proxy = ObjectWrap(5.0)
40
+ proxy /= 2.0
41
+ assert 2.5 == proxy
@@ -5,7 +5,9 @@ from collections import defaultdict
5
5
  from collections.abc import MutableMapping, Set
6
6
  from sys import version_info
7
7
  from types import CodeType, FrameType
8
- from typing import Any, Callable, Iterable, Mapping, Tuple, Union
8
+ from typing import Any, Callable, Iterable, List, Mapping, Tuple, Union
9
+
10
+ from z3 import ExprRef
9
11
 
10
12
  from crosshair.core import (
11
13
  ATOMIC_IMMUTABLE_TYPES,
@@ -83,13 +85,6 @@ class MultiSubscriptableContainer:
83
85
  if isinstance(container, Mapping):
84
86
  kv_pairs: Iterable[Tuple[Any, Any]] = container.items()
85
87
  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
88
  kv_pairs = enumerate(container)
94
89
 
95
90
  values_by_type = defaultdict(list)
@@ -118,7 +113,7 @@ class MultiSubscriptableContainer:
118
113
  keys_by_value_id[id(cur_value)].append(cur_key)
119
114
  for value_type, cur_pairs in values_by_type.items():
120
115
  hypothetical_result = symbolic_for_pytype(value_type)(
121
- "item_at_" + space.uniq(), value_type
116
+ "item_" + space.uniq(), value_type
122
117
  )
123
118
  with ResumedTracing():
124
119
  condition_pairs = []
@@ -139,20 +134,17 @@ class MultiSubscriptableContainer:
139
134
  space.add(any([all(pair) for pair in condition_pairs]))
140
135
  return hypothetical_result
141
136
 
142
- for (value_id, value), probability_true in with_uniform_probabilities(
143
- values_by_id.items()
144
- ):
137
+ exprs_and_values: List[Tuple[ExprRef, object]] = []
138
+ for value_id, value in values_by_id.items():
145
139
  keys_for_value = keys_by_value_id[value_id]
146
140
  with ResumedTracing():
147
141
  is_match = any([key == k for k in keys_for_value])
148
142
  if isinstance(is_match, SymbolicBool):
149
- if space.smt_fork(
150
- is_match.var,
151
- probability_true=probability_true,
152
- ):
153
- return value
143
+ exprs_and_values.append((is_match.var, value))
154
144
  elif is_match:
155
145
  return value
146
+ if exprs_and_values:
147
+ return space.smt_fanout(exprs_and_values, desc="multi_subscript")
156
148
 
157
149
  if type(container) is dict:
158
150
  raise KeyError # ( f"Key {key} not found in dict")
crosshair/path_cover.py CHANGED
@@ -133,7 +133,11 @@ def path_cover(
133
133
  selected: List[PathSummary] = []
134
134
  while paths:
135
135
  next_best = max(
136
- paths, key=lambda p: len(p.coverage.offsets_covered - opcodes_found)
136
+ paths,
137
+ key=lambda p: (
138
+ len(p.coverage.offsets_covered - opcodes_found), # high coverage
139
+ -len(p.formatted_args), # with small input size
140
+ ),
137
141
  )
138
142
  cur_offsets = next_best.coverage.offsets_covered
139
143
  if coverage_type == CoverageType.OPCODE:
@@ -2,7 +2,7 @@ import math
2
2
  from collections import defaultdict
3
3
  from typing import Counter, Dict, List, Optional, Sequence, Tuple
4
4
 
5
- from z3 import ExprRef # type: ignore
5
+ from z3 import ExprRef
6
6
 
7
7
  from crosshair.statespace import (
8
8
  AbstractPathingOracle,
@@ -11,9 +11,11 @@ from crosshair.statespace import (
11
11
  NodeLike,
12
12
  RootNode,
13
13
  SearchTreeNode,
14
+ StateSpace,
14
15
  WorstResultNode,
15
16
  )
16
17
  from crosshair.util import CrossHairInternal, debug, in_debug
18
+ from crosshair.z3util import z3And, z3Not, z3Or
17
19
 
18
20
  CodeLoc = Tuple[str, ...]
19
21
 
@@ -60,7 +62,8 @@ class CoveragePathingOracle(AbstractPathingOracle):
60
62
  # (even just a 10% change could be much larger than it would be otherwise)
61
63
  _delta_probabilities = {-1: 0.1, 0: 0.25, 1: 0.9}
62
64
 
63
- def pre_path_hook(self, root: RootNode) -> None:
65
+ def pre_path_hook(self, space: StateSpace) -> None:
66
+ root = space._root
64
67
  visits = self.visits
65
68
  _delta_probabilities = self._delta_probabilities
66
69
 
@@ -213,16 +216,50 @@ class PreferNegativeOracle(AbstractPathingOracle):
213
216
  return 0.25
214
217
 
215
218
 
219
+ class ConstrainedOracle(AbstractPathingOracle):
220
+ """
221
+ A pathing oracle that prefers to take a path that satisfies
222
+ explicitly provided constraints.
223
+ """
224
+
225
+ def __init__(self, inner_oracle: AbstractPathingOracle):
226
+ self.inner_oracle = inner_oracle
227
+ self.exprs: List[ExprRef] = []
228
+
229
+ def prefer(self, expr: ExprRef):
230
+ self.exprs.append(expr)
231
+
232
+ def pre_path_hook(self, space: StateSpace) -> None:
233
+ self.space = space
234
+ self.exprs = []
235
+ self.inner_oracle.pre_path_hook(space)
236
+
237
+ def post_path_hook(self, path: Sequence["SearchTreeNode"]) -> None:
238
+ self.inner_oracle.post_path_hook(path)
239
+
240
+ def decide(
241
+ self, root, node: "WorstResultNode", engine_probability: Optional[float]
242
+ ) -> float:
243
+ # We always run the inner oracle in case it's tracking something about the path.
244
+ default_probability = self.inner_oracle.decide(root, node, engine_probability)
245
+ if not self.space.is_possible(z3And(*[node.expr, *self.exprs])):
246
+ return 0.0
247
+ elif not self.space.is_possible(z3And(*[z3Not(node.expr), *self.exprs])):
248
+ return 1.0
249
+ else:
250
+ return default_probability
251
+
252
+
216
253
  class RotatingOracle(AbstractPathingOracle):
217
254
  def __init__(self, oracles: List[AbstractPathingOracle]):
218
255
  self.oracles = oracles
219
256
  self.index = -1
220
257
 
221
- def pre_path_hook(self, root: "RootNode") -> None:
258
+ def pre_path_hook(self, space: StateSpace) -> None:
222
259
  oracles = self.oracles
223
260
  self.index = (self.index + 1) % len(oracles)
224
261
  for oracle in oracles:
225
- oracle.pre_path_hook(root)
262
+ oracle.pre_path_hook(space)
226
263
 
227
264
  def post_path_hook(self, path: Sequence["SearchTreeNode"]) -> None:
228
265
  for oracle in self.oracles:
@@ -0,0 +1,21 @@
1
+ import random
2
+
3
+ import z3
4
+
5
+ from crosshair.pathing_oracle import ConstrainedOracle, PreferNegativeOracle
6
+ from crosshair.statespace import RootNode, SimpleStateSpace, WorstResultNode
7
+
8
+
9
+ def test_constrained_oracle():
10
+ oracle = ConstrainedOracle(PreferNegativeOracle())
11
+ x = z3.Int("x")
12
+ root = RootNode()
13
+ space = SimpleStateSpace()
14
+ oracle.pre_path_hook(space)
15
+ oracle.prefer(x >= 7)
16
+ rand = random.Random()
17
+ assert oracle.decide(root, WorstResultNode(rand, x < 7, space.solver), None) == 0.0
18
+ assert oracle.decide(root, WorstResultNode(rand, x >= 3, space.solver), None) == 1.0
19
+ assert (
20
+ oracle.decide(root, WorstResultNode(rand, x == 7, space.solver), None) == 0.25
21
+ )
crosshair/statespace.py CHANGED
@@ -46,7 +46,7 @@ from crosshair.util import (
46
46
  in_debug,
47
47
  name_of_type,
48
48
  )
49
- from crosshair.z3util import z3Aassert, z3Not, z3PopNot
49
+ from crosshair.z3util import z3Aassert, z3Not, z3Or, z3PopNot
50
50
 
51
51
 
52
52
  @functools.total_ordering
@@ -219,7 +219,7 @@ class StateSpaceCounter(Counter):
219
219
 
220
220
 
221
221
  class AbstractPathingOracle:
222
- def pre_path_hook(self, root: "RootNode") -> None:
222
+ def pre_path_hook(self, space: "StateSpace") -> None:
223
223
  pass
224
224
 
225
225
  def post_path_hook(self, path: Sequence["SearchTreeNode"]) -> None:
@@ -350,6 +350,7 @@ def solver_is_sat(solver, *exprs) -> bool:
350
350
  ret = solver.check(*exprs)
351
351
  if ret == z3.unknown:
352
352
  debug("Z3 Unknown satisfiability. Reason:", solver.reason_unknown())
353
+ debug("Call stack at time of unknown sat:", ch_stack())
353
354
  if solver.reason_unknown() == "interrupted from keyboard":
354
355
  raise KeyboardInterrupt
355
356
  if exprs:
@@ -427,7 +428,7 @@ class RootNode(SinglePathNode):
427
428
  )
428
429
  from crosshair.pathing_oracle import CoveragePathingOracle # circular import
429
430
 
430
- self.pathing_oracle = CoveragePathingOracle()
431
+ self.pathing_oracle: AbstractPathingOracle = CoveragePathingOracle()
431
432
  self.iteration = 0
432
433
 
433
434
 
@@ -704,6 +705,17 @@ def debug_path_tree(node, highlights, prefix="") -> List[str]:
704
705
  return [f"{prefix} -> {str(node)} {node.stats()}"]
705
706
 
706
707
 
708
+ def make_default_solver() -> z3.Solver:
709
+ """Create a new solver with default settings."""
710
+ smt_tactic = z3.Tactic("smt")
711
+ solver = smt_tactic.solver()
712
+ solver.set("mbqi", True)
713
+ # turn off every randomization thing we can think of:
714
+ solver.set("random-seed", 42)
715
+ solver.set("smt.random-seed", 42)
716
+ return solver
717
+
718
+
707
719
  class StateSpace:
708
720
  """Holds various information about the SMT solver's current state."""
709
721
 
@@ -717,18 +729,12 @@ class StateSpace:
717
729
  model_check_timeout: float,
718
730
  search_root: RootNode,
719
731
  ):
720
- smt_tactic = z3.Tactic("smt")
721
- self.solver = smt_tactic.solver()
732
+ self.solver = make_default_solver()
722
733
  if model_check_timeout < 1 << 63:
723
734
  self.smt_timeout: Optional[int] = int(model_check_timeout * 1000 + 1)
724
735
  self.solver.set(timeout=self.smt_timeout)
725
736
  else:
726
737
  self.smt_timeout = None
727
- self.solver.set(mbqi=True)
728
- # turn off every randomization thing we can think of:
729
- self.solver.set("random-seed", 42)
730
- self.solver.set("smt.random-seed", 42)
731
- # self.solver.set('randomize', False)
732
738
  self.choices_made: List[SearchTreeNode] = []
733
739
  self.status_cap: Optional[VerificationStatus] = None
734
740
  self.heaps: List[List[Tuple[z3.ExprRef, Type, object]]] = [[]]
@@ -745,7 +751,7 @@ class StateSpace:
745
751
  self._deferred_assumptions = []
746
752
  assert search_root.iteration is not None
747
753
  search_root.iteration += 1
748
- search_root.pathing_oracle.pre_path_hook(search_root)
754
+ search_root.pathing_oracle.pre_path_hook(self)
749
755
 
750
756
  def add(self, expr) -> None:
751
757
  with NoTracing():
@@ -1049,6 +1055,51 @@ class StateSpace:
1049
1055
  self.next_uniq += 1
1050
1056
  return "_{:x}".format(self.next_uniq)
1051
1057
 
1058
+ @assert_tracing(False)
1059
+ def smt_fanout(
1060
+ self,
1061
+ exprs_and_results: Sequence[Tuple[z3.ExprRef, object]],
1062
+ desc: str,
1063
+ weights: Optional[Sequence[float]] = None,
1064
+ none_of_the_above_weight: float = 0.0,
1065
+ ):
1066
+ """Performs a weighted binary search over the given SMT expressions."""
1067
+ exprs = [e for (e, _) in exprs_and_results]
1068
+ final_weights = [1.0] * len(exprs) if weights is None else weights
1069
+ if CROSSHAIR_EXTRA_ASSERTS:
1070
+ if len(final_weights) != len(exprs):
1071
+ raise CrossHairInternal("inconsistent smt_fanout exprs and weights")
1072
+ if not all(0 < w for w in final_weights):
1073
+ raise CrossHairInternal("smt_fanout weights must be greater than zero")
1074
+ if not self.is_possible(z3Or(*exprs)):
1075
+ raise CrossHairInternal(
1076
+ "no smt_fanout option is possible: " + repr(exprs)
1077
+ )
1078
+ if self.is_possible(z3Not(z3Or(*exprs))):
1079
+ raise CrossHairInternal(
1080
+ "smt_fanout options are not exhaustive: " + repr(exprs)
1081
+ )
1082
+
1083
+ def attempt(start: int, end: int):
1084
+ size = end - start
1085
+ if size == 1:
1086
+ return exprs_and_results[start][1]
1087
+ mid = (start + end) // 2
1088
+ left_exprs = exprs[start:mid]
1089
+ left_weight = sum(final_weights[start:mid])
1090
+ right_weight = sum(final_weights[mid:end])
1091
+ if self.smt_fork(
1092
+ z3Or(*left_exprs),
1093
+ probability_true=left_weight / (left_weight + right_weight),
1094
+ desc=f"{desc}_fan_size_{size}",
1095
+ ):
1096
+ return attempt(start, mid)
1097
+ else:
1098
+ return attempt(mid, end)
1099
+
1100
+ return attempt(0, len(exprs))
1101
+
1102
+ @assert_tracing(False)
1052
1103
  def smt_fork(
1053
1104
  self,
1054
1105
  expr: Optional[z3.ExprRef] = None,
@@ -1062,6 +1113,14 @@ class StateSpace:
1062
1113
  def defer_assumption(self, description: str, checker: Callable[[], bool]) -> None:
1063
1114
  self._deferred_assumptions.append((description, checker))
1064
1115
 
1116
+ def extend_timeouts(
1117
+ self, constant_factor: float = 0.0, smt_multiple: Optional[float] = None
1118
+ ) -> None:
1119
+ self.execution_deadline += constant_factor
1120
+ if self.smt_timeout is not None and smt_multiple is not None:
1121
+ self.smt_timeout = int(self.smt_timeout * smt_multiple)
1122
+ self.solver.set(timeout=self.smt_timeout)
1123
+
1065
1124
  def detach_path(self, currently_handling: Optional[BaseException] = None) -> None:
1066
1125
  """
1067
1126
  Mark the current path exhausted.
@@ -1075,13 +1134,9 @@ class StateSpace:
1075
1134
  if self.is_detached:
1076
1135
  debug("Path is already detached")
1077
1136
  return
1078
- else:
1079
- # Give ourselves a time extension for deferred assumptions and
1080
- # (likely) counterexample generation to follow.
1081
- self.execution_deadline += 4.0
1082
- if self.smt_timeout is not None:
1083
- self.smt_timeout = self.smt_timeout * 2
1084
- self.solver.set(timeout=self.smt_timeout)
1137
+ # Give ourselves a time extension for deferred assumptions and
1138
+ # (likely) counterexample generation to follow.
1139
+ self.extend_timeouts(constant_factor=4.0, smt_multiple=2.0)
1085
1140
  for description, checker in self._deferred_assumptions:
1086
1141
  with ResumedTracing():
1087
1142
  check_ret = checker()
@@ -81,3 +81,19 @@ def test_model_value_to_python_AlgebraicNumRef():
81
81
  rt2 = z3.simplify(z3.Sqrt(2))
82
82
  assert type(rt2) == z3.AlgebraicNumRef
83
83
  model_value_to_python(rt2)
84
+
85
+
86
+ def test_smt_fanout(space: SimpleStateSpace):
87
+ option1 = z3.Bool("option1")
88
+ option2 = z3.Bool("option2")
89
+ space.add(z3.Xor(option1, option2)) # Ensure exactly one option can be set
90
+ exprs_and_results = [(option1, "result1"), (option2, "result2")]
91
+
92
+ result = space.smt_fanout(exprs_and_results, desc="choose_one")
93
+ assert result in ("result1", "result2")
94
+ if result == "result1":
95
+ assert space.is_possible(option1)
96
+ assert not space.is_possible(option2)
97
+ else:
98
+ assert not space.is_possible(option1)
99
+ assert space.is_possible(option2)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: crosshair-tool
3
- Version: 0.0.94
3
+ Version: 0.0.96
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
@@ -1,5 +1,5 @@
1
1
  _crosshair_tracers.cpython-38-x86_64-linux-gnu.so,sha256=I2PQ2mjtjrfVX3uIISbP3TZ2i1ZIigIgZIWJxRFSX2Q,75984
2
- crosshair/__init__.py,sha256=5u1xF74Cj90mIqU71fUSrQIoexYRT3mwC7WOse0f2EQ,936
2
+ crosshair/__init__.py,sha256=pX7dpKGv0frAXPUfHUH9_8CmbQ0ItGRkG9yPi7_uApI,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
@@ -12,16 +12,16 @@ crosshair/auditwall_test.py,sha256=VPcw_OW3nl3BkOZY4wEEtVDyTamdgqD4IjRccI2p5vI,2
12
12
  crosshair/codeconfig.py,sha256=GgF-ND8Ha3FysSTQ-JuezHjlhGVBbo5aCJov1Ps3VSE,3959
13
13
  crosshair/codeconfig_test.py,sha256=RnC-RnNpr6If4eHmOepDZ33MCmfyhup08dzHKCm5xWA,3350
14
14
  crosshair/condition_parser.py,sha256=oquaht026eZUigh2lyaFLXYDbmENdBKjddszx0a-B3w,42647
15
- crosshair/condition_parser_test.py,sha256=eUQYnVkHewn8qg-XbzcElb0mHkPxPJAX548dPVyxqqk,15532
15
+ crosshair/condition_parser_test.py,sha256=UcgxzqrBLUMScif_RrgHvrhjzWx1KUPgAQOEmfJw7lc,15500
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=7CHbmWvCVK2MlKXJUUe3eILPWGZniF8qXkBJNDkU4qg,64168
19
+ crosshair/core.py,sha256=nUdmPCcEWxaYawkydj2AJ7VdXe8IIu7hVyDi83Bxwec,64262
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
- crosshair/core_test.py,sha256=540ASl3zOQf3AnZORGJus4PMYHP5YM15zTWhNI16Dew,33009
22
+ crosshair/core_test.py,sha256=P-r-qzHPZ2yLmSBZByYPccJZfxYi2ZCwn9o1mEfzRe8,33019
23
23
  crosshair/diff_behavior.py,sha256=_5X_pTN0_-rSPrh8dfpODJG_phFMn7fWc-_zLgO3UTk,11253
24
- crosshair/diff_behavior_test.py,sha256=ckK3HScFrmRRZdyq1iCmkwToksMJG2UVUjABnfnSwCM,7242
24
+ crosshair/diff_behavior_test.py,sha256=nCpzOjrw0qsYgVhD2iCvKiNAt82SrfUWxWS5mPSE73w,7215
25
25
  crosshair/dynamic_typing.py,sha256=ANq42kxSQ5B0STZF3uwOEys_fLCj20cMCCcBH6dbXWo,12758
26
26
  crosshair/dynamic_typing_test.py,sha256=8p4NOJ6i9q9mozgCYlZP3VCs-TFMDaE_7W-TyNEse4o,5907
27
27
  crosshair/enforce.py,sha256=FsZx3D-KtGrhb8xdAZbPUtwvVmEu8IAn7rwf7tmkrRY,10010
@@ -33,18 +33,19 @@ crosshair/lsp_server.py,sha256=j7SX4pdVwa2MrtkNIjajLilzl5CZTY6PrBQsa26cdNo,8670
33
33
  crosshair/lsp_server_test.py,sha256=7LO1Qqxkper3Xt2krgOlGqF1O_uDObo76o4FZbIqykY,969
34
34
  crosshair/main.py,sha256=TkaOr39tMV9ZHQXgfJobKFEVW60XpHUqdY520nMIWw8,34659
35
35
  crosshair/main_test.py,sha256=2xpgNqog__XcYffGcwPeEEmr0Vy4EgVZE8GCAjQnE8U,14834
36
- crosshair/objectproxy.py,sha256=Uc6mNkJBInvETGWBHI10GSNEIrBYDIxlAZ30M3PAIhQ,8935
37
- crosshair/objectproxy_test.py,sha256=KykRJLSHCDA7jb_XPBDhXHnS6Q1fG4oIJ579CeSEz3k,567
38
- crosshair/opcode_intercept.py,sha256=z4Yb9prYE2UK21AxhjAeXyXAk5IriDuCSSCeNhbDu2A,21880
36
+ crosshair/objectproxy.py,sha256=1cO_ApA0AKPfCRu6MIsxUOKUUEGn0b1U4IHxTC4nDGI,9790
37
+ crosshair/objectproxy_test.py,sha256=UJuO_jUt8_OEUtgQWyhlekPOdvtM8IQ5M9I_1AqXPWM,1081
38
+ crosshair/opcode_intercept.py,sha256=ZNePiOMX5xmREkA-x2t1MuM8fmHPbaogMOmGYn21NzU,21579
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
42
  crosshair/patch_equivalence_test.py,sha256=eoLaGRvrR9nGUO_ybZ9XsWhs5ejC4IEPd0k-ihG3Nsg,2580
43
- crosshair/path_cover.py,sha256=wV0Vy8IPDzqXQ2VI8a94FxltS9p-Y1oF17OKePjvpgs,6710
43
+ crosshair/path_cover.py,sha256=TCofZ9D5q7hpEIOnifp53BvY_YyPpZZC-heZw_NuTOQ,6838
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=3M93zXMorWr8m5c1KM2zGU5X1M3lcV-AIw0lX74eN00,9219
47
+ crosshair/pathing_oracle.py,sha256=PvqX7396SaQK3m-bMpCwBKmHW9SYiioooUWNvmmEqNw,10497
48
+ crosshair/pathing_oracle_test.py,sha256=-6U-n1k6MahHYnQ6lvI9ahFjPr9vUfaUAnFThXR2k78,709
48
49
  crosshair/pure_importer.py,sha256=-t4eowrZOQmfqK1N2tjI5POoaxRGavytwMmbRivelFg,878
49
50
  crosshair/pure_importer_test.py,sha256=Xjtlwn1mj7g-6VA87lrvzfUADCjlmn1wgHtbrnc0uuY,421
50
51
  crosshair/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -54,8 +55,8 @@ crosshair/simplestructs.py,sha256=CiZSuHH_j_bYitaW-n7vWd_42xSyV6Jh8es3BQLlcHk,34
54
55
  crosshair/simplestructs_test.py,sha256=6uDdrSISHLhwnFuESkR8mUGw7m1llM6vCNDFChkfSs8,8639
55
56
  crosshair/smtlib.py,sha256=hh-P32KHoH9BCq3oDYGp2PfOeOb8CwDj8tTkgqroLD8,689
56
57
  crosshair/smtlib_test.py,sha256=edzEn19u2YYHxSzG9RrMiu2HTiEexAuehC3IlG9LuJM,511
57
- crosshair/statespace.py,sha256=TVjUhFZU0x72oLPN7-eRu7PoMh1OiQOy0uMASsFb_Qw,41236
58
- crosshair/statespace_test.py,sha256=LOblIarBbcB9oD_gVR5kK_4P2PWQymVGgJr3wNqP3Fs,2621
58
+ crosshair/statespace.py,sha256=h1un79C-vkyKJ1iLAU4lQlVu3A6YqqbpcrqHGCsTsrg,43442
59
+ crosshair/statespace_test.py,sha256=Eq7LNpjradHyCoCKU91Fxmo9DUpK2Mk4PyxxiAEp-Yk,3211
59
60
  crosshair/stubs_parser.py,sha256=rlBTQus5BlZ3Ygg6Xzk5dbQbDtRpv6w9i2HQmGrPVmc,14240
60
61
  crosshair/stubs_parser_test.py,sha256=0itTT0Udul_51RJXNv6KB97z44gYze6NZfKJL7yIDzA,1228
61
62
  crosshair/test_util.py,sha256=D9-f-DdzJemfAUkQL0cwKxPL8RZ-5gkVmghyRcKlBJI,10367
@@ -100,14 +101,14 @@ crosshair/libimpl/binascii_ch_test.py,sha256=hFqSfF1Q8jl2LNBIWaQ6vBJIIshPOmSwrR0
100
101
  crosshair/libimpl/binascii_test.py,sha256=LOBqLAJ77Kx8vorjVTaT3X0Z93zw4P5BvwUapMCiSLg,1970
101
102
  crosshair/libimpl/binasciilib.py,sha256=9w4C37uxRNOmz9EUuhJduHlMKn0f7baY5fwwdvx1uto,5070
102
103
  crosshair/libimpl/bisectlib_test.py,sha256=ZQtYmBYD0Pb1IiFelsgdvqyeUMKaqaDb1BRb87LTSbI,753
103
- crosshair/libimpl/builtinslib.py,sha256=UU2WDnQbPQMIETjvKfJlDXAFYyAXcRVvGt4SFYs-Jgk,171663
104
- crosshair/libimpl/builtinslib_ch_test.py,sha256=W4wWapqlxSjsC5XgREfgxS9e_iwKxgNQhbFE3umUfNI,30504
105
- crosshair/libimpl/builtinslib_test.py,sha256=Y_jRe5J6pPaJ_Nuk1lJ1biP5yczsfCj--NgNhwcbAfQ,90654
104
+ crosshair/libimpl/builtinslib.py,sha256=j275QUgWqnq62immbBqUzlPesOq0t-oTb0C9WL7sIiA,173276
105
+ crosshair/libimpl/builtinslib_ch_test.py,sha256=Fyu2fZLXI-pUaS3gyyM6zMQhsTFdpRCnnp1rXwxKXwk,30854
106
+ crosshair/libimpl/builtinslib_test.py,sha256=jbN2t-zteWqF5TxA7oo4c9UsPrqklBY7wlEt16rxqtI,92230
106
107
  crosshair/libimpl/codecslib.py,sha256=lB87T1EYSBh4JXaqzjSpQG9CMfKtgckwA7f6OIR0S-Q,2668
107
108
  crosshair/libimpl/codecslib_test.py,sha256=8K64njhxnTe7qLh-_onARNsm_qSG7BWM5dXgADYi54A,2536
108
109
  crosshair/libimpl/collectionslib.py,sha256=VuDIOUOGORiUT9J0tDVECr6oC-yUR3WAiFnMeVGzi-o,8583
109
110
  crosshair/libimpl/collectionslib_ch_test.py,sha256=PYitnmXXEZfm25FzBodEX1hOpwqnDspbqt5aqzeVar0,5855
110
- crosshair/libimpl/collectionslib_test.py,sha256=3h7XTToKWauMhjrEwLXVI0jT8FZKBlkvlw0oiPMmuKM,9164
111
+ crosshair/libimpl/collectionslib_test.py,sha256=0qmCRQbxfX8vMZLaok1GS68NIocR--RyTVVDqbtHalU,9185
111
112
  crosshair/libimpl/copylib.py,sha256=icHJWeFK_tsPSdJhvlS5fVcBwlh0uJh0nzXsRJCGtzs,527
112
113
  crosshair/libimpl/copylib_test.py,sha256=VpS9ICvIjzKw0kccsl1v5FB1chHHEIWdOAerEQ_pGMw,705
113
114
  crosshair/libimpl/datetimelib.py,sha256=kVWcoHMX-dPW-un7XoEIURfWlVOst6h_JFmjrhFiE1U,79168
@@ -119,8 +120,8 @@ crosshair/libimpl/decimallib_test.py,sha256=393MkVB9-LPcA7JJK6wGAbDyd-YejkjwrXRa
119
120
  crosshair/libimpl/encodings_ch_test.py,sha256=0qLsioOuFUZkOjP4J9Wct4CGBaBY8BnHx9paZHnIofI,2513
120
121
  crosshair/libimpl/fractionlib.py,sha256=qdbiAHHC480YdKq3wYK_piZ3UD7oT64YfuNclypMUfQ,458
121
122
  crosshair/libimpl/fractionlib_test.py,sha256=g7uNHTfzDebyc-SgH_4ziUAz7rJLZlHGZmPpny2P6hs,2357
122
- crosshair/libimpl/functoolslib.py,sha256=LnMtmq2Sawde6XtPd6Yg_YOc6S5ai-pMpBWPQAkR3X4,783
123
- crosshair/libimpl/functoolslib_test.py,sha256=DswrS51n93EaxPvDGB-d3tZSLawEp38zQ5sNdYlbn50,1114
123
+ crosshair/libimpl/functoolslib.py,sha256=YD0g9UnC4v_wZXR3ekQa2gLrKJnr6dHdYtT9qIMUIGM,1009
124
+ crosshair/libimpl/functoolslib_test.py,sha256=eaT_JWu-C3j8l9ekwDXd2dhPJvuB577T9DuXRohL0fc,1604
124
125
  crosshair/libimpl/hashliblib.py,sha256=Ki_cw28OnhZExgKbSoh5GaDbBfNRIOqH7O2aYQJGS3M,1234
125
126
  crosshair/libimpl/hashliblib_test.py,sha256=HhPdm5CBTAeqrs41NpCxkexWYWyIf1JiA4cls72WQfM,406
126
127
  crosshair/libimpl/heapqlib.py,sha256=TWH55dg-Hi5FRz2oZuXHcBU_xJzHjvhe9YQVvw7ZbfI,1311
@@ -167,9 +168,9 @@ crosshair/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
167
168
  crosshair/tools/check_help_in_doc.py,sha256=P21AH3mYrTVuBgWD6v65YXqBqmqpQDUTQeoZ10rB6TU,8235
168
169
  crosshair/tools/check_init_and_setup_coincide.py,sha256=kv61bXqKSKF_5J-kLNEhCrCPyszg7iZQWDu_Scnec98,3502
169
170
  crosshair/tools/generate_demo_table.py,sha256=0SeO0xQdiT-mbLNHt4rYL0wcc2DMh0v3qtzBdoQonDk,3831
170
- crosshair_tool-0.0.94.dist-info/LICENSE,sha256=NVyMvNqn1pH6RSHs6RWRcJyJvORnpgGFBlF73buqYJ0,4459
171
- crosshair_tool-0.0.94.dist-info/METADATA,sha256=TPswJ5qMY4_kuA_pNeu-OiXn22HtZABqBpS9PkMdFx8,6558
172
- crosshair_tool-0.0.94.dist-info/WHEEL,sha256=AtKzrIIwO6LyEQPNa-CKogjoLSeXFnST8-hqmpwwZQA,110
173
- crosshair_tool-0.0.94.dist-info/entry_points.txt,sha256=u5FIPVn1jqn4Kzg5K_iNnbP6L4hQw5FWjQ0UMezG2VE,96
174
- crosshair_tool-0.0.94.dist-info/top_level.txt,sha256=2jLWtM-BWg_ZYNbNfrcds0HFZD62a6J7ZIbcgcQrRk4,29
175
- crosshair_tool-0.0.94.dist-info/RECORD,,
171
+ crosshair_tool-0.0.96.dist-info/LICENSE,sha256=NVyMvNqn1pH6RSHs6RWRcJyJvORnpgGFBlF73buqYJ0,4459
172
+ crosshair_tool-0.0.96.dist-info/METADATA,sha256=UaPu-bjAbWd7htrz1n-BFz0AuNy_NtGKhZklVoNs6x8,6558
173
+ crosshair_tool-0.0.96.dist-info/WHEEL,sha256=AtKzrIIwO6LyEQPNa-CKogjoLSeXFnST8-hqmpwwZQA,110
174
+ crosshair_tool-0.0.96.dist-info/entry_points.txt,sha256=u5FIPVn1jqn4Kzg5K_iNnbP6L4hQw5FWjQ0UMezG2VE,96
175
+ crosshair_tool-0.0.96.dist-info/top_level.txt,sha256=2jLWtM-BWg_ZYNbNfrcds0HFZD62a6J7ZIbcgcQrRk4,29
176
+ crosshair_tool-0.0.96.dist-info/RECORD,,