crosshair-tool 0.0.84__cp38-cp38-win32.whl → 0.0.85__cp38-cp38-win32.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.84" # Do not forget to update in setup.py!
18
+ __version__ = "0.0.85" # 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,31 +538,6 @@ static const uint8_t _ch_DE_INSTRUMENT[256] = {
538
538
  #endif
539
539
  #endif
540
540
 
541
- static const uint8_t _ch_TRACABLE_INSTRUCTIONS[256] = {
542
- // This must be manually kept in sync the the various
543
- // instructions that we care about on the python side.
544
- [MAP_ADD] = 1,
545
- [BINARY_SUBSCR] = 1,
546
- [BINARY_SLICE] = 1,
547
- [CONTAINS_OP] = 1,
548
- [BUILD_STRING] = 1,
549
- #if PY_VERSION_HEX < 0x030D0000
550
- // <= 3.12
551
- [FORMAT_VALUE] = 1,
552
- #elif PY_VERSION_HEX < 0x030E0000
553
- // 3.13
554
- [CALL_KW] = 1,
555
- [CONVERT_VALUE] = 1,
556
- #endif
557
- [UNARY_NOT] = 1,
558
- [SET_ADD] = 1,
559
- [IS_OP] = 1,
560
- [BINARY_OP] = 1,
561
- [CALL] = 1,
562
- [CALL_FUNCTION_EX] = 1,
563
- };
564
-
565
-
566
541
  /* Get the underlying opcode, stripping instrumentation */
567
542
  int _ch_Py_GetBaseOpcode(PyCodeObject *code, int i)
