crosshair-tool 0.0.56__cp39-cp39-macosx_11_0_arm64.whl → 0.0.100__cp39-cp39-macosx_11_0_arm64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. _crosshair_tracers.cpython-39-darwin.so +0 -0
  2. crosshair/__init__.py +1 -1
  3. crosshair/_mark_stacks.h +51 -24
  4. crosshair/_tracers.h +9 -5
  5. crosshair/_tracers_test.py +19 -9
  6. crosshair/auditwall.py +9 -8
  7. crosshair/auditwall_test.py +31 -19
  8. crosshair/codeconfig.py +3 -2
  9. crosshair/condition_parser.py +17 -133
  10. crosshair/condition_parser_test.py +54 -96
  11. crosshair/conftest.py +1 -1
  12. crosshair/copyext.py +91 -22
  13. crosshair/copyext_test.py +33 -0
  14. crosshair/core.py +259 -203
  15. crosshair/core_and_libs.py +20 -0
  16. crosshair/core_regestered_types_test.py +82 -0
  17. crosshair/core_test.py +693 -664
  18. crosshair/diff_behavior.py +76 -21
  19. crosshair/diff_behavior_test.py +132 -23
  20. crosshair/dynamic_typing.py +128 -18
  21. crosshair/dynamic_typing_test.py +91 -4
  22. crosshair/enforce.py +1 -6
  23. crosshair/enforce_test.py +15 -23
  24. crosshair/examples/check_examples_test.py +2 -1
  25. crosshair/fnutil.py +2 -3
  26. crosshair/fnutil_test.py +0 -7
  27. crosshair/fuzz_core_test.py +70 -83
  28. crosshair/libimpl/arraylib.py +10 -7
  29. crosshair/libimpl/binascii_ch_test.py +30 -0
  30. crosshair/libimpl/binascii_test.py +67 -0
  31. crosshair/libimpl/binasciilib.py +150 -0
  32. crosshair/libimpl/bisectlib_test.py +5 -5
  33. crosshair/libimpl/builtinslib.py +1002 -682
  34. crosshair/libimpl/builtinslib_ch_test.py +108 -30
  35. crosshair/libimpl/builtinslib_test.py +431 -143
  36. crosshair/libimpl/codecslib.py +22 -2
  37. crosshair/libimpl/codecslib_test.py +41 -9
  38. crosshair/libimpl/collectionslib.py +44 -8
  39. crosshair/libimpl/collectionslib_test.py +108 -20
  40. crosshair/libimpl/copylib.py +1 -1
  41. crosshair/libimpl/copylib_test.py +18 -0
  42. crosshair/libimpl/datetimelib.py +84 -67
  43. crosshair/libimpl/datetimelib_ch_test.py +12 -7
  44. crosshair/libimpl/datetimelib_test.py +5 -6
  45. crosshair/libimpl/decimallib.py +5257 -0
  46. crosshair/libimpl/decimallib_ch_test.py +78 -0
  47. crosshair/libimpl/decimallib_test.py +76 -0
  48. crosshair/libimpl/encodings/_encutil.py +21 -11
  49. crosshair/libimpl/fractionlib.py +16 -0
  50. crosshair/libimpl/fractionlib_test.py +80 -0
  51. crosshair/libimpl/functoolslib.py +19 -7
  52. crosshair/libimpl/functoolslib_test.py +22 -6
  53. crosshair/libimpl/hashliblib.py +30 -0
  54. crosshair/libimpl/hashliblib_test.py +18 -0
  55. crosshair/libimpl/heapqlib.py +32 -5
  56. crosshair/libimpl/heapqlib_test.py +15 -12
  57. crosshair/libimpl/iolib.py +7 -4
  58. crosshair/libimpl/ipaddresslib.py +8 -0
  59. crosshair/libimpl/itertoolslib_test.py +1 -1
  60. crosshair/libimpl/mathlib.py +165 -2
  61. crosshair/libimpl/mathlib_ch_test.py +44 -0
  62. crosshair/libimpl/mathlib_test.py +59 -16
  63. crosshair/libimpl/oslib.py +7 -0
  64. crosshair/libimpl/pathliblib_test.py +10 -0
  65. crosshair/libimpl/randomlib.py +1 -0
  66. crosshair/libimpl/randomlib_test.py +6 -4
  67. crosshair/libimpl/relib.py +180 -59
  68. crosshair/libimpl/relib_ch_test.py +26 -2
  69. crosshair/libimpl/relib_test.py +77 -14
  70. crosshair/libimpl/timelib.py +35 -13
  71. crosshair/libimpl/timelib_test.py +13 -3
  72. crosshair/libimpl/typeslib.py +15 -0
  73. crosshair/libimpl/typeslib_test.py +36 -0
  74. crosshair/libimpl/unicodedatalib_test.py +3 -3
  75. crosshair/libimpl/weakreflib.py +13 -0
  76. crosshair/libimpl/weakreflib_test.py +69 -0
  77. crosshair/libimpl/zliblib.py +15 -0
  78. crosshair/libimpl/zliblib_test.py +13 -0
  79. crosshair/lsp_server.py +21 -10
  80. crosshair/main.py +48 -28
  81. crosshair/main_test.py +59 -14
  82. crosshair/objectproxy.py +39 -14
  83. crosshair/objectproxy_test.py +27 -13
  84. crosshair/opcode_intercept.py +212 -24
  85. crosshair/opcode_intercept_test.py +172 -18
  86. crosshair/options.py +0 -1
  87. crosshair/patch_equivalence_test.py +5 -21
  88. crosshair/path_cover.py +7 -5
  89. crosshair/path_search.py +6 -4
  90. crosshair/path_search_test.py +1 -2
  91. crosshair/pathing_oracle.py +53 -10
  92. crosshair/pathing_oracle_test.py +21 -0
  93. crosshair/pure_importer_test.py +5 -21
  94. crosshair/register_contract.py +16 -6
  95. crosshair/register_contract_test.py +2 -14
  96. crosshair/simplestructs.py +154 -85
  97. crosshair/simplestructs_test.py +16 -2
  98. crosshair/smtlib.py +24 -0
  99. crosshair/smtlib_test.py +14 -0
  100. crosshair/statespace.py +319 -196
  101. crosshair/statespace_test.py +45 -0
  102. crosshair/stubs_parser.py +0 -2
  103. crosshair/test_util.py +87 -25
  104. crosshair/test_util_test.py +26 -0
  105. crosshair/tools/check_init_and_setup_coincide.py +0 -3
  106. crosshair/tools/generate_demo_table.py +2 -2
  107. crosshair/tracers.py +141 -49
  108. crosshair/type_repo.py +11 -4
  109. crosshair/unicode_categories.py +1 -0
  110. crosshair/util.py +158 -76
  111. crosshair/util_test.py +13 -20
  112. crosshair/watcher.py +4 -4
  113. crosshair/z3util.py +1 -1
  114. {crosshair_tool-0.0.56.dist-info → crosshair_tool-0.0.100.dist-info}/METADATA +45 -36
  115. crosshair_tool-0.0.100.dist-info/RECORD +176 -0
  116. {crosshair_tool-0.0.56.dist-info → crosshair_tool-0.0.100.dist-info}/WHEEL +2 -1
  117. crosshair/examples/hypothesis/__init__.py +0 -2
  118. crosshair/examples/hypothesis/bugs_detected/simple_strategies.py +0 -74
  119. crosshair_tool-0.0.56.dist-info/RECORD +0 -152
  120. /crosshair/{examples/hypothesis/bugs_detected/__init__.py → py.typed} +0 -0
  121. {crosshair_tool-0.0.56.dist-info → crosshair_tool-0.0.100.dist-info}/entry_points.txt +0 -0
  122. {crosshair_tool-0.0.56.dist-info → crosshair_tool-0.0.100.dist-info/licenses}/LICENSE +0 -0
  123. {crosshair_tool-0.0.56.dist-info → crosshair_tool-0.0.100.dist-info}/top_level.txt +0 -0
@@ -3,34 +3,37 @@ import collections
3
3
  import copy
4
4
  import enum
5
5
  import io
6
- import math
7
6
  import operator as ops
7
+ import os
8
8
  import re
9
9
  import string
10
10
  import sys
11
11
  import typing
12
+ import warnings
12
13
  from abc import ABCMeta
13
14
  from array import array
15
+ from collections.abc import Mapping
14
16
  from dataclasses import dataclass
15
- from functools import wraps
16
17
  from itertools import zip_longest
18
+ from math import inf, isfinite, isinf, isnan, nan
17
19
  from numbers import Integral, Number, Real
