crosshair-tool 0.0.95__cp311-cp311-win32.whl → 0.0.97__cp311-cp311-win32.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of crosshair-tool might be problematic. Click here for more details.
- _crosshair_tracers.cp311-win32.pyd +0 -0
- crosshair/__init__.py +1 -1
- crosshair/_tracers_test.py +5 -5
- crosshair/codeconfig.py +3 -2
- crosshair/condition_parser.py +1 -0
- crosshair/condition_parser_test.py +0 -2
- crosshair/core.py +8 -9
- crosshair/core_test.py +2 -3
- crosshair/diff_behavior_test.py +0 -2
- crosshair/dynamic_typing.py +3 -3
- crosshair/enforce.py +1 -0
- crosshair/examples/check_examples_test.py +1 -0
- crosshair/fnutil.py +2 -3
- crosshair/fnutil_test.py +1 -4
- crosshair/fuzz_core_test.py +9 -1
- crosshair/libimpl/arraylib.py +1 -1
- crosshair/libimpl/builtinslib.py +77 -24
- crosshair/libimpl/builtinslib_ch_test.py +15 -5
- crosshair/libimpl/builtinslib_test.py +38 -1
- crosshair/libimpl/collectionslib_test.py +4 -4
- crosshair/libimpl/datetimelib.py +1 -3
- crosshair/libimpl/datetimelib_ch_test.py +5 -5
- crosshair/libimpl/encodings/_encutil.py +11 -6
- crosshair/libimpl/functoolslib.py +8 -2
- crosshair/libimpl/functoolslib_test.py +22 -6
- crosshair/libimpl/relib.py +1 -1
- crosshair/libimpl/unicodedatalib_test.py +3 -3
- crosshair/main.py +5 -3
- crosshair/opcode_intercept.py +45 -17
- crosshair/path_cover.py +5 -1
- crosshair/pathing_oracle.py +40 -3
- crosshair/pathing_oracle_test.py +21 -0
- crosshair/register_contract.py +1 -0
- crosshair/register_contract_test.py +2 -4
- crosshair/simplestructs.py +10 -8
- crosshair/statespace.py +74 -19
- crosshair/statespace_test.py +16 -0
- crosshair/tools/generate_demo_table.py +2 -2
- crosshair/tracers.py +8 -6
- crosshair/util.py +6 -6
- {crosshair_tool-0.0.95.dist-info → crosshair_tool-0.0.97.dist-info}/METADATA +4 -5
- {crosshair_tool-0.0.95.dist-info → crosshair_tool-0.0.97.dist-info}/RECORD +46 -45
- {crosshair_tool-0.0.95.dist-info → crosshair_tool-0.0.97.dist-info}/WHEEL +0 -0
- {crosshair_tool-0.0.95.dist-info → crosshair_tool-0.0.97.dist-info}/entry_points.txt +0 -0
- {crosshair_tool-0.0.95.dist-info → crosshair_tool-0.0.97.dist-info}/licenses/LICENSE +0 -0
- {crosshair_tool-0.0.95.dist-info → crosshair_tool-0.0.97.dist-info}/top_level.txt +0 -0
|
Binary file
|
crosshair/__init__.py
CHANGED
|
@@ -15,7 +15,7 @@ from crosshair.statespace import StateSpace
|
|
|
15
15
|
from crosshair.tracers import NoTracing, ResumedTracing
|
|
16
16
|
from crosshair.util import IgnoreAttempt, debug
|
|
17
17
|
|
|
18
|
-
__version__ = "0.0.
|
|
18
|
+
__version__ = "0.0.97" # Do not forget to update in setup.py!
|
|
19
19
|
__author__ = "Phillip Schanely"
|
|
20
20
|
__license__ = "MIT"
|
|
21
21
|
__status__ = "Alpha"
|
crosshair/_tracers_test.py
CHANGED
|
@@ -19,19 +19,19 @@ class ExampleModule:
|
|
|
19
19
|
|
|
20
20
|
def test_CTracer_module_refcounts_dont_leak():
|
|
21
21
|
mod = ExampleModule()
|
|
22
|
-
|
|
22
|
+
base_count = sys.getrefcount(mod)
|
|
23
23
|
tracer = CTracer()
|
|
24
24
|
tracer.push_module(mod)
|
|
25
|
-
assert sys.getrefcount(mod) ==
|
|
25
|
+
assert sys.getrefcount(mod) == base_count + 1
|
|
26
26
|
tracer.push_module(mod)
|
|
27
27
|
tracer.start()
|
|
28
28
|
tracer.stop()
|
|
29
|
-
assert sys.getrefcount(mod) ==
|
|
29
|
+
assert sys.getrefcount(mod) == base_count + 2
|
|
30
30
|
tracer.pop_module(mod)
|
|
31
|
-
assert sys.getrefcount(mod) ==
|
|
31
|
+
assert sys.getrefcount(mod) == base_count + 1
|
|
32
32
|
del tracer
|
|
33
33
|
gc.collect()
|
|
34
|
-
assert sys.getrefcount(mod) ==
|
|
34
|
+
assert sys.getrefcount(mod) == base_count
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
def _get_depths(fn):
|
crosshair/codeconfig.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"""Configure analysis options at different levels."""
|
|
2
|
+
|
|
2
3
|
import importlib.resources
|
|
3
4
|
import inspect
|
|
4
5
|
import re
|
|
@@ -25,7 +26,7 @@ def get_directives(source_text: str) -> Iterable[Tuple[int, int, str]]:
|
|
|
25
26
|
ret = []
|
|
26
27
|
tokens = tokenize.generate_tokens(StringIO(source_text).readline)
|
|
27
28
|
# TODO catch tokenize.TokenError ... just in case?
|
|
28
|
-
for
|
|
29
|
+
for toktyp, tokval, begin, _, _ in tokens:
|
|
29
30
|
linenum, colnum = begin
|
|
30
31
|
if toktyp == tokenize.COMMENT:
|
|
31
32
|
directive = _COMMENT_TOKEN_RE.sub(r"\1", tokval)
|
|
@@ -39,7 +40,7 @@ class InvalidDirective(Exception):
|
|
|
39
40
|
|
|
40
41
|
|
|
41
42
|
def parse_directives(
|
|
42
|
-
directive_lines: Iterable[Tuple[int, int, str]]
|
|
43
|
+
directive_lines: Iterable[Tuple[int, int, str]],
|
|
43
44
|
) -> AnalysisOptionSet:
|
|
44
45
|
"""
|
|
45
46
|
Parse options from directives in comments.
|
crosshair/condition_parser.py
CHANGED
|
@@ -520,6 +520,7 @@ class ConcreteConditionParser(ConditionParser):
|
|
|
520
520
|
"__delattr__",
|
|
521
521
|
"__replace__", # Will raise an exception with most arbitrary **kwargs.
|
|
522
522
|
"__annotate__", # a staticmethod, but not isinstance(staticmethod)
|
|
523
|
+
"__annotate_func__",
|
|
523
524
|
):
|
|
524
525
|
pass
|
|
525
526
|
elif method_name == "__del__":
|
crosshair/core.py
CHANGED
|
@@ -377,7 +377,8 @@ def with_symbolic_self(symbolic_cls: Type, fn: Callable):
|
|
|
377
377
|
elif any(isinstance(a, CrossHairValue) for a in args) or (
|
|
378
378
|
kwargs and any(isinstance(a, CrossHairValue) for a in kwargs.values())
|
|
379
379
|
):
|
|
380
|
-
|
|
380
|
+
# NOTE: _ch_create_from_literal is suppoerted for very few types right now
|
|
381
|
+
self = symbolic_cls._ch_create_from_literal(self)
|
|
381
382
|
target_fn = getattr(symbolic_cls, fn.__name__)
|
|
382
383
|
else:
|
|
383
384
|
args = map(realize, args)
|
|
@@ -562,12 +563,12 @@ class SymbolicFactory:
|
|
|
562
563
|
@overload
|
|
563
564
|
def __call__(
|
|
564
565
|
self, typ: Callable[..., _T], suffix: str = "", allow_subtypes: bool = True
|
|
565
|
-
) -> _T:
|
|
566
|
-
...
|
|
566
|
+
) -> _T: ...
|
|
567
567
|
|
|
568
568
|
@overload
|
|
569
|
-
def __call__(
|
|
570
|
-
|
|
569
|
+
def __call__(
|
|
570
|
+
self, typ: Any, suffix: str = "", allow_subtypes: bool = True
|
|
571
|
+
) -> Any: ...
|
|
571
572
|
|
|
572
573
|
def __call__(self, typ, suffix: str = "", allow_subtypes: bool = True):
|
|
573
574
|
"""
|
|
@@ -652,8 +653,7 @@ def proxy_for_type(
|
|
|
652
653
|
typ: Callable[..., _T],
|
|
653
654
|
varname: str,
|
|
654
655
|
allow_subtypes: bool = False,
|
|
655
|
-
) -> _T:
|
|
656
|
-
...
|
|
656
|
+
) -> _T: ...
|
|
657
657
|
|
|
658
658
|
|
|
659
659
|
@overload
|
|
@@ -661,8 +661,7 @@ def proxy_for_type(
|
|
|
661
661
|
typ: Any,
|
|
662
662
|
varname: str,
|
|
663
663
|
allow_subtypes: bool = False,
|
|
664
|
-
) -> Any:
|
|
665
|
-
...
|
|
664
|
+
) -> Any: ...
|
|
666
665
|
|
|
667
666
|
|
|
668
667
|
def proxy_for_type(
|
crosshair/core_test.py
CHANGED
|
@@ -5,7 +5,6 @@ import re
|
|
|
5
5
|
import sys
|
|
6
6
|
import time
|
|
7
7
|
from typing import *
|
|
8
|
-
from unittest import skipIf
|
|
9
8
|
|
|
10
9
|
import pytest # type: ignore
|
|
11
10
|
|
|
@@ -736,7 +735,7 @@ def test_newtype() -> None:
|
|
|
736
735
|
assert isinstance(x, SymbolicInt)
|
|
737
736
|
|
|
738
737
|
|
|
739
|
-
@
|
|
738
|
+
@pytest.mark.skipif(sys.version_info < (3, 12), reason="type statements added in 3.12")
|
|
740
739
|
def test_type_statement() -> None:
|
|
741
740
|
env: dict[str, Any] = {}
|
|
742
741
|
exec("type MyIntNew = int\n", env)
|
|
@@ -747,7 +746,7 @@ def test_type_statement() -> None:
|
|
|
747
746
|
assert isinstance(x, SymbolicInt)
|
|
748
747
|
|
|
749
748
|
|
|
750
|
-
@
|
|
749
|
+
@pytest.mark.skipif(sys.version_info < (3, 12), reason="type statements added in 3.12")
|
|
751
750
|
def test_parameterized_type_statement() -> None:
|
|
752
751
|
env: dict[str, Any] = {}
|
|
753
752
|
exec("type Pair[A, B] = tuple[B, A]\n", env)
|
crosshair/diff_behavior_test.py
CHANGED
crosshair/dynamic_typing.py
CHANGED
|
@@ -59,7 +59,7 @@ def unify_callable_args(
|
|
|
59
59
|
return True
|
|
60
60
|
if len(value_types) != len(recv_types):
|
|
61
61
|
return False
|
|
62
|
-
for
|
|
62
|
+
for varg, rarg in zip(value_types, recv_types):
|
|
63
63
|
# note reversal here: Callable is contravariant in argument types
|
|
64
64
|
if not unify(rarg, varg, bindings):
|
|
65
65
|
return False
|
|
@@ -206,7 +206,7 @@ def unify(
|
|
|
206
206
|
vargs = [object for _ in rargs]
|
|
207
207
|
else:
|
|
208
208
|
return False
|
|
209
|
-
for
|
|
209
|
+
for varg, targ in zip(vargs, rargs):
|
|
210
210
|
if not unify(varg, targ, bindings):
|
|
211
211
|
return False
|
|
212
212
|
return True
|
|
@@ -302,7 +302,7 @@ def intersect_signatures(
|
|
|
302
302
|
is_squishy1 = var_pos1 is not None or var_key1 is not None
|
|
303
303
|
is_squishy2 = var_pos2 is not None or var_key2 is not None
|
|
304
304
|
out_params: Dict[str, Parameter] = {}
|
|
305
|
-
for
|
|
305
|
+
for p1, p2 in zip_longest(pos1, pos2):
|
|
306
306
|
if p1 is None:
|
|
307
307
|
if is_squishy1:
|
|
308
308
|
out_params[p2.name] = p2
|
crosshair/enforce.py
CHANGED
|
@@ -36,6 +36,7 @@ def WithEnforcement(fn: Callable) -> Callable:
|
|
|
36
36
|
Enforcement is normally disabled when calling from some internal files, for
|
|
37
37
|
performance reasons. Use WithEnforcement to ensure it is enabled anywhere.
|
|
38
38
|
"""
|
|
39
|
+
|
|
39
40
|
# This local function has a special name that we look for while tracing
|
|
40
41
|
# (see the wants_codeobj method below):
|
|
41
42
|
def _crosshair_with_enforcement(*a, **kw):
|
crosshair/fnutil.py
CHANGED
|
@@ -41,8 +41,7 @@ if sys.version_info >= (3, 8):
|
|
|
41
41
|
from typing import Protocol
|
|
42
42
|
|
|
43
43
|
class Descriptor(Protocol):
|
|
44
|
-
def __get__(self, instance: object, cls: type) -> Any:
|
|
45
|
-
...
|
|
44
|
+
def __get__(self, instance: object, cls: type) -> Any: ...
|
|
46
45
|
|
|
47
46
|
else:
|
|
48
47
|
Descriptor = Any
|
|
@@ -344,7 +343,7 @@ def walk_paths(paths: Iterable[Path], ignore_missing=False) -> Iterable[Path]:
|
|
|
344
343
|
else:
|
|
345
344
|
raise FileNotFoundError(str(path))
|
|
346
345
|
if path.is_dir():
|
|
347
|
-
for
|
|
346
|
+
for dirpath, _dirs, files in os.walk(str(path)):
|
|
348
347
|
for curfile in files:
|
|
349
348
|
if analyzable_filename(curfile):
|
|
350
349
|
yield Path(dirpath) / curfile
|
crosshair/fnutil_test.py
CHANGED
|
@@ -26,10 +26,7 @@ def test_fn_globals_on_builtin() -> None:
|
|
|
26
26
|
|
|
27
27
|
def test_resolve_signature_invalid_annotations() -> None:
|
|
28
28
|
sig = resolve_signature(with_invalid_type_annotation)
|
|
29
|
-
|
|
30
|
-
assert sig == "TypeThatIsNotDefined"
|
|
31
|
-
else:
|
|
32
|
-
assert sig == "name 'TypeThatIsNotDefined' is not defined"
|
|
29
|
+
assert sig == "name 'TypeThatIsNotDefined' is not defined"
|
|
33
30
|
|
|
34
31
|
|
|
35
32
|
@pytest.mark.skipif(
|
crosshair/fuzz_core_test.py
CHANGED
|
@@ -47,7 +47,7 @@ from crosshair.statespace import (
|
|
|
47
47
|
from crosshair.stubs_parser import signature_from_stubs
|
|
48
48
|
from crosshair.test_util import flexible_equal
|
|
49
49
|
from crosshair.tracers import COMPOSITE_TRACER, NoTracing, ResumedTracing
|
|
50
|
-
from crosshair.util import CrosshairUnsupported, debug, type_args_of
|
|
50
|
+
from crosshair.util import CrosshairUnsupported, debug, is_iterable, type_args_of
|
|
51
51
|
|
|
52
52
|
FUZZ_SEED = 1348
|
|
53
53
|
|
|
@@ -267,6 +267,14 @@ class FuzzTester:
|
|
|
267
267
|
) -> object:
|
|
268
268
|
for name in typed_args.keys():
|
|
269
269
|
literal, symbolic = literal_args[name], symbolic_args[name]
|
|
270
|
+
with NoTracing():
|
|
271
|
+
# TODO: transition into general purpose SMT expr extractor
|
|
272
|
+
# for equality with constant
|
|
273
|
+
if hasattr(symbolic, "_smt_promote_literal"):
|
|
274
|
+
symbolic.var = symbolic._smt_promote_literal(literal) # type: ignore
|
|
275
|
+
elif is_iterable(literal) and is_iterable(symbolic):
|
|
276
|
+
with ResumedTracing():
|
|
277
|
+
space.add(len(literal) == len(symbolic)) # type: ignore
|
|
270
278
|
if literal != symbolic:
|
|
271
279
|
raise IgnoreAttempt(
|
|
272
280
|
f'symbolic "{name}" not equal to literal "{name}"'
|
crosshair/libimpl/arraylib.py
CHANGED
|
@@ -32,7 +32,7 @@ INT_TYPE_SIZE = {c: array(c).itemsize for c in INT_TYPE_BOUNDS.keys()}
|
|
|
32
32
|
|
|
33
33
|
def pick_code(space: StateSpace) -> Tuple[str, int, int]:
|
|
34
34
|
last_idx = len(INT_TYPE_BOUNDS) - 1
|
|
35
|
-
for
|
|
35
|
+
for idx, (code, rng) in enumerate(INT_TYPE_BOUNDS.items()):
|
|
36
36
|
if idx < last_idx:
|
|
37
37
|
if space.smt_fork(desc=f"not_{code}_array"):
|
|
38
38
|
continue
|
crosshair/libimpl/builtinslib.py
CHANGED
|
@@ -189,6 +189,7 @@ def smt_not(x: object) -> Union[bool, "SymbolicBool"]:
|
|
|
189
189
|
|
|
190
190
|
_NONHEAP_PYTYPES = set([int, float, bool, NoneType, complex])
|
|
191
191
|
|
|
192
|
+
|
|
192
193
|
# TODO: isn't this pretty close to isinstance(typ, AtomicSymbolicValue)?
|
|
193
194
|
def pytype_uses_heap(typ: Type) -> bool:
|
|
194
195
|
return not (typ in _NONHEAP_PYTYPES)
|
|
@@ -346,7 +347,7 @@ class AtomicSymbolicValue(SymbolicValue):
|
|
|
346
347
|
raise CrossHairInternal(f"_pytype not implemented in {cls}")
|
|
347
348
|
|
|
348
349
|
@classmethod
|
|
349
|
-
def _smt_promote_literal(cls,
|
|
350
|
+
def _smt_promote_literal(cls, literal: object) -> Optional[z3.ExprRef]:
|
|
350
351
|
raise CrossHairInternal(f"_smt_promote_literal not implemented in {cls}")
|
|
351
352
|
|
|
352
353
|
@classmethod
|
|
@@ -900,6 +901,7 @@ def setup_binops():
|
|
|
900
901
|
if isinstance(a, FiniteFloat)
|
|
901
902
|
else a.var
|
|
902
903
|
)
|
|
904
|
+
assert smt_a is not None
|
|
903
905
|
if isinstance(b, NonFiniteFloat):
|
|
904
906
|
if isnan(b.val):
|
|
905
907
|
return (nan, nan)
|
|
@@ -912,6 +914,7 @@ def setup_binops():
|
|
|
912
914
|
return (-1.0, b.val)
|
|
913
915
|
else:
|
|
914
916
|
return (0.0, a.val if isinstance(a, FiniteFloat) else a)
|
|
917
|
+
assert smt_b is not None
|
|
915
918
|
|
|
916
919
|
remainder = z3.Real(f"remainder{space.uniq()}")
|
|
917
920
|
modproduct = z3.Int(f"modproduct{space.uniq()}")
|
|
@@ -1120,7 +1123,7 @@ class SymbolicBool(SymbolicIntable, AtomicSymbolicValue):
|
|
|
1120
1123
|
return bool
|
|
1121
1124
|
|
|
1122
1125
|
@classmethod
|
|
1123
|
-
def _smt_promote_literal(cls, literal) -> Optional[z3.
|
|
1126
|
+
def _smt_promote_literal(cls, literal) -> Optional[z3.ExprRef]:
|
|
1124
1127
|
if isinstance(literal, bool):
|
|
1125
1128
|
return z3.BoolVal(literal)
|
|
1126
1129
|
return None
|
|
@@ -1189,7 +1192,7 @@ class SymbolicInt(SymbolicIntable, AtomicSymbolicValue):
|
|
|
1189
1192
|
return int
|
|
1190
1193
|
|
|
1191
1194
|
@classmethod
|
|
1192
|
-
def _smt_promote_literal(cls, literal) -> Optional[z3.
|
|
1195
|
+
def _smt_promote_literal(cls, literal) -> Optional[z3.ExprRef]:
|
|
1193
1196
|
if isinstance(literal, int):
|
|
1194
1197
|
return z3IntVal(literal)
|
|
1195
1198
|
return None
|
|
@@ -1410,7 +1413,7 @@ class PreciseIeeeSymbolicFloat(SymbolicFloat):
|
|
|
1410
1413
|
return _PRECISE_IEEE_FLOAT_SORT
|
|
1411
1414
|
|
|
1412
1415
|
@classmethod
|
|
1413
|
-
def _smt_promote_literal(cls, literal) -> Optional[z3.
|
|
1416
|
+
def _smt_promote_literal(cls, literal) -> Optional[z3.ExprRef]:
|
|
1414
1417
|
if isinstance(literal, float):
|
|
1415
1418
|
return z3.FPVal(literal, cls._ch_smt_sort())
|
|
1416
1419
|
return None
|
|
@@ -1533,7 +1536,7 @@ class RealBasedSymbolicFloat(SymbolicFloat):
|
|
|
1533
1536
|
return z3.RealSort()
|
|
1534
1537
|
|
|
1535
1538
|
@classmethod
|
|
1536
|
-
def _smt_promote_literal(cls, literal) -> Optional[z3.
|
|
1539
|
+
def _smt_promote_literal(cls, literal) -> Optional[z3.ExprRef]:
|
|
1537
1540
|
if isinstance(literal, float) and isfinite(literal):
|
|
1538
1541
|
return z3.RealVal(literal)
|
|
1539
1542
|
return None
|
|
@@ -1591,6 +1594,7 @@ class RealBasedSymbolicFloat(SymbolicFloat):
|
|
|
1591
1594
|
denominator = SymbolicInt("denominator" + space.uniq())
|
|
1592
1595
|
space.add(denominator.var > 0)
|
|
1593
1596
|
space.add(numerator.var == denominator.var * self.var)
|
|
1597
|
+
|
|
1594
1598
|
# There are many valid integer ratios to return. Experimentally, both
|
|
1595
1599
|
# z3 and CPython tend to pick the same ones. But verify this, while
|
|
1596
1600
|
# deferring materialization:
|
|
@@ -2330,7 +2334,7 @@ class SymbolicRange:
|
|
|
2330
2334
|
return False
|
|
2331
2335
|
if len(self) != len(other):
|
|
2332
2336
|
return False
|
|
2333
|
-
for
|
|
2337
|
+
for v1, v2 in zip(self, other):
|
|
2334
2338
|
if v1 != v2:
|
|
2335
2339
|
return False
|
|
2336
2340
|
return True
|
|
@@ -2447,7 +2451,7 @@ class SymbolicType(AtomicSymbolicValue, SymbolicValue, Untracable):
|
|
|
2447
2451
|
return type
|
|
2448
2452
|
|
|
2449
2453
|
@classmethod
|
|
2450
|
-
def _smt_promote_literal(cls, literal) -> Optional[z3.
|
|
2454
|
+
def _smt_promote_literal(cls, literal) -> Optional[z3.ExprRef]:
|
|
2451
2455
|
if isinstance(literal, type):
|
|
2452
2456
|
return context_statespace().extra(SymbolicTypeRepository).get_type(literal)
|
|
2453
2457
|
return None
|
|
@@ -2668,10 +2672,25 @@ class SymbolicCallable:
|
|
|
2668
2672
|
__annotations__: dict = {}
|
|
2669
2673
|
|
|
2670
2674
|
def __init__(self, values: list):
|
|
2675
|
+
"""
|
|
2676
|
+
A function that will ignore its arguments and produce return values
|
|
2677
|
+
from the list given.
|
|
2678
|
+
If the given list is exhausted, the function will just repeatedly
|
|
2679
|
+
return the final value in the list.
|
|
2680
|
+
|
|
2681
|
+
If `values` is concrete, it must be non-mepty.
|
|
2682
|
+
If `values` is a symbolic list, it will be forced to be non-empty
|
|
2683
|
+
(the caller must enure that's possible).
|
|
2684
|
+
"""
|
|
2671
2685
|
assert not is_tracing()
|
|
2672
2686
|
with ResumedTracing():
|
|
2673
|
-
|
|
2674
|
-
|
|
2687
|
+
has_values = len(values) > 0
|
|
2688
|
+
if isinstance(values, CrossHairValue):
|
|
2689
|
+
space = context_statespace()
|
|
2690
|
+
assert space.is_possible(has_values)
|
|
2691
|
+
space.add(has_values)
|
|
2692
|
+
else:
|
|
2693
|
+
assert has_values
|
|
2675
2694
|
self.values = values
|
|
2676
2695
|
self.idx = 0
|
|
2677
2696
|
|
|
@@ -2695,6 +2714,7 @@ class SymbolicCallable:
|
|
|
2695
2714
|
if idx >= len(values):
|
|
2696
2715
|
return values[-1]
|
|
2697
2716
|
else:
|
|
2717
|
+
self.idx += 1
|
|
2698
2718
|
return values[idx]
|
|
2699
2719
|
|
|
2700
2720
|
def __bool__(self):
|
|
@@ -2767,7 +2787,7 @@ class SymbolicBoundedIntTuple(collections.abc.Sequence):
|
|
|
2767
2787
|
with NoTracing():
|
|
2768
2788
|
self._create_up_to(realize(otherlen))
|
|
2769
2789
|
constraints = []
|
|
2770
|
-
for
|
|
2790
|
+
for int1, int2 in zip(self._created_vars, tracing_iter(other)):
|
|
2771
2791
|
smtint2 = force_to_smt_sort(int2, SymbolicInt)
|
|
2772
2792
|
constraints.append(int1.var == smtint2)
|
|
2773
2793
|
return SymbolicBool(z3.And(*constraints))
|
|
@@ -2894,7 +2914,7 @@ class AnySymbolicStr(AbcString):
|
|
|
2894
2914
|
raise TypeError
|
|
2895
2915
|
if self == other:
|
|
2896
2916
|
return True if op in (ops.le, ops.ge) else False
|
|
2897
|
-
for
|
|
2917
|
+
for mych, otherch in zip_longest(iter(self), iter(other)):
|
|
2898
2918
|
if mych == otherch:
|
|
2899
2919
|
continue
|
|
2900
2920
|
if mych is None:
|
|
@@ -3156,7 +3176,7 @@ class AnySymbolicStr(AbcString):
|
|
|
3156
3176
|
|
|
3157
3177
|
else:
|
|
3158
3178
|
raise TypeError
|
|
3159
|
-
for
|
|
3179
|
+
for idx, ch in enumerate(self):
|
|
3160
3180
|
if not filter(ch):
|
|
3161
3181
|
return self[idx:]
|
|
3162
3182
|
return ""
|
|
@@ -3168,7 +3188,7 @@ class AnySymbolicStr(AbcString):
|
|
|
3168
3188
|
mylen = self.__len__()
|
|
3169
3189
|
if mylen == 0:
|
|
3170
3190
|
return []
|
|
3171
|
-
for
|
|
3191
|
+
for idx, ch in enumerate(self):
|
|
3172
3192
|
codepoint = ord(ch)
|
|
3173
3193
|
with NoTracing():
|
|
3174
3194
|
space = context_statespace()
|
|
@@ -3406,6 +3426,26 @@ class AnySymbolicStr(AbcString):
|
|
|
3406
3426
|
return "0" * fill_length + self
|
|
3407
3427
|
|
|
3408
3428
|
|
|
3429
|
+
def _unfindable_range(start: Optional[int], end: Optional[int], mylen: int) -> bool:
|
|
3430
|
+
"""
|
|
3431
|
+
Emulates some preliminary checks that CPython makes before searching
|
|
3432
|
+
for substrings within some bounds. (in e.g. str.find, str.startswith, etc)
|
|
3433
|
+
"""
|
|
3434
|
+
if start is None or start == 0 or start <= -mylen:
|
|
3435
|
+
return False
|
|
3436
|
+
|
|
3437
|
+
# At this point, we know that `start` is defined and points to an index after 0
|
|
3438
|
+
if end is None or end >= mylen:
|
|
3439
|
+
return start > mylen
|
|
3440
|
+
|
|
3441
|
+
# At this point, we know that `end` is defined and points to an index before the end of the string
|
|
3442
|
+
if start < 0:
|
|
3443
|
+
start += mylen
|
|
3444
|
+
if end < 0:
|
|
3445
|
+
end += mylen
|
|
3446
|
+
return end < start
|
|
3447
|
+
|
|
3448
|
+
|
|
3409
3449
|
class LazyIntSymbolicStr(AnySymbolicStr, CrossHairValue):
|
|
3410
3450
|
"""
|
|
3411
3451
|
A symbolic string that lazily generates SymbolicInt-based characters as needed.
|
|
@@ -3444,10 +3484,8 @@ class LazyIntSymbolicStr(AnySymbolicStr, CrossHairValue):
|
|
|
3444
3484
|
codepoints = tuple(self._codepoints)
|
|
3445
3485
|
return "".join(chr(realize(x)) for x in codepoints)
|
|
3446
3486
|
|
|
3447
|
-
# This is normally an AtomicSymbolicValue method, but sometimes it's used in a
|
|
3448
|
-
# duck-typing way.
|
|
3449
3487
|
@classmethod
|
|
3450
|
-
def
|
|
3488
|
+
def _ch_create_from_literal(cls, val: object) -> Optional[CrossHairValue]:
|
|
3451
3489
|
if isinstance(val, str):
|
|
3452
3490
|
return LazyIntSymbolicStr(list(map(ord, val)))
|
|
3453
3491
|
return None
|
|
@@ -3481,6 +3519,10 @@ class LazyIntSymbolicStr(AnySymbolicStr, CrossHairValue):
|
|
|
3481
3519
|
with NoTracing():
|
|
3482
3520
|
if not isinstance(i, (Integral, slice)):
|
|
3483
3521
|
raise TypeError(type(i))
|
|
3522
|
+
# This could/should? be symbolic by naming all the possibilities.
|
|
3523
|
+
# Note the slice case still must realize the return length.
|
|
3524
|
+
# Especially because we no longer explore realization trees except
|
|
3525
|
+
# as a last resort.
|
|
3484
3526
|
i = deep_realize(i)
|
|
3485
3527
|
with ResumedTracing():
|
|
3486
3528
|
newcontents = self._codepoints[i]
|
|
@@ -3561,11 +3603,15 @@ class LazyIntSymbolicStr(AnySymbolicStr, CrossHairValue):
|
|
|
3561
3603
|
return any(self.endswith(s, start, end) for s in substr)
|
|
3562
3604
|
if not isinstance(substr, str):
|
|
3563
3605
|
raise TypeError
|
|
3606
|
+
substrlen = len(substr)
|
|
3564
3607
|
if start is None and end is None:
|
|
3565
3608
|
matchable = self
|
|
3566
3609
|
else:
|
|
3567
3610
|
matchable = self[start:end]
|
|
3568
|
-
|
|
3611
|
+
if substrlen == 0:
|
|
3612
|
+
return not _unfindable_range(start, end, len(self))
|
|
3613
|
+
else:
|
|
3614
|
+
return matchable[-substrlen:] == substr
|
|
3569
3615
|
|
|
3570
3616
|
def startswith(self, substr, start=None, end=None):
|
|
3571
3617
|
if isinstance(substr, tuple):
|
|
@@ -3575,6 +3621,10 @@ class LazyIntSymbolicStr(AnySymbolicStr, CrossHairValue):
|
|
|
3575
3621
|
if start is None and end is None:
|
|
3576
3622
|
matchable = self
|
|
3577
3623
|
else:
|
|
3624
|
+
# Wacky special case: the empty string is findable off the left
|
|
3625
|
+
# side but not the right!
|
|
3626
|
+
if _unfindable_range(start, end, len(self)):
|
|
3627
|
+
return False
|
|
3578
3628
|
matchable = self[start:end]
|
|
3579
3629
|
return matchable[: len(substr)] == substr
|
|
3580
3630
|
|
|
@@ -3615,7 +3665,7 @@ class LazyIntSymbolicStr(AnySymbolicStr, CrossHairValue):
|
|
|
3615
3665
|
end += mylen
|
|
3616
3666
|
matchstr = self[start:end] if start != 0 or end is not mylen else self
|
|
3617
3667
|
if len(substr) == 0:
|
|
3618
|
-
#
|
|
3668
|
+
# An oddity of CPython. We can find the empty string when over-slicing
|
|
3619
3669
|
# off the left side of the string, but not off the right:
|
|
3620
3670
|
# ''.find('', 3, 4) == -1
|
|
3621
3671
|
# ''.find('', -4, -3) == 0
|
|
@@ -3962,7 +4012,7 @@ class SymbolicByteArray(BytesLike, ShellMutableSequence): # type: ignore
|
|
|
3962
4012
|
|
|
3963
4013
|
|
|
3964
4014
|
class SymbolicMemoryView(BytesLike):
|
|
3965
|
-
format = "B"
|
|
4015
|
+
format = "B" # type: ignore
|
|
3966
4016
|
itemsize = 1
|
|
3967
4017
|
ndim = 1
|
|
3968
4018
|
strides = (1,)
|
|
@@ -4771,11 +4821,11 @@ def _str_percent_format(self, other):
|
|
|
4771
4821
|
return self.__mod__(deep_realize(other))
|
|
4772
4822
|
|
|
4773
4823
|
|
|
4774
|
-
def _bytes_join(self, itr) ->
|
|
4824
|
+
def _bytes_join(self, itr) -> bytes:
|
|
4775
4825
|
return _join(self, itr, self_type=bytes, item_type=Buffer)
|
|
4776
4826
|
|
|
4777
4827
|
|
|
4778
|
-
def _bytearray_join(self, itr) ->
|
|
4828
|
+
def _bytearray_join(self, itr) -> bytes:
|
|
4779
4829
|
return _join(self, itr, self_type=bytearray, item_type=Buffer)
|
|
4780
4830
|
|
|
4781
4831
|
|
|
@@ -4790,14 +4840,17 @@ def _str_format_map(self, map) -> Union[AnySymbolicStr, str]:
|
|
|
4790
4840
|
|
|
4791
4841
|
|
|
4792
4842
|
def _str_startswith(self, substr, start=None, end=None) -> bool:
|
|
4793
|
-
if not isinstance(self, str):
|
|
4794
|
-
raise TypeError
|
|
4795
4843
|
with NoTracing():
|
|
4844
|
+
if isinstance(self, LazyIntSymbolicStr):
|
|
4845
|
+
with ResumedTracing():
|
|
4846
|
+
return self.startswith(substr, start, end)
|
|
4847
|
+
elif not isinstance(self, str):
|
|
4848
|
+
raise TypeError
|
|
4796
4849
|
# Handle native values with native implementation:
|
|
4797
4850
|
if type(substr) is str:
|
|
4798
4851
|
return self.startswith(substr, start, end)
|
|
4799
4852
|
if type(substr) is tuple:
|
|
4800
|
-
if all(type(
|
|
4853
|
+
if all(type(s) is str for s in substr):
|
|
4801
4854
|
return self.startswith(substr, start, end)
|
|
4802
4855
|
symbolic_self = LazyIntSymbolicStr([ord(c) for c in self])
|
|
4803
4856
|
return symbolic_self.startswith(substr, start, end)
|
|
@@ -178,7 +178,7 @@ def check_iter(obj: Union[str, List[int], Dict[int, int]]) -> ResultComparison:
|
|
|
178
178
|
|
|
179
179
|
|
|
180
180
|
def check_len(
|
|
181
|
-
s: Union[Dict[int, int], Tuple[int, ...], str, List[int], Set[int]]
|
|
181
|
+
s: Union[Dict[int, int], Tuple[int, ...], str, List[int], Set[int]],
|
|
182
182
|
) -> ResultComparison:
|
|
183
183
|
"""post: _"""
|
|
184
184
|
return compare_results(len, s)
|
|
@@ -606,9 +606,14 @@ def check_str_endswith(
|
|
|
606
606
|
string: str, suffix: str, start: Optional[int], end: Optional[int]
|
|
607
607
|
) -> ResultComparison:
|
|
608
608
|
"""post: _"""
|
|
609
|
+
# crosshair: max_uninteresting_iterations=100
|
|
610
|
+
|
|
611
|
+
for i in (len(string), len(suffix), start, end):
|
|
612
|
+
if i is not None and abs(i) >= 1:
|
|
613
|
+
pass
|
|
614
|
+
|
|
609
615
|
return compare_results(
|
|
610
|
-
lambda s, *a: s.endswith(*a),
|
|
611
|
-
string,
|
|
616
|
+
lambda s, *a, **kw: s.endswith(*a, **kw), string, suffix, start, end
|
|
612
617
|
)
|
|
613
618
|
|
|
614
619
|
|
|
@@ -825,6 +830,11 @@ def check_str_startswith(
|
|
|
825
830
|
end: Optional[int],
|
|
826
831
|
) -> ResultComparison:
|
|
827
832
|
"""post: _"""
|
|
833
|
+
# crosshair: max_uninteresting_iterations=100
|
|
834
|
+
|
|
835
|
+
for i in (len(string), len(prefix), start, end):
|
|
836
|
+
if i is not None and abs(i) >= 1:
|
|
837
|
+
pass
|
|
828
838
|
return compare_results(
|
|
829
839
|
lambda s, *a, **kw: s.startswith(*a, **kw), string, prefix, start, end
|
|
830
840
|
)
|
|
@@ -974,7 +984,7 @@ def check_bytes___init__(source: Union[int, List[int], bytes, bytearray, memoryv
|
|
|
974
984
|
|
|
975
985
|
|
|
976
986
|
def check_bytearray___init__(
|
|
977
|
-
source: Union[int, List[int], bytes, bytearray, memoryview]
|
|
987
|
+
source: Union[int, List[int], bytes, bytearray, memoryview],
|
|
978
988
|
):
|
|
979
989
|
"""
|
|
980
990
|
post: _
|
|
@@ -984,7 +994,7 @@ def check_bytearray___init__(
|
|
|
984
994
|
|
|
985
995
|
|
|
986
996
|
def check_memoryview___init__(
|
|
987
|
-
source: Union[int, List[int], bytes, bytearray, memoryview]
|
|
997
|
+
source: Union[int, List[int], bytes, bytearray, memoryview],
|
|
988
998
|
):
|
|
989
999
|
"""
|
|
990
1000
|
post: _
|