568
543
  {
crosshair/_tracers.h CHANGED
@@ -88,4 +88,6 @@ typedef struct TraceSwap {
88
88
 
89
89
  extern PyTypeObject TraceSwapType;
90
90
 
91
+ extern const uint8_t _ch_TRACABLE_INSTRUCTIONS[256];
92
+
91
93
  #endif /* _COVERAGE_TRACER_H */
@@ -89,7 +89,10 @@ def _log_execution_stacks(fn, *a, **kw):
89
89
  return stacks
90
90
 
91
91
 
92
- @pytest.mark.skipif(sys.version_info < (3, 12), reason="stack depth on 3.12+")
92
+ @pytest.mark.skipif(
93
+ sys.version_info < (3, 12) or sys.version_info >= (3, 14),
94
+ reason="stack depths only in 3.12 & 3.13",
95
+ )
93
96
  def test_one_function_stack_depth():
94
97
  _E = (TypeError, KeyboardInterrupt)
95
98
 
@@ -100,7 +103,10 @@ def test_one_function_stack_depth():
100
103
  _log_execution_stacks(a, 4)
101
104
 
102
105
 
103
- @pytest.mark.skipif(sys.version_info < (3, 12), reason="stack depth on 3.12+")
106
+ @pytest.mark.skipif(
107
+ sys.version_info < (3, 12) or sys.version_info >= (3, 14),
108
+ reason="stack depths only in 3.12 & 3.13",
109
+ )
104
110
  def test_stack_get():
105
111
  def to_be_traced(x):
106
112
  r = 8 - x
@@ -47,6 +47,7 @@ from crosshair.options import AnalysisKind
47
47
  from crosshair.register_contract import get_contract
48
48
  from crosshair.tracers import NoTracing
49
49
  from crosshair.util import (
50
+ CrossHairInternal,
50
51
  DynamicScopeVar,
51
52
  EvalFriendlyReprContext,
52
53
  IdKeyedDict,
@@ -485,6 +486,7 @@ class ConcreteConditionParser(ConditionParser):
485
486
  method = cls.__dict__.get(method_name, None)
486
487
  super_method_conditions = super_methods.get(method_name)
487
488
  if super_method_conditions is not None:
489
+ # Re-type the super's `self` argument to be this class:
488
490
  revised_sig = set_first_arg_type(super_method_conditions.sig, cls)
489
491
  super_method_conditions = replace(
490
492
  super_method_conditions, sig=revised_sig
@@ -511,17 +513,15 @@ class ConcreteConditionParser(ConditionParser):
511
513
  final_pre = list(conditions.pre)
512
514
  final_post = list(conditions.post)
513
515
  if method_name in (
514
- "__new__", # isn't passed a concrete instance.
516
+ "__new__", # a staticmethod, but not isinstance(staticmethod)
515
517
  "__repr__", # is itself required for reporting problems with invariants.
516
518
  # [set/del]attr can do anything; we can't resonably enforce invariants:
517
519
  "__setattr__",
518
520
  "__delattr__",
521
+ "__replace__", # Will raise an exception with most arbitrary **kwargs.
522
+ "__annotate__", # a staticmethod, but not isinstance(staticmethod)
519
523
  ):
520
524
  pass
521
- elif method_name == "__replace__":
522
- # TODO: remove this case when fixed in 3.13
523
- # see https://github.com/python/cpython/issues/114198
524
- pass
525
525
  elif method_name == "__del__":
526
526
  final_pre.extend(inv)
527
527
  elif method_name == "__init__":
@@ -140,7 +140,7 @@ class TestPep316Parser:
140
140
  "self.x >= 0",
141
141
  "self.y >= 0",
142
142
  }
143
- assert set(class_conditions.methods.keys()) == {"isready", "__init__"}
143
+ assert {"isready", "__init__"} <= set(class_conditions.methods.keys())
144
144
  method = class_conditions.methods["isready"]
145
145
  assert set([c.expr_source for c in method.pre]) == {
146
146
  "self.x >= 0",
crosshair/copyext.py CHANGED
@@ -1,4 +1,9 @@
1
- from copy import _deepcopy_atomic # type: ignore
1
+ import sys
2
+
3
+ if sys.version_info >= (3, 14):
4
+ from copy import _atomic_types
5
+ else:
6
+ from copy import _deepcopy_atomic # type: ignore
2
7
  from copy import _deepcopy_dict # type: ignore
3
8
  from copy import _deepcopy_dispatch # type: ignore
4
9
  from copy import _deepcopy_list # type: ignore
@@ -9,7 +14,7 @@ from copy import Error
9
14
  from copyreg import dispatch_table # type: ignore
10
15
  from enum import Enum
11
16
  from types import MappingProxyType
12
- from typing import Any, Dict, Tuple
17
+ from typing import Any, Callable, Dict, Tuple
13
18
 
14
19
  from crosshair.tracers import ResumedTracing
15
20
  from crosshair.util import (
@@ -85,17 +90,28 @@ def deepcopyext(obj: object, mode: CopyMode, memo: Dict) -> Any:
85
90
  return cpy
86
91
 
87
92
 
93
+ if sys.version_info >= (3, 14):
94
+
95
+ def lookup_dispatch(cls: type) -> Callable:
96
+ if cls in _atomic_types:
97
+ return lambda obj, memo: obj
98
+ return _deepcopy_dispatch.get(cls)
99
+
100
+ else:
101
+
102
+ def lookup_dispatch(cls: type) -> Callable:
103
+ return _deepcopy_dispatch.get(cls)
104
+
105
+
88
106
  def _deepconstruct(obj: object, mode: CopyMode, memo: Dict):
89
107
  cls = type(obj)
90
108
 
91
109
  def subdeepcopy(obj: object, memo: Dict):
92
110
  return deepcopyext(obj, mode, memo)
93
111
 
94
- if cls in _deepcopy_dispatch:
95
- creator = _deepcopy_dispatch[cls]
96
- if creator is _deepcopy_atomic:
97
- return obj
98
- elif creator in (_deepcopy_dict, _deepcopy_list, _deepcopy_tuple):
112
+ creator = lookup_dispatch(cls)
113
+ if creator is not None:
114
+ if creator in (_deepcopy_dict, _deepcopy_list, _deepcopy_tuple):
99
115
  return creator(obj, memo, deepcopy=subdeepcopy)
100
116
  else:
101
117
  # TODO: We loose subdeepcopy in this case - won't
crosshair/copyext_test.py CHANGED
@@ -7,7 +7,7 @@ import pytest
7
7
  from crosshair.copyext import CopyMode, deepcopyext
8
8
  from crosshair.core_and_libs import proxy_for_type, standalone_statespace
9
9
  from crosshair.libimpl.builtinslib import SymbolicInt
10
- from crosshair.tracers import NoTracing, ResumedTracing
10
+ from crosshair.tracers import NoTracing
11
11
 
12
12
 
13
13
  def test_deepcopyext_best_effort():
@@ -33,6 +33,16 @@ def test_deepcopyext_symbolic_set():
33
33
  deepcopyext(s, CopyMode.REALIZE, {})
34
34
 
35
35
 
36
+ def test_deepcopyext_realize_simple(space):
37
+ x = SymbolicInt("x")
38
+ input = (x,)
39
+ output = deepcopyext(input, CopyMode.REALIZE, {})
40
+ assert input is not output
41
+ assert input[0] is not output[0]
42
+ assert type(input[0]) is SymbolicInt
43
+ assert type(output[0]) is int
44
+
45
+
36
46
  def test_deepcopyext_realize(space):
37
47
  x = SymbolicInt("x")
38
48
  lock = RLock()
@@ -4,7 +4,7 @@ from inspect import Parameter, Signature
4
4
  from itertools import zip_longest
5
5
  from typing import Any, Callable, Dict, List, Mapping, Optional, Sequence, Tuple, Type
6
6
 
7
- import typing_inspect
7
+ import typing_inspect # type: ignore
8
8
 
9
9
  from crosshair.util import debug # type: ignore
10
10
 
crosshair/fnutil_test.py CHANGED
@@ -26,7 +26,10 @@ def test_fn_globals_on_builtin() -> None:
26
26
 
27
27
  def test_resolve_signature_invalid_annotations() -> None:
28
28
  sig = resolve_signature(with_invalid_type_annotation)
29
- assert sig == "name 'TypeThatIsNotDefined' is not defined"
29
+ if sys.version_info >= (3, 14):
30
+ assert sig == "TypeThatIsNotDefined"
31
+ else:
32
+ assert sig == "name 'TypeThatIsNotDefined' is not defined"
30
33
 
31
34
 
32
35
  @pytest.mark.skipif(
@@ -30,19 +30,6 @@ INT_TYPE_BOUNDS: Dict[str, Tuple[int, int]] = {
30
30
  INT_TYPE_SIZE = {c: array(c).itemsize for c in INT_TYPE_BOUNDS.keys()}
31
31
 
32
32
 
33
- if sys.version_info >= (3, 12):
34
- from collections.abc import Buffer
35
-
36
- def is_bytes_like(obj: object) -> bool:
37
- return isinstance(obj, Buffer)
38
-
39
- else:
40
- from collections.abc import ByteString
41
-
42
- def is_bytes_like(obj: object) -> bool:
43
- return isinstance(obj, (ByteString, array))
44
-
45
-
46
33
  def pick_code(space: StateSpace) -> Tuple[str, int, int]:
47
34
  last_idx = len(INT_TYPE_BOUNDS) - 1
48
35
  for (idx, (code, rng)) in enumerate(INT_TYPE_BOUNDS.items()):
@@ -17,7 +17,7 @@ from dataclasses import dataclass
17
17
  from itertools import zip_longest
18
18
  from math import inf, isfinite, isinf, isnan, nan
19
19
  from numbers import Integral, Number, Real
20
- from sys import maxunicode
20
+ from sys import maxunicode, version_info
21
21
  from typing import (
22
22
  Any,
23
23
  BinaryIO,
@@ -58,6 +58,8 @@ try:
58
58
  except ImportError:
59
59
  from z3 import FfpEQ as fpEQ
60
60
 
61
+ import sys
62
+
61
63
  from crosshair.abcstring import AbcString
62
64
  from crosshair.core import (
63
65
  SymbolicFactory,
@@ -116,6 +118,7 @@ from crosshair.util import (
116
118
  assert_tracing,
117
119
  ch_stack,
118
120
  debug,
121
+ is_bytes_like,
119
122
  is_hashable,
120
123
  is_iterable,
121
124
  memo,
@@ -1213,10 +1216,11 @@ class SymbolicInt(SymbolicIntable, AtomicSymbolicValue):
1213
1216
  cur_divisor = 10
1214
1217
  while True:
1215
1218
  leftover = self // cur_divisor
1216
- if leftover == 0:
1219
+ if leftover != 0:
1220
+ codepoints.append(48 + (leftover % 10))
1221
+ cur_divisor *= 10
1222
+ else:
1217
1223
  break
1218
- codepoints.append(48 + (leftover % 10))
1219
- cur_divisor *= 10
1220
1224
  with NoTracing():
1221
1225
  codepoints.reverse()
1222
1226
  return LazyIntSymbolicStr(codepoints)
@@ -1316,7 +1320,7 @@ class SymbolicInt(SymbolicIntable, AtomicSymbolicValue):
1316
1320
  z3.If(val < 128, 7, 8)))))))))
1317
1321
  # fmt: on
1318
1322
 
1319
- if sys.version_info >= (3, 12):
1323
+ if version_info >= (3, 12):
1320
1324
 
1321
1325
  def is_integer(self):
1322
1326
  return True
@@ -2934,7 +2938,7 @@ class AnySymbolicStr(AbcString):
2934
2938
  def capitalize(self):
2935
2939
  if self.__len__() == 0:
2936
2940
  return ""
2937
- if sys.version_info >= (3, 8):
2941
+ if version_info >= (3, 8):
2938
2942
  firstchar = self[0].title()
2939
2943
  else:
2940
2944
  firstchar = self[0].upper()
@@ -3172,7 +3176,7 @@ class AnySymbolicStr(AbcString):
3172
3176
  return ""
3173
3177
 
3174
3178
  def splitlines(self, keepends=False):
3175
- if sys.version_info < (3, 12):
3179
+ if version_info < (3, 12):
3176
3180
  if not isinstance(keepends, int):
3177
3181
  raise TypeError
3178
3182
  mylen = self.__len__()
@@ -3438,6 +3442,7 @@ class LazyIntSymbolicStr(AnySymbolicStr, CrossHairValue):
3438
3442
  SliceView,
3439
3443
  SequenceConcatenation,
3440
3444
  list, # TODO: are we sharing mutable state here?
3445
+ tuple,
3441
3446
  ),
3442
3447
  ):