18
- from sys import maxunicode
20
+ from sys import maxunicode, version_info
19
21
  from typing import (
20
22
  Any,
21
23
  BinaryIO,
22
- ByteString,
23
24
  Callable,
24
25
  Dict,
25
26
  FrozenSet,
26
27
  Hashable,
27
28
  Iterable,
28
29
  List,
30
+ MutableMapping,
29
31
  NamedTuple,
30
32
  NoReturn,
31
33
  Optional,
32
34
  Sequence,
33
35
  Set,
36
+ Sized,
34
37
  SupportsAbs,
35
38
  SupportsBytes,
36
39
  SupportsComplex,
@@ -49,9 +52,16 @@ from typing import (
49
52
  import typing_inspect # type: ignore
50
53
  import z3 # type: ignore
51
54
 
55
+ try:
56
+ # For reasons unknown, different distributions of z3 4.13.0 use differnt names.
57
+ from z3 import fpEQ
58
+ except ImportError:
59
+ from z3 import FfpEQ as fpEQ
60
+
61
+ import sys
62
+
52
63
  from crosshair.abcstring import AbcString
53
64
  from crosshair.core import (
54
- CrossHairValue,
55
65
  SymbolicFactory,
56
66
  deep_realize,
57
67
  iter_types,
@@ -61,14 +71,16 @@ from crosshair.core import (
61
71
  realize,
62
72
  register_patch,
63
73
  register_type,
74
+ with_checked_self,
64
75
  with_realized_args,
65
76
  with_symbolic_self,
66
77
  with_uniform_probabilities,
67
78
  )
68
79
  from crosshair.objectproxy import ObjectProxy
69
80
  from crosshair.simplestructs import (
81
+ FrozenSetBase,
82
+ LinearSet,
70
83
  SequenceConcatenation,
71
- SetBase,
72
84
  ShellMutableMap,
73
85
  ShellMutableSequence,
74
86
  ShellMutableSet,
@@ -97,19 +109,30 @@ from crosshair.type_repo import PYTYPE_SORT, SymbolicTypeRepository
97
109
  from crosshair.unicode_categories import UnicodeMaskCache
98
110
  from crosshair.util import (
99
111
  ATOMIC_IMMUTABLE_TYPES,
100
- CrosshairInternal,
112
+ CrossHairInternal,
101
113
  CrosshairUnsupported,
114
+ CrossHairValue,
115
+ IdKeyedDict,
102
116
  IgnoreAttempt,
117
+ UnknownSatisfiability,
118
+ assert_tracing,
119
+ ch_stack,
103
120
  debug,
121
+ is_bytes_like,
104
122
  is_hashable,
105
123
  is_iterable,
106
124
  memo,
107
125
  name_of_type,
108
126
  smtlib_typename,
109
- test_stack,
110
127
  type_arg_of,
111
128
  )
112
- from crosshair.z3util import z3And, z3Eq, z3Ge, z3Gt, z3IntVal, z3Or
129
+ from crosshair.z3util import z3And, z3Eq, z3Ge, z3Gt, z3IntVal, z3Not, z3Or
130
+
131
+ if sys.version_info >= (3, 12):
132
+ from collections.abc import Buffer
133
+ else:
134
+ from collections.abc import ByteString as Buffer
135
+
113
136
 
114
137
  _T = TypeVar("_T")
115
138
  _VT = TypeVar("_VT")
@@ -145,6 +168,18 @@ def smt_or(a: bool, b: bool) -> bool:
145
168
  return a or b
146
169
 
147
170
 
171
+ def smt_xor(a: bool, b: bool) -> bool:
172
+ with NoTracing():
173
+ if isinstance(a, SymbolicBool) or isinstance(b, SymbolicBool):
174
+ if isinstance(a, SymbolicBool) and isinstance(b, SymbolicBool):
175
+ return SymbolicBool(z3.Xor(a.var, b.var))
176
+ elif isinstance(a, bool):
177
+ return SymbolicBool(z3Not(b.var)) if a else b
178
+ elif isinstance(b, bool):
179
+ return SymbolicBool(z3Not(a.var)) if b else a
180
+ return a ^ b
181
+
182
+
148
183
  def smt_not(x: object) -> Union[bool, "SymbolicBool"]:
149
184
  with NoTracing():
150
185
  if isinstance(x, SymbolicBool):
@@ -154,6 +189,7 @@ def smt_not(x: object) -> Union[bool, "SymbolicBool"]:
154
189
 
155
190
  _NONHEAP_PYTYPES = set([int, float, bool, NoneType, complex])
156
191
 
192
+
157
193
  # TODO: isn't this pretty close to isinstance(typ, AtomicSymbolicValue)?
158
194
  def pytype_uses_heap(typ: Type) -> bool:
159
195
  return not (typ in _NONHEAP_PYTYPES)
@@ -170,7 +206,6 @@ def typeable_value(val: object) -> object:
170
206
 
171
207
  _SMT_INT_SORT = z3.IntSort()
172
208
  _SMT_BOOL_SORT = z3.BoolSort()
173
- _SMT_FLOAT_SORT = z3.RealSort() # difficulty getting the solver to use z3.Float64()
174
209
 
175
210
 
176
211
  @memo
@@ -193,31 +228,11 @@ SymbolicGenerator = Callable[[Union[str, z3.ExprRef], type], object]
193
228
 
194
229
 
195
230
  def origin_of(typ: Type) -> Type:
196
- typ = _WRAPPER_TYPE_TO_PYTYPE.get(typ, typ) # TODO is the still required?
197
231
  if hasattr(typ, "__origin__"):
198
232
  return typ.__origin__
199
233
  return typ
200
234
 
201
235
 
202
- # TODO: refactor away casting in SMT-sapce:
203
- def smt_int_to_float(a: z3.ExprRef) -> z3.ExprRef:
204
- if _SMT_FLOAT_SORT == z3.Float64():
205
- return z3.fpRealToFP(z3.RNE(), z3.ToReal(a), _SMT_FLOAT_SORT)
206
- elif _SMT_FLOAT_SORT == z3.RealSort():
207
- return z3.ToReal(a)
208
- else:
209
- raise CrosshairInternal()
210
-
211
-
212
- def smt_bool_to_float(a: z3.ExprRef) -> z3.ExprRef:
213
- if _SMT_FLOAT_SORT == z3.Float64():
214
- return z3.If(a, z3.FPVal(1.0, _SMT_FLOAT_SORT), z3.FPVal(0.0, _SMT_FLOAT_SORT))
215
- elif _SMT_FLOAT_SORT == z3.RealSort():
216
- return z3.If(a, z3.RealVal(1), z3.RealVal(0))
217
- else:
218
- raise CrosshairInternal()
219
-
220
-
221
236
  def smt_coerce(val: Any) -> z3.ExprRef:
222
237
  if isinstance(val, SymbolicValue):
223
238
  return val.var
@@ -249,17 +264,17 @@ def invoke_dunder(obj: object, method_name: str, *args, **kwargs):
249
264
  class SymbolicValue(CrossHairValue):
250
265
  def __init__(self, smtvar: Union[str, z3.ExprRef], typ: Type):
251
266
  if is_tracing():
252
- raise CrosshairInternal
267
+ raise CrossHairInternal
253
268
  self.snapshot = SnapshotRef(-1)
254
269
  self.python_type = typ
255
270
  if type(smtvar) is str:
256
- self.var = self.__init_var__(typ, smtvar)
271
+ self.var: Any = self.__init_var__(typ, smtvar)
257
272
  else:
258
273
  self.var = smtvar
259
274
  # TODO test that smtvar's sort matches expected?
260
275
 
261
276
  def __init_var__(self, typ, varname):
262
- raise CrosshairInternal(f"__init_var__ not implemented in {type(self)}")
277
+ raise CrossHairInternal(f"__init_var__ not implemented in {type(self)}")
263
278
 
264
279
  def __deepcopy__(self, memo):
265
280
  result = copy.copy(self)
@@ -315,7 +330,7 @@ class SymbolicValue(CrossHairValue):
315
330
  class AtomicSymbolicValue(SymbolicValue):
316
331
  def __init_var__(self, typ, varname):
317
332
  if is_tracing():
318
- raise CrosshairInternal("Tracing while creating symbolic")
333
+ raise CrossHairInternal("Tracing while creating symbolic")
319
334
  z3type = self.__class__._ch_smt_sort()
320
335
  return z3.Const(varname, z3type)
321
336
 
@@ -324,20 +339,20 @@ class AtomicSymbolicValue(SymbolicValue):
324
339
 
325
340
  @classmethod
326
341
  def _ch_smt_sort(cls) -> z3.SortRef:
327
- raise CrosshairInternal(f"_ch_smt_sort not implemented in {cls}")
342
+ raise CrossHairInternal(f"_ch_smt_sort not implemented in {cls}")
328
343
 
329
344
  @classmethod
330
345
  def _pytype(cls) -> Type:
331
- raise CrosshairInternal(f"_pytype not implemented in {cls}")
346
+ # TODO: unify this with __ch_pytype__()? (this is classmethod though)
347
+ raise CrossHairInternal(f"_pytype not implemented in {cls}")
332
348
 
333
349
  @classmethod
334
- def _smt_promote_literal(cls, val: object) -> Optional[z3.SortRef]:
335
- raise CrosshairInternal(f"_smt_promote_literal not implemented in {cls}")
350
+ def _smt_promote_literal(cls, literal: object) -> Optional[z3.ExprRef]:
351
+ raise CrossHairInternal(f"_smt_promote_literal not implemented in {cls}")
336
352
 
337
353
  @classmethod
354
+ @assert_tracing(False)
338
355
  def _coerce_to_smt_sort(cls, input_value: Any) -> Optional[z3.ExprRef]:
339
- if is_tracing():
340
- raise CrosshairInternal("_coerce_to_smt_sort called while tracing")
341
356
  input_value = typeable_value(input_value)
342
357
  target_pytype = cls._pytype()
343
358
 
@@ -376,17 +391,61 @@ class AtomicSymbolicValue(SymbolicValue):
376
391
 
377
392
 
378
393
  _PYTYPE_TO_WRAPPER_TYPE: Dict[
379
- type, Tuple[Type[AtomicSymbolicValue], ...]
394
+ type, Tuple[Tuple[Type[AtomicSymbolicValue], float], ...]
380
395
  ] = {} # to be populated later
381
- _WRAPPER_TYPE_TO_PYTYPE: Dict[SymbolicGenerator, type] = {}
382
396
 
383
397
 
384
- def crosshair_types_for_python_type(typ: Type) -> Tuple[Type[AtomicSymbolicValue], ...]:
398
+ def crosshair_types_for_python_type(
399
+ typ: Type,
400
+ ) -> Tuple[Tuple[Type[AtomicSymbolicValue], float], ...]:
385
401
  typ = normalize_pytype(typ)
386
402
  origin = origin_of(typ)
387
403
  return _PYTYPE_TO_WRAPPER_TYPE.get(origin, ())
388
404
 
389
405
 
406
+ def python_types_using_atomic_symbolics() -> Iterable[Type[AtomicSymbolicValue]]:
407
+ return _PYTYPE_TO_WRAPPER_TYPE.keys()
408
+
409
+
410
+ class ModelingDirector:
411
+ def __init__(self, *a) -> None:
412
+ # Maps python type to the symbolic type we've chosen to represent it (on this iteration)
413
+ self.global_representations: MutableMapping[
414
+ type, Optional[Type[AtomicSymbolicValue]]
415
+ ] = IdKeyedDict()
416
+
417
+ def get(self, typ: Type) -> Optional[Type[AtomicSymbolicValue]]:
418
+ representation_type = self.global_representations.get(typ, _MISSING)
419
+ if representation_type is _MISSING:
420
+ ch_types = crosshair_types_for_python_type(typ)
421
+ if not ch_types:
422
+ representation_type = None
423
+ elif len(ch_types) == 1:
424
+ representation_type = ch_types[0][0]
425
+ else:
426
+ space = context_statespace()
427
+ for option, probability in ch_types[:-1]:
428
+ # NOTE: fork_parallel() is closer to what we want than smt_fork();
429
+ # however, exhausting an incomplete representation path
430
+ # (e.g. RealBasedSymbolicFloat) should not exhaust the branch.
431
+ if space.smt_fork(
432
+ desc=f"use_{option.__name__}", probability_true=probability
433
+ ):
434
+ representation_type = option
435
+ break
436
+ else:
437
+ representation_type = ch_types[-1][0]
438
+ self.global_representations[typ] = representation_type
439
+ return representation_type
440
+
441
+ def choose(self, typ: Type) -> Type[AtomicSymbolicValue]:
442
+ chosen = self.get(typ)
443
+ if chosen is None:
444
+ raise CrossHairInternal(f"No symbolics for {typ}")
445
+ return chosen
446
+
447
+
448
+ @assert_tracing(False)
390
449
  def smt_to_ch_value(
391
450
  space: StateSpace, snapshot: SnapshotRef, smt_val: z3.ExprRef, pytype: type
392
451
  ) -> object:
@@ -395,11 +454,13 @@ def smt_to_ch_value(
395
454
  typ, smtlib_typename(typ) + "_inheap" + space.uniq(), allow_subtypes=True
396
455
  )
397
456
 
457
+ if isinstance(pytype, SymbolicType):
458
+ pytype = realize(pytype)
459
+
398
460
  if smt_val.sort() == HeapRef:
399
461
  return space.find_key_in_heap(smt_val, pytype, proxy_generator, snapshot)
400
- ch_type = crosshair_types_for_python_type(pytype)
401
- assert ch_type
402
- return ch_type[0](smt_val, pytype)
462
+ ch_type = space.extra(ModelingDirector).choose(pytype)
463
+ return ch_type(smt_val, pytype)
403
464
 
404
465
 
405
466
  def force_to_smt_sort(
@@ -448,12 +509,24 @@ class FiniteFloat(KindedFloat):
448
509
 
449
510
 
450
511
  class NonFiniteFloat(KindedFloat):
451
- pass
512
+ def get_finite_comparable(self, other: Union[FiniteFloat, "SymbolicFloat"]):
513
+ # These three cases help cover operations like `a * -inf` which is either
514
+ # positive of negative infinity depending on the sign of `a`.
515
+ if isinstance(other, FiniteFloat):
516
+ comparable: Union[float, SymbolicFloat] = other.val
517
+ else:
518
+ comparable = other
519
+ if comparable > 0: # type: ignore
520
+ return 1
521
+ elif comparable < 0:
522
+ return -1
523
+ else:
524
+ return 0
452
525
 
453
526
 
454
527
  def numeric_binop(op: BinFn, a: Number, b: Number):
455
528
  if not is_tracing():
456
- raise CrosshairInternal("Numeric operation on symbolic while not tracing")
529
+ raise CrossHairInternal("Numeric operation on symbolic while not tracing")
457
530
  with NoTracing():
458
531
  return numeric_binop_internal(op, a, b)
459
532
 
@@ -563,15 +636,27 @@ _BITWISE_OPS: Set[BinFn] = {
563
636
  ops.rshift,
564
637
  ops.lshift,
565
638
  }
639
+ _VALID_OPS_ON_COMPLEX_TYPES: Set[BinFn] = {
640
+ ops.eq,
641
+ ops.ne,
642
+ ops.add,
643
+ ops.sub,
644
+ ops.mul,
645
+ ops.truediv,
646
+ ops.pow,
647
+ }
566
648
 
567
649
 
568
650
  def apply_smt(op: BinFn, x: z3.ExprRef, y: z3.ExprRef) -> z3.ExprRef:
569
651
  # Mostly, z3 overloads operators and things just work.
570
652
  # But some special cases need to be checked first.
653
+ # TODO: we should investigate using the op override mechanism to
654
+ # dispatch to the right SMT operations.
571
655
  space = context_statespace()
572
656
  if op in _ARITHMETIC_OPS:
573
657
  if op in (ops.truediv, ops.floordiv, ops.mod):
574
- if space.smt_fork(y == 0):
658
+ iszero = (fpEQ(y, 0.0)) if isinstance(y, z3.FPRef) else (y == 0)
659
+ if space.smt_fork(iszero):
575
660
  raise ZeroDivisionError
576
661
  if op == ops.floordiv:
577
662
  if space.smt_fork(y >= 0):
@@ -585,15 +670,18 @@ def apply_smt(op: BinFn, x: z3.ExprRef, y: z3.ExprRef) -> z3.ExprRef:
585
670
  else:
586
671
  return -x / -y
587
672
  if op == ops.mod:
588
- if space.smt_fork(y >= 0):
673
+ if space.smt_fork(z3.Or(y >= 0, x % y == 0)):
589
674
  return x % y
590
- elif space.smt_fork(x % y == 0):
591
- return z3IntVal(0)
592
675
  else:
593
676
  return (x % y) + y
594
677
  elif op == ops.pow:
595
678
  if space.smt_fork(z3.And(x == 0, y < 0)):
596
679
  raise ZeroDivisionError("zero cannot be raised to a negative power")
680
+ if z3.is_fp(x) or z3.is_fp(y):
681
+ # Smtlib does not support exponentiation on true floats
682
+ raise UnknownSatisfiability("pow on floats is not supported by smtlib")
683
+ if x.is_int() and y.is_int():
684
+ return z3.ToInt(op(x, y))
597
685
  return op(x, y)
598
686
 
599
687
 
@@ -604,10 +692,10 @@ _ALL_OPS = _ARITHMETIC_AND_COMPARISON_OPS.union(_BITWISE_OPS)
604
692
  def setup_binops():
605
693
  # Lower entries take precendence when searching.
606
694
 
607
- # We check NaN and infitity immediately; not all
608
- # symbolic floats support these cases.
695
+ # We check NaN and infitity immediately;
696
+ # RealBasedSymbolicFloats don't support these cases.
609
697
  def _(a: Real, b: float):
610
- if math.isfinite(b):
698
+ if isfinite(b):
611
699
  return (a, FiniteFloat(b)) # type: ignore
612
700
  return (a, NonFiniteFloat(b))
613
701
 
@@ -623,7 +711,7 @@ def setup_binops():
623
711
  # Implicitly upconvert symbolic ints to floats.
624
712
  def _(a: SymbolicInt, b: Union[float, FiniteFloat, SymbolicFloat, complex]):
625
713
  with NoTracing():
626
- return (SymbolicFloat(z3.ToReal(a.var)), b)
714
+ return (a.__float__(), b)
627
715
 
628
716
  setup_promotion(_, _ARITHMETIC_AND_COMPARISON_OPS)
629
717
 
@@ -641,61 +729,66 @@ def setup_binops():
641
729
 
642
730
  # complex
643
731
  def _(op: BinFn, a: SymbolicNumberAble, b: complex):
732
+ if op not in _VALID_OPS_ON_COMPLEX_TYPES:
733
+ raise TypeError
644
734
  return op(complex(a), b) # type: ignore
645
735
 
646
736
  setup_binop(_, _ALL_OPS)
647
737
 
648
738
  # float
649
- def _(op: BinFn, a: SymbolicFloat, b: SymbolicFloat):
739
+ def _(op: BinFn, a: SymbolicFloat, b: KindedFloat):
650
740
  with NoTracing():
651
- return SymbolicFloat(apply_smt(op, a.var, b.var))
741
+ symbolic_type = context_statespace().extra(ModelingDirector).choose(float)
742
+ bval = symbolic_type._smt_promote_literal(b.val)
743
+ return SymbolicBool(apply_smt(op, a.var, bval))
652
744
 
653
- setup_binop(_, _ARITHMETIC_OPS)
745
+ setup_binop(_, _COMPARISON_OPS)
654
746
 
655
- def _(op: BinFn, a: SymbolicFloat, b: SymbolicFloat):
747
+ def _(op: BinFn, a: SymbolicFloat, b: KindedFloat):
656
748
  with NoTracing():
657
- return SymbolicBool(apply_smt(op, a.var, b.var))
749
+ symbolic_type = context_statespace().extra(ModelingDirector).choose(float)
750
+ bval = symbolic_type._smt_promote_literal(b.val)
751
+ return symbolic_type(apply_smt(op, a.var, bval), float)
658
752
 
659
- setup_binop(_, _COMPARISON_OPS)
753
+ setup_binop(_, _ARITHMETIC_OPS)
660
754
 
661
- def _(op: BinFn, a: SymbolicFloat, b: FiniteFloat):
755
+ def _(op: BinFn, a: KindedFloat, b: SymbolicFloat):
662
756
  with NoTracing():
663
- return SymbolicFloat(apply_smt(op, a.var, z3.RealVal(b.val)))
757
+ symbolic_type = context_statespace().extra(ModelingDirector).choose(float)
758
+ aval = symbolic_type._smt_promote_literal(a.val)
759
+ return symbolic_type(apply_smt(op, aval, b.var), float)
664
760
 
665
761
  setup_binop(_, _ARITHMETIC_OPS)
666
762
 
667
- def _(op: BinFn, a: FiniteFloat, b: SymbolicFloat):
763
+ def _(op: BinFn, a: SymbolicFloat, b: SymbolicFloat):
668
764
  with NoTracing():
669
- return SymbolicFloat(apply_smt(op, z3.RealVal(a.val), b.var))
765
+ symbolic_type = context_statespace().extra(ModelingDirector).choose(float)
766
+ return symbolic_type(apply_smt(op, a.var, b.var), float)
670
767
 
671
768
  setup_binop(_, _ARITHMETIC_OPS)
672
769
 
673
- def _(op: BinFn, a: Union[FiniteFloat, SymbolicFloat], b: NonFiniteFloat):
674
- if isinstance(a, FiniteFloat):
675
- comparable_a: Union[float, SymbolicFloat] = a.val
676
- else:
677
- comparable_a = a
678
- # These three cases help cover operations like `a * -inf` which is either
679
- # positive of negative infinity depending on the sign of `a`.
680
- if comparable_a > 0: # type: ignore
681
- return op(1, b.val) # type: ignore
682
- elif comparable_a < 0:
683
- return op(-1, b.val) # type: ignore
684
- else:
685
- return op(0, b.val) # type: ignore
770
+ def _(op: BinFn, a: SymbolicFloat, b: SymbolicFloat):
771
+ with NoTracing():
772
+ return SymbolicBool(apply_smt(op, a.var, b.var))
773
+
774
+ setup_binop(_, _COMPARISON_OPS)
775
+
776
+ def _(op: BinFn, a: Union[FiniteFloat, RealBasedSymbolicFloat], b: NonFiniteFloat):
777
+ return op(b.get_finite_comparable(a), b.val)
686
778
 
687
779
  setup_binop(_, _ARITHMETIC_AND_COMPARISON_OPS)
688
780
 
689
- def _(op: BinFn, a: NonFiniteFloat, b: NonFiniteFloat):
690
- return op(a.val, b.val) # type: ignore
781
+ def _(op: BinFn, a: NonFiniteFloat, b: Union[FiniteFloat, RealBasedSymbolicFloat]):
782
+ return op(a.val, a.get_finite_comparable(b))
691
783
 
692
784
  setup_binop(_, _ARITHMETIC_AND_COMPARISON_OPS)
693
785
 
694
- def _(op: BinFn, a: SymbolicFloat, b: FiniteFloat):
695
- with NoTracing():
696
- return SymbolicBool(apply_smt(op, a.var, z3.RealVal(b.val)))
786
+ # def _(
787
+ # op: BinFn, a: NonFiniteFloat, b: NonFiniteFloat
788
+ # ): # TODO: isn't this impossible (one must be symbolic)?
789
+ # return op(a.val, b.val) # type: ignore
697
790
 
698
- setup_binop(_, _COMPARISON_OPS)
791
+ # setup_binop(_, _ARITHMETIC_AND_COMPARISON_OPS)
699
792
 
700
793
  # int
701
794
  def _(op: BinFn, a: SymbolicInt, b: SymbolicInt):
@@ -746,19 +839,6 @@ def setup_binops():
746
839
 
747
840
  setup_binop(_, {ops.lshift, ops.rshift})
748
841
 
749
- _AND_MASKS_TO_MOD = {
750
- # It's common to use & to mask low bits. We can avoid realization by converting
751
- # these situations into mod operations.
752
- 0x01: 2,
753
- 0x03: 4,
754
- 0x07: 8,
755
- 0x0F: 16,
756
- 0x1F: 32,
757
- 0x3F: 64,
758
- 0x7F: 128,
759
- 0xFF: 256,
760
- }
761
-
762
842
  def _(op: BinFn, a: Integral, b: Integral):
763
843
  with NoTracing():
764
844
  if isinstance(b, SymbolicInt):
@@ -769,9 +849,12 @@ def setup_binops():
769
849
  b = realize(b)
770
850
  if b == 0:
771
851
  return 0
772
- mask_mod = _AND_MASKS_TO_MOD.get(b)
773
- if mask_mod and isinstance(a, SymbolicInt):
774
- if context_statespace().smt_fork(a.var >= 0, probability_true=0.75):
852
+ # It's common to use & to mask low bits. We can avoid realization by converting
853
+ # these situations into mod operations.
854
+ mask_mod = b + 1
855
+ if b > 0 and mask_mod & b == 0 and isinstance(a, SymbolicInt): # type: ignore
856
+ space = context_statespace()
857
+ if space.smt_fork(a.var >= 0, probability_true=0.75):
775
858
  return SymbolicInt(a.var % mask_mod)
776
859
  else:
777
860
  return SymbolicInt(b - ((-a.var - 1) % mask_mod))
@@ -782,9 +865,8 @@ def setup_binops():
782
865
  setup_binop(_, {ops.and_})
783
866
 
784
867
  # TODO: is this necessary still?
785
- def _(
786
- op: BinFn, a: Integral, b: Integral
787
- ): # Floor division over ints requires realization, at present
868
+ # Floor division over ints requires realization, at present:
869
+ def _(op: BinFn, a: Integral, b: Integral):
788
870
  return op(a.__index__(), b.__index__()) # type: ignore
789
871
 
790
872
  setup_binop(_, {ops.truediv})
@@ -794,35 +876,74 @@ def setup_binops():
794
876
 
795
877
  setup_promotion(_, {ops.truediv})
796
878
 
797
- def _float_divmod(a: Union[float, SymbolicFloat], b: Union[float, SymbolicFloat]):
879
+ # TODO : precise float divmod
880
+ def _float_divmod(
881
+ a: Union[NonFiniteFloat, FiniteFloat, RealBasedSymbolicFloat],
882
+ b: Union[NonFiniteFloat, FiniteFloat, RealBasedSymbolicFloat],
883
+ ):
798
884
  with NoTracing():
799
- smt_a = SymbolicFloat._coerce_to_smt_sort(a)
800
- smt_b = SymbolicFloat._coerce_to_smt_sort(b)
801
- if smt_a is None or smt_b is None:
802
- raise CrosshairInternal
803
885
  space = context_statespace()
886
+ bval = RealBasedSymbolicFloat._coerce_to_smt_sort(a)
887
+ # division by zero is checked first
888
+ if not isinstance(b, NonFiniteFloat):
889
+ smt_b = (
890
+ RealBasedSymbolicFloat._smt_promote_literal(b.val)
891
+ if isinstance(b, FiniteFloat)
892
+ else b.var
893
+ )
894
+ if space.smt_fork(smt_b == 0):
895
+ raise ZeroDivisionError
896
+ # then the non-finite cases:
897
+ if isinstance(a, NonFiniteFloat):
898
+ return (nan, nan)
899
+ smt_a = (
900
+ RealBasedSymbolicFloat._smt_promote_literal(a.val)
901
+ if isinstance(a, FiniteFloat)
902
+ else a.var
903
+ )
904
+ assert smt_a is not None
905
+ if isinstance(b, NonFiniteFloat):
906
+ if isnan(b.val):
907
+ return (nan, nan)
908
+ # Deduced the rules for infinitiy based on experimentation!:
909
+ positive_a = space.smt_fork(smt_a >= 0)
910
+ positive_b = b.val == inf
911
+ if positive_a ^ positive_b:
912
+ if space.smt_fork(smt_a == 0):
913
+ return (0.0, 0.0) if positive_b else (-0.0, -0.0)
914
+ return (-1.0, b.val)
915
+ else:
916
+ return (0.0, a.val if isinstance(a, FiniteFloat) else a)
917
+ assert smt_b is not None
918
+
804
919
  remainder = z3.Real(f"remainder{space.uniq()}")
805
920
  modproduct = z3.Int(f"modproduct{space.uniq()}")
806
921
  # From https://docs.python.org/3.3/reference/expressions.html#binary-arithmetic-operations:
807
922
  # The modulo operator always yields a result with the same sign as its second operand (or zero).
808
923
  # absolute value of the result is strictly smaller than the absolute value of the second operand.
809
924
  space.add(smt_b * modproduct + remainder == smt_a)
810
- if space.smt_fork(smt_b == 0):
811
- raise ZeroDivisionError
812
- elif space.smt_fork(smt_b > 0):
925
+ if space.smt_fork(smt_b > 0):
813
926
  space.add(remainder >= 0)
814
927
  space.add(remainder < smt_b)
815
928
  else:
816
929
  space.add(remainder <= 0)
817
930
  space.add(smt_b < remainder)
818
- return (SymbolicInt(modproduct), SymbolicFloat(remainder))
931
+ return (SymbolicInt(modproduct), RealBasedSymbolicFloat(remainder))
819
932
 
820
- def _(op: BinFn, a: Union[float, SymbolicFloat], b: Union[float, SymbolicFloat]):
933
+ def _(
934
+ op: BinFn,
935
+ a: Union[NonFiniteFloat, FiniteFloat, RealBasedSymbolicFloat],
936
+ b: Union[NonFiniteFloat, FiniteFloat, RealBasedSymbolicFloat],
937
+ ):
821
938
  return _float_divmod(a, b)[1]
822
939
 
823
940
  setup_binop(_, {ops.mod})
824
941
 
825
- def _(op: BinFn, a: Union[float, SymbolicFloat], b: Union[float, SymbolicFloat]):
942
+ def _(
943
+ op: BinFn,
944
+ a: Union[NonFiniteFloat, FiniteFloat, RealBasedSymbolicFloat],
945
+ b: Union[NonFiniteFloat, FiniteFloat, RealBasedSymbolicFloat],
946
+ ):
826
947
  return _float_divmod(a, b)[0]
827
948
 
828
949
  setup_binop(_, {ops.floordiv})
@@ -865,6 +986,9 @@ class SymbolicNumberAble(SymbolicValue, Real):
865
986
  def __eq__(self, other):
866
987
  return numeric_binop(ops.eq, self, other)
867
988
 
989
+ def __ne__(self, other):
990
+ return numeric_binop(ops.ne, self, other)
991
+
868
992
  def __add__(self, other):
869
993
  return numeric_binop(ops.add, self, other)
870
994
 
@@ -885,7 +1009,7 @@ class SymbolicNumberAble(SymbolicValue, Real):
885
1009
 
886
1010
  def __pow__(self, other, mod=None):
887
1011
  if mod is not None:
888
- return pow(realize(self), pow, mod)
1012
+ return pow(realize(self), realize(other), realize(mod))
889
1013
  return numeric_binop(ops.pow, self, other)
890
1014
 
891
1015
  def __rpow__(self, other, mod=None):
@@ -989,6 +1113,7 @@ class SymbolicBool(SymbolicIntable, AtomicSymbolicValue):
989
1113
  def __init__(self, smtvar: Union[str, z3.ExprRef], typ: Type = bool):
990
1114
  assert typ == bool
991
1115
  SymbolicValue.__init__(self, smtvar, typ)
1116
+ self._ch_decision_triggers: List[Callable[[bool], None]] = []
992
1117
 
993
1118
  @classmethod
994
1119
  def _ch_smt_sort(cls) -> z3.SortRef:
@@ -999,14 +1124,21 @@ class SymbolicBool(SymbolicIntable, AtomicSymbolicValue):
999
1124
  return bool
1000
1125
 
1001
1126
  @classmethod
1002
- def _smt_promote_literal(cls, literal) -> Optional[z3.SortRef]:
1127
+ def _smt_promote_literal(cls, literal) -> Optional[z3.ExprRef]:
1003
1128
  if isinstance(literal, bool):
1004
1129
  return z3.BoolVal(literal)
1005
1130
  return None
1006
1131
 
1007
- def __ch_realize__(self) -> object:
1132
+ def __ch_realize__(self) -> bool:
1008
1133
  with NoTracing():
1009
- return context_statespace().choose_possible(self.var)
1134
+ realized = context_statespace().choose_possible(self.var)
1135
+ for trigger in self._ch_decision_triggers:
1136
+ trigger(realized)
1137
+ return realized
1138
+
1139
+ def __abs__(self):
1140
+ with NoTracing():
1141
+ return SymbolicInt(z3.If(self.var, 1, 0))
1010
1142
 
1011
1143
  def __neg__(self):
1012
1144
  with NoTracing():
@@ -1022,9 +1154,8 @@ class SymbolicBool(SymbolicIntable, AtomicSymbolicValue):
1022
1154
  with NoTracing():
1023
1155
  return SymbolicInt(z3.If(self.var, 1, 0))
1024
1156
 
1025
- def __bool__(self):
1026
- with NoTracing():
1027
- return context_statespace().choose_possible(self.var)
1157
+ def __bool__(self) -> bool:
1158
+ return self.__ch_realize__()
1028
1159
 
1029
1160
  def __int__(self):
1030
1161
  with NoTracing():
@@ -1032,7 +1163,10 @@ class SymbolicBool(SymbolicIntable, AtomicSymbolicValue):
1032
1163
 
1033
1164
  def __float__(self):
1034
1165
  with NoTracing():
1035
- return SymbolicFloat(smt_bool_to_float(self.var))
1166
+ symbolic_type = context_statespace().extra(ModelingDirector).choose(float)
1167
+ smt_false = symbolic_type._coerce_to_smt_sort(0)
1168
+ smt_true = symbolic_type._coerce_to_smt_sort(1)
1169
+ return symbolic_type(z3.If(self.var, smt_true, smt_false))
1036
1170
 
1037
1171
  def __complex__(self):
1038
1172
  with NoTracing():
@@ -1046,6 +1180,10 @@ class SymbolicBool(SymbolicIntable, AtomicSymbolicValue):
1046
1180
  class SymbolicInt(SymbolicIntable, AtomicSymbolicValue):
1047
1181
  def __init__(self, smtvar: Union[str, z3.ExprRef], typ: Type = int):
1048
1182
  assert typ == int
1183
+ if (not isinstance(smtvar, str)) and (not smtvar.is_int()):
1184
+ raise CrossHairInternal(
1185
+ f"non-integer SMT value given to SymbolicInt ({smtvar})"
1186
+ )
1049
1187
  SymbolicIntable.__init__(self, smtvar, typ)
1050
1188
 
1051
1189
  @classmethod
@@ -1057,7 +1195,7 @@ class SymbolicInt(SymbolicIntable, AtomicSymbolicValue):
1057
1195
  return int
1058
1196
 
1059
1197
  @classmethod
1060
- def _smt_promote_literal(cls, literal) -> Optional[z3.SortRef]:
1198
+ def _smt_promote_literal(cls, literal) -> Optional[z3.ExprRef]:
1061
1199
  if isinstance(literal, int):
1062
1200
  return z3IntVal(literal)
1063
1201
  return None
@@ -1071,12 +1209,18 @@ class SymbolicInt(SymbolicIntable, AtomicSymbolicValue):
1071
1209
 
1072
1210
  def __repr__(self):
1073
1211
  if self < 0:
1074
- return "-" + abs(self).__repr__()
1075
- codepoints = []
1076
- while self >= 10:
1077
- codepoints.append(48 + (self % 10))
1078
- self = self // 10
1079
- codepoints.append(48 + self)
1212
+ return "-" + (-self).__repr__()
1213
+ if self < 10:
1214
+ return LazyIntSymbolicStr([48 + self])
1215
+ codepoints = [48 + (self % 10)]
1216
+ cur_divisor = 10
1217
+ while True:
1218
+ leftover = self // cur_divisor
1219
+ if leftover != 0:
1220
+ codepoints.append(48 + (leftover % 10))
1221
+ cur_divisor *= 10
1222
+ else:
1223
+ break
1080
1224
  with NoTracing():
1081
1225
  codepoints.reverse()
1082
1226
  return LazyIntSymbolicStr(codepoints)
@@ -1102,7 +1246,18 @@ class SymbolicInt(SymbolicIntable, AtomicSymbolicValue):
1102
1246
 
1103
1247
  def __float__(self):
1104
1248
  with NoTracing():
1105
- return SymbolicFloat(smt_int_to_float(self.var))
1249
+ symbolic_type = context_statespace().extra(ModelingDirector).choose(float)
1250
+ if symbolic_type is RealBasedSymbolicFloat:
1251
+ return RealBasedSymbolicFloat(z3.ToReal(self.var))
1252
+ elif symbolic_type is PreciseIeeeSymbolicFloat:
1253
+ # TODO: We can likely do better with: int -> bit vector -> float
1254
+ return PreciseIeeeSymbolicFloat(
1255
+ z3.fpRealToFP(
1256
+ z3.RNE(), z3.ToReal(self.var), _PRECISE_IEEE_FLOAT_SORT
1257
+ )
1258
+ )
1259
+ else:
1260
+ raise CrossHairInternal
1106
1261
 
1107
1262
  def __complex__(self):
1108
1263
  return complex(self.__float__())
@@ -1165,7 +1320,7 @@ class SymbolicInt(SymbolicIntable, AtomicSymbolicValue):
1165
1320
  z3.If(val < 128, 7, 8)))))))))
