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/util.py
CHANGED
|
@@ -10,9 +10,9 @@ import pathlib
|
|
|
10
10
|
import re
|
|
11
11
|
import sys
|
|
12
12
|
import threading
|
|
13
|
-
import time
|
|
14
13
|
import traceback
|
|
15
14
|
import types
|
|
15
|
+
from array import array
|
|
16
16
|
from dataclasses import dataclass
|
|
17
17
|
from enum import Enum
|
|
18
18
|
from inspect import (
|
|
@@ -21,8 +21,10 @@ from inspect import (
|
|
|
21
21
|
getmodulename,
|
|
22
22
|
getsourcefile,
|
|
23
23
|
getsourcelines,
|
|
24
|
+
isdatadescriptor,
|
|
24
25
|
isfunction,
|
|
25
26
|
)
|
|
27
|
+
from time import monotonic
|
|
26
28
|
from types import BuiltinFunctionType, FunctionType, MethodDescriptorType, TracebackType
|
|
27
29
|
from typing import (
|
|
28
30
|
Any,
|
|
@@ -30,12 +32,13 @@ from typing import (
|
|
|
30
32
|
Dict,
|
|
31
33
|
Generator,
|
|
32
34
|
Generic,
|
|
33
|
-
Iterable,
|
|
34
35
|
List,
|
|
35
36
|
Mapping,
|
|
36
37
|
MutableMapping,
|
|
37
38
|
Optional,
|
|
39
|
+
Sequence,
|
|
38
40
|
Set,
|
|
41
|
+
TextIO,
|
|
39
42
|
Tuple,
|
|
40
43
|
Type,
|
|
41
44
|
TypeVar,
|
|
@@ -43,12 +46,27 @@ from typing import (
|
|
|
43
46
|
cast,
|
|
44
47
|
)
|
|
45
48
|
|
|
46
|
-
import typing_inspect
|
|
49
|
+
import typing_inspect # type: ignore
|
|
47
50
|
|
|
48
51
|
from crosshair.auditwall import opened_auditwall
|
|
49
52
|
from crosshair.tracers import COMPOSITE_TRACER, NoTracing, ResumedTracing, is_tracing
|
|
50
53
|
|
|
51
|
-
|
|
54
|
+
_DEBUG_STREAM: Optional[TextIO] = None
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# NOTE: many of these is_* functions should use a TypeGuard in 3.10 (or even TypeIs in 3.13)
|
|
58
|
+
|
|
59
|
+
if sys.version_info >= (3, 12):
|
|
60
|
+
from collections.abc import Buffer
|
|
61
|
+
|
|
62
|
+
def is_bytes_like(obj: object) -> bool:
|
|
63
|
+
return isinstance(obj, Buffer)
|
|
64
|
+
|
|
65
|
+
else:
|
|
66
|
+
from collections.abc import ByteString
|
|
67
|
+
|
|
68
|
+
def is_bytes_like(obj: object) -> bool:
|
|
69
|
+
return isinstance(obj, (ByteString, array))
|
|
52
70
|
|
|
53
71
|
|
|
54
72
|
def is_iterable(o: object) -> bool:
|
|
@@ -111,8 +129,43 @@ def true_type(obj: object) -> Type:
|
|
|
111
129
|
return type(obj)
|
|
112
130
|
|
|
113
131
|
|
|
132
|
+
CROSSHAIR_EXTRA_ASSERTS = os.environ.get("CROSSHAIR_EXTRA_ASSERTS", "0") == "1"
|
|
133
|
+
|
|
134
|
+
if CROSSHAIR_EXTRA_ASSERTS:
|
|
135
|
+
|
|
136
|
+
def assert_tracing(should_be_tracing):
|
|
137
|
+
def decorator(fn):
|
|
138
|
+
fn_name = fn.__qualname__
|
|
139
|
+
|
|
140
|
+
@functools.wraps(fn)
|
|
141
|
+
def check_tracing(*a, **kw):
|
|
142
|
+
if is_tracing() != should_be_tracing:
|
|
143
|
+
with NoTracing():
|
|
144
|
+
if should_be_tracing:
|
|
145
|
+
raise CrossHairInternal(
|
|
146
|
+
f"should be tracing when calling {fn_name}, but isn't"
|
|
147
|
+
)
|
|
148
|
+
else:
|
|
149
|
+
raise CrossHairInternal(
|
|
150
|
+
f"should not be tracing when calling {fn_name}, but is"
|
|
151
|
+
)
|
|
152
|
+
return fn(*a, **kw)
|
|
153
|
+
|
|
154
|
+
return check_tracing
|
|
155
|
+
|
|
156
|
+
return decorator
|
|
157
|
+
|
|
158
|
+
else:
|
|
159
|
+
|
|
160
|
+
def assert_tracing(should_be_tracing):
|
|
161
|
+
def decorator(fn):
|
|
162
|
+
return fn
|
|
163
|
+
|
|
164
|
+
return decorator
|
|
165
|
+
|
|
166
|
+
|
|
114
167
|
class IdKeyedDict(collections.abc.MutableMapping):
|
|
115
|
-
def __init__(self):
|
|
168
|
+
def __init__(self) -> None:
|
|
116
169
|
# Confusingly, we hold both the key object and value object in
|
|
117
170
|
# our inner dict. Holding the key object ensures that we don't
|
|
118
171
|
# GC the key object, which could lead to reusing the same id()
|
|
@@ -129,7 +182,9 @@ class IdKeyedDict(collections.abc.MutableMapping):
|
|
|
129
182
|
return self.inner.__delitem__(id(k))
|
|
130
183
|
|
|
131
184
|
def __iter__(self):
|
|
132
|
-
|
|
185
|
+
raise NotImplementedError
|
|
186
|
+
# No use cases for this yet, but we could do something like this:
|
|
187
|
+
# return (actual_key_object for actual_key_object, _ in self.inner.values())
|
|
133
188
|
|
|
134
189
|
def __len__(self):
|
|
135
190
|
return len(self.inner)
|
|
@@ -160,23 +215,30 @@ def sourcelines(thing: object) -> Tuple[str, int, Tuple[str, ...]]:
|
|
|
160
215
|
|
|
161
216
|
def frame_summary_for_fn(
|
|
162
217
|
fn: Callable, frames: traceback.StackSummary
|
|
163
|
-
) -> Tuple[str, int]:
|
|
218
|
+
) -> Tuple[Optional[str], int]:
|
|
164
219
|
fn_name = fn.__name__
|
|
165
|
-
|
|
220
|
+
try:
|
|
221
|
+
fn_file = getsourcefile(fn) # Can return None OR raise TypeError
|
|
222
|
+
except TypeError:
|
|
223
|
+
fn_file = None
|
|
224
|
+
if fn_file is None:
|
|
225
|
+
return (None, 0)
|
|
166
226
|
for frame in reversed(frames):
|
|
167
227
|
if frame.name == fn_name and samefile(frame.filename, fn_file):
|
|
168
228
|
return (frame.filename, frame.lineno or 1)
|
|
169
229
|
return sourcelines(fn)[:2]
|
|
170
230
|
|
|
171
231
|
|
|
172
|
-
def set_debug(
|
|
173
|
-
global
|
|
174
|
-
|
|
232
|
+
def set_debug(new_debug: bool, output: TextIO = sys.stderr):
|
|
233
|
+
global _DEBUG_STREAM
|
|
234
|
+
if new_debug:
|
|
235
|
+
_DEBUG_STREAM = output
|
|
236
|
+
else:
|
|
237
|
+
_DEBUG_STREAM = None
|
|
175
238
|
|
|
176
239
|
|
|
177
240
|
def in_debug() -> bool:
|
|
178
|
-
|
|
179
|
-
return _DEBUG
|
|
241
|
+
return bool(_DEBUG_STREAM)
|
|
180
242
|
|
|
181
243
|
|
|
182
244
|
def debug(*a):
|
|
@@ -190,7 +252,7 @@ def debug(*a):
|
|
|
190
252
|
symbolic will change the path exploration that CrossHair normally takes, leading to
|
|
191
253
|
different outcomes in verbose and non-verbose mode.
|
|
192
254
|
"""
|
|
193
|
-
if not
|
|
255
|
+
if not _DEBUG_STREAM:
|
|
194
256
|
return
|
|
195
257
|
with NoTracing():
|
|
196
258
|
stack = traceback.extract_stack()
|
|
@@ -198,9 +260,9 @@ def debug(*a):
|
|
|
198
260
|
indent = len(stack) - 3
|
|
199
261
|
print(
|
|
200
262
|
"{:06.3f}|{}|{}() {}".format(
|
|
201
|
-
|
|
263
|
+
monotonic(), " " * indent, frame.name, " ".join(map(str, a))
|
|
202
264
|
),
|
|
203
|
-
file=
|
|
265
|
+
file=_DEBUG_STREAM,
|
|
204
266
|
)
|
|
205
267
|
|
|
206
268
|
|
|
@@ -214,43 +276,32 @@ def warn(*a):
|
|
|
214
276
|
debug("WARNING:", *a)
|
|
215
277
|
|
|
216
278
|
|
|
217
|
-
TracebackLike = Union[None, TracebackType,
|
|
218
|
-
|
|
279
|
+
TracebackLike = Union[None, TracebackType, Sequence[traceback.FrameSummary]]
|
|
219
280
|
|
|
220
|
-
def test_stack(tb: TracebackLike = None) -> str:
|
|
221
|
-
return tiny_stack(tb, ignore=re.compile("^$"))
|
|
222
281
|
|
|
223
|
-
|
|
224
|
-
|
|
282
|
+
def ch_stack(
|
|
283
|
+
tb: TracebackLike = None,
|
|
284
|
+
last_n_frames: int = sys.maxsize,
|
|
285
|
+
currently_handling: Optional[BaseException] = None,
|
|
286
|
+
) -> str:
|
|
225
287
|
with NoTracing():
|
|
226
|
-
if
|
|
227
|
-
|
|
288
|
+
if currently_handling:
|
|
289
|
+
if tb is not None:
|
|
290
|
+
raise CrossHairInternal
|
|
291
|
+
lower_frames = traceback.extract_tb(currently_handling.__traceback__)
|
|
292
|
+
higher_frames = traceback.extract_stack()[:-2]
|
|
293
|
+
frames: Sequence[traceback.FrameSummary] = higher_frames + lower_frames
|
|
294
|
+
elif tb is None:
|
|
295
|
+
frames = traceback.extract_stack()[:-1]
|
|
228
296
|
elif isinstance(tb, TracebackType):
|
|
229
297
|
frames = traceback.extract_tb(tb)
|
|
230
298
|
else:
|
|
231
299
|
frames = tb
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
def _tiny_stack_frames(
|
|
236
|
-
frames: Iterable[traceback.FrameSummary],
|
|
237
|
-
ignore=re.compile(r".*\b(crosshair|z3|typing_inspect|unittest)\b"),
|
|
238
|
-
) -> str:
|
|
239
|
-
output: List[str] = []
|
|
240
|
-
ignore_ct = 0
|
|
241
|
-
for frame in frames:
|
|
242
|
-
if ignore.match(frame.filename) and not frame.filename.endswith("_test.py"):
|
|
243
|
-
ignore_ct += 1
|
|
244
|
-
else:
|
|
245
|
-
if ignore_ct > 0:
|
|
246
|
-
if output:
|
|
247
|
-
output.append(f"(...x{ignore_ct})")
|
|
248
|
-
ignore_ct = 0
|
|
300
|
+
output: List[str] = []
|
|
301
|
+
for frame in frames[-last_n_frames:]:
|
|
249
302
|
filename = os.path.split(frame.filename)[1]
|
|
250
303
|
output.append(f"({frame.name} {filename}:{frame.lineno})")
|
|
251
|
-
|
|
252
|
-
output.append(f"(...x{ignore_ct})")
|
|
253
|
-
return " ".join(output)
|
|
304
|
+
return " ".join(output)
|
|
254
305
|
|
|
255
306
|
|
|
256
307
|
class ErrorDuringImport(Exception):
|
|
@@ -300,17 +351,19 @@ def load_file(filename: str) -> types.ModuleType:
|
|
|
300
351
|
raise ErrorDuringImport from e
|
|
301
352
|
|
|
302
353
|
|
|
303
|
-
|
|
354
|
+
@contextlib.contextmanager
|
|
355
|
+
def imported_alternative(name: str, suppress: Tuple[str, ...] = ()):
|
|
304
356
|
"""Load an alternative version of a module with some modules suppressed."""
|
|
305
357
|
modules = sys.modules
|
|
306
358
|
orig_module = importlib.import_module(name) # Ensure the regular version is loaded
|
|
307
|
-
prev = modules.copy()
|
|
308
359
|
modules.update({k: None for k in suppress}) # type: ignore
|
|
360
|
+
alternative = importlib.reload(orig_module)
|
|
309
361
|
try:
|
|
310
|
-
|
|
362
|
+
yield
|
|
311
363
|
finally:
|
|
312
|
-
|
|
313
|
-
|
|
364
|
+
for k in suppress:
|
|
365
|
+
del modules[k]
|
|
366
|
+
importlib.reload(alternative)
|
|
314
367
|
|
|
315
368
|
|
|
316
369
|
def format_boundargs_as_dictionary(bound_args: BoundArguments) -> str:
|
|
@@ -320,7 +373,7 @@ def format_boundargs_as_dictionary(bound_args: BoundArguments) -> str:
|
|
|
320
373
|
|
|
321
374
|
def format_boundargs(bound_args: BoundArguments) -> str:
|
|
322
375
|
arg_strings: List[str] = []
|
|
323
|
-
for
|
|
376
|
+
for name, param in bound_args.signature.parameters.items():
|
|
324
377
|
param_kind = param.kind
|
|
325
378
|
vals = bound_args.arguments.get(name, param.default)
|
|
326
379
|
if param_kind == Parameter.VAR_POSITIONAL:
|
|
@@ -348,12 +401,13 @@ def eval_friendly_repr(obj: object) -> str:
|
|
|
348
401
|
assert not is_tracing()
|
|
349
402
|
with ResumedTracing(), EvalFriendlyReprContext() as ctx:
|
|
350
403
|
try:
|
|
404
|
+
# TODO: probably only the repr should have tracing enabled
|
|
351
405
|
return ctx.cleanup(repr(obj))
|
|
352
406
|
except Exception as e:
|
|
353
407
|
if isinstance(e, (IgnoreAttempt, UnexploredPath)):
|
|
354
408
|
raise
|
|
355
409
|
debug("Repr failed, ", type(e), ":", str(e))
|
|
356
|
-
debug("Repr failed at:",
|
|
410
|
+
debug("Repr failed at:", ch_stack(currently_handling=e))
|
|
357
411
|
return UNABLE_TO_REPR_TEXT
|
|
358
412
|
|
|
359
413
|
|
|
@@ -438,7 +492,7 @@ class EvalFriendlyReprContext:
|
|
|
438
492
|
|
|
439
493
|
def __enter__(self):
|
|
440
494
|
if not is_tracing():
|
|
441
|
-
raise
|
|
495
|
+
raise CrossHairInternal
|
|
442
496
|
OVERRIDES: Dict[type, Callable[[Any], Union[str, ReferencedIdentifier]]] = {
|
|
443
497
|
object: lambda o: "object()",
|
|
444
498
|
list: lambda o: f"[{', '.join(map(repr, o))}]", # (de-optimize C-level repr)
|
|
@@ -454,9 +508,9 @@ class EvalFriendlyReprContext:
|
|
|
454
508
|
oid = id(obj)
|
|
455
509
|
typ = type(obj)
|
|
456
510
|
if obj in instance_overrides:
|
|
457
|
-
repr_fn: Callable[
|
|
458
|
-
[
|
|
459
|
-
|
|
511
|
+
repr_fn: Callable[[Any], Union[str, ReferencedIdentifier]] = (
|
|
512
|
+
instance_overrides[obj]
|
|
513
|
+
)
|
|
460
514
|
elif typ == float:
|
|
461
515
|
if math.isfinite(obj):
|
|
462
516
|
repr_fn = repr
|
|
@@ -494,7 +548,7 @@ class EvalFriendlyReprContext:
|
|
|
494
548
|
counts = collections.Counter(re.compile(r"\b_ch_efr_\d+_\b").findall(output))
|
|
495
549
|
assignment_remaps = {}
|
|
496
550
|
nextvarnum = 1
|
|
497
|
-
for
|
|
551
|
+
for varname, count in counts.items():
|
|
498
552
|
if count > 1:
|
|
499
553
|
assignment_remaps[varname + ":="] = f"v{nextvarnum}:="
|
|
500
554
|
assignment_remaps[varname] = f"v{nextvarnum}"
|
|
@@ -576,10 +630,16 @@ class DynamicScopeVar(Generic[_T]):
|
|
|
576
630
|
|
|
577
631
|
class AttributeHolder:
|
|
578
632
|
def __init__(self, attrs: Mapping[str, object]):
|
|
579
|
-
for
|
|
633
|
+
for k, v in attrs.items():
|
|
580
634
|
self.__dict__[k] = v
|
|
581
635
|
|
|
582
636
|
|
|
637
|
+
class CrossHairValue:
|
|
638
|
+
"""Base class for values that are pretending to be other values."""
|
|
639
|
+
|
|
640
|
+
pass
|
|
641
|
+
|
|
642
|
+
|
|
583
643
|
class ControlFlowException(BaseException):
|
|
584
644
|
# CrossHair sometimes uses exceptions to abort a path mid-execution.
|
|
585
645
|
# We extend such exceptions from BaseException instead of Exception,
|
|
@@ -587,11 +647,15 @@ class ControlFlowException(BaseException):
|
|
|
587
647
|
pass
|
|
588
648
|
|
|
589
649
|
|
|
590
|
-
class
|
|
650
|
+
class CrossHairInternal(ControlFlowException):
|
|
591
651
|
def __init__(self, *a):
|
|
592
652
|
ControlFlowException.__init__(self, *a)
|
|
593
|
-
|
|
594
|
-
|
|
653
|
+
if in_debug():
|
|
654
|
+
debug("CrossHairInternal:", str(self))
|
|
655
|
+
debug("CrossHairInternal stack trace:")
|
|
656
|
+
for entry in traceback.format_stack()[:-1]:
|
|
657
|
+
for line in entry.splitlines():
|
|
658
|
+
debug("", line)
|
|
595
659
|
|
|
596
660
|
|
|
597
661
|
class UnexploredPath(ControlFlowException):
|
|
@@ -604,6 +668,10 @@ class UnknownSatisfiability(UnexploredPath):
|
|
|
604
668
|
debug("UnknownSatisfiability", str(self))
|
|
605
669
|
|
|
606
670
|
|
|
671
|
+
class NotDeterministic(Exception):
|
|
672
|
+
pass
|
|
673
|
+
|
|
674
|
+
|
|
607
675
|
class PathTimeout(UnexploredPath):
|
|
608
676
|
pass
|
|
609
677
|
|
|
@@ -618,28 +686,42 @@ class IgnoreAttempt(ControlFlowException):
|
|
|
618
686
|
def __init__(self, *a):
|
|
619
687
|
if in_debug():
|
|
620
688
|
debug(f"IgnoreAttempt", *a)
|
|
621
|
-
debug("IgnoreAttempt stack:",
|
|
689
|
+
debug("IgnoreAttempt stack:", ch_stack())
|
|
622
690
|
|
|
623
691
|
|
|
624
|
-
|
|
692
|
+
if (3, 10) <= sys.version_info < (3, 14):
|
|
693
|
+
# Specialize some definitions for the Python versions where
|
|
694
|
+
# typing.Union != types.UnionType:
|
|
625
695
|
|
|
696
|
+
def origin_of(typ: Type) -> Type:
|
|
697
|
+
if hasattr(typ, "__origin__"):
|
|
698
|
+
return typ.__origin__
|
|
699
|
+
elif isinstance(typ, types.UnionType):
|
|
700
|
+
return cast(Type, Union)
|
|
701
|
+
else:
|
|
702
|
+
return typ
|
|
626
703
|
|
|
627
|
-
def
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
704
|
+
def type_args_of(typ: Type) -> Tuple[Type, ...]:
|
|
705
|
+
if getattr(typ, "__args__", None):
|
|
706
|
+
if isinstance(typ, types.UnionType):
|
|
707
|
+
return typ.__args__
|
|
708
|
+
return typing_inspect.get_args(typ, evaluate=True)
|
|
709
|
+
else:
|
|
710
|
+
return ()
|
|
634
711
|
|
|
712
|
+
else:
|
|
635
713
|
|
|
636
|
-
def type_args_of(typ: Type) -> Tuple[Type, ...]:
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
714
|
+
def type_args_of(typ: Type) -> Tuple[Type, ...]:
|
|
715
|
+
if getattr(typ, "__args__", None):
|
|
716
|
+
return typing_inspect.get_args(typ, evaluate=True)
|
|
717
|
+
else:
|
|
718
|
+
return ()
|
|
719
|
+
|
|
720
|
+
def origin_of(typ: Type) -> Type:
|
|
721
|
+
origin = getattr(typ, "__origin__", None)
|
|
722
|
+
# 3.14 unifies typing.Union and types.Union, so that's good!
|
|
723
|
+
# But a of 3.14.0a6, types.Union.__origin__ yields a data descriptor, so we need to check that.
|
|
724
|
+
return typ if origin is None or isdatadescriptor(origin) else origin
|
|
643
725
|
|
|
644
726
|
|
|
645
727
|
def type_arg_of(typ: Type, index: int) -> Type:
|
crosshair/util_test.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import sys
|
|
2
2
|
import traceback
|
|
3
|
+
import types
|
|
3
4
|
from dataclasses import dataclass
|
|
4
5
|
from enum import Enum
|
|
5
6
|
from inspect import signature
|
|
@@ -10,11 +11,11 @@ import pytest
|
|
|
10
11
|
|
|
11
12
|
from crosshair.tracers import PatchingModule
|
|
12
13
|
from crosshair.util import (
|
|
13
|
-
|
|
14
|
+
CrossHairInternal,
|
|
14
15
|
DynamicScopeVar,
|
|
15
|
-
_tiny_stack_frames,
|
|
16
16
|
eval_friendly_repr,
|
|
17
17
|
format_boundargs,
|
|
18
|
+
imported_alternative,
|
|
18
19
|
is_pure_python,
|
|
19
20
|
renamed_function,
|
|
20
21
|
sourcelines,
|
|
@@ -77,23 +78,18 @@ def test_dynamic_scope_var_with_exception():
|
|
|
77
78
|
assert var.get_if_in_scope() is None
|
|
78
79
|
|
|
79
80
|
|
|
80
|
-
def
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
FS("/other/package/d.py", 4, "food"),
|
|
88
|
-
FS("/crosshair/e.py", 5, "fooe"),
|
|
89
|
-
]
|
|
90
|
-
)
|
|
91
|
-
assert s == "(fooa a.py:1) (...x2) (food d.py:4) (...x1)"
|
|
81
|
+
def test_imported_alternative():
|
|
82
|
+
import heapq
|
|
83
|
+
|
|
84
|
+
assert type(heapq.heapify) == types.BuiltinFunctionType
|
|
85
|
+
with imported_alternative("heapq", ("_heapq",)):
|
|
86
|
+
assert type(heapq.heapify) == types.FunctionType
|
|
87
|
+
assert type(heapq.heapify) == types.BuiltinFunctionType
|
|
92
88
|
|
|
93
89
|
|
|
94
90
|
class UnhashableCallable:
|
|
95
91
|
def __hash__(self):
|
|
96
|
-
raise
|
|
92
|
+
raise CrossHairInternal("Do not hash")
|
|
97
93
|
|
|
98
94
|
def __call__(self):
|
|
99
95
|
return 42
|
|
@@ -144,10 +140,8 @@ def test_eval_friendly_repr():
|
|
|
144
140
|
# Special float values:
|
|
145
141
|
assert eval_friendly_repr(float("nan")) == 'float("nan")'
|
|
146
142
|
# MethodDescriptorType
|
|
147
|
-
assert (
|
|
148
|
-
|
|
149
|
-
== "RandomState.randint"
|
|
150
|
-
)
|
|
143
|
+
assert isinstance(str.join, types.MethodDescriptorType)
|
|
144
|
+
assert eval_friendly_repr(str.join) == "str.join"
|
|
151
145
|
# enums:
|
|
152
146
|
assert eval_friendly_repr(Color.RED) == "Color.RED"
|
|
153
147
|
# basic dataclass
|
|
@@ -165,7 +159,6 @@ def test_eval_friendly_repr():
|
|
|
165
159
|
assert repr(Color.RED) == "<Color.RED: 0>"
|
|
166
160
|
|
|
167
161
|
|
|
168
|
-
@pytest.mark.skipif(sys.version_info < (3, 8), reason="Python 3.8+ required")
|
|
169
162
|
def test_renamed_function():
|
|
170
163
|
def crash_on_seven(x):
|
|
171
164
|
if x == 7:
|
crosshair/watcher.py
CHANGED
|
@@ -35,7 +35,7 @@ from crosshair.core_and_libs import (
|
|
|
35
35
|
from crosshair.fnutil import NotFound, walk_paths
|
|
36
36
|
from crosshair.options import AnalysisOptionSet
|
|
37
37
|
from crosshair.util import (
|
|
38
|
-
|
|
38
|
+
CrossHairInternal,
|
|
39
39
|
ErrorDuringImport,
|
|
40
40
|
debug,
|
|
41
41
|
load_file,
|
|
@@ -142,7 +142,7 @@ def pool_worker_main() -> None:
|
|
|
142
142
|
output: WorkItemOutput = (filename, stats, messages)
|
|
143
143
|
print(serialize(output))
|
|
144
144
|
except BaseException as e:
|
|
145
|
-
raise
|
|
145
|
+
raise CrossHairInternal("Worker failed while analyzing " + str(filename)) from e
|
|
146
146
|
|
|
147
147
|
|
|
148
148
|
class Pool:
|
|
@@ -244,8 +244,8 @@ class Watcher:
|
|
|
244
244
|
pool = self._pool
|
|
245
245
|
for filename, _ in sorted(self._modtimes.items(), key=lambda pair: -pair[1]):
|
|
246
246
|
worker_timeout = max(
|
|
247
|
-
10.0, max_uninteresting_iterations *
|
|
248
|
-
) # TODO: times
|
|
247
|
+
10.0, max_uninteresting_iterations * 1_000.0
|
|
248
|
+
) # TODO: times 1000? is that right?
|
|
249
249
|
iter_options = AnalysisOptionSet(
|
|
250
250
|
max_uninteresting_iterations=max_uninteresting_iterations,
|
|
251
251
|
)
|
crosshair/z3util.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: crosshair-tool
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.100
|
|
4
4
|
Summary: Analyze Python code for correctness using symbolic execution.
|
|
5
5
|
Home-page: https://github.com/pschanely/CrossHair
|
|
6
6
|
Author: Phillip Schanely
|
|
@@ -8,64 +8,73 @@ Author-email: pschanely+vE7F@gmail.com
|
|
|
8
8
|
License: MIT
|
|
9
9
|
Classifier: Development Status :: 3 - Alpha
|
|
10
10
|
Classifier: Intended Audience :: Developers
|
|
11
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
12
11
|
Classifier: Operating System :: OS Independent
|
|
13
12
|
Classifier: Programming Language :: Python :: 3
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.7
|
|
15
13
|
Classifier: Programming Language :: Python :: 3.8
|
|
16
14
|
Classifier: Programming Language :: Python :: 3.9
|
|
17
15
|
Classifier: Programming Language :: Python :: 3.10
|
|
18
16
|
Classifier: Programming Language :: Python :: 3.11
|
|
19
17
|
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
19
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
21
20
|
Classifier: Topic :: Software Development :: Quality Assurance
|
|
22
21
|
Classifier: Topic :: Software Development :: Testing
|
|
23
|
-
Requires-Python: >=3.
|
|
22
|
+
Requires-Python: >=3.8
|
|
24
23
|
Description-Content-Type: text/markdown
|
|
25
24
|
License-File: LICENSE
|
|
26
25
|
Requires-Dist: packaging
|
|
27
|
-
Requires-Dist: typing-inspect
|
|
28
|
-
Requires-Dist:
|
|
29
|
-
Requires-Dist: z3-solver
|
|
30
|
-
Requires-Dist:
|
|
31
|
-
Requires-Dist: pygls
|
|
32
|
-
Requires-Dist: typeshed-client
|
|
26
|
+
Requires-Dist: typing-inspect>=0.7.1
|
|
27
|
+
Requires-Dist: typing_extensions>=3.10.0
|
|
28
|
+
Requires-Dist: z3-solver>=4.13.0.0
|
|
29
|
+
Requires-Dist: importlib_metadata>=4.0.0
|
|
30
|
+
Requires-Dist: pygls>=1.0.0
|
|
31
|
+
Requires-Dist: typeshed-client>=2.0.5
|
|
33
32
|
Provides-Extra: dev
|
|
34
|
-
Requires-Dist: autodocsumm
|
|
35
|
-
Requires-Dist: black
|
|
36
|
-
Requires-Dist: deal
|
|
37
|
-
Requires-Dist:
|
|
38
|
-
Requires-Dist:
|
|
39
|
-
Requires-Dist:
|
|
40
|
-
Requires-Dist:
|
|
41
|
-
Requires-Dist:
|
|
42
|
-
Requires-Dist:
|
|
43
|
-
Requires-Dist: pytest
|
|
44
|
-
Requires-Dist: pytest-xdist
|
|
45
|
-
Requires-Dist:
|
|
46
|
-
Requires-Dist: sphinx
|
|
47
|
-
Requires-Dist:
|
|
48
|
-
Requires-Dist:
|
|
49
|
-
Requires-Dist:
|
|
50
|
-
|
|
51
|
-
|
|
33
|
+
Requires-Dist: autodocsumm<1,>=0.2.2; extra == "dev"
|
|
34
|
+
Requires-Dist: black==25.9.0; extra == "dev"
|
|
35
|
+
Requires-Dist: deal>=4.13.0; extra == "dev"
|
|
36
|
+
Requires-Dist: icontract>=2.4.0; extra == "dev"
|
|
37
|
+
Requires-Dist: isort==5.11.5; extra == "dev"
|
|
38
|
+
Requires-Dist: mypy==1.18.1; extra == "dev"
|
|
39
|
+
Requires-Dist: numpy==1.24.0; python_version < "3.12" and extra == "dev"
|
|
40
|
+
Requires-Dist: numpy==2.3.3; python_version >= "3.12" and extra == "dev"
|
|
41
|
+
Requires-Dist: pre-commit~=2.20; extra == "dev"
|
|
42
|
+
Requires-Dist: pytest; extra == "dev"
|
|
43
|
+
Requires-Dist: pytest-xdist; extra == "dev"
|
|
44
|
+
Requires-Dist: setuptools; extra == "dev"
|
|
45
|
+
Requires-Dist: sphinx>=3.4.3; extra == "dev"
|
|
46
|
+
Requires-Dist: sphinx-rtd-theme>=0.5.1; extra == "dev"
|
|
47
|
+
Requires-Dist: rst2pdf>=0.102; extra == "dev"
|
|
48
|
+
Requires-Dist: z3-solver==4.15.4.0; extra == "dev"
|
|
49
|
+
Dynamic: author
|
|
50
|
+
Dynamic: author-email
|
|
51
|
+
Dynamic: classifier
|
|
52
|
+
Dynamic: description
|
|
53
|
+
Dynamic: description-content-type
|
|
54
|
+
Dynamic: home-page
|
|
55
|
+
Dynamic: license
|
|
56
|
+
Dynamic: license-file
|
|
57
|
+
Dynamic: provides-extra
|
|
58
|
+
Dynamic: requires-dist
|
|
59
|
+
Dynamic: requires-python
|
|
60
|
+
Dynamic: summary
|
|
52
61
|
|
|
53
62
|
<img src="https://raw.githubusercontent.com/pschanely/CrossHair/main/doc/source/logo-gray.png" width="5%" align="left">
|
|
54
63
|
|
|
55
64
|
# CrossHair
|
|
56
65
|
|
|
57
|
-
[](https://pepy.tech/project/crosshair-tool)
|
|
66
|
+
[](https://discord.gg/rUeTaYTWbb)
|
|
67
|
+
[](https://github.com/pschanely/CrossHair/actions?query=workflow%3ACheck+event%3Apush)
|
|
68
|
+
[](https://pepy.tech/project/crosshair-tool)
|
|
60
69
|
|
|
61
70
|
An analysis tool for Python that blurs the line between testing and
|
|
62
71
|
type systems.
|
|
63
72
|
|
|
64
73
|
> **_THE LATEST NEWS:_**
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
74
|
+
Python's most popular property-based testing tool,
|
|
75
|
+
[Hypothesis](https://hypothesis.readthedocs.io/en/latest/),
|
|
76
|
+
now supports running CrossHair as an
|
|
77
|
+
[optional backend](https://hypothesis.readthedocs.io/en/latest/strategies.html#alternative-backends)!
|
|
69
78
|
|
|
70
79
|
|
|
71
80
|
If you have a function with
|