3443
3448
  self._codepoints = smtvar
@@ -3975,7 +3980,7 @@ class BytesLike(Buffer, AbcString, CrossHairValue):
3975
3980
  return False
3976
3981
  return list(self) == list(other)
3977
3982
 
3978
- if sys.version_info >= (3, 12):
3983
+ if version_info >= (3, 12):
3979
3984
 
3980
3985
  def __buffer__(self, flags: int):
3981
3986
  with NoTracing():
@@ -4135,7 +4140,10 @@ class SymbolicBytes(BytesLike):
4135
4140
  accumulated = []
4136
4141
  high = None
4137
4142
  if not isinstance(hexstr, str):
4138
- raise TypeError
4143
+ if is_bytes_like(hexstr) and version_info >= (3, 14):
4144
+ hexstr = LazyIntSymbolicStr(tuple(hexstr))
4145
+ else:
4146
+ raise TypeError
4139
4147
  for idx, ch in enumerate(hexstr):
4140
4148
  if not ch.isascii():
4141
4149
  raise ValueError(
@@ -4894,7 +4902,7 @@ def _int_from_bytes(
4894
4902
  ) -> int:
4895
4903
  if byteorder is _MISSING:
4896
4904
  # byteorder defaults to "big" as of 3.11
4897
- if sys.version_info >= (3, 11):
4905
+ if version_info >= (3, 11):
4898
4906
  byteorder = "big"
4899
4907
  else:
4900
4908
  raise TypeError
@@ -5108,7 +5116,7 @@ def make_registrations():
5108
5116
 
5109
5117
  register_type(Union, make_union_choice)
5110
5118
 
5111
- if sys.version_info >= (3, 8):
5119
+ if version_info >= (3, 8):
5112
5120
  from typing import Final
5113
5121
 
5114
5122
  register_type(Final, lambda p, t: p(t))
@@ -5266,7 +5274,7 @@ def make_registrations():
5266
5274
  "upper",
5267
5275
  "zfill",
5268
5276
  ]
