crosshair-tool 0.0.99__cp312-cp312-macosx_10_13_x86_64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- _crosshair_tracers.cpython-312-darwin.so +0 -0
- crosshair/__init__.py +42 -0
- crosshair/__main__.py +8 -0
- crosshair/_mark_stacks.h +790 -0
- crosshair/_preliminaries_test.py +18 -0
- crosshair/_tracers.h +94 -0
- crosshair/_tracers_pycompat.h +522 -0
- crosshair/_tracers_test.py +138 -0
- crosshair/abcstring.py +245 -0
- crosshair/auditwall.py +190 -0
- crosshair/auditwall_test.py +77 -0
- crosshair/codeconfig.py +113 -0
- crosshair/codeconfig_test.py +117 -0
- crosshair/condition_parser.py +1237 -0
- crosshair/condition_parser_test.py +497 -0
- crosshair/conftest.py +30 -0
- crosshair/copyext.py +155 -0
- crosshair/copyext_test.py +84 -0
- crosshair/core.py +1763 -0
- crosshair/core_and_libs.py +149 -0
- crosshair/core_regestered_types_test.py +82 -0
- crosshair/core_test.py +1316 -0
- crosshair/diff_behavior.py +314 -0
- crosshair/diff_behavior_test.py +261 -0
- crosshair/dynamic_typing.py +346 -0
- crosshair/dynamic_typing_test.py +210 -0
- crosshair/enforce.py +282 -0
- crosshair/enforce_test.py +182 -0
- crosshair/examples/PEP316/__init__.py +1 -0
- crosshair/examples/PEP316/bugs_detected/__init__.py +0 -0
- crosshair/examples/PEP316/bugs_detected/getattr_magic.py +16 -0
- crosshair/examples/PEP316/bugs_detected/hash_consistent_with_equals.py +31 -0
- crosshair/examples/PEP316/bugs_detected/shopping_cart.py +24 -0
- crosshair/examples/PEP316/bugs_detected/showcase.py +39 -0
- crosshair/examples/PEP316/correct_code/__init__.py +0 -0
- crosshair/examples/PEP316/correct_code/arith.py +60 -0
- crosshair/examples/PEP316/correct_code/chess.py +77 -0
- crosshair/examples/PEP316/correct_code/nesting_inference.py +17 -0
- crosshair/examples/PEP316/correct_code/numpy_examples.py +132 -0
- crosshair/examples/PEP316/correct_code/rolling_average.py +35 -0
- crosshair/examples/PEP316/correct_code/showcase.py +104 -0
- crosshair/examples/__init__.py +0 -0
- crosshair/examples/check_examples_test.py +146 -0
- crosshair/examples/deal/__init__.py +1 -0
- crosshair/examples/icontract/__init__.py +1 -0
- crosshair/examples/icontract/bugs_detected/__init__.py +0 -0
- crosshair/examples/icontract/bugs_detected/showcase.py +41 -0
- crosshair/examples/icontract/bugs_detected/wrong_sign.py +8 -0
- crosshair/examples/icontract/correct_code/__init__.py +0 -0
- crosshair/examples/icontract/correct_code/arith.py +51 -0
- crosshair/examples/icontract/correct_code/showcase.py +94 -0
- crosshair/fnutil.py +391 -0
- crosshair/fnutil_test.py +75 -0
- crosshair/fuzz_core_test.py +516 -0
- crosshair/libimpl/__init__.py +0 -0
- crosshair/libimpl/arraylib.py +161 -0
- 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 +23 -0
- crosshair/libimpl/builtinslib.py +5228 -0
- crosshair/libimpl/builtinslib_ch_test.py +1191 -0
- crosshair/libimpl/builtinslib_test.py +3735 -0
- crosshair/libimpl/codecslib.py +86 -0
- crosshair/libimpl/codecslib_test.py +86 -0
- crosshair/libimpl/collectionslib.py +264 -0
- crosshair/libimpl/collectionslib_ch_test.py +252 -0
- crosshair/libimpl/collectionslib_test.py +332 -0
- crosshair/libimpl/copylib.py +23 -0
- crosshair/libimpl/copylib_test.py +18 -0
- crosshair/libimpl/datetimelib.py +2559 -0
- crosshair/libimpl/datetimelib_ch_test.py +354 -0
- crosshair/libimpl/datetimelib_test.py +112 -0
- crosshair/libimpl/decimallib.py +5257 -0
- crosshair/libimpl/decimallib_ch_test.py +78 -0
- crosshair/libimpl/decimallib_test.py +76 -0
- crosshair/libimpl/encodings/__init__.py +23 -0
- crosshair/libimpl/encodings/_encutil.py +187 -0
- crosshair/libimpl/encodings/ascii.py +44 -0
- crosshair/libimpl/encodings/latin_1.py +40 -0
- crosshair/libimpl/encodings/utf_8.py +93 -0
- crosshair/libimpl/encodings_ch_test.py +83 -0
- crosshair/libimpl/fractionlib.py +16 -0
- crosshair/libimpl/fractionlib_test.py +80 -0
- crosshair/libimpl/functoolslib.py +34 -0
- crosshair/libimpl/functoolslib_test.py +56 -0
- crosshair/libimpl/hashliblib.py +30 -0
- crosshair/libimpl/hashliblib_test.py +18 -0
- crosshair/libimpl/heapqlib.py +47 -0
- crosshair/libimpl/heapqlib_test.py +21 -0
- crosshair/libimpl/importliblib.py +18 -0
- crosshair/libimpl/importliblib_test.py +38 -0
- crosshair/libimpl/iolib.py +216 -0
- crosshair/libimpl/iolib_ch_test.py +128 -0
- crosshair/libimpl/iolib_test.py +19 -0
- crosshair/libimpl/ipaddresslib.py +8 -0
- crosshair/libimpl/itertoolslib.py +44 -0
- crosshair/libimpl/itertoolslib_test.py +44 -0
- crosshair/libimpl/jsonlib.py +984 -0
- crosshair/libimpl/jsonlib_ch_test.py +42 -0
- crosshair/libimpl/jsonlib_test.py +51 -0
- crosshair/libimpl/mathlib.py +179 -0
- crosshair/libimpl/mathlib_ch_test.py +44 -0
- crosshair/libimpl/mathlib_test.py +67 -0
- crosshair/libimpl/oslib.py +7 -0
- crosshair/libimpl/pathliblib_test.py +10 -0
- crosshair/libimpl/randomlib.py +178 -0
- crosshair/libimpl/randomlib_test.py +120 -0
- crosshair/libimpl/relib.py +846 -0
- crosshair/libimpl/relib_ch_test.py +169 -0
- crosshair/libimpl/relib_test.py +493 -0
- crosshair/libimpl/timelib.py +72 -0
- crosshair/libimpl/timelib_test.py +82 -0
- crosshair/libimpl/typeslib.py +15 -0
- crosshair/libimpl/typeslib_test.py +36 -0
- crosshair/libimpl/unicodedatalib.py +75 -0
- crosshair/libimpl/unicodedatalib_test.py +42 -0
- crosshair/libimpl/urlliblib.py +23 -0
- crosshair/libimpl/urlliblib_test.py +19 -0
- 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 +261 -0
- crosshair/lsp_server_test.py +30 -0
- crosshair/main.py +973 -0
- crosshair/main_test.py +543 -0
- crosshair/objectproxy.py +376 -0
- crosshair/objectproxy_test.py +41 -0
- crosshair/opcode_intercept.py +601 -0
- crosshair/opcode_intercept_test.py +304 -0
- crosshair/options.py +218 -0
- crosshair/options_test.py +10 -0
- crosshair/patch_equivalence_test.py +75 -0
- crosshair/path_cover.py +209 -0
- crosshair/path_cover_test.py +138 -0
- crosshair/path_search.py +161 -0
- crosshair/path_search_test.py +52 -0
- crosshair/pathing_oracle.py +271 -0
- crosshair/pathing_oracle_test.py +21 -0
- crosshair/pure_importer.py +27 -0
- crosshair/pure_importer_test.py +16 -0
- crosshair/py.typed +0 -0
- crosshair/register_contract.py +273 -0
- crosshair/register_contract_test.py +190 -0
- crosshair/simplestructs.py +1165 -0
- crosshair/simplestructs_test.py +283 -0
- crosshair/smtlib.py +24 -0
- crosshair/smtlib_test.py +14 -0
- crosshair/statespace.py +1199 -0
- crosshair/statespace_test.py +108 -0
- crosshair/stubs_parser.py +352 -0
- crosshair/stubs_parser_test.py +43 -0
- crosshair/test_util.py +329 -0
- crosshair/test_util_test.py +26 -0
- crosshair/tools/__init__.py +0 -0
- crosshair/tools/check_help_in_doc.py +264 -0
- crosshair/tools/check_init_and_setup_coincide.py +119 -0
- crosshair/tools/generate_demo_table.py +127 -0
- crosshair/tracers.py +544 -0
- crosshair/tracers_test.py +154 -0
- crosshair/type_repo.py +151 -0
- crosshair/unicode_categories.py +589 -0
- crosshair/unicode_categories_test.py +27 -0
- crosshair/util.py +741 -0
- crosshair/util_test.py +173 -0
- crosshair/watcher.py +307 -0
- crosshair/watcher_test.py +107 -0
- crosshair/z3util.py +76 -0
- crosshair/z3util_test.py +11 -0
- crosshair_tool-0.0.99.dist-info/METADATA +144 -0
- crosshair_tool-0.0.99.dist-info/RECORD +176 -0
- crosshair_tool-0.0.99.dist-info/WHEEL +6 -0
- crosshair_tool-0.0.99.dist-info/entry_points.txt +3 -0
- crosshair_tool-0.0.99.dist-info/licenses/LICENSE +93 -0
- crosshair_tool-0.0.99.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,516 @@
|
|
|
1
|
+
import builtins
|
|
2
|
+
import copy
|
|
3
|
+
import enum
|
|
4
|
+
import random
|
|
5
|
+
import re
|
|
6
|
+
import time
|
|
7
|
+
import traceback
|
|
8
|
+
from collections.abc import Hashable, Mapping, Sized
|
|
9
|
+
from inspect import (
|
|
10
|
+
Parameter,
|
|
11
|
+
Signature,
|
|
12
|
+
getmembers,
|
|
13
|
+
isbuiltin,
|
|
14
|
+
isfunction,
|
|
15
|
+
ismethoddescriptor,
|
|
16
|
+
)
|
|
17
|
+
from types import ModuleType
|
|
18
|
+
from typing import (
|
|
19
|
+
Callable,
|
|
20
|
+
Dict,
|
|
21
|
+
FrozenSet,
|
|
22
|
+
Iterable,
|
|
23
|
+
List,
|
|
24
|
+
Optional,
|
|
25
|
+
Sequence,
|
|
26
|
+
Set,
|
|
27
|
+
Tuple,
|
|
28
|
+
Type,
|
|
29
|
+
TypeVar,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
import pytest
|
|
33
|
+
|
|
34
|
+
import crosshair.core_and_libs # ensure patches/plugins are loaded
|
|
35
|
+
from crosshair.abcstring import AbcString
|
|
36
|
+
from crosshair.core import Patched, deep_realize, proxy_for_type, realize
|
|
37
|
+
from crosshair.fnutil import resolve_signature
|
|
38
|
+
from crosshair.libimpl.builtinslib import origin_of
|
|
39
|
+
from crosshair.statespace import (
|
|
40
|
+
CallAnalysis,
|
|
41
|
+
CrossHairInternal,
|
|
42
|
+
IgnoreAttempt,
|
|
43
|
+
RootNode,
|
|
44
|
+
StateSpace,
|
|
45
|
+
StateSpaceContext,
|
|
46
|
+
)
|
|
47
|
+
from crosshair.stubs_parser import signature_from_stubs
|
|
48
|
+
from crosshair.test_util import flexible_equal
|
|
49
|
+
from crosshair.tracers import COMPOSITE_TRACER, NoTracing, ResumedTracing
|
|
50
|
+
from crosshair.util import CrosshairUnsupported, debug, is_iterable, type_args_of
|
|
51
|
+
|
|
52
|
+
FUZZ_SEED = 1348
|
|
53
|
+
|
|
54
|
+
T = TypeVar("T")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def simple_name(value: object) -> str:
|
|
58
|
+
return re.sub(r"[\W_]+", "_", str(value))
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
IMMUTABLE_BASE_TYPES = [bool, int, float, str, frozenset]
|
|
62
|
+
ALL_BASE_TYPES = IMMUTABLE_BASE_TYPES + [set, dict, list]
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def gen_type(r: random.Random, type_root: Type) -> type:
|
|
66
|
+
if type_root is Hashable:
|
|
67
|
+
base = r.choice(IMMUTABLE_BASE_TYPES)
|
|
68
|
+
elif type_root is object:
|
|
69
|
+
base = r.choice(ALL_BASE_TYPES)
|
|
70
|
+
else:
|
|
71
|
+
base = type_root
|
|
72
|
+
if base is dict:
|
|
73
|
+
kt = gen_type(r, Hashable)
|
|
74
|
+
vt = gen_type(r, object)
|
|
75
|
+
return Dict[kt, vt] # type: ignore
|
|
76
|
+
elif base is list:
|
|
77
|
+
return List[gen_type(r, object)] # type: ignore
|
|
78
|
+
elif base is set:
|
|
79
|
+
return Set[gen_type(r, Hashable)] # type: ignore
|
|
80
|
+
elif base is frozenset:
|
|
81
|
+
return FrozenSet[gen_type(r, Hashable)] # type: ignore
|
|
82
|
+
else:
|
|
83
|
+
return base
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
# TODO: consider replacing this with typeshed someday!
|
|
87
|
+
_SIGNATURE_OVERRIDES = {
|
|
88
|
+
getattr: Signature(
|
|
89
|
+
[
|
|
90
|
+
Parameter("obj", Parameter.POSITIONAL_ONLY, annotation=object),
|
|
91
|
+
Parameter("attr", Parameter.POSITIONAL_ONLY, annotation=str),
|
|
92
|
+
Parameter("default", Parameter.POSITIONAL_ONLY, annotation=object),
|
|
93
|
+
]
|
|
94
|
+
),
|
|
95
|
+
dict.items: Signature(),
|
|
96
|
+
dict.keys: Signature(),
|
|
97
|
+
# TODO: fuzz test values() somehow. items() and keys() are sets and
|
|
98
|
+
# therefore comparable -- not values() though:
|
|
99
|
+
# dict.values: Signature(),
|
|
100
|
+
dict.clear: Signature(),
|
|
101
|
+
dict.copy: Signature(),
|
|
102
|
+
dict.pop: Signature(
|
|
103
|
+
[
|
|
104
|
+
Parameter("k", Parameter.POSITIONAL_ONLY, annotation=object),
|
|
105
|
+
Parameter("d", Parameter.POSITIONAL_ONLY, annotation=object),
|
|
106
|
+
]
|
|
107
|
+
),
|
|
108
|
+
dict.update: Signature(
|
|
109
|
+
[Parameter("d", Parameter.POSITIONAL_ONLY, annotation=dict)]
|
|
110
|
+
),
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def value_for_type(typ: Type, r: random.Random) -> object:
|
|
115
|
+
"""
|
|
116
|
+
post: isinstance(_, typ)
|
|
117
|
+
"""
|
|
118
|
+
origin = origin_of(typ)
|
|
119
|
+
type_args = type_args_of(typ)
|
|
120
|
+
if typ is bool:
|
|
121
|
+
return r.choice([True, False])
|
|
122
|
+
elif typ is int:
|
|
123
|
+
return r.choice([-1, 0, 1, 2, 10])
|
|
124
|
+
elif typ is float:
|
|
125
|
+
return r.choice([-1.0, 0.0, 1.0, 2.0, 10.0]) # TODO: Inf, NaN
|
|
126
|
+
elif typ is str:
|
|
127
|
+
return r.choice(
|
|
128
|
+
["", "x", "0", "xyz"]
|
|
129
|
+
) # , '\0']) # TODO: null does not work properly yet
|
|
130
|
+
elif typ is bytes:
|
|
131
|
+
return r.choice([b"", b"ab", b"abc", b"\x00"])
|
|
132
|
+
elif origin in (list, set, frozenset):
|
|
133
|
+
(item_type,) = type_args
|
|
134
|
+
items = []
|
|
135
|
+
for _ in range(r.choice([0, 0, 0, 1, 1, 2])):
|
|
136
|
+
items.append(value_for_type(item_type, r))
|
|
137
|
+
return origin(items)
|
|
138
|
+
elif origin is dict:
|
|
139
|
+
(key_type, val_type) = type_args
|
|
140
|
+
ret = {}
|
|
141
|
+
for _ in range(r.choice([0, 0, 0, 1, 1, 1, 2])):
|
|
142
|
+
ret[value_for_type(key_type, r)] = value_for_type(val_type, r) # type: ignore
|
|
143
|
+
return ret
|
|
144
|
+
raise NotImplementedError
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def get_signature(method) -> Optional[Signature]:
|
|
148
|
+
override_sig = _SIGNATURE_OVERRIDES.get(method, None)
|
|
149
|
+
if override_sig:
|
|
150
|
+
return override_sig
|
|
151
|
+
sig = resolve_signature(method)
|
|
152
|
+
if isinstance(sig, Signature):
|
|
153
|
+
return sig
|
|
154
|
+
stub_sigs, stub_sigs_valid = signature_from_stubs(method)
|
|
155
|
+
if stub_sigs_valid and len(stub_sigs) == 1:
|
|
156
|
+
debug("using signature from stubs:", stub_sigs[0])
|
|
157
|
+
return stub_sigs[0]
|
|
158
|
+
return None
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def get_testable_members(classes: Iterable[type]) -> Iterable[Tuple[type, str]]:
|
|
162
|
+
for cls in classes:
|
|
163
|
+
for method_name, method in getmembers(cls):
|
|
164
|
+
if method_name.startswith("__"):
|
|
165
|
+
continue
|
|
166
|
+
if not (isfunction(method) or ismethoddescriptor(method)):
|
|
167
|
+
# TODO: fuzz test class/staticmethods with symbolic args
|
|
168
|
+
continue
|
|
169
|
+
yield cls, method_name
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class TrialStatus(enum.Enum):
|
|
173
|
+
NORMAL = 0
|
|
174
|
+
UNSUPPORTED = 1
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
class FuzzTester:
|
|
178
|
+
r: random.Random
|
|
179
|
+
|
|
180
|
+
def __init__(self, seed):
|
|
181
|
+
self.r = random.Random(seed)
|
|
182
|
+
|
|
183
|
+
def symbolic_run(
|
|
184
|
+
self,
|
|
185
|
+
fn: Callable[[StateSpace, Dict[str, object]], object],
|
|
186
|
+
typed_args: Dict[str, type],
|
|
187
|
+
) -> Tuple[
|
|
188
|
+
object, # return value
|
|
189
|
+
Optional[Dict[str, object]], # arguments after execution
|
|
190
|
+
Optional[BaseException], # exception thrown, if any
|
|
191
|
+
StateSpace,
|
|
192
|
+
]:
|
|
193
|
+
search_root = RootNode()
|
|
194
|
+
with COMPOSITE_TRACER, NoTracing():
|
|
195
|
+
for itr in range(1, 200):
|
|
196
|
+
debug("iteration", itr)
|
|
197
|
+
space = StateSpace(
|
|
198
|
+
time.monotonic() + 30.0, 3.0, search_root=search_root
|
|
199
|
+
)
|
|
200
|
+
symbolic_args = {}
|
|
201
|
+
try:
|
|
202
|
+
with Patched(), StateSpaceContext(space):
|
|
203
|
+
symbolic_args = {
|
|
204
|
+
name: proxy_for_type(typ, name)
|
|
205
|
+
for name, typ in typed_args.items()
|
|
206
|
+
}
|
|
207
|
+
with ResumedTracing():
|
|
208
|
+
ret = fn(space, symbolic_args)
|
|
209
|
+
ret = (deep_realize(ret), symbolic_args, None, space)
|
|
210
|
+
space.detach_path()
|
|
211
|
+
return ret
|
|
212
|
+
except IgnoreAttempt as e:
|
|
213
|
+
debug("ignore iteration attempt: ", str(e))
|
|
214
|
+
except Exception as e:
|
|
215
|
+
debug(
|
|
216
|
+
"exception during symbolic execution:", traceback.format_exc()
|
|
217
|
+
)
|
|
218
|
+
return (None, symbolic_args, e, space)
|
|
219
|
+
top_analysis, space_exhausted = space.bubble_status(CallAnalysis())
|
|
220
|
+
if space_exhausted:
|
|
221
|
+
return (
|
|
222
|
+
None,
|
|
223
|
+
symbolic_args,
|
|
224
|
+
CrossHairInternal(f"exhausted after {itr} iterations"),
|
|
225
|
+
space,
|
|
226
|
+
)
|
|
227
|
+
raise CrossHairInternal("Unable to find a successful symbolic execution")
|
|
228
|
+
|
|
229
|
+
def runexpr(self, expr, bindings):
|
|
230
|
+
try:
|
|
231
|
+
return (eval(expr, {}, bindings), None)
|
|
232
|
+
except Exception as e:
|
|
233
|
+
debug(f'eval of "{expr}" produced exception {type(e)}: {e}')
|
|
234
|
+
return (None, e)
|
|
235
|
+
|
|
236
|
+
def run_function_trials(
|
|
237
|
+
self, fns: Sequence[Tuple[str, Callable]], num_trials: int
|
|
238
|
+
) -> None:
|
|
239
|
+
for fn_name, fn in fns:
|
|
240
|
+
debug("Checking function", fn_name)
|
|
241
|
+
sig = get_signature(fn)
|
|
242
|
+
if not sig:
|
|
243
|
+
debug("Skipping", fn_name, " - unable to inspect signature")
|
|
244
|
+
continue
|
|
245
|
+
arg_names = [chr(ord("a") + i) for i in range(len(sig.parameters))]
|
|
246
|
+
arg_expr_strings = [
|
|
247
|
+
(a if p.kind != Parameter.KEYWORD_ONLY else f"{p.name}={a}")
|
|
248
|
+
for a, p in zip(arg_names, list(sig.parameters.values()))
|
|
249
|
+
]
|
|
250
|
+
expr_str = fn_name + "(" + ",".join(arg_expr_strings) + ")"
|
|
251
|
+
arg_type_roots = {name: object for name in arg_names}
|
|
252
|
+
for trial_num in range(num_trials):
|
|
253
|
+
self.run_trial(expr_str, arg_type_roots)
|
|
254
|
+
|
|
255
|
+
def run_trial(self, expr_str: str, arg_type_roots: Dict[str, Type]) -> TrialStatus:
|
|
256
|
+
expr = expr_str.format(*arg_type_roots.keys())
|
|
257
|
+
typed_args = {
|
|
258
|
+
name: gen_type(self.r, type_root)
|
|
259
|
+
for name, type_root in arg_type_roots.items()
|
|
260
|
+
}
|
|
261
|
+
literal_args = {
|
|
262
|
+
name: value_for_type(typ, self.r) for name, typ in typed_args.items()
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
def symbolic_checker(
|
|
266
|
+
space: StateSpace, symbolic_args: Dict[str, object]
|
|
267
|
+
) -> object:
|
|
268
|
+
for name in typed_args.keys():
|
|
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
|
|
278
|
+
if literal != symbolic:
|
|
279
|
+
raise IgnoreAttempt(
|
|
280
|
+
f'symbolic "{name}" not equal to literal "{name}"'
|
|
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
|
+
)
|
|
287
|
+
return eval(expr, symbolic_args.copy())
|
|
288
|
+
|
|
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
|
|
306
|
+
):
|
|
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} ***** ")
|
|
322
|
+
debug(
|
|
323
|
+
f" ***** Expected return: {literal_ret} (exc: {type(literal_exc)} {literal_exc})"
|
|
324
|
+
)
|
|
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,
|
|
341
|
+
)
|
|
342
|
+
return TrialStatus.NORMAL
|
|
343
|
+
|
|
344
|
+
def fuzz_function(self, module: ModuleType, method_name: str):
|
|
345
|
+
method = getattr(module, method_name)
|
|
346
|
+
sig = get_signature(method)
|
|
347
|
+
if not sig:
|
|
348
|
+
return
|
|
349
|
+
arg_names = [chr(ord("a") + i) for i in range(len(sig.parameters))]
|
|
350
|
+
arg_expr_strings = [
|
|
351
|
+
(a if p.kind != Parameter.KEYWORD_ONLY else f"{p.name}={a}")
|
|
352
|
+
for a, p in zip(arg_names, list(sig.parameters.values())[1:])
|
|
353
|
+
]
|
|
354
|
+
expr_str = method_name + "(" + ",".join(arg_expr_strings) + ")"
|
|
355
|
+
arg_type_roots = {name: object for name in arg_names}
|
|
356
|
+
self.run_trial(expr_str, arg_type_roots)
|
|
357
|
+
|
|
358
|
+
def fuzz_method(self, cls: type, method_name: str):
|
|
359
|
+
method = getattr(cls, method_name)
|
|
360
|
+
sig = get_signature(method)
|
|
361
|
+
if not sig:
|
|
362
|
+
return
|
|
363
|
+
arg_names = [chr(ord("a") + i - 1) for i in range(1, len(sig.parameters))]
|
|
364
|
+
arg_expr_strings = [
|
|
365
|
+
(a if p.kind != Parameter.KEYWORD_ONLY else f"{p.name}={a}")
|
|
366
|
+
for a, p in zip(arg_names, list(sig.parameters.values())[1:])
|
|
367
|
+
]
|
|
368
|
+
expr_str = "self." + method_name + "(" + ",".join(arg_expr_strings) + ")"
|
|
369
|
+
arg_type_roots: Dict[str, type] = {name: object for name in arg_names}
|
|
370
|
+
arg_type_roots["self"] = cls
|
|
371
|
+
self.run_trial(expr_str, arg_type_roots)
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
@pytest.mark.parametrize("seed", range(25))
|
|
375
|
+
@pytest.mark.parametrize(
|
|
376
|
+
"expr_str",
|
|
377
|
+
[
|
|
378
|
+
"iter({})",
|
|
379
|
+
"reversed({})",
|
|
380
|
+
"len({})",
|
|
381
|
+
# "repr({})", # false positive with unstable set ordering
|
|
382
|
+
"str({})",
|
|
383
|
+
"+{}",
|
|
384
|
+
"-{}",
|
|
385
|
+
"~{}",
|
|
386
|
+
# TODO: we aren't `dir()`-compatable right now.
|
|
387
|
+
],
|
|
388
|
+
ids=simple_name,
|
|
389
|
+
)
|
|
390
|
+
def test_unary_ops(expr_str, seed) -> None:
|
|
391
|
+
tester = FuzzTester(seed)
|
|
392
|
+
arg_type_roots = {"a": object}
|
|
393
|
+
tester.run_trial(expr_str, arg_type_roots)
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
@pytest.mark.parametrize("seed", set(range(25)) - {17})
|
|
397
|
+
@pytest.mark.parametrize(
|
|
398
|
+
"expr_str",
|
|
399
|
+
[
|
|
400
|
+
"{} + {}",
|
|
401
|
+
"{} - {}",
|
|
402
|
+
"{} * {}",
|
|
403
|
+
"{} / {}",
|
|
404
|
+
"{} < {}",
|
|
405
|
+
"{} <= {}",
|
|
406
|
+
"{} >= {}",
|
|
407
|
+
"{} > {}",
|
|
408
|
+
"{} == {}",
|
|
409
|
+
"{}[{}]",
|
|
410
|
+
"{}.__delitem__({})",
|
|
411
|
+
"{} in {}",
|
|
412
|
+
"{} & {}",
|
|
413
|
+
"{} | {}",
|
|
414
|
+
"{} ^ {}",
|
|
415
|
+
"{} and {}",
|
|
416
|
+
"{} or {}",
|
|
417
|
+
"{} // {}",
|
|
418
|
+
"{} ** {}",
|
|
419
|
+
"{} % {}",
|
|
420
|
+
],
|
|
421
|
+
ids=simple_name,
|
|
422
|
+
)
|
|
423
|
+
def test_binary_ops(expr_str, seed) -> None:
|
|
424
|
+
tester = FuzzTester(seed)
|
|
425
|
+
arg_type_roots = {"a": object, "b": object}
|
|
426
|
+
tester.run_trial(expr_str, arg_type_roots)
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
IGNORED_BUILTINS = [
|
|
430
|
+
"breakpoint",
|
|
431
|
+
"copyright",
|
|
432
|
+
"credits",
|
|
433
|
+
"dir",
|
|
434
|
+
"exit",
|
|
435
|
+
"help",
|
|
436
|
+
"id",
|
|
437
|
+
"input",
|
|
438
|
+
"license",
|
|
439
|
+
"locals",
|
|
440
|
+
"object",
|
|
441
|
+
"open",
|
|
442
|
+
"property",
|
|
443
|
+
"quit",
|
|
444
|
+
# TODO: debug and un-ignore the following:
|
|
445
|
+
"isinstance",
|
|
446
|
+
"issubclass",
|
|
447
|
+
"float",
|
|
448
|
+
]
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
@pytest.mark.parametrize("seed", range(4))
|
|
452
|
+
@pytest.mark.parametrize(
|
|
453
|
+
"module,method_name",
|
|
454
|
+
[
|
|
455
|
+
(builtins, name)
|
|
456
|
+
for (name, _) in getmembers(builtins, isbuiltin)
|
|
457
|
+
if not name.startswith("_")
|
|
458
|
+
],
|
|
459
|
+
ids=simple_name,
|
|
460
|
+
)
|
|
461
|
+
def test_builtin_functions(seed, module, method_name) -> None:
|
|
462
|
+
if method_name not in IGNORED_BUILTINS:
|
|
463
|
+
FuzzTester(seed).fuzz_function(module, method_name)
|
|
464
|
+
# fns = [
|
|
465
|
+
# (name, fn)
|
|
466
|
+
# for name, fn in getmembers(builtins)
|
|
467
|
+
# if (hasattr(fn, "__call__") and not name.startswith("_") and name not in ignore)
|
|
468
|
+
# ]
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
@pytest.mark.parametrize("seed", range(4))
|
|
472
|
+
@pytest.mark.parametrize(
|
|
473
|
+
"cls,method_name",
|
|
474
|
+
# we don't inspect str directly, because many signature() fails on several members:
|
|
475
|
+
get_testable_members([AbcString]),
|
|
476
|
+
ids=simple_name,
|
|
477
|
+
)
|
|
478
|
+
def test_str_methods(seed, cls, method_name) -> None:
|
|
479
|
+
FuzzTester(seed).fuzz_method(str, method_name)
|
|
480
|
+
# # we don't inspect str directly, because many signature() fails on several members:
|
|
481
|
+
# # TODO test maketrans()
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
@pytest.mark.parametrize("seed", range(5))
|
|
485
|
+
@pytest.mark.parametrize(
|
|
486
|
+
"cls,method_name",
|
|
487
|
+
get_testable_members([list, dict]),
|
|
488
|
+
ids=simple_name,
|
|
489
|
+
)
|
|
490
|
+
def test_container_methods(seed, cls, method_name) -> None:
|
|
491
|
+
FuzzTester(seed).fuzz_method(cls, method_name)
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
# TODO: deal with iteration order (and then increase repeat count)
|
|
495
|
+
@pytest.mark.parametrize("seed", range(3))
|
|
496
|
+
@pytest.mark.parametrize(
|
|
497
|
+
"cls,method_name",
|
|
498
|
+
get_testable_members([set, frozenset]),
|
|
499
|
+
ids=simple_name,
|
|
500
|
+
)
|
|
501
|
+
def test_set_methods(seed, cls, method_name) -> None:
|
|
502
|
+
FuzzTester(seed).fuzz_method(cls, method_name)
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
@pytest.mark.parametrize("seed", range(10))
|
|
506
|
+
@pytest.mark.parametrize(
|
|
507
|
+
"cls,method_name",
|
|
508
|
+
get_testable_members([int, float]),
|
|
509
|
+
ids=simple_name,
|
|
510
|
+
)
|
|
511
|
+
def test_numeric_methods(seed, cls, method_name) -> None:
|
|
512
|
+
FuzzTester(seed).fuzz_method(cls, method_name)
|
|
513
|
+
# TODO test int properties: real, imag, numerator, denominator
|
|
514
|
+
# TODO test int.conjugate()
|
|
515
|
+
# TODO test float properties: real, imag
|
|
516
|
+
# TODO test float.conjugate()
|
|
File without changes
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from array import array
|
|
3
|
+
from typing import BinaryIO, Dict, Iterable, List, Sequence, Tuple
|
|
4
|
+
|
|
5
|
+
import z3 # type: ignore
|
|
6
|
+
|
|
7
|
+
from crosshair import SymbolicFactory, realize, register_patch
|
|
8
|
+
from crosshair.core import register_type
|
|
9
|
+
from crosshair.libimpl.builtinslib import SymbolicArrayBasedUniformTuple
|
|
10
|
+
from crosshair.simplestructs import ShellMutableSequence
|
|
11
|
+
from crosshair.statespace import StateSpace
|
|
12
|
+
from crosshair.tracers import NoTracing
|
|
13
|
+
from crosshair.util import CrossHairValue
|
|
14
|
+
|
|
15
|
+
INT_TYPE_BOUNDS: Dict[str, Tuple[int, int]] = {
|
|
16
|
+
# (min, max) ranges - inclusive on min, exclusive on max.
|
|
17
|
+
# Order is significant - we choose earlier codes more readily.
|
|
18
|
+
"L": (0, 1 << 32),
|
|
19
|
+
"B": (0, 1 << 8),
|
|
20
|
+
"l": (-(1 << 31), (1 << 31)),
|
|
21
|
+
"b": (-(1 << 7), (1 << 7)),
|
|
22
|
+
"Q": (0, 1 << 64),
|
|
23
|
+
"q": (-(1 << 63), (1 << 63)),
|
|
24
|
+
"I": (0, 1 << 16),
|
|
25
|
+
"i": (-(1 << 15), (1 << 15)),
|
|
26
|
+
"H": (0, 1 << 16),
|
|
27
|
+
"h": (-(1 << 15), (1 << 15)),
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
INT_TYPE_SIZE = {c: array(c).itemsize for c in INT_TYPE_BOUNDS.keys()}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def pick_code(space: StateSpace) -> Tuple[str, int, int]:
|
|
34
|
+
last_idx = len(INT_TYPE_BOUNDS) - 1
|
|
35
|
+
for idx, (code, rng) in enumerate(INT_TYPE_BOUNDS.items()):
|
|
36
|
+
if idx < last_idx:
|
|
37
|
+
if space.smt_fork(desc=f"not_{code}_array"):
|
|
38
|
+
continue
|
|
39
|
+
return (code, *rng)
|
|
40
|
+
assert False, "Not Reachable"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def make_array(creator: SymbolicFactory) -> object:
|
|
44
|
+
space = creator.space
|
|
45
|
+
code, minval, maxval = pick_code(space)
|
|
46
|
+
nums = SymbolicArrayBasedUniformTuple(creator.varname, Tuple[int, ...])
|
|
47
|
+
z3_array = nums._arr()
|
|
48
|
+
qvar = z3.Int("arrvar" + space.uniq())
|
|
49
|
+
space.add(z3.ForAll([qvar], minval <= z3.Select(z3_array, qvar)))
|
|
50
|
+
space.add(z3.ForAll([qvar], z3.Select(z3_array, qvar) < maxval))
|
|
51
|
+
return SymbolicArray(code, nums)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def check_int(item, minval, maxval):
|
|
55
|
+
if not (minval <= item < maxval):
|
|
56
|
+
raise OverflowError
|
|
57
|
+
return item
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _array(typecode: str, iterable: Iterable = ()):
|
|
61
|
+
realized_type = realize(typecode)
|
|
62
|
+
bounds = INT_TYPE_BOUNDS.get(typecode)
|
|
63
|
+
if bounds:
|
|
64
|
+
args = [check_int(x, *bounds) for x in iterable]
|
|
65
|
+
return SymbolicArray(realized_type, args)
|
|
66
|
+
return array(realized_type, realize(iterable))
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class SymbolicArray(
|
|
70
|
+
ShellMutableSequence,
|
|
71
|
+
CrossHairValue,
|
|
72
|
+
):
|
|
73
|
+
def __init__(self, typecode: str, items: Sequence = ()):
|
|
74
|
+
# All arguments are presumed valid here
|
|
75
|
+
self.typecode = typecode
|
|
76
|
+
self.itemsize = INT_TYPE_SIZE[typecode]
|
|
77
|
+
self._snapshots: List[array] = []
|
|
78
|
+
super().__init__(items)
|
|
79
|
+
|
|
80
|
+
def _realized_inner(self) -> array:
|
|
81
|
+
with NoTracing():
|
|
82
|
+
realized = self.__ch_realize__()
|
|
83
|
+
self.inner = realized
|
|
84
|
+
return realized
|
|
85
|
+
|
|
86
|
+
def _iter_checker(self, items: Iterable[int]) -> Iterable[int]:
|
|
87
|
+
bounds = INT_TYPE_BOUNDS.get(self.typecode)
|
|
88
|
+
if bounds:
|
|
89
|
+
return (check_int(i, *bounds) for i in items)
|
|
90
|
+
else:
|
|
91
|
+
return items
|
|
92
|
+
|
|
93
|
+
__hash__ = None # type: ignore
|
|
94
|
+
|
|
95
|
+
def __ch_realize__(self):
|
|
96
|
+
return array(self.typecode, self.inner)
|
|
97
|
+
|
|
98
|
+
def __ch_pytype__(self):
|
|
99
|
+
return array
|
|
100
|
+
|
|
101
|
+
def __eq__(self, other):
|
|
102
|
+
if not isinstance(other, array):
|
|
103
|
+
return False
|
|
104
|
+
return ShellMutableSequence.__eq__(self, other)
|
|
105
|
+
|
|
106
|
+
def _spawn(self, items: Sequence) -> ShellMutableSequence:
|
|
107
|
+
return SymbolicArray(self.typecode, items)
|
|
108
|
+
|
|
109
|
+
def append(self, value) -> None:
|
|
110
|
+
bounds = INT_TYPE_BOUNDS.get(self.typecode)
|
|
111
|
+
if bounds:
|
|
112
|
+
check_int(value, *bounds)
|
|
113
|
+
return super().append(value)
|
|
114
|
+
|
|
115
|
+
def buffer_info(self) -> Tuple[int, int]:
|
|
116
|
+
return self._realized_inner().buffer_info()
|
|
117
|
+
|
|
118
|
+
def byteswap(self) -> None:
|
|
119
|
+
self._realized_inner().byteswap()
|
|
120
|
+
|
|
121
|
+
# count() handled by superclass
|
|
122
|
+
|
|
123
|
+
def extend(self, nums: Iterable) -> None:
|
|
124
|
+
super().extend(self._iter_checker(nums))
|
|
125
|
+
|
|
126
|
+
def from_bytes(self, b: Sequence) -> None:
|
|
127
|
+
self.extend(b)
|
|
128
|
+
|
|
129
|
+
def fromfile(self, fd: BinaryIO, num_bytes: int) -> None:
|
|
130
|
+
self._realized_inner().fromfile(fd, num_bytes)
|
|
131
|
+
|
|
132
|
+
def fromlist(self, nums: List) -> None:
|
|
133
|
+
self.extend(nums)
|
|
134
|
+
|
|
135
|
+
def fromunicode(self, s: str) -> None:
|
|
136
|
+
self._realized_inner().fromunicode(s)
|
|
137
|
+
|
|
138
|
+
# index() handled by superclass
|
|
139
|
+
# insert() handled by superclass
|
|
140
|
+
# pop() handled by superclass
|
|
141
|
+
# remove() handled by superclass
|
|
142
|
+
# reverse() handled by superclass
|
|
143
|
+
|
|
144
|
+
def tobytes(self) -> bytes:
|
|
145
|
+
return self._realized_inner().tobytes()
|
|
146
|
+
|
|
147
|
+
def tofile(self, fh: BinaryIO) -> None:
|
|
148
|
+
self._realized_inner().tofile(fh)
|
|
149
|
+
|
|
150
|
+
def tolist(self) -> List:
|
|
151
|
+
return list(self.inner)
|
|
152
|
+
|
|
153
|
+
def tounicode(self) -> str:
|
|
154
|
+
return self._realized_inner().tounicode()
|
|
155
|
+
|
|
156
|
+
# TODO: test repr
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def make_registrations():
|
|
160
|
+
register_type(array, make_array)
|
|
161
|
+
register_patch(array, _array)
|
|
@@ -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 == []
|