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.
- _crosshair_tracers.cpython-39-darwin.so +0 -0
- crosshair/__init__.py +1 -1
- crosshair/_mark_stacks.h +51 -24
- crosshair/_tracers.h +9 -5
- crosshair/_tracers_test.py +19 -9
- crosshair/auditwall.py +9 -8
- crosshair/auditwall_test.py +31 -19
- crosshair/codeconfig.py +3 -2
- crosshair/condition_parser.py +17 -133
- crosshair/condition_parser_test.py +54 -96
- crosshair/conftest.py +1 -1
- crosshair/copyext.py +91 -22
- crosshair/copyext_test.py +33 -0
- crosshair/core.py +259 -203
- crosshair/core_and_libs.py +20 -0
- crosshair/core_regestered_types_test.py +82 -0
- crosshair/core_test.py +693 -664
- crosshair/diff_behavior.py +76 -21
- crosshair/diff_behavior_test.py +132 -23
- crosshair/dynamic_typing.py +128 -18
- crosshair/dynamic_typing_test.py +91 -4
- crosshair/enforce.py +1 -6
- crosshair/enforce_test.py +15 -23
- crosshair/examples/check_examples_test.py +2 -1
- crosshair/fnutil.py +2 -3
- crosshair/fnutil_test.py +0 -7
- crosshair/fuzz_core_test.py +70 -83
- crosshair/libimpl/arraylib.py +10 -7
- crosshair/libimpl/binascii_ch_test.py +30 -0
- crosshair/libimpl/binascii_test.py +67 -0
- crosshair/libimpl/binasciilib.py +150 -0
- crosshair/libimpl/bisectlib_test.py +5 -5
- crosshair/libimpl/builtinslib.py +1002 -682
- crosshair/libimpl/builtinslib_ch_test.py +108 -30
- crosshair/libimpl/builtinslib_test.py +431 -143
- crosshair/libimpl/codecslib.py +22 -2
- crosshair/libimpl/codecslib_test.py +41 -9
- crosshair/libimpl/collectionslib.py +44 -8
- crosshair/libimpl/collectionslib_test.py +108 -20
- crosshair/libimpl/copylib.py +1 -1
- crosshair/libimpl/copylib_test.py +18 -0
- crosshair/libimpl/datetimelib.py +84 -67
- crosshair/libimpl/datetimelib_ch_test.py +12 -7
- crosshair/libimpl/datetimelib_test.py +5 -6
- crosshair/libimpl/decimallib.py +5257 -0
- crosshair/libimpl/decimallib_ch_test.py +78 -0
- crosshair/libimpl/decimallib_test.py +76 -0
- crosshair/libimpl/encodings/_encutil.py +21 -11
- crosshair/libimpl/fractionlib.py +16 -0
- crosshair/libimpl/fractionlib_test.py +80 -0
- crosshair/libimpl/functoolslib.py +19 -7
- crosshair/libimpl/functoolslib_test.py +22 -6
- crosshair/libimpl/hashliblib.py +30 -0
- crosshair/libimpl/hashliblib_test.py +18 -0
- crosshair/libimpl/heapqlib.py +32 -5
- crosshair/libimpl/heapqlib_test.py +15 -12
- crosshair/libimpl/iolib.py +7 -4
- crosshair/libimpl/ipaddresslib.py +8 -0
- crosshair/libimpl/itertoolslib_test.py +1 -1
- crosshair/libimpl/mathlib.py +165 -2
- crosshair/libimpl/mathlib_ch_test.py +44 -0
- crosshair/libimpl/mathlib_test.py +59 -16
- crosshair/libimpl/oslib.py +7 -0
- crosshair/libimpl/pathliblib_test.py +10 -0
- crosshair/libimpl/randomlib.py +1 -0
- crosshair/libimpl/randomlib_test.py +6 -4
- crosshair/libimpl/relib.py +180 -59
- crosshair/libimpl/relib_ch_test.py +26 -2
- crosshair/libimpl/relib_test.py +77 -14
- crosshair/libimpl/timelib.py +35 -13
- crosshair/libimpl/timelib_test.py +13 -3
- crosshair/libimpl/typeslib.py +15 -0
- crosshair/libimpl/typeslib_test.py +36 -0
- crosshair/libimpl/unicodedatalib_test.py +3 -3
- crosshair/libimpl/weakreflib.py +13 -0
- crosshair/libimpl/weakreflib_test.py +69 -0
- crosshair/libimpl/zliblib.py +15 -0
- crosshair/libimpl/zliblib_test.py +13 -0
- crosshair/lsp_server.py +21 -10
- crosshair/main.py +48 -28
- crosshair/main_test.py +59 -14
- crosshair/objectproxy.py +39 -14
- crosshair/objectproxy_test.py +27 -13
- crosshair/opcode_intercept.py +212 -24
- crosshair/opcode_intercept_test.py +172 -18
- crosshair/options.py +0 -1
- crosshair/patch_equivalence_test.py +5 -21
- crosshair/path_cover.py +7 -5
- crosshair/path_search.py +6 -4
- crosshair/path_search_test.py +1 -2
- crosshair/pathing_oracle.py +53 -10
- crosshair/pathing_oracle_test.py +21 -0
- crosshair/pure_importer_test.py +5 -21
- crosshair/register_contract.py +16 -6
- crosshair/register_contract_test.py +2 -14
- crosshair/simplestructs.py +154 -85
- crosshair/simplestructs_test.py +16 -2
- crosshair/smtlib.py +24 -0
- crosshair/smtlib_test.py +14 -0
- crosshair/statespace.py +319 -196
- crosshair/statespace_test.py +45 -0
- crosshair/stubs_parser.py +0 -2
- crosshair/test_util.py +87 -25
- crosshair/test_util_test.py +26 -0
- crosshair/tools/check_init_and_setup_coincide.py +0 -3
- crosshair/tools/generate_demo_table.py +2 -2
- crosshair/tracers.py +141 -49
- crosshair/type_repo.py +11 -4
- crosshair/unicode_categories.py +1 -0
- crosshair/util.py +158 -76
- crosshair/util_test.py +13 -20
- crosshair/watcher.py +4 -4
- crosshair/z3util.py +1 -1
- {crosshair_tool-0.0.56.dist-info → crosshair_tool-0.0.100.dist-info}/METADATA +45 -36
- crosshair_tool-0.0.100.dist-info/RECORD +176 -0
- {crosshair_tool-0.0.56.dist-info → crosshair_tool-0.0.100.dist-info}/WHEEL +2 -1
- crosshair/examples/hypothesis/__init__.py +0 -2
- crosshair/examples/hypothesis/bugs_detected/simple_strategies.py +0 -74
- crosshair_tool-0.0.56.dist-info/RECORD +0 -152
- /crosshair/{examples/hypothesis/bugs_detected/__init__.py → py.typed} +0 -0
- {crosshair_tool-0.0.56.dist-info → crosshair_tool-0.0.100.dist-info}/entry_points.txt +0 -0
- {crosshair_tool-0.0.56.dist-info → crosshair_tool-0.0.100.dist-info/licenses}/LICENSE +0 -0
- {crosshair_tool-0.0.56.dist-info → crosshair_tool-0.0.100.dist-info}/top_level.txt +0 -0
crosshair/dynamic_typing_test.py
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import collections
|
|
2
|
+
import sys
|
|
3
|
+
from inspect import Parameter, Signature, signature
|
|
2
4
|
from typing import (
|
|
3
5
|
Callable,
|
|
4
6
|
Dict,
|
|
@@ -6,15 +8,25 @@ from typing import (
|
|
|
6
8
|
Iterable,
|
|
7
9
|
List,
|
|
8
10
|
Mapping,
|
|
11
|
+
Optional,
|
|
9
12
|
Sequence,
|
|
10
13
|
Tuple,
|
|
11
14
|
TypeVar,
|
|
12
15
|
Union,
|
|
13
16
|
)
|
|
14
17
|
|
|
18
|
+
import pytest
|
|
15
19
|
from typing_extensions import TypedDict
|
|
16
20
|
|
|
17
|
-
from crosshair.dynamic_typing import
|
|
21
|
+
from crosshair.dynamic_typing import (
|
|
22
|
+
get_bindings_from_type_arguments,
|
|
23
|
+
intersect_signatures,
|
|
24
|
+
realize,
|
|
25
|
+
unify,
|
|
26
|
+
)
|
|
27
|
+
from crosshair.options import AnalysisOptionSet
|
|
28
|
+
from crosshair.statespace import CANNOT_CONFIRM
|
|
29
|
+
from crosshair.test_util import check_states
|
|
18
30
|
|
|
19
31
|
_T = TypeVar("_T")
|
|
20
32
|
_U = TypeVar("_U")
|
|
@@ -49,7 +61,12 @@ def test_typedicts():
|
|
|
49
61
|
def test_typevars():
|
|
50
62
|
bindings = collections.ChainMap()
|
|
51
63
|
assert unify(Tuple[int, str, List[int]], Tuple[int, _T, _U], bindings)
|
|
52
|
-
|
|
64
|
+
|
|
65
|
+
ret = realize(Mapping[_U, _T], bindings)
|
|
66
|
+
if sys.version_info >= (3, 9):
|
|
67
|
+
assert ret == collections.abc.Mapping[List[int], str]
|
|
68
|
+
else:
|
|
69
|
+
assert ret == Mapping[List[int], str]
|
|
53
70
|
|
|
54
71
|
|
|
55
72
|
def test_bound_vtypears():
|
|
@@ -68,7 +85,13 @@ def test_callable():
|
|
|
68
85
|
|
|
69
86
|
assert not unify(Callable[[List], bool], Callable[[Iterable], bool], bindings)
|
|
70
87
|
assert unify(Callable[[int, _T], List[int]], Callable[[int, str], _U], bindings)
|
|
71
|
-
|
|
88
|
+
if sys.version_info >= (3, 9):
|
|
89
|
+
assert (
|
|
90
|
+
realize(Callable[[_U], _T], bindings)
|
|
91
|
+
== collections.abc.Callable[[List[int]], str]
|
|
92
|
+
)
|
|
93
|
+
else:
|
|
94
|
+
assert realize(Callable[[_U], _T], bindings) == Callable[[List[int]], str]
|
|
72
95
|
|
|
73
96
|
|
|
74
97
|
def test_plain_callable():
|
|
@@ -120,4 +143,68 @@ class Pair(Generic[_U, _T]):
|
|
|
120
143
|
def test_bindings_from_type_arguments():
|
|
121
144
|
var_mapping = get_bindings_from_type_arguments(Pair[int, str])
|
|
122
145
|
assert var_mapping == {_U: int, _T: str}
|
|
123
|
-
|
|
146
|
+
if sys.version_info >= (3, 9):
|
|
147
|
+
assert realize(List[_U], var_mapping) == list[int]
|
|
148
|
+
else:
|
|
149
|
+
assert realize(List[_U], var_mapping) == List[int]
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def test_intersect_signatures_basic():
|
|
153
|
+
def f1(x: int, y: str, **kw) -> List[bool]:
|
|
154
|
+
return []
|
|
155
|
+
|
|
156
|
+
def f2(x: bool, *extra: str, **kw) -> List[int]:
|
|
157
|
+
return []
|
|
158
|
+
|
|
159
|
+
intersection = intersect_signatures(signature(f1), signature(f2))
|
|
160
|
+
assert intersection is not None
|
|
161
|
+
assert intersection.parameters == {
|
|
162
|
+
"x": Parameter("x", kind=Parameter.KEYWORD_ONLY, annotation=bool),
|
|
163
|
+
"y": Parameter("y", kind=Parameter.KEYWORD_ONLY, annotation=str),
|
|
164
|
+
"kw": Parameter("kw", kind=Parameter.VAR_KEYWORD),
|
|
165
|
+
}
|
|
166
|
+
assert intersection.return_annotation == List[bool]
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def test_intersect_signatures_typevars():
|
|
170
|
+
_T = TypeVar("_T")
|
|
171
|
+
|
|
172
|
+
def f1(cc, *args, **kwds):
|
|
173
|
+
pass
|
|
174
|
+
|
|
175
|
+
def f2(dd, left: Optional[_T], right: Optional[_T]):
|
|
176
|
+
pass
|
|
177
|
+
|
|
178
|
+
intersection = intersect_signatures(signature(f1), signature(f2))
|
|
179
|
+
assert intersection is not None
|
|
180
|
+
expected = {
|
|
181
|
+
"dd": Parameter("dd", kind=Parameter.KEYWORD_ONLY),
|
|
182
|
+
"left": Parameter("left", kind=Parameter.KEYWORD_ONLY, annotation=Optional[_T]),
|
|
183
|
+
"right": Parameter(
|
|
184
|
+
"right", kind=Parameter.KEYWORD_ONLY, annotation=Optional[_T]
|
|
185
|
+
),
|
|
186
|
+
}
|
|
187
|
+
assert intersection.parameters == expected
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
@pytest.mark.skip(
|
|
191
|
+
reason="The inspect module doesn't expose runtime type information yet"
|
|
192
|
+
)
|
|
193
|
+
def test_intersect_signature_with_crosshair():
|
|
194
|
+
def check_intersect_signatures(
|
|
195
|
+
sig1: Signature, sig2: Signature, pos_args: List, kw_args: Mapping[str, object]
|
|
196
|
+
) -> None:
|
|
197
|
+
"""post: True"""
|
|
198
|
+
|
|
199
|
+
def _sig_bindable(sig: Signature) -> bool:
|
|
200
|
+
try:
|
|
201
|
+
sig.bind(*pos_args, **kw_args)
|
|
202
|
+
return True
|
|
203
|
+
except TypeError:
|
|
204
|
+
return False
|
|
205
|
+
|
|
206
|
+
if _sig_bindable(sig1) or _sig_bindable(sig2):
|
|
207
|
+
intersection = intersect_signatures(sig1, sig2)
|
|
208
|
+
assert _sig_bindable(intersection)
|
|
209
|
+
|
|
210
|
+
check_states(check_intersect_signatures, CANNOT_CONFIRM)
|
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):
|
|
@@ -191,12 +192,6 @@ class EnforcedConditions(TracingModule):
|
|
|
191
192
|
self.fns_enforcing: Optional[Set[Callable]] = None
|
|
192
193
|
self.codeobj_cache: Dict[object, bool] = {}
|
|
193
194
|
|
|
194
|
-
def __enter__(self): # TODO: no longer used as a context manager; remove
|
|
195
|
-
return self
|
|
196
|
-
|
|
197
|
-
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
198
|
-
return False
|
|
199
|
-
|
|
200
195
|
@contextlib.contextmanager
|
|
201
196
|
def currently_enforcing(self, fn: Callable):
|
|
202
197
|
if self.fns_enforcing is None:
|
crosshair/enforce_test.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import abc
|
|
2
2
|
import sys
|
|
3
|
-
import unittest
|
|
4
3
|
from contextlib import ExitStack
|
|
5
4
|
|
|
6
5
|
import pytest
|
|
@@ -53,26 +52,25 @@ class Enforcement(ExitStack):
|
|
|
53
52
|
super().__enter__()
|
|
54
53
|
enforced_conditions = EnforcedConditions(Pep316Parser())
|
|
55
54
|
self.enter_context(COMPOSITE_TRACER)
|
|
56
|
-
self.enter_context(enforced_conditions)
|
|
57
55
|
self.enter_context(enforced_conditions.enabled_enforcement())
|
|
58
56
|
COMPOSITE_TRACER.trace_caller()
|
|
59
57
|
|
|
60
58
|
|
|
61
|
-
class
|
|
59
|
+
class TestCore:
|
|
62
60
|
def test_enforce_conditions(self) -> None:
|
|
63
|
-
|
|
61
|
+
assert foo(-1) == -2 # unchecked
|
|
64
62
|
with Enforcement():
|
|
65
|
-
|
|
66
|
-
with
|
|
63
|
+
assert foo(50) == 100
|
|
64
|
+
with pytest.raises(PreconditionFailed):
|
|
67
65
|
foo(-1)
|
|
68
|
-
with
|
|
66
|
+
with pytest.raises(PostconditionFailed):
|
|
69
67
|
foo(0)
|
|
70
68
|
|
|
71
69
|
def test_class_enforce(self) -> None:
|
|
72
70
|
Pokeable().pokeby(-1) # no exception (yet!)
|
|
73
71
|
with Enforcement():
|
|
74
72
|
Pokeable().poke()
|
|
75
|
-
with
|
|
73
|
+
with pytest.raises(PreconditionFailed):
|
|
76
74
|
Pokeable().pokeby(-1)
|
|
77
75
|
|
|
78
76
|
def test_enforce_on_uncopyable_value(self) -> None:
|
|
@@ -82,7 +80,7 @@ class CoreTest(unittest.TestCase):
|
|
|
82
80
|
|
|
83
81
|
not_copyable = NotCopyable()
|
|
84
82
|
with Enforcement():
|
|
85
|
-
with
|
|
83
|
+
with pytest.raises(AttributeError):
|
|
86
84
|
same_thing(not_copyable)
|
|
87
85
|
|
|
88
86
|
|
|
@@ -115,19 +113,19 @@ class DerivedFooable(BaseFooable):
|
|
|
115
113
|
"""pre: x > 0"""
|
|
116
114
|
|
|
117
115
|
|
|
118
|
-
class
|
|
116
|
+
class TestTrickyCases:
|
|
119
117
|
def test_attrs_restored_properly(self) -> None:
|
|
120
118
|
orig_class_dict = DerivedFooable.__dict__.copy()
|
|
121
119
|
with Enforcement():
|
|
122
120
|
pass
|
|
123
121
|
for k, v in orig_class_dict.items():
|
|
124
|
-
|
|
125
|
-
DerivedFooable.__dict__[k]
|
|
126
|
-
)
|
|
122
|
+
assert (
|
|
123
|
+
DerivedFooable.__dict__[k] is v
|
|
124
|
+
), f'member "{k}" changed afer encforcement'
|
|
127
125
|
|
|
128
126
|
def test_enforcement_of_class_methods(self) -> None:
|
|
129
127
|
with Enforcement():
|
|
130
|
-
with
|
|
128
|
+
with pytest.raises(PreconditionFailed):
|
|
131
129
|
BaseFooable.class_foo(50)
|
|
132
130
|
with Enforcement():
|
|
133
131
|
DerivedFooable.class_foo(50)
|
|
@@ -135,14 +133,14 @@ class TrickyCasesTest(unittest.TestCase):
|
|
|
135
133
|
def test_enforcement_of_static_methods(self) -> None:
|
|
136
134
|
with Enforcement():
|
|
137
135
|
DerivedFooable.static_foo(50)
|
|
138
|
-
with
|
|
136
|
+
with pytest.raises(PreconditionFailed):
|
|
139
137
|
BaseFooable.static_foo(50)
|
|
140
138
|
|
|
141
139
|
def test_super_method_enforced(self) -> None:
|
|
142
140
|
with Enforcement():
|
|
143
|
-
with
|
|
141
|
+
with pytest.raises(PreconditionFailed):
|
|
144
142
|
DerivedFooable().foo_only_in_super(50)
|
|
145
|
-
with
|
|
143
|
+
with pytest.raises(PreconditionFailed):
|
|
146
144
|
DerivedFooable().foo(-1)
|
|
147
145
|
# Derived class has a weaker precondition, so this is OK:
|
|
148
146
|
DerivedFooable().foo(50)
|
|
@@ -182,9 +180,3 @@ def test_enforcement_init_on_abcmeta() -> None:
|
|
|
182
180
|
with pytest.raises(PostconditionFailed):
|
|
183
181
|
WithMetaclass(55)
|
|
184
182
|
WithMetaclass(99)
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
if __name__ == "__main__":
|
|
188
|
-
if ("-v" in sys.argv) or ("--verbose" in sys.argv):
|
|
189
|
-
set_debug(True)
|
|
190
|
-
unittest.main()
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"""Run functional tests of the tool on all the examples."""
|
|
2
|
+
|
|
2
3
|
import argparse
|
|
3
4
|
import os
|
|
4
5
|
import pathlib
|
|
@@ -24,7 +25,7 @@ def extract_linenums(text: str) -> List[int]:
|
|
|
24
25
|
|
|
25
26
|
def find_examples() -> Iterable[Path]:
|
|
26
27
|
examples_dir = pathlib.Path(os.path.realpath(__file__)).parent
|
|
27
|
-
for path in sorted(examples_dir.glob("
|
|
28
|
+
for path in sorted(examples_dir.glob("*/**/*.py")):
|
|
28
29
|
if path.stem != "__init__":
|
|
29
30
|
yield path
|
|
30
31
|
|
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
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import builtins
|
|
2
2
|
import inspect
|
|
3
3
|
import sys
|
|
4
|
-
import unittest
|
|
5
4
|
from dataclasses import dataclass
|
|
6
5
|
from typing import Generic
|
|
7
6
|
|
|
@@ -74,9 +73,3 @@ def test_load_function_at_line():
|
|
|
74
73
|
|
|
75
74
|
def test_FunctionInfo_get_callable_on_generic():
|
|
76
75
|
assert FunctionInfo.from_class(Generic, "__class_getitem__").get_callable() is None
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if __name__ == "__main__":
|
|
80
|
-
if ("-v" in sys.argv) or ("--verbose" in sys.argv):
|
|
81
|
-
set_debug(True)
|
|
82
|
-
unittest.main()
|
crosshair/fuzz_core_test.py
CHANGED
|
@@ -38,15 +38,16 @@ from crosshair.fnutil import resolve_signature
|
|
|
38
38
|
from crosshair.libimpl.builtinslib import origin_of
|
|
39
39
|
from crosshair.statespace import (
|
|
40
40
|
CallAnalysis,
|
|
41
|
-
|
|
41
|
+
CrossHairInternal,
|
|
42
42
|
IgnoreAttempt,
|
|
43
43
|
RootNode,
|
|
44
44
|
StateSpace,
|
|
45
45
|
StateSpaceContext,
|
|
46
46
|
)
|
|
47
47
|
from crosshair.stubs_parser import signature_from_stubs
|
|
48
|
+
from crosshair.test_util import flexible_equal
|
|
48
49
|
from crosshair.tracers import COMPOSITE_TRACER, NoTracing, ResumedTracing
|
|
49
|
-
from crosshair.util import CrosshairUnsupported, debug, type_args_of
|
|
50
|
+
from crosshair.util import CrosshairUnsupported, debug, is_iterable, type_args_of
|
|
50
51
|
|
|
51
52
|
FUZZ_SEED = 1348
|
|
52
53
|
|
|
@@ -176,8 +177,8 @@ class TrialStatus(enum.Enum):
|
|
|
176
177
|
class FuzzTester:
|
|
177
178
|
r: random.Random
|
|
178
179
|
|
|
179
|
-
def __init__(self, seed
|
|
180
|
-
self.r = random.Random(
|
|
180
|
+
def __init__(self, seed):
|
|
181
|
+
self.r = random.Random(seed)
|
|
181
182
|
|
|
182
183
|
def symbolic_run(
|
|
183
184
|
self,
|
|
@@ -220,15 +221,10 @@ class FuzzTester:
|
|
|
220
221
|
return (
|
|
221
222
|
None,
|
|
222
223
|
symbolic_args,
|
|
223
|
-
|
|
224
|
+
CrossHairInternal(f"exhausted after {itr} iterations"),
|
|
224
225
|
space,
|
|
225
226
|
)
|
|
226
|
-
|
|
227
|
-
None,
|
|
228
|
-
None,
|
|
229
|
-
CrosshairInternal("Unable to find a successful symbolic execution"),
|
|
230
|
-
space,
|
|
231
|
-
)
|
|
227
|
+
raise CrossHairInternal("Unable to find a successful symbolic execution")
|
|
232
228
|
|
|
233
229
|
def runexpr(self, expr, bindings):
|
|
234
230
|
try:
|
|
@@ -271,86 +267,77 @@ class FuzzTester:
|
|
|
271
267
|
) -> object:
|
|
272
268
|
for name in typed_args.keys():
|
|
273
269
|
literal, symbolic = literal_args[name], symbolic_args[name]
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
#
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
if isinstance(literal, Mapping):
|
|
283
|
-
assert isinstance(symbolic, Mapping)
|
|
284
|
-
literal, symbolic = list(literal.items()), list(
|
|
285
|
-
symbolic.items()
|
|
286
|
-
)
|
|
287
|
-
else:
|
|
288
|
-
assert isinstance(symbolic, Iterable)
|
|
289
|
-
literal, symbolic = list(literal), list(symbolic)
|
|
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
|
|
290
278
|
if literal != symbolic:
|
|
291
279
|
raise IgnoreAttempt(
|
|
292
280
|
f'symbolic "{name}" not equal to literal "{name}"'
|
|
293
281
|
)
|
|
282
|
+
if repr(literal) != repr(symbolic):
|
|
283
|
+
# dict/set ordering, -0.0 vs 0.0, etc
|
|
284
|
+
raise IgnoreAttempt(
|
|
285
|
+
f'symbolic "{name}" not repr-equal to literal "{name}"'
|
|
286
|
+
)
|
|
294
287
|
return eval(expr, symbolic_args.copy())
|
|
295
288
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
symbolic_ret, Iterable
|
|
314
|
-
):
|
|
315
|
-
literal_ret = list(literal_ret)
|
|
316
|
-
symbolic_ret = list(symbolic_ret)
|
|
317
|
-
postexec_symbolic_args = deep_realize(postexec_symbolic_args)
|
|
318
|
-
symbolic_ret = deep_realize(symbolic_ret)
|
|
319
|
-
symbolic_exc = deep_realize(symbolic_exc)
|
|
320
|
-
rets_differ = realize(bool(literal_ret != symbolic_ret))
|
|
321
|
-
postexec_args_differ = realize(
|
|
322
|
-
bool(postexec_literal_args != postexec_symbolic_args)
|
|
323
|
-
)
|
|
324
|
-
if (
|
|
325
|
-
rets_differ
|
|
326
|
-
or postexec_args_differ
|
|
327
|
-
or type(literal_exc) != type(symbolic_exc)
|
|
289
|
+
debug(f" ===== {expr} with {literal_args} ===== ")
|
|
290
|
+
compile(expr, "<string>", "eval")
|
|
291
|
+
postexec_literal_args = copy.deepcopy(literal_args)
|
|
292
|
+
literal_ret, literal_exc = self.runexpr(expr, postexec_literal_args)
|
|
293
|
+
(
|
|
294
|
+
symbolic_ret,
|
|
295
|
+
postexec_symbolic_args,
|
|
296
|
+
symbolic_exc,
|
|
297
|
+
space,
|
|
298
|
+
) = self.symbolic_run(symbolic_checker, typed_args)
|
|
299
|
+
if isinstance(symbolic_exc, CrosshairUnsupported):
|
|
300
|
+
return TrialStatus.UNSUPPORTED
|
|
301
|
+
with Patched(), StateSpaceContext(space), COMPOSITE_TRACER, NoTracing():
|
|
302
|
+
# compare iterators as the values they produce:
|
|
303
|
+
with ResumedTracing():
|
|
304
|
+
if isinstance(literal_ret, Iterable) and isinstance(
|
|
305
|
+
symbolic_ret, Iterable
|
|
328
306
|
):
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
307
|
+
literal_ret = list(literal_ret)
|
|
308
|
+
symbolic_ret = list(symbolic_ret)
|
|
309
|
+
postexec_symbolic_args = deep_realize(postexec_symbolic_args)
|
|
310
|
+
symbolic_ret = deep_realize(symbolic_ret)
|
|
311
|
+
symbolic_exc = deep_realize(symbolic_exc)
|
|
312
|
+
rets_differ = not realize(flexible_equal(literal_ret, symbolic_ret))
|
|
313
|
+
postexec_args_differ = realize(
|
|
314
|
+
bool(postexec_literal_args != postexec_symbolic_args)
|
|
315
|
+
)
|
|
316
|
+
if (
|
|
317
|
+
rets_differ
|
|
318
|
+
or postexec_args_differ
|
|
319
|
+
or type(literal_exc) != type(symbolic_exc)
|
|
320
|
+
):
|
|
321
|
+
debug(f" ***** BEGIN FAILURE FOR {expr} WITH {literal_args} ***** ")
|
|
343
322
|
debug(
|
|
344
|
-
"
|
|
345
|
-
type(literal_exc),
|
|
346
|
-
literal_exc,
|
|
347
|
-
"vs",
|
|
348
|
-
type(symbolic_exc),
|
|
349
|
-
symbolic_exc,
|
|
323
|
+
f" ***** Expected return: {literal_ret} (exc: {type(literal_exc)} {literal_exc})"
|
|
350
324
|
)
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
325
|
+
debug(f" ***** postexec args: {postexec_literal_args}")
|
|
326
|
+
debug(
|
|
327
|
+
f" ***** Symbolic return: {symbolic_ret} (exc: {type(symbolic_exc)} {symbolic_exc})"
|
|
328
|
+
)
|
|
329
|
+
debug(f" ***** postexec args: {postexec_symbolic_args}")
|
|
330
|
+
debug(f" ***** END FAILURE FOR {expr} ***** ")
|
|
331
|
+
assert literal_ret == symbolic_ret
|
|
332
|
+
assert False, f"Mismatch while evaluating {expr} with {literal_args}"
|
|
333
|
+
debug(" OK ret= ", literal_ret, "vs", symbolic_ret)
|
|
334
|
+
debug(
|
|
335
|
+
" OK exc= ",
|
|
336
|
+
type(literal_exc),
|
|
337
|
+
literal_exc,
|
|
338
|
+
"vs",
|
|
339
|
+
type(symbolic_exc),
|
|
340
|
+
symbolic_exc,
|
|
354
341
|
)
|
|
355
342
|
return TrialStatus.NORMAL
|
|
356
343
|
|
|
@@ -406,7 +393,7 @@ def test_unary_ops(expr_str, seed) -> None:
|
|
|
406
393
|
tester.run_trial(expr_str, arg_type_roots)
|
|
407
394
|
|
|
408
395
|
|
|
409
|
-
@pytest.mark.parametrize("seed", range(25))
|
|
396
|
+
@pytest.mark.parametrize("seed", set(range(25)) - {17})
|
|
410
397
|
@pytest.mark.parametrize(
|
|
411
398
|
"expr_str",
|
|
412
399
|
[
|
crosshair/libimpl/arraylib.py
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
|
+
import sys
|
|
1
2
|
from array import array
|
|
2
|
-
from typing import BinaryIO,
|
|
3
|
+
from typing import BinaryIO, Dict, Iterable, List, Sequence, Tuple
|
|
3
4
|
|
|
4
5
|
import z3 # type: ignore
|
|
5
6
|
|
|
6
7
|
from crosshair import SymbolicFactory, realize, register_patch
|
|
7
|
-
from crosshair.core import
|
|
8
|
+
from crosshair.core import register_type
|
|
8
9
|
from crosshair.libimpl.builtinslib import SymbolicArrayBasedUniformTuple
|
|
9
10
|
from crosshair.simplestructs import ShellMutableSequence
|
|
10
11
|
from crosshair.statespace import StateSpace
|
|
11
12
|
from crosshair.tracers import NoTracing
|
|
13
|
+
from crosshair.util import CrossHairValue
|
|
12
14
|
|
|
13
15
|
INT_TYPE_BOUNDS: Dict[str, Tuple[int, int]] = {
|
|
14
16
|
# (min, max) ranges - inclusive on min, exclusive on max.
|
|
@@ -28,13 +30,9 @@ INT_TYPE_BOUNDS: Dict[str, Tuple[int, int]] = {
|
|
|
28
30
|
INT_TYPE_SIZE = {c: array(c).itemsize for c in INT_TYPE_BOUNDS.keys()}
|
|
29
31
|
|
|
30
32
|
|
|
31
|
-
def is_bytes_like(obj: object) -> bool:
|
|
32
|
-
return isinstance(obj, (ByteString, array))
|
|
33
|
-
|
|
34
|
-
|
|
35
33
|
def pick_code(space: StateSpace) -> Tuple[str, int, int]:
|
|
36
34
|
last_idx = len(INT_TYPE_BOUNDS) - 1
|
|
37
|
-
for
|
|
35
|
+
for idx, (code, rng) in enumerate(INT_TYPE_BOUNDS.items()):
|
|
38
36
|
if idx < last_idx:
|
|
39
37
|
if space.smt_fork(desc=f"not_{code}_array"):
|
|
40
38
|
continue
|
|
@@ -100,6 +98,11 @@ class SymbolicArray(
|
|
|
100
98
|
def __ch_pytype__(self):
|
|
101
99
|
return array
|
|
102
100
|
|
|
101
|
+
def __eq__(self, other):
|
|
102
|
+
if not isinstance(other, array):
|
|
103
|
+
return False
|
|
104
|
+
return ShellMutableSequence.__eq__(self, other)
|
|
105
|
+
|
|
103
106
|
def _spawn(self, items: Sequence) -> ShellMutableSequence:
|
|
104
107
|
return SymbolicArray(self.typecode, items)
|
|
105
108
|
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import binascii
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
from crosshair.core import analyze_function, run_checkables
|
|
7
|
+
from crosshair.statespace import MessageType
|
|
8
|
+
from crosshair.test_util import compare_results
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def check_b2a_base64(byts: bytes, newline: bool):
|
|
12
|
+
"""post: _"""
|
|
13
|
+
return compare_results(binascii.b2a_base64, byts, newline=newline)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def check_a2b_base64(byts: bytes, strict_mode: bool):
|
|
17
|
+
"""post: _"""
|
|
18
|
+
kw = {"strict_mode": strict_mode} if sys.version_info >= (3, 11) else {}
|
|
19
|
+
return compare_results(binascii.a2b_base64, byts, **kw)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# This is the only real test definition.
|
|
23
|
+
# It runs crosshair on each of the "check" functions defined above.
|
|
24
|
+
@pytest.mark.parametrize("fn_name", [fn for fn in dir() if fn.startswith("check_")])
|
|
25
|
+
def test_builtin(fn_name: str) -> None:
|
|
26
|
+
this_module = sys.modules[__name__]
|
|
27
|
+
fn = getattr(this_module, fn_name)
|
|
28
|
+
messages = run_checkables(analyze_function(fn))
|
|
29
|
+
errors = [m for m in messages if m.state > MessageType.PRE_UNSAT]
|
|
30
|
+
assert errors == []
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import binascii
|
|
2
|
+
import sys
|
|
3
|
+
from array import array
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from crosshair.test_util import summarize_execution
|
|
8
|
+
from crosshair.tracers import ResumedTracing
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@pytest.mark.parametrize(
|
|
12
|
+
"input_bytes",
|
|
13
|
+
[
|
|
14
|
+
b"", # empty
|
|
15
|
+
b"//==", # padding 2
|
|
16
|
+
b"AAA=", # padding 1
|
|
17
|
+
b"9999", # no padding
|
|
18
|
+
b"3=4=", # discontinuous padding
|
|
19
|
+
b"34=&=", # unexpected chars in padding
|
|
20
|
+
b"34=", # wrong padding
|
|
21
|
+
b"333====", # over-padding
|
|
22
|
+
b"This/12+yearOld/Fox=", # valid, long
|
|
23
|
+
"", # empty string
|
|
24
|
+
"9009", # nonempty string
|
|
25
|
+
"\u2165", # unicode string
|
|
26
|
+
],
|
|
27
|
+
)
|
|
28
|
+
@pytest.mark.parametrize("strict_mode", [True, False])
|
|
29
|
+
def test_base64_decode(space, input_bytes, strict_mode):
|
|
30
|
+
kw = {"strict_mode": strict_mode} if sys.version_info >= (3, 11) else {}
|
|
31
|
+
concrete_result = summarize_execution(
|
|
32
|
+
binascii.a2b_base64, (input_bytes,), kw, detach_path=False
|
|
33
|
+
)
|
|
34
|
+
with ResumedTracing():
|
|
35
|
+
symbolic_result = summarize_execution(
|
|
36
|
+
binascii.a2b_base64, (input_bytes,), kw, detach_path=False
|
|
37
|
+
)
|
|
38
|
+
assert concrete_result == symbolic_result
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@pytest.mark.parametrize("newline", [True, False])
|
|
42
|
+
@pytest.mark.parametrize(
|
|
43
|
+
"input_bytes",
|
|
44
|
+
[
|
|
45
|
+
b"", # empty
|
|
46
|
+
b"H",
|
|
47
|
+
b"Ha",
|
|
48
|
+
b"Hai",
|
|
49
|
+
b"Hair",
|
|
50
|
+
"", # empty string
|
|
51
|
+
"Hair", # nonempty string
|
|
52
|
+
"\u2165", # unicode string
|
|
53
|
+
bytearray(b"Hair"), # bytearray
|
|
54
|
+
memoryview(b"Hair"), # memoryview
|
|
55
|
+
array("B", b"Hair"), # array
|
|
56
|
+
],
|
|
57
|
+
)
|
|
58
|
+
def test_base64_encode(space, input_bytes, newline):
|
|
59
|
+
kw = {"newline": newline}
|
|
60
|
+
concrete_result = summarize_execution(
|
|
61
|
+
binascii.b2a_base64, (input_bytes,), kw, detach_path=False
|
|
62
|
+
)
|
|
63
|
+
with ResumedTracing():
|
|
64
|
+
symbolic_result = summarize_execution(
|
|
65
|
+
binascii.b2a_base64, (input_bytes,), kw, detach_path=False
|
|
66
|
+
)
|
|
67
|
+
assert concrete_result == symbolic_result
|