1166
1321
  # fmt: on
1167
1322
 
1168
- if sys.version_info >= (3, 12):
1323
+ if version_info >= (3, 12):
1169
1324
 
1170
1325
  def is_integer(self):
1171
1326
  return True
@@ -1199,41 +1354,123 @@ class SymbolicInt(SymbolicIntable, AtomicSymbolicValue):
1199
1354
  return (self, 1)
1200
1355
 
1201
1356
 
1202
- def make_bounded_int(
1203
- varname: str, minimum: Optional[int] = None, maximum: Optional[int] = None
1204
- ) -> SymbolicInt:
1205
- space = context_statespace()
1206
- symbolic = SymbolicInt(varname)
1207
- if minimum is not None:
1208
- space.add(symbolic.var >= minimum)
1209
- if maximum is not None:
1210
- space.add(symbolic.var <= maximum)
1211
- return symbolic
1357
+ class SymbolicBoundedInt(SymbolicInt):
1358
+ def __init__(
1359
+ self,
1360
+ smtvar: Union[str, z3.ExprRef],
1361
+ typ: Type,
1362
+ minimum: Optional[int] = None,
1363
+ maximum: Optional[int] = None,
1364
+ ):
1365
+ SymbolicInt.__init__(self, smtvar, typ)
1366
+ space = context_statespace()
1367
+ self._ch_minimum = minimum
1368
+ self._ch_maximum = maximum
1369
+ if minimum is not None:
1370
+ space.add(self.var >= minimum)
1371
+ if maximum is not None:
1372
+ space.add(self.var <= maximum)
1373
+
1374
+ def __lt__(self, other):
1375
+ with NoTracing():
1376
+ if isinstance(other, int):
1377
+ if self._ch_minimum is not None and other < self._ch_minimum:
1378
+ return False
1379
+ if self._ch_maximum is not None and other > self._ch_maximum:
1380
+ return True
1381
+ with ResumedTracing():
1382
+ ret = super().__lt__(other)
1383
+ if isinstance(other, int):
1384
+ if isinstance(ret, SymbolicBool):
1385
+ ret._ch_decision_triggers.append(
1386
+ lambda islt: (
1387
+ self._ch_intersect_bounds(None, other - 1)
1388
+ if islt
1389
+ else self._ch_intersect_bounds(other, None)
1390
+ )
1391
+ )
1392
+ return ret
1212
1393
 
1394
+ def __gt__(self, other):
1395
+ with NoTracing():
1396
+ if isinstance(other, int):
1397
+ if self._ch_maximum is not None and other > self._ch_maximum:
1398
+ return False
1399
+ if self._ch_minimum is not None and other < self._ch_minimum:
1400
+ return True
1401
+ with ResumedTracing():
1402
+ ret = super().__gt__(other)
1403
+ if isinstance(other, int):
1404
+ if isinstance(ret, SymbolicBool):
1405
+ ret._ch_decision_triggers.append(
1406
+ lambda isgt: (
1407
+ self._ch_intersect_bounds(other + 1, None)
1408
+ if isgt
1409
+ else self._ch_intersect_bounds(None, other)
1410
+ )
1411
+ )
1412
+ return ret
1213
1413
 
1214
- _Z3_ONE_HALF = z3.RealVal("1/2")
1414
+ def _ch_intersect_bounds(
1415
+ self, new_min: Optional[int], new_max: Optional[int]
1416
+ ) -> None:
1417
+ space = context_statespace()
1418
+ if new_min is not None:
1419
+ if self._ch_minimum is None or new_min > self._ch_minimum:
1420
+ self._ch_minimum = new_min
1421
+ space.add(
1422
+ self.var >= int(new_min)
1423
+ ) # cast b/c z3 isn't tolerant of enum ints
1424
+ if new_max is not None:
1425
+ if self._ch_maximum is None or new_max < self._ch_maximum:
1426
+ self._ch_maximum = new_max
1427
+ space.add(
1428
+ self.var <= int(new_max)
1429
+ ) # cast b/c z3 isn't tolerant of enum ints
1215
1430
 
1431
+ def _unary_op(self, op):
1432
+ with NoTracing():
1433
+ if op is ops.neg:
1434
+ new_min = -self._ch_maximum if self._ch_maximum is not None else None
1435
+ new_max = -self._ch_minimum if self._ch_minimum is not None else None
1436
+ return SymbolicBoundedInt(op(self.var), int, new_min, new_max)
1437
+ elif op is ops.abs:
1438
+ if self._ch_minimum is not None and self._ch_minimum >= 0:
1439
+ return self
1440
+ if self._ch_maximum is not None and self._ch_maximum <= 0:
1441
+ return SymbolicBoundedInt(
1442
+ -self.var,
1443
+ int,
1444
+ -self._ch_maximum,
1445
+ -self._ch_minimum if self._ch_minimum is not None else None,
1446
+ )
1447
+ if self._ch_maximum is not None or self._ch_minimum is not None:
1448
+ # range includes zero; compute max abs value
1449
+ max_abs = max(
1450
+ abs(self._ch_minimum or 0),
1451
+ abs(self._ch_maximum or 0),
1452
+ )
1453
+ return SymbolicInt(op(self.var), int, 0, max_abs)
1454
+ return SymbolicInt(op(self.var), int, 0, None)
1455
+ elif op is ops.pos:
1456
+ return self
1457
+ elif op is ops.invert:
1458
+ pass # TODO: we could do something for bitwise negation
1459
+ # Default to unbounded:
1460
+ return SymbolicInt(op(self.var), int)
1216
1461
 
1217
- class SymbolicFloat(SymbolicNumberAble, AtomicSymbolicValue):
1218
- def __init__(self, smtvar: Union[str, z3.ExprRef], typ: Type = float):
1219
- assert typ is float, f"SymbolicFloat with unexpected python type ({type(typ)})"
1220
- context_statespace().cap_result_at_unknown()
1221
- SymbolicValue.__init__(self, smtvar, typ)
1222
1462
 
1223
- @classmethod
1224
- def _ch_smt_sort(cls) -> z3.SortRef:
1225
- return z3.RealSort()
1463
+ def make_bounded_int(
1464
+ varname: str, minimum: Optional[int] = None, maximum: Optional[int] = None
1465
+ ) -> SymbolicInt:
1466
+ return SymbolicBoundedInt(varname, int, minimum, maximum)
1467
+
1226
1468
 
1469
+ class SymbolicFloat(SymbolicNumberAble, AtomicSymbolicValue):
1227
1470
  @classmethod
1228
1471
  def _pytype(cls) -> Type:
1229
1472
  return float
1230
1473
 
1231
- @classmethod
1232
- def _smt_promote_literal(cls, literal) -> Optional[z3.SortRef]:
1233
- if isinstance(literal, float):
1234
- return z3.RealVal(literal)
1235
- return None
1236
-
1237
1474
  def __ch_realize__(self) -> object:
1238
1475
  return context_statespace().find_model_value(self.var).__float__() # type: ignore
1239
1476
 
@@ -1247,11 +1484,6 @@ class SymbolicFloat(SymbolicNumberAble, AtomicSymbolicValue):
1247
1484
  with NoTracing():
1248
1485
  return SymbolicBool(self.var != 0).__bool__()
1249
1486
 
1250
- def __int__(self):
1251
- with NoTracing():
1252
- var = self.var
1253
- return SymbolicInt(z3.If(var >= 0, z3.ToInt(var), -z3.ToInt(-var)))
1254
-
1255
1487
  def __float__(self):
1256
1488
  with NoTracing():
1257
1489
  return self.__ch_realize__()
@@ -1260,6 +1492,163 @@ class SymbolicFloat(SymbolicNumberAble, AtomicSymbolicValue):
1260
1492
  with NoTracing():
1261
1493
  return complex(self.__float__())