5269
- if sys.version_info >= (3, 9):
5277
+ if version_info >= (3, 9):
5270
5278
  names_to_str_patch.append("removeprefix")
5271
5279
  names_to_str_patch.append("removesuffix")
5272
5280
  for name in names_to_str_patch:
@@ -5322,12 +5330,12 @@ def make_registrations():
5322
5330
  # Patches on int
5323
5331
  register_patch(int.__repr__, with_checked_self(int, "__repr__"))
5324
5332
  register_patch(int.as_integer_ratio, with_checked_self(int, "as_integer_ratio"))
5325
- if sys.version_info >= (3, 10):
5333
+ if version_info >= (3, 10):
5326
5334
  register_patch(int.bit_count, with_checked_self(int, "bit_count"))
5327
5335
  register_patch(int.bit_length, with_checked_self(int, "bit_length"))
5328
5336
  register_patch(int.conjugate, with_checked_self(int, "conjugate"))
5329
5337
  register_patch(int.from_bytes, _int_from_bytes)
5330
- if sys.version_info >= (3, 12):
5338
+ if version_info >= (3, 12):
5331
5339
  register_patch(int.is_integer, with_checked_self(int, "is_integer"))
5332
5340
  register_patch(int.to_bytes, with_checked_self(int, "to_bytes"))