1262
1494
 
1495
+ def hex(self) -> str:
1496
+ return realize(self).hex()
1497
+
1498
+
1499
+ _PRECISE_IEEE_FLOAT_SORT = {
1500
+ 11: z3.Float16(),
1501
+ 24: z3.Float32(),
1502
+ 53: z3.Float64(),
1503
+ }[sys.float_info.mant_dig]
1504
+
1505
+
1506
+ class PreciseIeeeSymbolicFloat(SymbolicFloat):
1507
+ def __init__(self, smtvar: Union[str, z3.ExprRef], typ: Type = float):
1508
+ if not isinstance(smtvar, str) and not z3.is_fp(smtvar):
1509
+ raise CrossHairInternal(
1510
+ f"non-float SMT value ({name_of_type(type(smtvar))}) given to PreciseIeeeSymbolicFloat"
1511
+ )
1512
+ SymbolicValue.__init__(self, smtvar, typ)
1513
+
1514
+ @classmethod
1515
+ def _ch_smt_sort(cls) -> z3.SortRef:
1516
+ return _PRECISE_IEEE_FLOAT_SORT
1517
+
1518
+ @classmethod
1519
+ def _smt_promote_literal(cls, literal) -> Optional[z3.ExprRef]:
1520
+ if isinstance(literal, float):
1521
+ return z3.FPVal(literal, cls._ch_smt_sort())
1522
+ return None
1523
+
1524
+ def __eq__(self, other):
1525
+ with NoTracing():
1526
+ coerced = type(self)._coerce_to_smt_sort(other)
1527
+ if coerced is None:
1528
+ return False
1529
+ return SymbolicBool(fpEQ(self.var, coerced))
1530
+
1531
+ # __hash__ has to be explicitly reassigned because we define __eq__
1532
+ __hash__ = SymbolicFloat.__hash__
1533
+
1534
+ def __bool__(self):
1535
+ with NoTracing():
1536
+ return not SymbolicBool(z3.fpIsZero(self.var))
1537
+
1538
+ def __ne__(self, other):
1539
+ with NoTracing():
1540
+ coerced = type(self)._coerce_to_smt_sort(other)
1541
+ if coerced is None:
1542
+ return True
1543
+ return SymbolicBool(z3.Not(fpEQ(self.var, coerced)))
1544
+
1545
+ def __int__(self):
1546
+ with NoTracing():
1547
+ self._check_finite_convert_to("integer")
1548
+ return SymbolicInt(
1549
+ z3.ToInt(z3.fpToReal(z3.fpRoundToIntegral(z3.RTZ(), self.var)))
1550
+ )
1551
+
1552
+ def __round__(self, ndigits=None):
1553
+ self_is_finite = isfinite(self)
1554
+ if ndigits is None:
1555
+ if not self_is_finite:
1556
+ # CPython only errors like this when ndigits is None (for ... reasons)
1557
+ if isinf(self):
1558
+ raise OverflowError("cannot convert float infinity to integer")
1559
+ else:
1560
+ raise ValueError("cannot convert float NaN to integer")
1561
+ elif ndigits != 0:
1562
+ if not isinstance(ndigits, int):
1563
+ raise TypeError(
1564
+ f"'{name_of_type(type(ndigits))}' object cannot be interpreted as an integer"
1565
+ )
1566
+ factor = 10**ndigits
1567
+ return round(self * factor, 0) / factor
1568
+ with NoTracing():
1569
+ if self_is_finite:
1570
+ smt_rounded_real = z3.fpRoundToIntegral(z3.RNE(), self.var)
1571
+ if ndigits is None:
1572
+ return SymbolicInt(z3.ToInt(z3.fpToReal(smt_rounded_real)))
1573
+ else:
1574
+ return PreciseIeeeSymbolicFloat(smt_rounded_real)
1575
+ # Non-finites returns themselves if you supply ndigits:
1576
+ return self
1577
+
1578
+ def _check_finite_convert_to(self, target: str) -> None:
1579
+ if isfinite(self):
1580
+ return
1581
+ elif isinf(self):
1582
+ raise OverflowError("cannot convert Infinity to " + target)
1583
+ else:
1584
+ raise ValueError("cannot convert NaN to " + target)
1585
+
1586
+ def __floor__(self):
1587
+ with NoTracing():
1588
+ self._check_finite_convert_to("integer")
1589
+ return PreciseIeeeSymbolicFloat(z3.fpRoundToIntegral(z3.RTN(), self.var))
1590
+
1591
+ def __floordiv__(self, other):
1592
+ r = self / other
1593
+ with NoTracing():
1594
+ return PreciseIeeeSymbolicFloat(z3.fpRoundToIntegral(z3.RTN(), r.var))
1595
+
1596
+ def __rfloordiv__(self, other):
1597
+ r = other / self
1598
+ with NoTracing():
1599
+ return PreciseIeeeSymbolicFloat(z3.fpRoundToIntegral(z3.RTN(), r.var))
1600
+
1601
+ def __ceil__(self):
1602
+ with NoTracing():
1603
+ self._check_finite_convert_to("integer")
1604
+ return PreciseIeeeSymbolicFloat(z3.fpRoundToIntegral(z3.RTP(), self.var))
1605
+
1606
+ def __pow__(self, other, mod=None):
1607
+ # TODO: consider losen-ing a little
1608
+ return pow(realize(self), realize(other), realize(mod))
1609
+
1610
+ def __trunc__(self):
1611
+ with NoTracing():
1612
+ self._check_finite_convert_to("integer")
1613
+ return PreciseIeeeSymbolicFloat(z3.fpRoundToIntegral(z3.RTZ(), self.var))
1614
+
1615
+ def as_integer_ratio(self) -> Tuple[Integral, Integral]:
1616
+ with NoTracing():
1617
+ self._check_finite_convert_to("integer ratio")
1618
+ return RealBasedSymbolicFloat(z3.fpToReal(self.var)).as_integer_ratio()
1619
+
1620
+ def is_integer(self) -> SymbolicBool:
1621
+ return self == self.__int__()
1622
+ # with NoTracing():
1623
+ # return SymbolicBool(z3.IsInt(self.var))
1624
+
1625
+
1626
+ _Z3_ONE_HALF = z3.RealVal("1/2")
1627
+
1628
+
1629
+ class RealBasedSymbolicFloat(SymbolicFloat):
1630
+ def __init__(self, smtvar: Union[str, z3.ExprRef], typ: Type = float):
1631
+ assert (
1632
+ typ is float
1633
+ ), f"RealBasedSymbolicFloat with unexpected python type ({type(typ)})"
1634
+ context_statespace().cap_result_at_unknown()
1635
+ SymbolicValue.__init__(self, smtvar, typ)
1636
+
1637
+ @classmethod
1638
+ def _ch_smt_sort(cls) -> z3.SortRef:
1639
+ return z3.RealSort()
1640
+
1641
+ @classmethod
1642
+ def _smt_promote_literal(cls, literal) -> Optional[z3.ExprRef]:
1643
+ if isinstance(literal, float) and isfinite(literal):
1644
+ return z3.RealVal(literal)
1645
+ return None
1646
+
1647
+ def __int__(self):
1648
+ with NoTracing():
1649
+ var = self.var
1650
+ return SymbolicInt(z3.If(var >= 0, z3.ToInt(var), -z3.ToInt(-var)))
1651
+
1263
1652
  def __round__(self, ndigits=None):
1264
1653
  if ndigits is not None:
1265
1654
  factor = 10 ** realize(
@@ -1308,6 +1697,7 @@ class SymbolicFloat(SymbolicNumberAble, AtomicSymbolicValue):
1308
1697
  denominator = SymbolicInt("denominator" + space.uniq())
1309
1698
  space.add(denominator.var > 0)
1310
1699
  space.add(numerator.var == denominator.var * self.var)
1700
+
1311
1701
  # There are many valid integer ratios to return. Experimentally, both
1312
1702
  # z3 and CPython tend to pick the same ones. But verify this, while
1313
1703
  # deferring materialization:
@@ -1323,9 +1713,6 @@ class SymbolicFloat(SymbolicNumberAble, AtomicSymbolicValue):
1323
1713
  with NoTracing():
1324
1714
  return SymbolicBool(z3.IsInt(self.var))
1325
1715
 
1326
- def hex(self) -> str:
1327
- return realize(self).hex()
1328
-
1329
1716
 
1330
1717
  class SymbolicDictOrSet(SymbolicValue):
1331
1718
  """
@@ -1335,15 +1722,16 @@ class SymbolicDictOrSet(SymbolicValue):
1335
1722
 
1336
1723
  def __init__(self, smtvar: Union[str, z3.ExprRef], typ: Type):
1337
1724
  self.key_pytype = normalize_pytype(type_arg_of(typ, 0))
1338
- ch_types = crosshair_types_for_python_type(self.key_pytype)
1339
- if ch_types:
1340
- self.ch_key_type: Optional[Type[AtomicSymbolicValue]] = ch_types[0]
1725
+ space = context_statespace()
1726
+ ch_type = space.extra(ModelingDirector).get(self.key_pytype)
1727
+ if ch_type:
1728
+ self.ch_key_type: Optional[Type[AtomicSymbolicValue]] = ch_type
1341
1729
  self.smt_key_sort = self.ch_key_type._ch_smt_sort()
1342
1730
  else:
1343
1731
  self.ch_key_type = None
1344
1732
  self.smt_key_sort = HeapRef
1345
1733
  SymbolicValue.__init__(self, smtvar, typ)
1346
- context_statespace().add(self._len() >= 0)
1734
+ space.add(self._len() >= 0)
1347
1735
 
1348
1736
  def __ch_realize__(self):
1349
1737
  return origin_of(self.python_type)(self)
@@ -1370,9 +1758,9 @@ class SymbolicDict(SymbolicDictOrSet, collections.abc.Mapping):
1370
1758
  def __init__(self, smtvar: Union[str, z3.ExprRef], typ: Type):
1371
1759
  space = context_statespace()
1372
1760
  self.val_pytype = normalize_pytype(type_arg_of(typ, 1))
1373
- val_ch_types = crosshair_types_for_python_type(self.val_pytype)
1374
- if val_ch_types:
1375
- self.ch_val_type: Optional[Type[AtomicSymbolicValue]] = val_ch_types[0]
1761
+ val_ch_type = space.extra(ModelingDirector).get(self.val_pytype)
1762
+ if val_ch_type:
1763
+ self.ch_val_type: Optional[Type[AtomicSymbolicValue]] = val_ch_type
1376
1764
  self.smt_val_sort = self.ch_val_type._ch_smt_sort()
1377
1765
  else:
1378
1766
  self.ch_val_type = None
@@ -1385,7 +1773,7 @@ class SymbolicDict(SymbolicDictOrSet, collections.abc.Mapping):
1385
1773
  self.val_constructor = arr_var.sort().range().constructor(1)
1386
1774
  self.val_accessor = arr_var.sort().range().accessor(1, 0)
1387
1775
  self.empty = z3.K(arr_var.sort().domain(), self.val_missing_constructor())
1388
- self._iter_cache: List[z3.Const] = []
1776
+ self._iter_cache: List[z3.Const] = [] # TODO: is this used?
1389
1777
  space.add((arr_var == self.empty) == (len_var == 0))
1390
1778
 
1391
1779
  def dict_can_be_iterated():
@@ -1431,6 +1819,10 @@ class SymbolicDict(SymbolicDictOrSet, collections.abc.Mapping):
1431
1819
 
1432
1820
  def __repr__(self):
1433
1821
  return str(dict(self.items()))
1822
+ # TODO: symbolic repr; something like this?:
1823
+ # itemiter = self.items()
1824
+ # with NoTracing():
1825
+ # return "{" + ", ".join(f"{repr(deep_realize(k))}: {repr(deep_realize(v))}" for k, v in tracing_iter(itemiter)) + "}"
1434
1826
 
1435
1827
  # TODO: __contains__ could be implemented without any path forks
1436
1828
 
@@ -1484,7 +1876,7 @@ class SymbolicDict(SymbolicDictOrSet, collections.abc.Mapping):
1484
1876
  space.add(is_missing(z3.Select(remaining, k)))
1485
1877
 
1486
1878
  if idx > len(iter_cache):
1487
- raise CrosshairInternal()
1879
+ raise CrossHairInternal()
1488
1880
  if idx == len(iter_cache):
1489
1881
  iter_cache.append(k)
1490
1882
  else:
@@ -1504,26 +1896,69 @@ class SymbolicDict(SymbolicDictOrSet, collections.abc.Mapping):
1504
1896
  return SymbolicDict(self.var, self.python_type)
1505
1897
 
1506
1898
 
1507
- class SymbolicSet(SymbolicDictOrSet, SetBase, collections.abc.Set):
1899
+ class SymbolicFrozenSet(SymbolicDictOrSet, FrozenSetBase):
1508
1900
  def __init__(self, smtvar: Union[str, z3.ExprRef], typ: Type):
1901
+ if origin_of(typ) != frozenset:
1902
+ raise CrossHairInternal
1509
1903
  SymbolicDictOrSet.__init__(self, smtvar, typ)
1510
1904
  self._iter_cache: List[z3.Const] = []
1511
1905
  self.empty = z3.K(self._arr().sort().domain(), False)
1512
- context_statespace().add((self._arr() == self.empty) == (self._len() == 0))
1906
+ space = context_statespace()
1907
+ space.add((self._arr() == self.empty) == (self._len() == 0))
1908
+ space.defer_assumption("symbolic set is consistent", self._is_consistent)
1909
+
1910
+ @assert_tracing(True)
1911
+ def _is_consistent(self) -> SymbolicBool:
1912
+ """
1913
+ Checks whether the set size is consistent with the SMT array size
1914
+
1915
+ Realizes the size. (but not the values)
1916
+ """
1917
+ my_len = len(self)
1918
+ with NoTracing():
1919
+ target_len = 0
1920
+ space = context_statespace()
1921
+ comparison_smt_array = self.empty
1922
+ items = []
1923
+ while True:
1924
+ with ResumedTracing():
1925
+ if target_len == my_len:
1926
+ break
1927
+ item = z3.Const(f"set_{target_len}_{space.uniq()}", self.smt_key_sort)
1928
+ items.append(item)
1929
+ comparison_smt_array = z3.Store(comparison_smt_array, item, True)
1930
+ target_len += 1
1931
+ if len(items) >= 2:
1932
+ space.add(z3.Distinct(*items))
1933
+ return SymbolicBool(comparison_smt_array == self._arr())
1934
+
1935
+ def __ch_is_deeply_immutable__(self) -> bool:
1936
+ return True
1513
1937
 
1514
1938
  def __ch_realize__(self):
1515
1939
  return python_type(self)(map(realize, self))
1516
1940
 
1941
+ def __repr__(self):
1942
+ if self:
1943
+ return "frozenset({" + ", ".join(map(repr, self)) + "})"
1944
+ else:
1945
+ return "frozenset()"
1946
+
1947
+ def __hash__(self):
1948
+ return deep_realize(self).__hash__()
1949
+
1517
1950
  def __eq__(self, other):
1518
1951
  (self_arr, self_len) = self.var
1519
- if isinstance(other, SymbolicSet):
1952
+ if isinstance(other, SymbolicFrozenSet):
1520
1953
  (other_arr, other_len) = other.var
1521
1954
  if other_arr.sort() == self_arr.sort():
1522
1955
  # TODO: this is wrong for HeapRef sets (which could customize __eq__)
1523
1956
  return SymbolicBool(
1524
1957
  z3.And(self_len == other_len, self_arr == other_arr)
1525
1958
  )
1526
- if not isinstance(other, (set, frozenset, SymbolicSet, collections.abc.Set)):
1959
+ if not isinstance(
1960
+ other, (set, frozenset, SymbolicFrozenSet, collections.abc.Set)
1961
+ ):
1527
1962
  return False
1528
1963
  # Manually check equality. Drive size from the (likely) concrete value 'other':
1529
1964
  if len(self) != len(other):
@@ -1578,16 +2013,24 @@ class SymbolicSet(SymbolicDictOrSet, SetBase, collections.abc.Set):
1578
2013
  arr_sort = self._arr().sort()
1579
2014
  keys_on_heap = is_heapref_sort(arr_sort.domain())
1580
2015
  already_yielded = []
1581
- while SymbolicBool(idx < len_var).__bool__():
1582
- if space.choose_possible(arr_var == self.empty, probability_true=0.0):
1583
- raise IgnoreAttempt("SymbolicSet in inconsistent state")
1584
- k = z3.Const("k" + str(idx) + space.uniq(), arr_sort.domain())
2016
+ while True:
2017
+ if idx < len(iter_cache):
2018
+ k = iter_cache[idx]
2019
+ elif SymbolicBool(idx < len_var).__bool__():
2020
+ if space.choose_possible(
2021
+ arr_var == self.empty, probability_true=0.0
2022
+ ):
2023
+ raise IgnoreAttempt("SymbolicFrozenSet in inconsistent state")
2024
+ k = z3.Const("k" + str(idx) + space.uniq(), arr_sort.domain())
2025
+ else:
2026
+ break
1585
2027
  remaining = z3.Const("remaining" + str(idx) + space.uniq(), arr_sort)
1586
2028
  space.add(arr_var == z3.Store(remaining, k, True))
2029
+ # TODO: this seems like it won't work the same for heaprefs which can be distinct but equal:
1587
2030
  space.add(z3.Not(z3.Select(remaining, k)))
1588
2031
 
1589
2032
  if idx > len(iter_cache):
1590
- raise CrosshairInternal()
2033
+ raise CrossHairInternal()
1591
2034
  if idx == len(iter_cache):
1592
2035
  iter_cache.append(k)
1593
2036
  else:
@@ -1613,7 +2056,7 @@ class SymbolicSet(SymbolicDictOrSet, SetBase, collections.abc.Set):
1613
2056
  # In this conditional, we reconcile the parallel symbolic variables for length
1614
2057
  # and contents:
1615
2058
  if space.choose_possible(arr_var != self.empty, probability_true=0.0):
1616
- raise IgnoreAttempt("SymbolicSet in inconsistent state")
2059
+ raise IgnoreAttempt("SymbolicFrozenSet in inconsistent state")
1617
2060
 
1618
2061
  def _set_op(self, attr, other):
1619
2062
  # We need to check the type of other here, because builtin sets
@@ -1657,40 +2100,15 @@ class SymbolicSet(SymbolicDictOrSet, SetBase, collections.abc.Set):
1657
2100
  return self._set_op("__sub__", other)
1658
2101
 
1659
2102
 
1660
- class SymbolicFrozenSet(SymbolicSet):
1661
- def __repr__(self):
1662
- return deep_realize(self).__repr__()
1663
-
1664
- def __hash__(self):
1665
- return deep_realize(self).__hash__()
1666
-
1667
- @classmethod
1668
- def _from_iterable(cls, it):
1669
- # overrides collections.abc.Set's version
1670
- return frozenset(it)
1671
-
1672
-
1673
- def flip_slice_vs_symbolic_len(
1674
- space: StateSpace,
2103
+ def flip_slice_vs_bounded_len(
1675
2104
  i: Union[int, slice],
1676
- smt_len: z3.ExprRef,
1677
- ) -> Union[z3.ExprRef, Tuple[z3.ExprRef, z3.ExprRef]]:
1678
- if is_tracing():
1679
- raise CrosshairInternal("index math while tracing")
1680
-
1681
- def normalize_symbolic_index(idx) -> z3.ExprRef:
1682
- if type(idx) is int:
1683
- return z3IntVal(idx) if idx >= 0 else (smt_len + z3IntVal(idx))
1684
- else:
1685
- smt_idx = SymbolicInt._coerce_to_smt_sort(idx)
1686
- if space.smt_fork(smt_idx >= 0): # type: ignore
1687
- return smt_idx
1688
- else:
1689
- return smt_len + smt_idx
2105
+ bounded_len: SymbolicBoundedInt,
2106
+ ) -> Union[int, Tuple[int, int]]:
2107
+ def normalize_symbolic_index(idx: int) -> int:
2108
+ return idx if idx >= 0 else (bounded_len + idx)
1690
2109
 
1691
2110
  if isinstance(i, Integral):
1692
- smt_i = SymbolicInt._coerce_to_smt_sort(i)
1693
- if space.smt_fork(z3.Or(smt_i >= smt_len, smt_i < -smt_len)):
2111
+ if any([i >= bounded_len, i < -bounded_len]):
1694
2112
  raise IndexError
1695
2113
  return normalize_symbolic_index(i)
1696
2114
  elif isinstance(i, slice):
@@ -1700,17 +2118,12 @@ def flip_slice_vs_symbolic_len(
1700
2118
  raise TypeError(
1701
2119
  "slice indices must be integers or None or have an __index__ method"
1702
2120
  )
1703
- if step is not None:
1704
- with ResumedTracing(): # Resume tracing for symbolic equality comparison:
1705
- if step != 1:
1706
- # TODO: do more with slices and steps
1707
- raise CrosshairUnsupported("slice steps not handled")
1708
2121
  if i.start is None:
1709
- start = z3IntVal(0)
2122
+ start = 0
1710
2123
  else:
1711
2124
  start = normalize_symbolic_index(start)
1712
2125
  if i.stop is None:
1713
- stop = smt_len
2126
+ stop = bounded_len
1714
2127
  else:
1715
2128
  stop = normalize_symbolic_index(stop)
1716
2129
  return (start, stop)
@@ -1718,32 +2131,30 @@ def flip_slice_vs_symbolic_len(
1718
2131
  raise TypeError("indices must be integers or slices, not " + str(type(i)))
1719
2132
 
1720
2133
 
1721
- def clip_range_to_symbolic_len(
1722
- space: StateSpace,
1723
- start: z3.ExprRef,
1724
- stop: z3.ExprRef,
1725
- smt_len: z3.ExprRef,
1726
- ) -> Tuple[z3.ExprRef, z3.ExprRef]:
1727
- if space.smt_fork(start < 0):
1728
- start = z3IntVal(0)
1729
- elif space.smt_fork(smt_len < start):
1730
- start = smt_len
1731
- if space.smt_fork(stop < 0):
1732
- stop = z3IntVal(0)
1733
- elif space.smt_fork(smt_len < stop):
1734
- stop = smt_len
2134
+ def clip_range_to_bounded_len(
2135
+ start: int,
2136
+ stop: int,
2137
+ bounded_len: SymbolicBoundedInt,
2138
+ ) -> Tuple[int, int]:
2139
+ if start < 0:
2140
+ start = 0
2141
+ elif bounded_len < start:
2142
+ start = bounded_len # type: ignore
2143
+ if stop < 0:
2144
+ stop = 0
2145
+ elif bounded_len < stop:
2146
+ stop = bounded_len # type: ignore
1735
2147
  return (start, stop)
1736
2148
 
1737
2149
 
1738
- def process_slice_vs_symbolic_len(
1739
- space: StateSpace,
2150
+ def process_slice_vs_bounded_len(
1740
2151
  i: Union[int, slice],
1741
- smt_len: z3.ExprRef,
1742
- ) -> Union[z3.ExprRef, Tuple[z3.ExprRef, z3.ExprRef]]:
1743
- ret = flip_slice_vs_symbolic_len(space, i, smt_len)
2152
+ bounded_len: SymbolicBoundedInt,
2153
+ ) -> Union[int, Tuple[int, int]]:
2154
+ ret = flip_slice_vs_bounded_len(i, bounded_len)
1744
2155
  if isinstance(ret, tuple):
1745
2156
  (start, stop) = ret
1746
- return clip_range_to_symbolic_len(space, start, stop, smt_len)
2157
+ return clip_range_to_bounded_len(start, stop, bounded_len)
1747
2158
  return ret
1748
2159
 
1749
2160
 
@@ -1788,17 +2199,17 @@ class SymbolicArrayBasedUniformTuple(SymbolicSequence):
1788
2199
  assert len(smtvar) == 2
1789
2200
 
1790
2201
  self.val_pytype = normalize_pytype(type_arg_of(typ, 0))
1791
- ch_types = crosshair_types_for_python_type(self.val_pytype)
1792
- if ch_types:
1793
- self.ch_item_type: Optional[Type[AtomicSymbolicValue]] = ch_types[0]
2202
+ space = context_statespace()
2203
+ val_ch_type = space.extra(ModelingDirector).get(self.val_pytype)
2204
+ if val_ch_type:
2205
+ self.ch_item_type: Optional[Type[AtomicSymbolicValue]] = val_ch_type
1794
2206
  self.item_smt_sort = self.ch_item_type._ch_smt_sort()
1795
2207
  else:
1796
2208
  self.ch_item_type = None
1797
2209
  self.item_smt_sort = HeapRef
1798
2210
 
1799
2211
  SymbolicValue.__init__(self, smtvar, typ)
1800
- len_var = self._len()
1801
- context_statespace().add(len_var >= 0)
2212
+ self._len_int = SymbolicBoundedInt(self._len(), int, minimum=0)
1802
2213
 
1803
2214
  def __init_var__(self, typ, varname):
1804
2215
  assert typ == self.python_type
@@ -1816,12 +2227,10 @@ class SymbolicArrayBasedUniformTuple(SymbolicSequence):
1816
2227
  return self.var[1]
1817
2228
 
1818
2229
  def __len__(self):
1819
- with NoTracing():
1820
- return SymbolicInt(self._len())
2230
+ return self._len_int
1821
2231
 
1822
2232
  def __bool__(self) -> bool:
1823
- with NoTracing():
1824
- return SymbolicBool(self._len() != 0).__bool__()
2233
+ return self._len_int > 0
1825
2234
 
1826
2235
  def __eq__(self, other):
1827
2236
  with NoTracing():
@@ -1851,9 +2260,10 @@ class SymbolicArrayBasedUniformTuple(SymbolicSequence):
1851
2260
  def __iter__(self):
1852
2261
  with NoTracing():
1853
2262
  space = context_statespace()
1854
- arr_var, len_var = self.var
2263
+ arr_var, _ = self.var
2264
+ len_int = self._len_int
1855
2265
  idx = 0
1856
- while space.smt_fork(idx < len_var):
2266
+ while idx < len_int:
1857
2267
  val = smt_to_ch_value(
1858
2268
  space, self.snapshot, z3.Select(arr_var, idx), self.val_pytype
1859
2269
  )
@@ -1904,16 +2314,14 @@ class SymbolicArrayBasedUniformTuple(SymbolicSequence):
1904
2314
  and i.step is None
1905
2315
  ):
1906
2316
  return self
1907
- idx_or_pair = process_slice_vs_symbolic_len(space, i, self._len())
2317
+ with ResumedTracing():
2318
+ idx_or_pair = process_slice_vs_bounded_len(i, self._len_int)
1908
2319
  if isinstance(idx_or_pair, tuple):
1909
2320
  (start, stop) = idx_or_pair
1910
- (myarr, mylen) = self.var
1911
- start = SymbolicInt(start)
1912
- stop = SymbolicInt(smt_min(mylen, smt_coerce(stop)))
1913
2321
  with ResumedTracing():
1914
2322
  return SliceView.slice(self, start, stop)
1915
2323
  else:
1916
- smt_result = z3.Select(self._arr(), idx_or_pair)
2324
+ smt_result = z3.Select(self._arr(), smt_coerce(idx_or_pair))
1917
2325
  return smt_to_ch_value(
1918
2326
  space, self.snapshot, smt_result, self.val_pytype
1919
2327
  )
@@ -2007,7 +2415,7 @@ class SymbolicRange:
2007
2415
  return False
2008
2416
  if len(self) != len(other):
2009
2417
  return False
2010
- for (v1, v2) in zip(self, other):
2418
+ for v1, v2 in zip(self, other):
2011
2419
  if v1 != v2:
2012
2420
  return False
2013
2421
  return True
@@ -2072,6 +2480,11 @@ class SymbolicList(
2072
2480
  def _spawn(self, items: Sequence) -> "ShellMutableSequence":
2073
2481
  return SymbolicList(items)
2074
2482
 
2483
+ def __eq__(self, other):
2484
+ if not isinstance(other, list):
2485
+ return False
2486
+ return ShellMutableSequence.__eq__(self, other)
2487
+
2075
2488
  def __lt__(self, other):
2076
2489
  if not isinstance(other, (list, SymbolicList)):
2077
2490
  raise TypeError
@@ -2097,6 +2510,10 @@ class SymbolicType(AtomicSymbolicValue, SymbolicValue, Untracable):
2097
2510
  # no paramaterized types allowed, e.g. SymbolicType("t", Type[List[int]])
2098
2511
  assert not hasattr(captype, "__args__")
2099
2512
  self.pytype_cap = origin_of(captype)
2513
+ if isinstance(self.pytype_cap, CrossHairValue):
2514
+ raise CrossHairInternal(
2515
+ "Cannot create symbolic type capped at a symbolic type"
2516
+ )
2100
2517
  assert isinstance(self.pytype_cap, (type, ABCMeta))
2101
2518
  type_repo = space.extra(SymbolicTypeRepository)
2102
2519
  smt_cap = type_repo.get_type(self.pytype_cap)
@@ -2115,13 +2532,13 @@ class SymbolicType(AtomicSymbolicValue, SymbolicValue, Untracable):
2115
2532
  return type
2116
2533
 
2117
2534
  @classmethod
2118
- def _smt_promote_literal(cls, literal) -> Optional[z3.SortRef]:
2535
+ def _smt_promote_literal(cls, literal) -> Optional[z3.ExprRef]:
2119
2536
  if isinstance(literal, type):
2120
2537
  return context_statespace().extra(SymbolicTypeRepository).get_type(literal)
2121
2538
  return None
2122
2539
 
2540
+ @assert_tracing(False)
2123
2541
  def _is_superclass_of_(self, other):
2124
- assert not is_tracing()
2125
2542
  if self is SymbolicType:
2126
2543
  return False
2127
2544
  if type(other) is SymbolicType:
@@ -2224,23 +2641,24 @@ class SymbolicType(AtomicSymbolicValue, SymbolicValue, Untracable):
2224
2641
  return hash(self._realized())
2225
2642
 
2226
2643
 
2644
+ @assert_tracing(True)
2227
2645
  def symbolic_obj_binop(symbolic_obj: "SymbolicObject", other, op):
2228
2646
  other_type = type(other)
2229
2647
  with NoTracing():
2230
2648
  mytype = symbolic_obj._typ
2231
-
2232
- # This just encourages a useful type realization; we discard the result:
2233
- other_smt_type = SymbolicType._coerce_to_smt_sort(other_type)
2234
- if other_smt_type is not None:
2235
- space = context_statespace()
2236
- space.smt_fork(z3Eq(mytype.var, other_smt_type), probability_true=0.9)
2237
-
2238
- # The following call then lowers the type cap.
2239
- # TODO: This does more work than is really needed. But it might be good for
2240
- # subclass realizations. We want the equality check above mostly because
2241
- # `object`` realizes to int|str and we don't want to spend lots of time
2242
- # considering (usually enum-based) int and str subclasses.
2243
- mytype._is_subclass_of_(other_type)
2649
+ if isinstance(mytype, SymbolicType):
2650
+ # This just encourages a useful type realization; we discard the result:
2651
+ other_smt_type = SymbolicType._coerce_to_smt_sort(other_type)
2652
+ if other_smt_type is not None:
2653
+ space = context_statespace()
2654
+ space.smt_fork(z3Eq(mytype.var, other_smt_type), probability_true=0.9)
2655
+
2656
+ # The following call then lowers the type cap.
2657
+ # TODO: This does more work than is really needed. But it might be good for
2658
+ # subclass realizations. We want the equality check above mostly because
2659
+ # `object`` realizes to int|str and we don't want to spend lots of time
2660
+ # considering (usually enum-based) int and str subclasses.
2661
+ mytype._is_subclass_of_(other_type)
2244
2662
  obj_with_known_type = symbolic_obj._wrapped()
2245
2663
  return op(obj_with_known_type, other)
2246
2664
 
@@ -2254,31 +2672,30 @@ class SymbolicObject(ObjectProxy, CrossHairValue, Untracable):
2254
2672
  members can be.
2255
2673
  """
2256
2674
 
2675
+ @assert_tracing(False)
2257
2676
  def __init__(self, smtvar: str, typ: Type):
2677
+ if not isinstance(typ, type):
2678
+ raise CrossHairInternal(f"Creating SymbolicObject with non-type {typ}")
2679
+ if isinstance(typ, CrossHairValue):
2680
+ raise CrossHairInternal(f"Creating SymbolicObject with symbolic type {typ}")
2258
2681
  object.__setattr__(self, "_typ", SymbolicType(smtvar + "_type", Type[typ]))
2259
2682
  object.__setattr__(self, "_space", context_statespace())
2260
2683
  object.__setattr__(self, "_varname", smtvar)
2261
2684
 
2685
+ @assert_tracing(False)
2262
2686
  def _realize(self):
2263
2687
  object.__getattribute__(self, "_space")
2264
2688
  varname = object.__getattribute__(self, "_varname")
2265
2689
 
2266
- typ = object.__getattribute__(self, "_typ")
2267
- pytype = realize(typ)
2268
- debug("materializing the type of symbolic", varname, "to be", pytype)
2690
+ pytype = realize(object.__getattribute__(self, "_typ"))
2691
+ debug(
2692
+ "materializing the type of symbolic", varname, "to be", pytype, ch_stack()
2693
+ )
2694
+ object.__setattr__(self, "_typ", pytype)
2269
2695
  if pytype is object:
2270
2696
  return object()
2271
2697
  return proxy_for_type(pytype, varname, allow_subtypes=False)
2272
2698
 
2273
- def _wrapped(self):
2274
- with NoTracing():
2275
- try:
2276
- inner = object.__getattribute__(self, "_inner")
2277
- except AttributeError:
2278
- inner = self._realize()
2279
- object.__setattr__(self, "_inner", inner)
2280
- return inner
2281
-
2282
2699
  def __ch_realize__(self):
2283
2700
  return realize(self._wrapped())
2284
2701
 
@@ -2290,6 +2707,8 @@ class SymbolicObject(ObjectProxy, CrossHairValue, Untracable):
2290
2707
  # That's usually bad for LazyObjects, which want to defer their
2291
2708
  # realization, so we simply don't do mutation checking for these
2292
2709
  # kinds of values right now.
2710
+ # TODO: we should do something else here; realizing the type doesn't
2711
+ # seem THAT terrible?
2293
2712
  result = self
2294
2713
  else:
2295
2714
  result = copy.deepcopy(inner)
@@ -2334,10 +2753,25 @@ class SymbolicCallable:
2334
2753
  __annotations__: dict = {}
2335
2754
 
2336
2755
  def __init__(self, values: list):
2756
+ """
2757
+ A function that will ignore its arguments and produce return values
2758
+ from the list given.
2759
+ If the given list is exhausted, the function will just repeatedly
2760
+ return the final value in the list.
2761
+
2762
+ If `values` is concrete, it must be non-mepty.
2763
+ If `values` is a symbolic list, it will be forced to be non-empty
2764
+ (the caller must enure that's possible).
2765
+ """
2337
2766
  assert not is_tracing()
2338
2767
  with ResumedTracing():
2339
- if not values:
2340
- raise IgnoreAttempt
2768
+ has_values = len(values) > 0
2769
+ if isinstance(values, CrossHairValue):
2770
+ space = context_statespace()
2771
+ assert space.is_possible(has_values)
2772
+ space.add(has_values)
2773
+ else:
2774
+ assert has_values
2341
2775
  self.values = values
2342
2776
  self.idx = 0
2343
2777
 
@@ -2361,6 +2795,7 @@ class SymbolicCallable:
2361
2795
  if idx >= len(values):
2362
2796
  return values[-1]
2363
2797
  else:
2798
+ self.idx += 1
2364
2799
  return values[idx]
2365
2800
 
2366
2801
  def __bool__(self):
@@ -2383,33 +2818,49 @@ class SymbolicUniformTuple(
2383
2818
  def __hash__(self):
2384
2819
  return tuple(self).__hash__()
2385
2820
 
2386
-
2387
- _SMTSTR_Z3_SORT = z3.SeqSort(z3.IntSort())
2821
+ def __eq__(self, other):
2822
+ if not isinstance(other, tuple):
2823
+ return False
2824
+ return SymbolicArrayBasedUniformTuple.__eq__(self, other)
2388
2825
 
2389
2826
 
2390
2827
  class SymbolicBoundedIntTuple(collections.abc.Sequence):
2391
2828
  def __init__(self, ranges: List[Tuple[int, int]], varname: str):
2392
2829
  assert not is_tracing()
2830
+ assert ranges
2393
2831
  self._ranges = ranges
2394
- space = context_statespace()
2395
- smtlen = z3.Int(varname + "len" + space.uniq())
2396
- space.add(smtlen >= 0)
2397
2832
  self._varname = varname
2398
- self._len = SymbolicInt(smtlen)
2833
+ self._len = SymbolicBoundedInt(varname + "len", int, 0, None)
2399
2834
  self._created_vars: List[SymbolicInt] = []
2400
2835
 
2836
+ def __ch_deep_realize__(self, memo):
2837
+ # Right now, SymbolicBoundedIntTuple falls short of being a CrossHairValue,
2838
+ # but it happens to be handy to have it realize like one would.
2839
+ concrete_size = realize(self._len)
2840
+ self._create_up_to(concrete_size)
2841
+ return tuple(realize(v) for v in self._created_vars[:concrete_size])
2842
+
2401
2843
  def _create_up_to(self, size: int) -> None:
2402
2844
  space = context_statespace()
2403
2845
  created_vars = self._created_vars
2846
+ ranges = self._ranges
2404
2847
  for idx in range(len(created_vars), size):
2405
2848
  assert idx == len(created_vars)
2406
- smtval = z3.Int(self._varname + "@" + str(idx))
2407
- constraints = [
2408
- z3And(minval <= smtval, smtval <= maxval)
2409
- for minval, maxval in self._ranges
2410
- ]
2411
- space.add(constraints[0] if len(constraints) == 1 else z3Or(*constraints))
2412
- created_vars.append(SymbolicInt(smtval))
2849
+ varname = self._varname + "@" + str(idx)
2850
+ if len(ranges) <= 1:
2851
+ created_vars.append(SymbolicBoundedInt(varname, int, *ranges[0]))
2852
+ else:
2853
+ smtval = z3.Int(varname)
2854
+ constraints = [
2855
+ z3And(minval <= smtval, smtval <= maxval)
2856
+ for minval, maxval in ranges
2857
+ ]
2858
+ space.add(z3Or(*constraints))
2859
+ global_min = min(start for start, _ in ranges)
2860
+ global_max = max(end for _, end in ranges)
2861
+ created_vars.append(
2862
+ SymbolicBoundedInt(smtval, int, global_min, global_max)
2863
+ )
2413
2864
  if idx % 1_000 == 999:
2414
2865
  space.check_timeout()
2415
2866
 
@@ -2417,8 +2868,7 @@ class SymbolicBoundedIntTuple(collections.abc.Sequence):
2417
2868
  return self._len
2418
2869
 
2419
2870
  def __bool__(self) -> bool:
2420
- with NoTracing():
2421
- return SymbolicBool(self._len.var == 0).__bool__()
2871
+ return self._len.var > 0
2422
2872
 
2423
2873
  def __eq__(self, other):
2424
2874
  if self is other:
@@ -2431,7 +2881,7 @@ class SymbolicBoundedIntTuple(collections.abc.Sequence):
2431
2881
  with NoTracing():
2432
2882
  self._create_up_to(realize(otherlen))
2433
2883
  constraints = []
2434
- for (int1, int2) in zip(self._created_vars, tracing_iter(other)):
2884
+ for int1, int2 in zip(self._created_vars, tracing_iter(other)):
2435
2885
  smtint2 = force_to_smt_sort(int2, SymbolicInt)
2436
2886
  constraints.append(int1.var == smtint2)
2437
2887
  return SymbolicBool(z3.And(*constraints))
@@ -2441,17 +2891,18 @@ class SymbolicBoundedIntTuple(collections.abc.Sequence):
2441
2891
 
2442
2892
  def __iter__(self):
2443
2893
  with NoTracing():
2444
- my_smt_len = self._len.var
2894
+ my_len = self._len
2445
2895
  created_vars = self._created_vars
2446
- space = context_statespace()
2447
- idx = -1
2448
- while True:
2449
- with NoTracing():
2896
+ idx = 0
2897
+ while True:
2898
+ needed_size = idx + 1
2899
+ with ResumedTracing():
2900
+ if not (idx < my_len):
2901
+ return
2902
+ self._create_up_to(needed_size)
2903
+ with ResumedTracing():
2904
+ yield created_vars[idx]
2450
2905
  idx += 1
2451
- if not space.smt_fork(idx < my_smt_len):
2452
- return
2453
- self._create_up_to(idx + 1)
2454
- yield created_vars[idx]
2455
2906
 
2456
2907
  def __add__(self, other: object):
2457
2908
  if isinstance(other, collections.abc.Sequence):
@@ -2558,7 +3009,7 @@ class AnySymbolicStr(AbcString):
2558
3009
  raise TypeError
2559
3010
  if self == other:
2560
3011
  return True if op in (ops.le, ops.ge) else False
2561
- for (mych, otherch) in zip_longest(iter(self), iter(other)):
3012
+ for mych, otherch in zip_longest(iter(self), iter(other)):
2562
3013
  if mych == otherch:
2563
3014
  continue
2564
3015
  if mych is None:
@@ -2588,7 +3039,7 @@ class AnySymbolicStr(AbcString):
2588
3039
  def capitalize(self):
2589
3040
  if self.__len__() == 0:
2590
3041
  return ""
2591
- if sys.version_info >= (3, 8):
3042
+ if version_info >= (3, 8):
2592
3043
  firstchar = self[0].title()
2593
3044
  else:
2594
3045
  firstchar = self[0].upper()
@@ -2820,19 +3271,19 @@ class AnySymbolicStr(AbcString):
2820
3271
 
2821
3272
  else:
2822
3273
  raise TypeError
2823
- for (idx, ch) in enumerate(self):
3274
+ for idx, ch in enumerate(self):
2824
3275
  if not filter(ch):
2825
3276
  return self[idx:]
2826
3277
  return ""
2827
3278
 
2828
3279
  def splitlines(self, keepends=False):
2829
- if sys.version_info < (3, 12):
3280
+ if version_info < (3, 12):
2830
3281
  if not isinstance(keepends, int):
2831
3282
  raise TypeError
2832
3283
  mylen = self.__len__()
2833
3284
  if mylen == 0:
2834
3285
  return []
2835
- for (idx, ch) in enumerate(self):
3286
+ for idx, ch in enumerate(self):
2836
3287
  codepoint = ord(ch)
2837
3288
  with NoTracing():
2838
3289
  space = context_statespace()
@@ -3070,6 +3521,26 @@ class AnySymbolicStr(AbcString):
3070
3521
  return "0" * fill_length + self
3071
3522
 
3072
3523
 
3524
+ def _unfindable_range(start: Optional[int], end: Optional[int], mylen: int) -> bool:
3525
+ """
3526
+ Emulates some preliminary checks that CPython makes before searching
3527
+ for substrings within some bounds. (in e.g. str.find, str.startswith, etc)
3528
+ """
3529
+ if start is None or start == 0 or start <= -mylen:
3530
+ return False
3531
+
3532
+ # At this point, we know that `start` is defined and points to an index after 0
3533
+ if end is None or end >= mylen:
3534
+ return start > mylen
3535
+
3536
+ # At this point, we know that `end` is defined and points to an index before the end of the string
3537
+ if start < 0:
3538
+ start += mylen
3539
+ if end < 0:
3540
+ end += mylen
3541
+ return end < start
3542
+
3543
+
3073
3544
  class LazyIntSymbolicStr(AnySymbolicStr, CrossHairValue):
3074
3545
  """
3075
3546
  A symbolic string that lazily generates SymbolicInt-based characters as needed.
@@ -3091,25 +3562,24 @@ class LazyIntSymbolicStr(AnySymbolicStr, CrossHairValue):
3091
3562
  SymbolicBoundedIntTuple,
3092
3563
  SliceView,
3093
3564
  SequenceConcatenation,
3094
- list,
3095
- SymbolicList,
3565
+ list, # TODO: are we sharing mutable state here?
3566
+ tuple,
3096
3567
  ),
3097
3568
  ):
3098
3569
  self._codepoints = smtvar
3570
+ elif isinstance(smtvar, SymbolicList):
3571
+ self._codepoints = smtvar.inner # use the (immutable) contents
3099
3572
  else:
3100
- raise CrosshairInternal(
3573
+ raise CrossHairInternal(
3101
3574
  f"Unexpected LazyIntSymbolicStr initializer of type {type(smtvar)}"
3102
3575
  )
3103
3576
 
3104
3577
  def __ch_realize__(self) -> object:
3105
- with ResumedTracing():
3106
- codepoints = tuple(self._codepoints)
3107
- return "".join(chr(realize(x)) for x in codepoints)
3578
+ codepoints = deep_realize(self._codepoints)
3579
+ return "".join(map(chr, codepoints))
3108
3580
 
3109
- # This is normally an AtomicSymbolicValue method, but sometimes it's used in a
3110
- # duck-typing way.
3111
3581
  @classmethod
3112
- def _smt_promote_literal(cls, val: object) -> Optional[z3.SortRef]:
3582
+ def _ch_create_from_literal(cls, val: object) -> Optional[CrossHairValue]:
3113
3583
  if isinstance(val, str):
3114
3584
  return LazyIntSymbolicStr(list(map(ord, val)))
3115
3585
  return None
@@ -3136,10 +3606,6 @@ class LazyIntSymbolicStr(AnySymbolicStr, CrossHairValue):
3136
3606
  otherpoints = [ord(ch) for ch in other]
3137
3607
  with ResumedTracing():
3138
3608
  return mypoints.__eq__(otherpoints)
3139
- elif isinstance(other, SeqBasedSymbolicStr):
3140
- with ResumedTracing():
3141
- otherpoints = [ord(ch) for ch in other]
3142
- return mypoints.__eq__(otherpoints)
3143
3609
  else:
3144
3610
  return NotImplemented
3145
3611
 
@@ -3147,6 +3613,10 @@ class LazyIntSymbolicStr(AnySymbolicStr, CrossHairValue):
3147
3613
  with NoTracing():
3148
3614
  if not isinstance(i, (Integral, slice)):
3149
3615
  raise TypeError(type(i))
3616
+ # This could/should? be symbolic by naming all the possibilities.
3617
+ # Note the slice case still must realize the return length.
3618
+ # Especially because we no longer explore realization trees except
3619
+ # as a last resort.
3150
3620
  i = deep_realize(i)
3151
3621
  with ResumedTracing():
3152
3622
  newcontents = self._codepoints[i]
@@ -3227,11 +3697,15 @@ class LazyIntSymbolicStr(AnySymbolicStr, CrossHairValue):
3227
3697
  return any(self.endswith(s, start, end) for s in substr)
3228
3698
  if not isinstance(substr, str):
3229
3699
  raise TypeError
3700
+ substrlen = len(substr)
3230
3701
  if start is None and end is None:
3231
3702
  matchable = self
3232
3703
  else:
3233
3704
  matchable = self[start:end]
3234
- return matchable[-len(substr) :] == substr
3705
+ if substrlen == 0:
3706
+ return not _unfindable_range(start, end, len(self))
3707
+ else:
3708
+ return matchable[-substrlen:] == substr
3235
3709
 
3236
3710
  def startswith(self, substr, start=None, end=None):
3237
3711
  if isinstance(substr, tuple):
@@ -3241,6 +3715,10 @@ class LazyIntSymbolicStr(AnySymbolicStr, CrossHairValue):
3241
3715
  if start is None and end is None:
3242
3716
  matchable = self
3243
3717
  else:
3718
+ # Wacky special case: the empty string is findable off the left
3719
+ # side but not the right!
3720
+ if _unfindable_range(start, end, len(self)):
3721
+ return False
3244
3722
  matchable = self[start:end]
3245
3723
  return matchable[: len(substr)] == substr
3246
3724
 
@@ -3281,7 +3759,7 @@ class LazyIntSymbolicStr(AnySymbolicStr, CrossHairValue):
3281
3759
  end += mylen
3282
3760
  matchstr = self[start:end] if start != 0 or end is not mylen else self
3283
3761
  if len(substr) == 0:
3284
- # Add oddity of CPython. We can find the empty string when over-slicing
3762
+ # An oddity of CPython. We can find the empty string when over-slicing
3285
3763
  # off the left side of the string, but not off the right:
3286
3764
  # ''.find('', 3, 4) == -1
3287
3765
  # ''.find('', -4, -3) == 0
@@ -3308,257 +3786,6 @@ class LazyIntSymbolicStr(AnySymbolicStr, CrossHairValue):
3308
3786
  return self._find(substr, start, end, from_right=True)
3309
3787
 
3310
3788
 
3311
- class SeqBasedSymbolicStr(AtomicSymbolicValue, SymbolicSequence, AnySymbolicStr):
3312
- def __init__(self, smtvar: Union[str, z3.ExprRef], typ: Type = str):
3313
- assert typ == str
3314
- SymbolicValue.__init__(self, smtvar, typ)
3315
- self.item_pytype = str
3316
- if isinstance(smtvar, str):
3317
- # Constrain fresh strings to valid codepoints
3318
- space = context_statespace()
3319
- idxvar = z3.Int("idxvar" + space.uniq())
3320
- z3seq = self.var
3321
- space.add(
3322
- z3.ForAll(
3323
- [idxvar], z3.And(0 <= z3seq[idxvar], z3seq[idxvar] <= maxunicode)
3324
- )
3325
- )
3326
-
3327
- @classmethod
3328
- def _ch_smt_sort(cls) -> z3.SortRef:
3329
- return _SMTSTR_Z3_SORT
3330
-
3331
- @classmethod
3332
- def _pytype(cls) -> Type:
3333
- return str
3334
-
3335
- @classmethod
3336
- def _smt_promote_literal(cls, literal) -> Optional[z3.SortRef]:
3337
- if isinstance(literal, str):
3338
- if len(literal) <= 1:
3339
- if len(literal) == 0:
3340
- return z3.Empty(_SMTSTR_Z3_SORT)
3341
- return z3.Unit(z3IntVal(ord(literal)))
3342
- return z3.Concat([z3.Unit(z3IntVal(ord(ch))) for ch in literal])
3343
- return None
3344
-
3345
- def __ch_realize__(self) -> object:
3346
- codepoints = context_statespace().find_model_value(self.var)
3347
- return "".join(chr(x) for x in codepoints)
3348
-
3349
- def __copy__(self):
3350
- return SeqBasedSymbolicStr(self.var)
3351
-
3352
- def __hash__(self):
3353
- return hash(self.__str__())
3354
-
3355
- @staticmethod
3356
- def _concat_strings(
3357
- a: Union[str, "SeqBasedSymbolicStr"], b: Union[str, "SeqBasedSymbolicStr"]
3358
- ) -> Union[str, "SeqBasedSymbolicStr"]:
3359
- assert not is_tracing()
3360
- # Assumes at least one argument is symbolic and not tracing
3361
- if isinstance(a, SeqBasedSymbolicStr) and isinstance(b, SeqBasedSymbolicStr):
3362
- return SeqBasedSymbolicStr(a.var + b.var)
3363
- elif isinstance(a, str) and isinstance(b, SeqBasedSymbolicStr):
3364
- return SeqBasedSymbolicStr(
3365
- SeqBasedSymbolicStr._coerce_to_smt_sort(a) + b.var
3366
- )
3367
- else:
3368
- assert isinstance(a, SeqBasedSymbolicStr)
3369
- assert isinstance(b, str)
3370
- return SeqBasedSymbolicStr(
3371
- a.var + SeqBasedSymbolicStr._coerce_to_smt_sort(b)
3372
- )
3373
-
3374
- def __add__(self, other):
3375
- with NoTracing():
3376
- if isinstance(other, (SeqBasedSymbolicStr, str)):
3377
- return SeqBasedSymbolicStr._concat_strings(self, other)
3378
- if isinstance(other, AnySymbolicStr):
3379
- return NotImplemented
3380
- raise TypeError
3381
-
3382
- def __radd__(self, other):
3383
- with NoTracing():
3384
- if isinstance(other, (SeqBasedSymbolicStr, str)):
3385
- return SeqBasedSymbolicStr._concat_strings(other, self)
3386
- if isinstance(other, AnySymbolicStr):
3387
- return NotImplemented
3388
- raise TypeError
3389
-
3390
- def __mul__(self, other):
3391
- if isinstance(other, Integral):
3392
- if other <= 1:
3393
- return self if other == 1 else ""
3394
- # Note that in SymbolicInt, we attempt string multiplication via regex.
3395
- # Z3 cannot do much with a symbolic regex, so we case-split on
3396
- # the repetition count.
3397
- return SeqBasedSymbolicStr(z3.Concat(*[self.var for _ in range(other)]))
3398
- return NotImplemented
3399
-
3400
- __rmul__ = __mul__
3401
-
3402
- def __mod__(self, other):
3403
- return self.__str__() % realize(other)
3404
-
3405
- def __contains__(self, other):
3406
- with NoTracing():
3407
- forced = force_to_smt_sort(other, SeqBasedSymbolicStr)
3408
- return SymbolicBool(z3.Contains(self.var, forced))
3409
-
3410
- def __getitem__(self, i: Union[int, slice]):
3411
- with NoTracing():
3412
- idx_or_pair = process_slice_vs_symbolic_len(
3413
- context_statespace(), i, z3.Length(self.var)
3414
- )
3415
- if isinstance(idx_or_pair, tuple):
3416
- (start, stop) = idx_or_pair
3417
- smt_result = z3.Extract(self.var, start, stop - start)
3418
- else:
3419
- smt_result = z3.Unit(self.var[idx_or_pair])
3420
- return SeqBasedSymbolicStr(smt_result)
3421
-
3422
- def endswith(self, substr):
3423
- with NoTracing():
3424
- smt_substr = force_to_smt_sort(substr, SeqBasedSymbolicStr)
3425
- return SymbolicBool(z3.SuffixOf(smt_substr, self.var))
3426
-
3427
- def find(self, substr, start=None, end=None):
3428
- if not isinstance(substr, str):
3429
- raise TypeError
3430
- with NoTracing():
3431
- space = context_statespace()
3432
- smt_my_len = z3.Length(self.var)
3433
- if start is None and end is None:
3434
- smt_start = z3IntVal(0)
3435
- smt_end = smt_my_len
3436
- smt_str = self.var
3437
- if len(substr) == 0:
3438
- return 0
3439
- else:
3440
- (smt_start, smt_end) = flip_slice_vs_symbolic_len(
3441
- space, slice(start, end, None), smt_my_len
3442
- )
3443
- if len(substr) == 0:
3444
- # Add oddity of CPython. We can find the empty string when over-slicing
3445
- # off the left side of the string, but not off the right:
3446
- # ''.find('', 3, 4) == -1
3447
- # ''.find('', -4, -3) == 0
3448
- if space.smt_fork(smt_start > smt_my_len):
3449
- return -1
3450
- elif space.smt_fork(smt_start > 0):
3451
- return SymbolicInt(smt_start)
3452
- else:
3453
- return 0
3454
- (smt_start, smt_end) = clip_range_to_symbolic_len(
3455
- space, smt_start, smt_end, smt_my_len
3456
- )
3457
- smt_str = z3.SubString(self.var, smt_start, smt_end - smt_start)
3458
-
3459
- smt_sub = force_to_smt_sort(substr, SeqBasedSymbolicStr)
3460
- if space.smt_fork(z3.Contains(smt_str, smt_sub)):
3461
- return SymbolicInt(z3.IndexOf(smt_str, smt_sub, 0) + smt_start)
3462
- else:
3463
- return -1
3464
-
3465
- def partition(self, sep: str):
3466
- if not isinstance(sep, str):
3467
- raise TypeError
3468
- if len(sep) == 0:
3469
- raise ValueError
3470
- with NoTracing():
3471
- space = context_statespace()
3472
- smt_str = self.var
3473
- smt_sep = force_to_smt_sort(sep, SeqBasedSymbolicStr)
3474
- if space.smt_fork(z3.Contains(smt_str, smt_sep)):
3475
- uniq = space.uniq()
3476
- # Divide my contents into 4 concatenated parts:
3477
- prefix = SeqBasedSymbolicStr(f"prefix{uniq}")
3478
- match1 = SeqBasedSymbolicStr(
3479
- f"match1{uniq}"
3480
- ) # the first character of the match
3481
- match_tail = SeqBasedSymbolicStr(f"match_tail{uniq}")
3482
- suffix = SeqBasedSymbolicStr(f"suffix{uniq}")
3483
- space.add(z3.Length(match1.var) == 1)
3484
- space.add(smt_sep == z3.Concat(match1.var, match_tail.var))
3485
- space.add(smt_str == z3.Concat(prefix.var, smt_sep, suffix.var))
3486
- space.add(
3487
- z3.Not(z3.Contains(z3.Concat(match_tail.var, suffix.var), smt_sep))
3488
- )
3489
- return (prefix, sep, suffix)
3490
- else:
3491
- return (self, "", "")
3492
-
3493
- def rfind(self, substr, start=None, end=None) -> Union[int, SymbolicInt]:
3494
- if not isinstance(substr, str):
3495
- raise TypeError
3496
- with NoTracing():
3497
- space = context_statespace()
3498
- smt_my_len = z3.Length(self.var)
3499
- if start is None and end is None:
3500
- smt_start = z3IntVal(0)
3501
- smt_end = smt_my_len
3502
- smt_str = self.var
3503
- if len(substr) == 0:
3504
- return SymbolicInt(smt_my_len)
3505
- else:
3506
- (smt_start, smt_end) = flip_slice_vs_symbolic_len(
3507
- space, slice(start, end, None), smt_my_len
3508
- )
3509
- if len(substr) == 0:
3510
- # Add oddity of CPython. We can find the empty string when over-slicing
3511
- # off the left side of the string, but not off the right:
3512
- # ''.find('', 3, 4) == -1
3513
- # ''.find('', -4, -3) == 0
3514
- if space.smt_fork(smt_start > smt_my_len):
3515
- return -1
3516
- elif space.smt_fork(smt_end < 0):
3517
- return 0
3518
- elif space.smt_fork(smt_end < smt_my_len):
3519
- return SymbolicInt(smt_end)
3520
- else:
3521
- return SymbolicInt(smt_my_len)
3522
- (smt_start, smt_end) = clip_range_to_symbolic_len(
3523
- space, smt_start, smt_end, smt_my_len
3524
- )
3525
- smt_str = z3.SubString(self.var, smt_start, smt_end - smt_start)
3526
- smt_sub = force_to_smt_sort(substr, SeqBasedSymbolicStr)
3527
- if space.smt_fork(z3.Contains(smt_str, smt_sub)):
3528
- uniq = space.uniq()
3529
- # Divide my contents into 4 concatenated parts:
3530
- prefix = SeqBasedSymbolicStr(f"prefix{uniq}")
3531
- match1 = SeqBasedSymbolicStr(f"match1{uniq}")
3532
- match_tail = SeqBasedSymbolicStr(f"match_tail{uniq}")
3533
- suffix = SeqBasedSymbolicStr(f"suffix{uniq}")
3534
- space.add(z3.Length(match1.var) == 1)
3535
- space.add(smt_sub == z3.Concat(match1.var, match_tail.var))
3536
- space.add(smt_str == z3.Concat(prefix.var, smt_sub, suffix.var))
3537
- space.add(
3538
- z3.Not(z3.Contains(z3.Concat(match_tail.var, suffix.var), smt_sub))
3539
- )
3540
- return SymbolicInt(smt_start + z3.Length(prefix.var))
3541
- else:
3542
- return -1
3543
-
3544
- def rpartition(self, sep: str):
3545
- result = self.rsplit(sep, maxsplit=1)
3546
- if len(result) == 1:
3547
- return ("", "", self)
3548
- elif len(result) == 2:
3549
- return (result[0], sep, result[1])
3550
-
3551
- def startswith(self, substr, start=None, end=None):
3552
- if isinstance(substr, tuple):
3553
- return any(self.startswith(s, start, end) for s in substr)
3554
- smt_substr = force_to_smt_sort(substr, SeqBasedSymbolicStr)
3555
- if start is not None or end is not None:
3556
- # TODO: "".startswith("", 1) should be False, not True
3557
- return self[start:end].startswith(substr)
3558
- with NoTracing():
3559
- return SymbolicBool(z3.PrefixOf(smt_substr, self.var))
3560
-
3561
-
3562
3789
  def buffer_to_byte_seq(obj: object) -> Optional[Sequence[int]]:
3563
3790
  if isinstance(obj, (bytes, bytearray)):
3564
3791
  return list(obj)
@@ -3620,9 +3847,7 @@ def is_ascii_space_ord(char_ord: int):
3620
3847
  )
3621
3848
 
3622
3849
 
3623
- # TODO: in python >= 3.12, use collections.abc.Buffer instead
3624
- # of collections.abc.ByteString (here and elsewhere)
3625
- class BytesLike(collections.abc.ByteString, AbcString, CrossHairValue):
3850
+ class BytesLike(Buffer, AbcString, CrossHairValue):
3626
3851
  def __eq__(self, other) -> bool:
3627
3852
  if not isinstance(other, _ALL_BYTES_TYPES):
3628
3853
  return False
@@ -3630,6 +3855,12 @@ class BytesLike(collections.abc.ByteString, AbcString, CrossHairValue):
3630
3855
  return False
3631
3856
  return list(self) == list(other)
3632
3857
 
3858
+ if version_info >= (3, 12):
3859
+
3860
+ def __buffer__(self, flags: int):
3861
+ with NoTracing():
3862
+ return memoryview(realize(self))
3863
+
3633
3864
  def _cmp_op(self, other, op) -> bool:
3634
3865
  # Surprisingly, none of (bytes, memoryview, array) are ordered-comparable with
3635
3866
  # the other types.
@@ -3703,7 +3934,6 @@ class BytesLike(collections.abc.ByteString, AbcString, CrossHairValue):
3703
3934
  chars.append(sep)
3704
3935
  low = make_hex_digit(byt)
3705
3936
  high = make_hex_digit(byt // 16)
3706
- debug("loiw", low, "high", high)
3707
3937
  chars.append(chr(high))
3708
3938
  chars.append(chr(low))
3709
3939
  # TODO: optimize by creating a LazyIntSymbolicStr directly
@@ -3732,6 +3962,9 @@ class SymbolicBytes(BytesLike):
3732
3962
  def __ch_pytype__(self):
3733
3963
  return bytes
3734
3964
 
3965
+ def __hash__(self):
3966
+ return deep_realize(self).__hash__()
3967
+
3735
3968
  def __repr__(self):
3736
3969
  # TODO: implement this preserving symbolics. These are the cases:
3737
3970
  # [9]: "\t"
@@ -3782,7 +4015,10 @@ class SymbolicBytes(BytesLike):
3782
4015
  accumulated = []
3783
4016
  high = None
3784
4017
  if not isinstance(hexstr, str):
3785
- raise TypeError
4018
+ if is_bytes_like(hexstr) and version_info >= (3, 14):
4019
+ hexstr = LazyIntSymbolicStr(tuple(hexstr))
4020
+ else:
4021
+ raise TypeError
3786
4022
  for idx, ch in enumerate(hexstr):
3787
4023
  if not ch.isascii():
3788
4024
  raise ValueError(
@@ -3870,7 +4106,7 @@ class SymbolicByteArray(BytesLike, ShellMutableSequence): # type: ignore
3870
4106
 
3871
4107
 
3872
4108
  class SymbolicMemoryView(BytesLike):
3873
- format = "B"
4109
+ format = "B" # type: ignore
3874
4110
  itemsize = 1
3875
4111
  ndim = 1
3876
4112
  strides = (1,)
@@ -3883,11 +4119,12 @@ class SymbolicMemoryView(BytesLike):
3883
4119
  assert not is_tracing()
3884
4120
  if not isinstance(obj, (_ALL_BYTES_TYPES, BytesLike)):
3885
4121
  raise TypeError
3886
- objlen = obj.__len__()
4122
+ with ResumedTracing():
4123
+ objlen = len(obj)
4124
+ self.readonly = isinstance(obj, bytes)
3887
4125
  self.obj = obj
3888
4126
  self.nbytes = objlen
3889
4127
  self.shape = (objlen,)
3890
- self.readonly = isinstance(obj, bytes)
3891
4128
  self._sliced = SliceView(obj, 0, objlen)
3892
4129
 
3893
4130
  def __ch_realize__(self):
@@ -3896,6 +4133,12 @@ class SymbolicMemoryView(BytesLike):
3896
4133
  self.obj = obj
3897
4134
  return memoryview(realize(obj))[realize(start) : realize(stop)]
3898
4135
 
4136
+ def __ch_deep_realize__(self, memo):
4137
+ sliced = self._sliced
4138
+ obj, start, stop = self.obj, sliced.start, sliced.stop
4139
+ self.obj = obj
4140
+ return memoryview(deep_realize(obj, memo))[realize(start) : realize(stop)]
4141
+
3899
4142
  def __ch_pytype__(self):
3900
4143
  return memoryview
3901
4144
 
@@ -3972,22 +4215,15 @@ class SymbolicMemoryView(BytesLike):
3972
4215
  return realize(self).cast(*map(realize, a))
3973
4216
 
3974
4217
 
3975
- _CACHED_TYPE_ENUMS: Dict[FrozenSet[type], z3.SortRef] = {}
3976
-
3977
-
3978
4218
  _PYTYPE_TO_WRAPPER_TYPE = {
3979
4219
  # These are mappings for AtomicSymbolic values - values that we directly represent
3980
4220
  # as single z3 values.
3981
- bool: (SymbolicBool,),
3982
- int: (SymbolicInt,),
3983
- float: (SymbolicFloat,),
3984
- type: (SymbolicType,),
4221
+ bool: ((SymbolicBool, 1.0),),
4222
+ int: ((SymbolicInt, 1.0),),
4223
+ float: ((RealBasedSymbolicFloat, 0.98), (PreciseIeeeSymbolicFloat, 0.02)),
4224
+ type: ((SymbolicType, 1.0),),
3985
4225
  }
3986
4226
 
3987
- _WRAPPER_TYPE_TO_PYTYPE = dict(
3988
- (v, k) for (k, vs) in _PYTYPE_TO_WRAPPER_TYPE.items() for v in vs
3989
- )
3990
-
3991
4227
 
3992
4228
  #
3993
4229
  # Symbolic-making helpers
@@ -4004,11 +4240,12 @@ def make_union_choice(creator: SymbolicFactory, *pytypes):
4004
4240
  return creator(pytypes[-1])
4005
4241
 
4006
4242
 
4007
- def make_optional_smt(smt_type):
4243
+ def make_concrete_or_symbolic(typ: type):
4008
4244
  def make(creator: SymbolicFactory, *type_args):
4245
+ nonlocal typ
4009
4246
  space = context_statespace()
4010
4247
  varname, pytype = creator.varname, creator.pytype
4011
- ret = smt_type(creator.varname, pytype)
4248
+ ret = typ(creator.varname, pytype)
4012
4249
 
4013
4250
  premature_stats, symbolic_stats = space.stats_lookahead()
4014
4251
  bad_iters = (
@@ -4052,11 +4289,36 @@ def make_dictionary(creator: SymbolicFactory, key_type=Any, value_type=Any):
4052
4289
  return ShellMutableMap(SymbolicDict(varname, creator.pytype))
4053
4290
 
4054
4291
 
4292
+ @assert_tracing(False)
4293
+ def make_float(varname: str, pytype: Type):
4294
+ if os.environ.get("CROSSHAIR_ONLY_FINITE_FLOATS") == "1":
4295
+ warnings.warn(
4296
+ "Support for CROSSHAIR_ONLY_FINITE_FLOATS will be removed in CrossHair v0.0.75",
4297
+ FutureWarning,
4298
+ )
4299
+ return RealBasedSymbolicFloat(varname, pytype)
4300
+ space = context_statespace()
4301
+ chosen_typ = space.extra(ModelingDirector).choose(float)
4302
+ if chosen_typ is RealBasedSymbolicFloat:
4303
+ if space.smt_fork(desc=f"{varname}_isfinite", probability_true=0.8):
4304
+ return RealBasedSymbolicFloat(varname, pytype)
4305
+ if space.smt_fork(desc=f"{varname}_isnan", probability_true=0.5):
4306
+ return nan
4307
+ if space.smt_fork(desc=f"{varname}_neginf", probability_true=0.25):
4308
+ return -inf
4309
+ return inf
4310
+ else:
4311
+ return chosen_typ(varname, pytype)
4312
+
4313
+
4055
4314
  def make_tuple(creator: SymbolicFactory, *type_args):
4056
4315
  if not type_args:
4057
4316
  type_args = (object, ...) # type: ignore
4058
4317
  if len(type_args) == 2 and type_args[1] == ...:
4059
4318
  return SymbolicUniformTuple(creator.varname, creator.pytype)
4319
+ elif len(type_args) == 1 and type_args[0] == ():
4320
+ # In python, the type for the empty tuple is written like Tuple[()]
4321
+ return ()
4060
4322
  else:
4061
4323
  return tuple(
4062
4324
  proxy_for_type(t, creator.varname + "_at_" + str(idx), allow_subtypes=True)
@@ -4197,36 +4459,38 @@ def _chr(i: int) -> Union[str, LazyIntSymbolicStr]:
4197
4459
  return chr(realize(i))
4198
4460
 
4199
4461
 
4200
- def _dict(arg=_MISSING) -> Union[dict, ShellMutableMap]:
4201
- if optional_context_statespace():
4202
- if isinstance(arg, dict):
4203
- return ShellMutableMap(arg)
4204
- elif arg is _MISSING:
4205
- return ShellMutableMap(SimpleDict([]))
4206
- elif not is_iterable(arg):
4207
- raise TypeError
4208
- else:
4209
- keys: List = []
4210
- key_compares: List = []
4211
- all_items: List = []
4212
- for pair in arg: # NOTE: `arg` can be an iterator; scan only once
4213
- if len(pair) != 2:
4214
- raise ValueError
4215
- (key, val) = pair
4216
- if not is_hashable(key):
4217
- raise ValueError
4218
- all_items.append(pair)
4219
- key_compares.extend(key == k for k in keys)
4220
- keys.append(key)
4221
- if not any(key_compares):
4222
- simpledict = SimpleDict(all_items)
4223
- else: # we have one or more key conflicts:
4224
- simpledict = SimpleDict([])
4225
- for key, val in reversed(all_items):
4226
- if key not in simpledict:
4227
- simpledict[key] = val
4228
- return ShellMutableMap(simpledict)
4229
- return dict() if arg is _MISSING else dict(arg)
4462
+ def _dict(arg=_MISSING, **kwargs) -> Union[dict, ShellMutableMap]:
4463
+ if not optional_context_statespace():
4464
+ newdict: Union[dict, ShellMutableMap] = dict() if arg is _MISSING else dict(arg)
4465
+ if isinstance(arg, Mapping):
4466
+ newdict = ShellMutableMap(SimpleDict(list(arg.items())))
4467
+ elif arg is _MISSING:
4468
+ newdict = ShellMutableMap(SimpleDict([]))
4469
+ elif is_iterable(arg):
4470
+ keys: List = []
4471
+ key_compares: List = []
4472
+ all_items: List = []
4473
+ for pair in arg: # NOTE: `arg` can be an iterator; scan only once
4474
+ if len(pair) != 2:
4475
+ raise ValueError
4476
+ (key, val) = pair
4477
+ if not is_hashable(key):
4478
+ raise ValueError
4479
+ all_items.append(pair)
4480
+ key_compares.extend(key == k for k in keys)
4481
+ keys.append(key)
4482
+ if not any(key_compares):
4483
+ simpledict = SimpleDict(all_items)
4484
+ else: # we have one or more key conflicts:
4485
+ simpledict = SimpleDict([])
4486
+ for key, val in reversed(all_items):
4487
+ if key not in simpledict:
4488
+ simpledict[key] = val
4489
+ newdict = ShellMutableMap(simpledict)
4490
+ else:
4491
+ raise TypeError
4492
+ newdict.update(kwargs)
4493
+ return newdict
4230
4494
 
4231
4495
 
4232
4496
  def _eval(expr: str, _globals=None, _locals=None) -> object:
@@ -4281,26 +4545,39 @@ def _hash(obj: Hashable) -> int:
4281
4545
  return invoke_dunder(obj, "__hash__")
4282
4546
 
4283
4547
 
4284
- def _int(val: object = 0, *a):
4548
+ def _int(val: Any = 0, base=_MISSING):
4285
4549
  with NoTracing():
4286
4550
  if isinstance(val, SymbolicInt):
4551
+ if base is not _MISSING:
4552
+ raise TypeError("int() can't convert non-string with explicit base")
4287
4553
  return val
4288
- if isinstance(val, AnySymbolicStr) and a == ():
4554
+ if isinstance(val, AnySymbolicStr):
4289
4555
  with ResumedTracing():
4290
- if not val:
4291
- return int(realize(val))
4556
+ if base is _MISSING:
4557
+ base = 10
4558
+ elif not hasattr(base, "__index__"):
4559
+ raise TypeError(
4560
+ f"{name_of_type(type(base))} object cannot be interpreted as an integer"
4561
+ )
4562
+ if any([base < 2, base > 10, not val]):
4563
+ # TODO: bses 11-36 are allowed, but require parsing the a-z and A-Z ranges.
4564
+ # TODO: base can be 0, which means to interpret the string as a literal e.g. '0b100'
4565
+ return int(realize(val), base=realize(base))
4292
4566
  ret = 0
4293
4567
  for ch in val:
4294
4568
  ch_num = ord(ch) - _ORD_OF_ZERO
4295
4569
  # Use `any()` to collapse symbolc conditions
4296
- if any((ch_num < 0, ch_num > 9)):
4570
+ if any((ch_num < 0, ch_num >= base)):
4297
4571
  # TODO parse other digits with data from unicodedata.decimal()
4298
4572
  return int(realize(val))
4299
4573
  else:
4300
- ret = (ret * 10) + ch_num
4574
+ ret = (ret * base) + ch_num
4301
4575
  return ret
4302
- # TODO: add symbolic handling when val is float (if possible)
4303
- return int(realize(val), *realize(a))
4576
+ elif isinstance(val, CrossHairValue):
4577
+ val = deep_realize(val)
4578
+ base = deep_realize(base)
4579
+
4580
+ return int(val) if base is _MISSING else int(val, base=base)
4304
4581
 
4305
4582
 
4306
4583
  _FLOAT_REGEX = re.compile(
@@ -4347,11 +4624,17 @@ def _float(val=0.0):
4347
4624
  ret = -ret
4348
4625
  return ret
4349
4626
  elif is_symbolic_int:
4350
- with NoTracing():
4351
- return SymbolicFloat(z3.ToReal(val.var))
4627
+ return val.__float__()
4352
4628
  return float(realize(val))
4353
4629
 
4354
4630
 
4631
+ def _frozenset(itr=()) -> Union[set, LinearSet]:
4632
+ if isinstance(itr, set):
4633
+ return LinearSet(itr)
4634
+ else:
4635
+ return LinearSet.check_unique_and_create(itr)
4636
+
4637
+
4355
4638
  # Trick the system into believing that symbolic values are
4356
4639
  # native types.
4357
4640
  def _issubclass(subclass, superclass):
@@ -4399,7 +4682,7 @@ def _len(ls):
4399
4682
  def _map(fn, *iters):
4400
4683
  # Wrap the `map` callback in a pure Python lambda.
4401
4684
  # This de-optimization ensures that the callback can be intercepted.
4402
- return map(lambda x: fn(x), *iters)
4685
+ return map(lambda *a: fn(*a), *iters)
4403
4686
 
4404
4687
 
4405
4688
  def _memoryview(source):
@@ -4415,15 +4698,12 @@ def _ord(c: str) -> int:
4415
4698
  with NoTracing():
4416
4699
  if isinstance(c, LazyIntSymbolicStr):
4417
4700
  return c._codepoints[0]
4418
- elif isinstance(c, SeqBasedSymbolicStr):
4419
- space = context_statespace()
4420
- ret = SymbolicInt("ord" + space.uniq())
4421
- space.add(c.var == z3.Unit(ret.var))
4422
- return ret
4423
4701
  return ord(realize(c))
4424
4702
 
4425
4703
 
4426
4704
  def _pow(base, exp, mod=None):
4705
+ # TODO: we should be able to loosen this up a little.
4706
+ # TODO: move this into the __pow__ definitions. (different smt vars will have different needs)
4427
4707
  return pow(realize(base), realize(exp), realize(mod))
4428
4708
 
4429
4709
 
@@ -4445,9 +4725,8 @@ def _repr(obj: object) -> str:
4445
4725
 
4446
4726
 
4447
4727
  def _set(itr=_MISSING) -> Union[set, ShellMutableSet]:
4448
- if optional_context_statespace():
4728
+ with NoTracing():
4449
4729
  return ShellMutableSet() if itr is _MISSING else ShellMutableSet(itr)
4450
- return set() if itr is _MISSING else set(itr)
4451
4730
 
4452
4731
 
4453
4732
  def _setattr(obj: object, name: str, value: object) -> None:
@@ -4494,7 +4773,7 @@ def _int_from_bytes(
4494
4773
  ) -> int:
4495
4774
  if byteorder is _MISSING:
4496
4775
  # byteorder defaults to "big" as of 3.11
4497
- if sys.version_info >= (3, 11):
4776
+ if version_info >= (3, 11):
4498
4777
  byteorder = "big"
4499
4778
  else:
4500
4779
  raise TypeError
@@ -4506,8 +4785,12 @@ def _int_from_bytes(
4506
4785
  little = True
4507
4786
  else:
4508
4787
  raise ValueError
4509
- if not is_iterable(b):
4510
- raise TypeError
4788
+ if not isinstance(b, Sized):
4789
+ if is_iterable(b):
4790
+ b = list(b)
4791
+ else:
4792
+ raise TypeError
4793
+
4511
4794
  byteitr: Iterable[int] = reversed(b) if little else b
4512
4795
  val = 0
4513
4796
  invert = None
@@ -4621,12 +4904,23 @@ def _str_join(self, itr) -> str:
4621
4904
  return _join(self, itr, self_type=str, item_type=str)
4622
4905
 
4623
4906
 
4624
- def _bytes_join(self, itr) -> str:
4625
- return _join(self, itr, self_type=bytes, item_type=collections.abc.ByteString)
4907
+ def _str_percent_format(self, other):
4908
+ # Almost nobody uses percent formatting anymore, so it's
4909
+ # probably OK to realize here.
4910
+ # TODO: However, collections.namedtuple still uses percent formatting to
4911
+ # do reprs, so we should consider handling the special case when there
4912
+ # are only "%r" substitutions.
4913
+ if not isinstance(self, str):
4914
+ raise TypeError
4915
+ return self.__mod__(deep_realize(other))
4916
+
4917
+
4918
+ def _bytes_join(self, itr) -> bytes:
4919
+ return _join(self, itr, self_type=bytes, item_type=Buffer)
4626
4920
 
4627
4921
 
4628
- def _bytearray_join(self, itr) -> str:
4629
- return _join(self, itr, self_type=bytearray, item_type=collections.abc.ByteString)
4922
+ def _bytearray_join(self, itr) -> bytes:
4923
+ return _join(self, itr, self_type=bytearray, item_type=Buffer)
4630
4924
 
4631
4925
 
4632
4926
  def _str_format(self, *a, **kw) -> Union[AnySymbolicStr, str]:
@@ -4640,14 +4934,17 @@ def _str_format_map(self, map) -> Union[AnySymbolicStr, str]:
4640
4934
 
4641
4935
 
4642
4936
  def _str_startswith(self, substr, start=None, end=None) -> bool:
4643
- if not isinstance(self, str):
4644
- raise TypeError
4645
4937
  with NoTracing():
4938
+ if isinstance(self, LazyIntSymbolicStr):
4939
+ with ResumedTracing():
4940
+ return self.startswith(substr, start, end)
4941
+ elif not isinstance(self, str):
4942
+ raise TypeError
4646
4943
  # Handle native values with native implementation:
4647
4944
  if type(substr) is str:
4648
4945
  return self.startswith(substr, start, end)
4649
4946
  if type(substr) is tuple:
4650
- if all(type(i) is str for i in substr):
4947
+ if all(type(s) is str for s in substr):
4651
4948
  return self.startswith(substr, start, end)
4652
4949
  symbolic_self = LazyIntSymbolicStr([ord(c) for c in self])
4653
4950
  return symbolic_self.startswith(substr, start, end)
@@ -4693,7 +4990,7 @@ def make_registrations():
4693
4990
 
4694
4991
  register_type(Union, make_union_choice)
4695
4992
 
4696
- if sys.version_info >= (3, 8):
4993
+ if version_info >= (3, 8):
4697
4994
  from typing import Final
4698
4995
 
4699
4996
  register_type(Final, lambda p, t: p(t))
@@ -4701,17 +4998,18 @@ def make_registrations():
4701
4998
  # Types modeled in the SMT solver:
4702
4999
 
4703
5000
  register_type(NoneType, lambda *a: None)
4704
- register_type(bool, make_optional_smt(SymbolicBool))
4705
- register_type(int, make_optional_smt(SymbolicInt))
4706
- register_type(float, make_optional_smt(SymbolicFloat))
4707
- register_type(str, make_optional_smt(LazyIntSymbolicStr))
4708
- register_type(list, make_optional_smt(SymbolicList))
5001
+ register_type(bool, make_concrete_or_symbolic(SymbolicBool))
5002
+ # register_type(int, make_concrete_or_symbolic(SymbolicInt))
5003
+ register_type(int, make_concrete_or_symbolic(SymbolicBoundedInt))
5004
+ register_type(float, make_concrete_or_symbolic(make_float))
5005
+ register_type(str, make_concrete_or_symbolic(LazyIntSymbolicStr))
5006
+ register_type(list, make_concrete_or_symbolic(SymbolicList))
4709
5007
  register_type(dict, make_dictionary)
4710
5008
  register_type(range, make_range)
4711
5009
  register_type(tuple, make_tuple)
4712
5010
  register_type(set, make_set)
4713
- register_type(frozenset, make_optional_smt(SymbolicFrozenSet))
4714
- register_type(type, make_optional_smt(SymbolicType))
5011
+ register_type(frozenset, make_concrete_or_symbolic(SymbolicFrozenSet))
5012
+ register_type(type, make_concrete_or_symbolic(SymbolicType))
4715
5013
  register_type(
4716
5014
  collections.abc.Callable,
4717
5015
  lambda p, *t: SymbolicCallable(p(List.__getitem__(t[1] if t else object))),
@@ -4743,8 +5041,8 @@ def make_registrations():
4743
5041
 
4744
5042
  register_type(NamedTuple, lambda p, *t: p(Tuple.__getitem__(tuple(t))))
4745
5043
 
4746
- register_type(re.Pattern, lambda p, t=None: p(re.compile)) # type: ignore
4747
- register_type(re.Match, lambda p, t=None: p(re.match)) # type: ignore
5044
+ register_type(re.Pattern, lambda p, t=None: re.compile(realize(p(str))))
5045
+ register_type(re.Match, make_raiser(CrosshairUnsupported))
4748
5046
 
4749
5047
  # Text: (elsewhere - identical to str)
4750
5048
  register_type(bytes, make_byte_string)
@@ -4762,7 +5060,7 @@ def make_registrations():
4762
5060
  register_type(SupportsFloat, lambda p: p(float))
4763
5061
  register_type(SupportsInt, lambda p: p(int))
4764
5062
  register_type(SupportsRound, lambda p: p(float))
4765
- register_type(SupportsBytes, lambda p: p(ByteString))
5063
+ register_type(SupportsBytes, lambda p: p(Buffer))
4766
5064
  register_type(SupportsComplex, lambda p: p(complex))
4767
5065
 
4768
5066
  # Patches
@@ -4797,6 +5095,7 @@ def make_registrations():
4797
5095
  register_patch(bytes, _bytes)
4798
5096
  register_patch(dict, _dict)
4799
5097
  register_patch(float, _float)
5098
+ register_patch(frozenset, _frozenset)
4800
5099
  register_patch(int, _int)
4801
5100
  register_patch(memoryview, _memoryview)
4802
5101
  register_patch(range, _range)
@@ -4850,7 +5149,7 @@ def make_registrations():
4850
5149
  "upper",
4851
5150
  "zfill",
4852
5151
  ]
4853
- if sys.version_info >= (3, 9):
5152
+ if version_info >= (3, 9):
4854
5153
  names_to_str_patch.append("removeprefix")
4855
5154
  names_to_str_patch.append("removesuffix")
4856
5155
  for name in names_to_str_patch:
@@ -4869,6 +5168,7 @@ def make_registrations():
4869
5168
  register_patch(str.__contains__, _str_contains)
4870
5169
  register_patch(str.join, _str_join)
4871
5170
  register_patch(str.__repr__, with_symbolic_self(LazyIntSymbolicStr, str.__repr__))
5171
+ register_patch(str.__mod__, _str_percent_format)
4872
5172
 
4873
5173
  # Patches on bytes
4874
5174
  register_patch(bytes.join, _bytes_join)
@@ -4879,28 +5179,48 @@ def make_registrations():
4879
5179
  register_patch(bytearray.fromhex, SymbolicByteArray.fromhex)
4880
5180
 
4881
5181
  # Patches on list
4882
- register_patch(list.index, _list_index)
5182
+ register_patch(list.__len__, with_checked_self(list, "__len__"))
4883
5183
  register_patch(list.__repr__, _list_repr)
5184
+ register_patch(list.copy, with_checked_self(list, "copy"))
5185
+ register_patch(list.index, _list_index)
5186
+ register_patch(list.pop, with_checked_self(list, "pop"))
4884
5187
 
4885
5188
  # Patches on dict
4886
- register_patch(dict.get, _dict_get)
5189
+ register_patch(dict.__len__, with_checked_self(dict, "__len__"))
4887
5190
  register_patch(dict.__repr__, _dict_repr)
5191
+ register_patch(dict.copy, with_checked_self(dict, "copy"))
5192
+ register_patch(dict.items, with_checked_self(dict, "items"))
5193
+ register_patch(dict.keys, with_checked_self(dict, "keys"))
4888
5194
  # TODO: dict.update (concrete w/ symbolic argument), __getitem__, & more?
5195
+ register_patch(dict.get, _dict_get)
5196
+ register_patch(dict.values, with_checked_self(dict, "values"))
4889
5197
 
4890
5198
  # Patches on set/frozenset
4891
5199
  register_patch(set.__repr__, _set_repr)
4892
5200
  register_patch(frozenset.__repr__, _frozenset_repr)
5201
+ register_patch(set.copy, with_checked_self(set, "copy"))
5202
+ register_patch(frozenset.copy, with_checked_self(frozenset, "copy"))
5203
+ register_patch(set.pop, with_checked_self(set, "pop"))
4893
5204
 
4894
5205
  # Patches on int
5206
+ register_patch(int.__repr__, with_checked_self(int, "__repr__"))
5207
+ register_patch(int.as_integer_ratio, with_checked_self(int, "as_integer_ratio"))
5208
+ if version_info >= (3, 10):
5209
+ register_patch(int.bit_count, with_checked_self(int, "bit_count"))
5210
+ register_patch(int.bit_length, with_checked_self(int, "bit_length"))
5211
+ register_patch(int.conjugate, with_checked_self(int, "conjugate"))
4895
5212
  register_patch(int.from_bytes, _int_from_bytes)
4896
- # We register int.__repr__ because the JSON serializer can call `int.__repr__(symbolic_int)`.
4897
- # In theory ALL special and regular methods of builtins should be overridden, but this would
4898
- # be costly. For now, we're just intercepting the important methods.
4899
- register_patch(int.__repr__, with_symbolic_self(SymbolicInt, int.__repr__))
5213
+ if version_info >= (3, 12):
5214
+ register_patch(int.is_integer, with_checked_self(int, "is_integer"))
5215
+ register_patch(int.to_bytes, with_checked_self(int, "to_bytes"))
4900
5216
 
4901
5217
  # Patches on float
5218
+ register_patch(float.__repr__, with_checked_self(float, "__repr__"))
4902
5219
  register_patch(float.fromhex, with_realized_args(float.fromhex))
4903
- register_patch(float.__repr__, with_symbolic_self(SymbolicFloat, float.__repr__))
5220
+ register_patch(float.as_integer_ratio, with_checked_self(float, "as_integer_ratio"))
5221
+ register_patch(float.conjugate, with_checked_self(float, "conjugate"))
5222
+ register_patch(float.hex, with_checked_self(float, "hex"))
5223
+ register_patch(float.is_integer, with_checked_self(float, "is_integer"))
4904
5224
 
4905
5225
  # Patches on tuples
4906
5226
  register_patch(tuple.__repr__, _tuple_repr)