5333
5341
 
@@ -126,8 +126,19 @@ class ListBasedDeque(collections.abc.MutableSequence, CrossHairValue, Generic[T]
126
126
  prefix.reverse()
127
127
  self._contents = prefix + self._contents
128
128
 
129
- def index(self, item: T, *bounds) -> int:
130
- return self._contents.index(item, *bounds)
129
+ if sys.version_info >= (3, 14):
130
+
131
+ def index(self, item: T, *bounds) -> int:
132
+ try:
133
+ return self._contents.index(item, *bounds)
134
+ except ValueError as exc:
135
+ exc.args = ("deque.index(x): x not in deque",)
136
+ raise
137
+
138
+ else:
139
+
140
+ def index(self, item: T, *bounds) -> int:
141
+ return self._contents.index(item, *bounds)
131
142
 
132
143
  def insert(self, index: int, item: T) -> None:
133
144
  self._contents.insert(index, item)
@@ -1,3 +1,4 @@
1
+ import re
1
2
  import sys
2
3
  from collections import Counter, defaultdict, deque, namedtuple
3
4
  from copy import deepcopy
@@ -98,7 +99,11 @@ def test_deque_index_with_start_index_throws_correct_exception(test_list) -> Non
98
99
  with pytest.raises(ValueError) as context:
99
100
  test_list.index(1, 2)
100
101
 
101
- assert context.match("1 is not in list")
102
+ if sys.version_info >= (3, 14):
103
+ # assert context.match(re.escape("list.index(x): x not in list"))
104
+ assert context.match(re.escape("deque.index(x): x not in deque"))
105
+ else:
106
+ assert context.match("1 is not in list")
102
107
 
103
108
 
104
109
  def test_deque_index_with_start_and_end_index(test_list) -> None:
@@ -112,7 +117,10 @@ def test_deque_index_with_start_and_end_index_throws_correct_exception(
112
117
  with pytest.raises(ValueError) as context:
113
118
  test_list.index(6, 0, 1)
114
119
 
115
- assert context.match("6 is not in list")
120
+ if sys.version_info >= (3, 14):
121
+ assert context.match(re.escape("deque.index(x): x not in deque"))
122
+ else:
123
+ assert context.match("6 is not in list")
116
124
 
117
125
 
118
126
  def test_deque_insert(test_list) -> None:
@@ -1,27 +1,45 @@
1
1
  import time as real_time
2
2
  from inspect import Signature
3
3
  from math import isfinite
4
- from typing import Any, Callable
4
+ from typing import Any, Literal
5
5
 
6
- from crosshair.core import FunctionInterps
6
+ from crosshair.core import register_patch
7
7
  from crosshair.register_contract import register_contract
8
8
  from crosshair.statespace import context_statespace
9
9
  from crosshair.tracers import NoTracing
10
10
 
11
11
 
12
- def _gte_last(fn: Callable, value: Any) -> bool:
12
+ class EarliestPossibleTime:
13
+ monotonic: float = 0.0
14
+ process_time: float = 0.0
15
+
16
+ def __init__(self, *a):
17
+ pass
18
+
19
+
20
+ # Imprecision at high values becomes a sort of artificial problem
21
+ _UNREALISTICALLY_LARGE_TIME_FLOAT = float(60 * 60 * 24 * 365 * 100_000)
22
+
23
+
24
+ def _gte_last(kind: Literal["monotonic", "process_time"], value: Any) -> bool:
25
+ with NoTracing():
26
+ earliest_times = context_statespace().extra(EarliestPossibleTime)
27
+ threshold = getattr(earliest_times, kind)
28
+ setattr(earliest_times, kind, value)
29
+ return all([threshold <= value, value < _UNREALISTICALLY_LARGE_TIME_FLOAT])
30
+
31
+
32
+ def _sleep(value: float) -> None:
13
33
  with NoTracing():
14
- interps = context_statespace().extra(FunctionInterps)
15
- previous = interps._interpretations[fn]
16
- if len(previous) < 2:
17
- return True
18
- return value >= previous[-2]
34
+ earliest_times = context_statespace().extra(EarliestPossibleTime)
35
+ earliest_times.monotonic += value
36
+ return None
19
37
 
20
38
 
21
39
  def make_registrations():
22
40
  register_contract(
23
41
  real_time.time,
24
- post=lambda __return__: __return__ > 0.0,
42
+ post=lambda __return__: __return__ > 0.0 and isfinite(__return__),
25
43
  sig=Signature(parameters=[], return_annotation=float),
26
44
  )
27
45
  register_contract(
@@ -31,23 +49,24 @@ def make_registrations():
31
49
  )
32
50
  register_contract(
33
51
  real_time.monotonic,
34
- post=lambda __return__: isfinite(__return__)
35
- and _gte_last(real_time.monotonic, __return__),
52
+ post=lambda __return__: _gte_last("monotonic", __return__)
53
+ and isfinite(__return__),
36
54
  sig=Signature(parameters=[], return_annotation=float),
37
55
  )
38
56
  register_contract(
39
57
  real_time.monotonic_ns,
40
- post=lambda __return__: isfinite(__return__)
41
- and _gte_last(real_time.monotonic_ns, __return__),
58
+ post=lambda __return__: _gte_last("monotonic", __return__ / 1_000_000_000),
42
59
  sig=Signature(parameters=[], return_annotation=int),
43
60
  )
44
61
  register_contract(
45
62
  real_time.process_time,
46
- post=lambda __return__: _gte_last(real_time.process_time, __return__),
63
+ post=lambda __return__: _gte_last("process_time", __return__)
64
+ and isfinite(__return__),
47
65
  sig=Signature(parameters=[], return_annotation=float),
48
66
  )
49
67
  register_contract(
50
68
  real_time.process_time_ns,
51
- post=lambda __return__: _gte_last(real_time.process_time_ns, __return__),
69
+ post=lambda __return__: _gte_last("process_time", __return__ / 1_000_000_000),
52
70
  sig=Signature(parameters=[], return_annotation=int),
53
71
  )
72
+ register_patch(real_time.sleep, _sleep)
@@ -2,7 +2,7 @@ import time
2
2
 
3
3
  import pytest
4
4
 
5
- from crosshair.statespace import CONFIRMED, POST_FAIL
5
+ from crosshair.statespace import CANNOT_CONFIRM, CONFIRMED, POST_FAIL
6
6
  from crosshair.test_util import check_states
7
7
 
8
8
 
@@ -69,4 +69,14 @@ def test_monotonic_ns():
69
69
  start = time.monotonic_ns()
70
70
  return time.monotonic_ns() - start
71
71
 
72
- check_states(f, CONFIRMED)
72
+ check_states(f, CANNOT_CONFIRM)
73
+
74
+
75
+ def test_sleep():
76
+ def f():
77
+ """post: _ >= 60.0"""
78
+ start = time.monotonic()
79
+ time.sleep(60.01)
80
+ return time.monotonic() - start
81
+
82
+ check_states(f, CANNOT_CONFIRM)
crosshair/lsp_server.py CHANGED
@@ -86,7 +86,7 @@ def publish_messages(
86
86
  if message.state < MessageType.PRE_UNSAT:
87
87
  continue
88
88
  # TODO: consider server.show_message_log()ing the long description
89
- diagnostics.append(get_diagnostic(message, doc.lines if doc else ()))
89
+ diagnostics.append(get_diagnostic(message, doc.lines if doc else []))
90
90
  server.publish_diagnostics(uri, diagnostics)
91
91
  if not diagnostics:
92
92
  # After we publish an empty set, it's safe to forget about the file:
@@ -26,10 +26,10 @@ from crosshair.tracers import (
26
26
  frame_stack_read,
27
27
  frame_stack_write,
28
28
  )
29
- from crosshair.util import CrossHairInternal, CrossHairValue
29
+ from crosshair.util import CROSSHAIR_EXTRA_ASSERTS, CrossHairInternal, CrossHairValue
30
30
  from crosshair.z3util import z3Not, z3Or
31
31
 
32
- BINARY_SUBSCR = dis.opmap["BINARY_SUBSCR"]
32
+ BINARY_SUBSCR = dis.opmap.get("BINARY_SUBSCR", 256)
33
33
  BINARY_SLICE = dis.opmap.get("BINARY_SLICE", 256)
34
34
  BUILD_STRING = dis.opmap["BUILD_STRING"]
35
35
  COMPARE_OP = dis.opmap["COMPARE_OP"]
@@ -61,11 +61,17 @@ _DEEPLY_CONCRETE_KEY_TYPES = (
61
61
 
62
62
 
63
63
  class SymbolicSubscriptInterceptor(TracingModule):
64
- opcodes_wanted = frozenset([BINARY_SUBSCR])
64
+ opcodes_wanted = frozenset([BINARY_SUBSCR, BINARY_OP])
65
65
 
66
66
  def trace_op(self, frame, codeobj, codenum):
67
67
  # Note that because this is called from inside a Python trace handler, tracing
68
68
  # is automatically disabled, so there's no need for a `with NoTracing():` guard.
69
+
70
+ if codenum == BINARY_OP:
71
+ oparg = frame_op_arg(frame)
72
+ if oparg != 26: # subscript operator, NB_SUBSCR
73
+ return
74
+
69
75
  key = frame_stack_read(frame, -1)
70
76
  if isinstance(key, _DEEPLY_CONCRETE_KEY_TYPES):
71
77
  return
@@ -345,7 +351,9 @@ class MapAddInterceptor(TracingModule):
345
351
  # Afterwards, overwrite the interpreter's resulting dict with ours:
346
352
  def post_op():
347
353
  old_dict_obj = frame_stack_read(frame, dict_offset + 2)
348
- if not isinstance(old_dict_obj, (dict, MutableMapping)):
354
+ if CROSSHAIR_EXTRA_ASSERTS and not isinstance(
355
+ old_dict_obj, (dict, MutableMapping)
356
+ ):
349
357
  raise CrossHairInternal("interpreter stack corruption detected")
350
358
  frame_stack_write(frame, dict_offset + 2, dict_obj)
351
359
 
@@ -427,7 +435,8 @@ class SetAddInterceptor(TracingModule):
427
435
  # Set and value are concrete; continue as normal.
428
436
  return
429
437
  # Have the interpreter do a fake addition, namely `set().add(1)`
430
- frame_stack_write(frame, set_offset, set())
438
+ dummy_set: Set = set()
439
+ frame_stack_write(frame, set_offset, dummy_set)
431
440
  frame_stack_write(frame, -1, 1)
432
441
 
433
442
  # And do our own addition separately:
@@ -435,6 +444,12 @@ class SetAddInterceptor(TracingModule):
435
444
 
436
445
  # Later, overwrite the interpreter's result with ours:
437
446
  def post_op():
447
+ if CROSSHAIR_EXTRA_ASSERTS:
448
+ to_replace = frame_stack_read(frame, set_offset + 1)
449
+ if to_replace is not dummy_set:
450
+ raise CrossHairInternal(
451
+ f"Found an instance of {type(to_replace)} where dummy set should be."
452
+ )
438
453
  frame_stack_write(frame, set_offset + 1, set_obj)
439
454
 
440
455
  COMPOSITE_TRACER.set_postop_callback(post_op, frame)
@@ -450,9 +465,9 @@ class IdentityInterceptor(TracingModule):
450
465
  def trace_op(self, frame: FrameType, codeobj: CodeType, codenum: int) -> None:
451
466
  arg1 = frame_stack_read(frame, -1)
452
467
  arg2 = frame_stack_read(frame, -2)
453
- if isinstance(arg1, SymbolicBool):
468
+ if isinstance(arg1, SymbolicBool) and isinstance(arg2, (bool, SymbolicBool)):
454
469
  frame_stack_write(frame, -1, arg1.__ch_realize__())
455
- if isinstance(arg2, SymbolicBool):
470
+ if isinstance(arg2, SymbolicBool) and isinstance(arg1, (bool, SymbolicBool)):
456
471
  frame_stack_write(frame, -2, arg2.__ch_realize__())
457
472
 
458
473
 
@@ -467,7 +482,7 @@ class ModuloInterceptor(TracingModule):
467
482
  if isinstance(left, str):
468
483
  if codenum == BINARY_OP:
469
484
  oparg = frame_op_arg(frame)
470
- if oparg != 6: # modulo operator (determined experimentally)
485
+ if oparg != 6: # modulo operator, NB_REMAINDER
471
486
  return
472
487
  frame_stack_write(frame, -2, DeoptimizedPercentFormattingStr(left))
473
488