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,80 @@
|
|
|
1
|
+
import math
|
|
2
|
+
from fractions import Fraction
|
|
3
|
+
|
|
4
|
+
from crosshair.core import deep_realize
|
|
5
|
+
from crosshair.core_and_libs import proxy_for_type
|
|
6
|
+
from crosshair.statespace import POST_FAIL
|
|
7
|
+
from crosshair.test_util import check_states
|
|
8
|
+
from crosshair.tracers import ResumedTracing, is_tracing
|
|
9
|
+
from crosshair.util import CrossHairInternal
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_fraction_realize(space):
|
|
13
|
+
n = proxy_for_type(int, "n")
|
|
14
|
+
d = proxy_for_type(int, "d")
|
|
15
|
+
with ResumedTracing():
|
|
16
|
+
space.add(d != 0)
|
|
17
|
+
deep_realize(Fraction(n, d))
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class UserFraction(Fraction):
|
|
21
|
+
def __int__(self):
|
|
22
|
+
if not is_tracing():
|
|
23
|
+
raise CrossHairInternal("tracing required while in user code")
|
|
24
|
+
return 1
|
|
25
|
+
|
|
26
|
+
def __round__(self, *a, **kw):
|
|
27
|
+
if not is_tracing():
|
|
28
|
+
raise CrossHairInternal("tracing required while in user code")
|
|
29
|
+
return super().__round__(*a, **kw)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_user_fraction_tracing(space):
|
|
33
|
+
n = proxy_for_type(int, "n")
|
|
34
|
+
d = proxy_for_type(int, "d")
|
|
35
|
+
with ResumedTracing():
|
|
36
|
+
space.add(d != 0)
|
|
37
|
+
fraction = UserFraction(n, d)
|
|
38
|
+
round(fraction) # (works via with_realized_args)
|
|
39
|
+
int(fraction) # (custom interception)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_fraction_copy_doesnt_realize(space):
|
|
43
|
+
n = proxy_for_type(int, "n")
|
|
44
|
+
with ResumedTracing():
|
|
45
|
+
space.add(n >= 0)
|
|
46
|
+
f1 = Fraction(n, 1)
|
|
47
|
+
f2 = f1.__copy__()
|
|
48
|
+
assert space.is_possible(f2.numerator == 2)
|
|
49
|
+
assert space.is_possible(f2.numerator == 3)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_fraction_can_be_one_half() -> None:
|
|
53
|
+
def f(f: Fraction):
|
|
54
|
+
"""post:_"""
|
|
55
|
+
return f != Fraction(1, 2)
|
|
56
|
+
|
|
57
|
+
check_states(f, POST_FAIL)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def test_int_from_fraction(space) -> None:
|
|
61
|
+
n = proxy_for_type(int, "n")
|
|
62
|
+
d = proxy_for_type(int, "d")
|
|
63
|
+
with ResumedTracing():
|
|
64
|
+
space.add(d > 0)
|
|
65
|
+
space.add(n == d * 3)
|
|
66
|
+
f = Fraction(n, d)
|
|
67
|
+
assert space.is_possible(d == 3)
|
|
68
|
+
assert not space.is_possible(f.denominator != 1)
|
|
69
|
+
truncated_fraction = int(f)
|
|
70
|
+
assert space.is_possible(truncated_fraction == 3)
|
|
71
|
+
assert not space.is_possible(truncated_fraction != 3)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def test_fraction_ceil_does_not_explode(space) -> None:
|
|
75
|
+
f = proxy_for_type(Fraction, "f")
|
|
76
|
+
with ResumedTracing():
|
|
77
|
+
math.ceil(f)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# TODO: The math module is patched with deep_realize, but many of the usual operators may not work. Test.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from functools import _lru_cache_wrapper, partial, reduce, update_wrapper, wraps
|
|
2
|
+
|
|
3
|
+
from crosshair.core import register_patch
|
|
4
|
+
|
|
5
|
+
# TODO: deal with lru_cache (note it needs to be intercepted at import-time)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _partial(func, *a1, **kw1):
|
|
9
|
+
if callable(func):
|
|
10
|
+
# We make a do-nothing wrapper to ensure that the tracer has a crack
|
|
11
|
+
# at this function when it is called.
|
|
12
|
+
def wrapper(*a2, **kw2):
|
|
13
|
+
return func(*a2, **kw2)
|
|
14
|
+
|
|
15
|
+
update_wrapper(wrapper, func)
|
|
16
|
+
return partial(wrapper, *a1, **kw1)
|
|
17
|
+
else:
|
|
18
|
+
raise TypeError
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _reduce(function, *a, **kw):
|
|
22
|
+
return reduce(lambda x, y: function(x, y), *a, **kw)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def make_registrations():
|
|
26
|
+
register_patch(partial, _partial)
|
|
27
|
+
register_patch(reduce, _reduce)
|
|
28
|
+
|
|
29
|
+
def call_with_skipped_cache(self, *a, **kw):
|
|
30
|
+
if not isinstance(self, _lru_cache_wrapper):
|
|
31
|
+
raise TypeError
|
|
32
|
+
return self.__wrapped__(*a, **kw)
|
|
33
|
+
|
|
34
|
+
register_patch(_lru_cache_wrapper.__call__, call_with_skipped_cache)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
import inspect
|
|
3
|
+
|
|
4
|
+
from crosshair.core import proxy_for_type, standalone_statespace
|
|
5
|
+
from crosshair.libimpl.builtinslib import LazyIntSymbolicStr
|
|
6
|
+
from crosshair.tracers import NoTracing, ResumedTracing
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def test_partial(space):
|
|
10
|
+
abc = LazyIntSymbolicStr(list(map(ord, "abc")))
|
|
11
|
+
xyz = LazyIntSymbolicStr(list(map(ord, "xyz")))
|
|
12
|
+
with ResumedTracing():
|
|
13
|
+
joiner = functools.partial(str.join, ",")
|
|
14
|
+
ret = joiner([abc, xyz])
|
|
15
|
+
assert ret == "abc,xyz"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_partial_is_interceptable(space):
|
|
19
|
+
x = proxy_for_type(str, "x")
|
|
20
|
+
y = proxy_for_type(str, "y")
|
|
21
|
+
with ResumedTracing():
|
|
22
|
+
joiner = functools.partial(str.startswith, x)
|
|
23
|
+
# Ensure we don't explode
|
|
24
|
+
list(map(joiner, ["foo", y]))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_partial_arg_is_inspectable(space):
|
|
28
|
+
with ResumedTracing():
|
|
29
|
+
joiner = functools.partial(str.join, ",")
|
|
30
|
+
assert isinstance(joiner, functools.partial)
|
|
31
|
+
assert inspect.getdoc(joiner.func) == inspect.getdoc(str.join)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_reduce():
|
|
35
|
+
with standalone_statespace as space:
|
|
36
|
+
with NoTracing():
|
|
37
|
+
string = LazyIntSymbolicStr(list(map(ord, "12 oofoo 12")))
|
|
38
|
+
tostrip = LazyIntSymbolicStr(list(map(ord, "2")))
|
|
39
|
+
ret = functools.reduce(str.strip, [string, "1", "2"])
|
|
40
|
+
assert ret == " oofoo 1"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
_global_state = [42]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@functools.lru_cache()
|
|
47
|
+
def whaa(x: int) -> int:
|
|
48
|
+
_global_state[0] += 1
|
|
49
|
+
return _global_state[0]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_lru_cache_is_ignored():
|
|
53
|
+
with standalone_statespace as space:
|
|
54
|
+
assert whaa(0) == 43
|
|
55
|
+
assert whaa(1) == 44
|
|
56
|
+
assert whaa(1) == 45
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
from crosshair.core import register_patch, with_realized_args
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def make_registrations():
|
|
8
|
+
if sys.version_info < (3, 12):
|
|
9
|
+
# As of Python 3.12, SymbolicBytes can implement __buffer__() to be compatible
|
|
10
|
+
# with hash functions. Prior to that, we patch them manually:
|
|
11
|
+
|
|
12
|
+
to_patch = {hashlib.new: None} # we don't use a set so that the patch order
|
|
13
|
+
# is deterministic, which matters for the patch_equivalence_test when
|
|
14
|
+
# run under pytest -n
|
|
15
|
+
for algo_string in sorted(hashlib.algorithms_available):
|
|
16
|
+
hash_constructor = getattr(hashlib, algo_string, None)
|
|
17
|
+
if hash_constructor is not None:
|
|
18
|
+
to_patch[hash_constructor] = None
|
|
19
|
+
try:
|
|
20
|
+
example_instance = hashlib.new(algo_string)
|
|
21
|
+
except ValueError:
|
|
22
|
+
if sys.version_info < (3, 9):
|
|
23
|
+
# in 3.8, some "available" algorithms aren't available
|
|
24
|
+
continue
|
|
25
|
+
else:
|
|
26
|
+
raise
|
|
27
|
+
update_method = getattr((type(example_instance)), "update")
|
|
28
|
+
to_patch[update_method] = None
|
|
29
|
+
for fn in to_patch:
|
|
30
|
+
register_patch(fn, with_realized_args(fn))
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
|
|
3
|
+
from crosshair.core import proxy_for_type, standalone_statespace
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_sha384():
|
|
7
|
+
with standalone_statespace:
|
|
8
|
+
x = proxy_for_type(bytes, "x")
|
|
9
|
+
hashlib.new("sha384", x)
|
|
10
|
+
hashlib.sha384(x)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def test_blake_via_update():
|
|
14
|
+
with standalone_statespace:
|
|
15
|
+
x = proxy_for_type(bytes, "x")
|
|
16
|
+
h = hashlib.blake2b()
|
|
17
|
+
h.update(x)
|
|
18
|
+
h.hexdigest()
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
import heapq
|
|
3
|
+
import types
|
|
4
|
+
|
|
5
|
+
import _heapq
|
|
6
|
+
|
|
7
|
+
from crosshair.core import register_patch
|
|
8
|
+
from crosshair.util import debug, imported_alternative, name_of_type
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _check_first_arg_is_list(fn):
|
|
12
|
+
functools.wraps(fn)
|
|
13
|
+
|
|
14
|
+
def wrapper(heap, *a, **kw):
|
|
15
|
+
if not isinstance(heap, list):
|
|
16
|
+
raise TypeError(
|
|
17
|
+
f"{fn.__name__} argument must be list, not {name_of_type(heap)}"
|
|
18
|
+
)
|
|
19
|
+
return fn(heap, *a, **kw)
|
|
20
|
+
|
|
21
|
+
return wrapper
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def make_registrations():
|
|
25
|
+
native_funcs = [
|
|
26
|
+
"_heapify_max",
|
|
27
|
+
"_heappop_max",
|
|
28
|
+
"_heapreplace_max",
|
|
29
|
+
"heapify",
|
|
30
|
+
"heappop",
|
|
31
|
+
"heappush",
|
|
32
|
+
"heappushpop",
|
|
33
|
+
"heapreplace",
|
|
34
|
+
]
|
|
35
|
+
with imported_alternative("heapq", ("_heapq",)):
|
|
36
|
+
|
|
37
|
+
# The pure python version doesn't always check argument types:
|
|
38
|
+
heapq.heappush = heapq.heappush
|
|
39
|
+
heapq.heappop = _check_first_arg_is_list(heapq.heappop)
|
|
40
|
+
|
|
41
|
+
pure_fns = {name: getattr(heapq, name) for name in native_funcs}
|
|
42
|
+
for name in native_funcs:
|
|
43
|
+
native_fn = getattr(heapq, name)
|
|
44
|
+
pure_fn = pure_fns[name]
|
|
45
|
+
assert isinstance(native_fn, types.BuiltinFunctionType)
|
|
46
|
+
assert isinstance(pure_fn, types.FunctionType)
|
|
47
|
+
register_patch(native_fn, _check_first_arg_is_list(pure_fn))
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import heapq
|
|
2
|
+
import sys
|
|
3
|
+
from typing import List
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from crosshair.core import proxy_for_type
|
|
8
|
+
from crosshair.tracers import ResumedTracing
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# TODO https://github.com/pschanely/CrossHair/issues/298
|
|
12
|
+
@pytest.mark.skip(
|
|
13
|
+
reason="heapq get reloaded somehow in parallel ci run, ruining the intercepts",
|
|
14
|
+
)
|
|
15
|
+
def test_heapify(space):
|
|
16
|
+
items = proxy_for_type(List[int], "items")
|
|
17
|
+
|
|
18
|
+
with ResumedTracing():
|
|
19
|
+
space.add(len(items) == 3)
|
|
20
|
+
heapq.heapify(items)
|
|
21
|
+
assert not space.is_possible(items[0] > items[1])
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from importlib._bootstrap import _find_and_load_unlocked # type: ignore
|
|
2
|
+
|
|
3
|
+
from crosshair import register_patch
|
|
4
|
+
from crosshair.tracers import NoTracing
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# Do not import with tracing enabled.
|
|
8
|
+
# (it's expensive, and won't behave deterministically)
|
|
9
|
+
#
|
|
10
|
+
# NOTE: The importlib._bootstrap._find_and_load_unlocked entry point covers both the
|
|
11
|
+
# regular import statement, as well as loads via the importlib module.
|
|
12
|
+
def __find_and_load_unlocked(*a, **kw):
|
|
13
|
+
with NoTracing():
|
|
14
|
+
return _find_and_load_unlocked(*a, **kw)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def make_registrations() -> None:
|
|
18
|
+
register_patch(_find_and_load_unlocked, __find_and_load_unlocked)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
from crosshair.main import Path, textwrap
|
|
5
|
+
from crosshair.test_util import simplefs
|
|
6
|
+
|
|
7
|
+
DYNAMIC_IMPORT = {
|
|
8
|
+
"__init__.py": "",
|
|
9
|
+
"outer.py": textwrap.dedent(
|
|
10
|
+
"""\
|
|
11
|
+
def outerfn(x: int) -> int:
|
|
12
|
+
''' post: _ == x '''
|
|
13
|
+
from .innerx import innerfn
|
|
14
|
+
return innerfn(x)
|
|
15
|
+
"""
|
|
16
|
+
),
|
|
17
|
+
"innerx.py": textwrap.dedent(
|
|
18
|
+
"""
|
|
19
|
+
from crosshair.tracers import is_tracing
|
|
20
|
+
assert not is_tracing()
|
|
21
|
+
def innerfn(x: int) -> int:
|
|
22
|
+
return x
|
|
23
|
+
"""
|
|
24
|
+
),
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_dynamic_import(tmp_path: Path):
|
|
29
|
+
# This imports another module while checking.
|
|
30
|
+
# The inner module asserts that tracing is not enabled.
|
|
31
|
+
simplefs(tmp_path, DYNAMIC_IMPORT)
|
|
32
|
+
ret = subprocess.run(
|
|
33
|
+
[sys.executable, "-m", "crosshair", "check", str(tmp_path / "outer.py")],
|
|
34
|
+
stdin=subprocess.DEVNULL,
|
|
35
|
+
capture_output=True,
|
|
36
|
+
text=True,
|
|
37
|
+
)
|
|
38
|
+
assert (ret.returncode, ret.stdout, ret.stderr) == (0, "", "")
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from io import SEEK_CUR, SEEK_END, SEEK_SET, StringIO, TextIOBase
|
|
3
|
+
from typing import Optional, Tuple, Union
|
|
4
|
+
|
|
5
|
+
from crosshair import ResumedTracing, SymbolicFactory, register_type
|
|
6
|
+
from crosshair.core import realize, register_patch
|
|
7
|
+
from crosshair.tracers import NoTracing
|
|
8
|
+
from crosshair.util import CrossHairValue, IgnoreAttempt
|
|
9
|
+
|
|
10
|
+
_UNIVERSAL_NEWLINE_RE = re.compile(r"(\r\n|\r|\n)")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class BackedStringIO(TextIOBase, CrossHairValue):
|
|
14
|
+
_contents: str
|
|
15
|
+
_pos: int
|
|
16
|
+
_discovered_newlines: set
|
|
17
|
+
_newline_mode: Optional[str]
|
|
18
|
+
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
initial_value: Optional[str] = None,
|
|
22
|
+
newline: Optional[str] = "\n",
|
|
23
|
+
pos: int = 0,
|
|
24
|
+
):
|
|
25
|
+
if not (isinstance(newline, (str, type(None)))):
|
|
26
|
+
raise TypeError
|
|
27
|
+
if newline not in (None, "", "\n", "\r", "\r\n"):
|
|
28
|
+
raise ValueError
|
|
29
|
+
if initial_value is None:
|
|
30
|
+
initial_value = ""
|
|
31
|
+
if not (isinstance(initial_value, str)):
|
|
32
|
+
raise TypeError
|
|
33
|
+
if pos < 0:
|
|
34
|
+
raise ValueError
|
|
35
|
+
self._newline_mode = newline
|
|
36
|
+
self._discovered_newlines = set()
|
|
37
|
+
self._pos = pos
|
|
38
|
+
self._contents = self._replace_newlines(initial_value) if initial_value else ""
|
|
39
|
+
|
|
40
|
+
def __repr__(self):
|
|
41
|
+
contents, newline_mode, pos = self._contents, self._newline_mode, self._pos
|
|
42
|
+
if pos == 0:
|
|
43
|
+
if newline_mode == "\n":
|
|
44
|
+
return f"BackedStringIO({contents!r})"
|
|
45
|
+
else:
|
|
46
|
+
return f"BackedStringIO({contents!r}, {newline_mode!r})"
|
|
47
|
+
return (
|
|
48
|
+
f"BackedStringIO({contents!r}, newline_mode={newline_mode!r}, pos={pos!r})"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
def __ch_pytype__(self):
|
|
52
|
+
return StringIO
|
|
53
|
+
|
|
54
|
+
def __ch_realize__(self):
|
|
55
|
+
if self.closed:
|
|
56
|
+
raise ValueError
|
|
57
|
+
contents, newline_mode = realize(self._contents), realize(self._newline_mode)
|
|
58
|
+
with NoTracing():
|
|
59
|
+
sio = StringIO(contents, newline_mode)
|
|
60
|
+
sio.seek(realize(self._pos))
|
|
61
|
+
return sio
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def newlines(self) -> Union[None, str, Tuple[str, ...]]: # type: ignore
|
|
65
|
+
discovered = self._discovered_newlines
|
|
66
|
+
# Fiddly! Trying to preserve the static tuple ordering that CPython has:
|
|
67
|
+
ret = tuple(nl for nl in ("\r", "\n", "\r\n") if nl in discovered)
|
|
68
|
+
if len(ret) > 1:
|
|
69
|
+
return ret
|
|
70
|
+
if len(ret) == 1:
|
|
71
|
+
return ret[0]
|
|
72
|
+
return None
|
|
73
|
+
|
|
74
|
+
def _replace_newlines(self, string: str) -> str:
|
|
75
|
+
newline_mode = self._newline_mode
|
|
76
|
+
if newline_mode is None:
|
|
77
|
+
|
|
78
|
+
def replace(match: re.Match) -> str:
|
|
79
|
+
self._discovered_newlines.add(match.group())
|
|
80
|
+
return "\n"
|
|
81
|
+
|
|
82
|
+
return _UNIVERSAL_NEWLINE_RE.sub(replace, string)
|
|
83
|
+
elif newline_mode == "":
|
|
84
|
+
self._discovered_newlines.update(_UNIVERSAL_NEWLINE_RE.findall(string))
|
|
85
|
+
return string
|
|
86
|
+
elif newline_mode == "\n":
|
|
87
|
+
return string
|
|
88
|
+
else:
|
|
89
|
+
return string.replace("\n", newline_mode)
|
|
90
|
+
|
|
91
|
+
def flush(self) -> None:
|
|
92
|
+
return
|
|
93
|
+
|
|
94
|
+
def getvalue(self) -> str:
|
|
95
|
+
return self._contents
|
|
96
|
+
|
|
97
|
+
def read(self, amount: Optional[int] = None) -> str:
|
|
98
|
+
if self.closed:
|
|
99
|
+
raise ValueError
|
|
100
|
+
if amount is None:
|
|
101
|
+
ret = self._contents[self._pos :]
|
|
102
|
+
else:
|
|
103
|
+
ret = self._contents[self._pos : self._pos + amount]
|
|
104
|
+
self._pos += len(ret)
|
|
105
|
+
return ret
|
|
106
|
+
|
|
107
|
+
def readable(self) -> bool:
|
|
108
|
+
if self.closed:
|
|
109
|
+
raise ValueError
|
|
110
|
+
return True
|
|
111
|
+
|
|
112
|
+
def readline(self, limit: Optional[int] = None) -> str: # type: ignore
|
|
113
|
+
if self.closed:
|
|
114
|
+
raise ValueError
|
|
115
|
+
contents, pos = self._contents, self._pos
|
|
116
|
+
if limit is None:
|
|
117
|
+
limit = len(contents)
|
|
118
|
+
if self._newline_mode == "":
|
|
119
|
+
# All other modes would have normalized the contents to \n already.
|
|
120
|
+
for match in _UNIVERSAL_NEWLINE_RE.finditer(contents, pos, limit):
|
|
121
|
+
self._pos = match.end()
|
|
122
|
+
return contents[pos : match.end()]
|
|
123
|
+
self._pos = limit
|
|
124
|
+
return contents[pos:limit]
|
|
125
|
+
else:
|
|
126
|
+
nl = self._newline_mode or "\n"
|
|
127
|
+
nl_size = len(nl)
|
|
128
|
+
idx = contents.find(nl, pos, limit)
|
|
129
|
+
if idx == -1:
|
|
130
|
+
self._pos = limit
|
|
131
|
+
return contents[pos:limit]
|
|
132
|
+
else:
|
|
133
|
+
self._pos = idx + nl_size
|
|
134
|
+
return contents[pos : idx + nl_size]
|
|
135
|
+
|
|
136
|
+
def write(self, string: str) -> int:
|
|
137
|
+
if self.closed:
|
|
138
|
+
raise ValueError
|
|
139
|
+
contents, pos = self._contents, self._pos
|
|
140
|
+
contentslen = len(contents)
|
|
141
|
+
if not string:
|
|
142
|
+
return 0
|
|
143
|
+
writestr = self._replace_newlines(string)
|
|
144
|
+
writelen = len(writestr)
|
|
145
|
+
if pos > contentslen:
|
|
146
|
+
self._contents += "\u0000" * (pos - contentslen)
|
|
147
|
+
contentslen = pos
|
|
148
|
+
if pos == contentslen:
|
|
149
|
+
self._contents += writestr
|
|
150
|
+
else:
|
|
151
|
+
self._contents = contents[:pos] + writestr + contents[pos + writelen :]
|
|
152
|
+
self._pos = pos + writelen
|
|
153
|
+
# Don't return `writelen` because all the input characters were "written":
|
|
154
|
+
return len(string)
|
|
155
|
+
|
|
156
|
+
def seek(self, amount: int, whence: int = SEEK_SET) -> int:
|
|
157
|
+
if self.closed:
|
|
158
|
+
raise ValueError
|
|
159
|
+
if whence == SEEK_CUR:
|
|
160
|
+
if amount != 0:
|
|
161
|
+
raise OSError
|
|
162
|
+
pos = self._pos + amount
|
|
163
|
+
elif whence == SEEK_END:
|
|
164
|
+
if amount != 0:
|
|
165
|
+
raise OSError
|
|
166
|
+
pos = len(self._contents) + amount
|
|
167
|
+
elif whence == SEEK_SET:
|
|
168
|
+
if amount < 0:
|
|
169
|
+
raise ValueError
|
|
170
|
+
pos = amount
|
|
171
|
+
else:
|
|
172
|
+
raise ValueError
|
|
173
|
+
self._pos = pos
|
|
174
|
+
return pos
|
|
175
|
+
|
|
176
|
+
def seekable(self) -> bool:
|
|
177
|
+
if self.closed:
|
|
178
|
+
raise ValueError
|
|
179
|
+
return True
|
|
180
|
+
|
|
181
|
+
def tell(self) -> int:
|
|
182
|
+
if self.closed:
|
|
183
|
+
raise ValueError
|
|
184
|
+
return self._pos
|
|
185
|
+
|
|
186
|
+
def truncate(self, size: Optional[int] = None) -> int:
|
|
187
|
+
if self.closed:
|
|
188
|
+
raise ValueError
|
|
189
|
+
if size is None:
|
|
190
|
+
size = self._pos
|
|
191
|
+
self._contents = self._contents[:size]
|
|
192
|
+
return size
|
|
193
|
+
|
|
194
|
+
def writable(self) -> bool:
|
|
195
|
+
if self.closed:
|
|
196
|
+
raise ValueError
|
|
197
|
+
return True
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def make_string_io(factory: SymbolicFactory) -> BackedStringIO:
|
|
201
|
+
contents = factory(str)
|
|
202
|
+
newline_mode = factory(Optional[str])
|
|
203
|
+
with ResumedTracing():
|
|
204
|
+
if newline_mode not in (None, "", "\n", "\r", "\r\n"):
|
|
205
|
+
raise IgnoreAttempt
|
|
206
|
+
return BackedStringIO(contents, newline_mode)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def _string_io(initial_value: str = "", newline="\n"):
|
|
210
|
+
return BackedStringIO(initial_value, newline)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def make_registrations() -> None:
|
|
214
|
+
register_type(StringIO, make_string_io)
|
|
215
|
+
register_patch(StringIO, _string_io)
|
|
216
|
+
# TODO: register_type io.TextIO, BytesIO, ...
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from io import StringIO
|
|
3
|
+
from typing import List, Optional, Tuple, Union
|
|
4
|
+
|
|
5
|
+
import pytest # type: ignore
|
|
6
|
+
|
|
7
|
+
from crosshair.core_and_libs import MessageType, analyze_function, run_checkables
|
|
8
|
+
from crosshair.libimpl.iolib import BackedStringIO
|
|
9
|
+
from crosshair.test_util import compare_returns
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _do_something(s: Union[StringIO, BackedStringIO], opname: str) -> object:
|
|
13
|
+
if opname == "closed":
|
|
14
|
+
return s.closed
|
|
15
|
+
elif opname == "flush":
|
|
16
|
+
s.flush()
|
|
17
|
+
elif opname == "read":
|
|
18
|
+
return s.read()
|
|
19
|
+
elif opname == "readlines":
|
|
20
|
+
return s.readlines()
|
|
21
|
+
elif opname == "readable":
|
|
22
|
+
return s.readable()
|
|
23
|
+
elif opname == "seek":
|
|
24
|
+
return s.seek(1)
|
|
25
|
+
elif opname == "seekable":
|
|
26
|
+
return s.seekable()
|
|
27
|
+
elif opname == "tell":
|
|
28
|
+
return s.tell()
|
|
29
|
+
elif opname == "truncate":
|
|
30
|
+
return s.truncate()
|
|
31
|
+
elif opname == "writable":
|
|
32
|
+
return s.writable()
|
|
33
|
+
elif opname == "write":
|
|
34
|
+
return s.write("")
|
|
35
|
+
return None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def check_stringio_readlines(s: StringIO, hint: int):
|
|
39
|
+
"""post: _"""
|
|
40
|
+
|
|
41
|
+
def readlines(s, hint: int):
|
|
42
|
+
return s.readlines(hint)
|
|
43
|
+
|
|
44
|
+
return compare_returns(readlines, s, hint)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def check_stringio_writelines(s: StringIO, lines: List[str]):
|
|
48
|
+
"""post: _"""
|
|
49
|
+
|
|
50
|
+
def writelines(s, lines: List[str]):
|
|
51
|
+
retval = s.writelines(lines)
|
|
52
|
+
return (retval, s.tell(), s.getvalue())
|
|
53
|
+
|
|
54
|
+
return compare_returns(writelines, s, lines)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def check_stringio_seek1(s: StringIO, o1: int, w1: int):
|
|
58
|
+
"""post: _"""
|
|
59
|
+
|
|
60
|
+
def seek_double(s, o1: int, w1: int) -> int:
|
|
61
|
+
s.seek(o1, w1)
|
|
62
|
+
return s.tell()
|
|
63
|
+
|
|
64
|
+
return compare_returns(seek_double, s, o1, w1)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def check_stringio_seek_seek(s: StringIO, o1: int, w1: int, o2: int, w2: int):
|
|
68
|
+
"""post: _"""
|
|
69
|
+
|
|
70
|
+
def seek_seek(s, o1: int, w1: int, o2: int, w2: int) -> int:
|
|
71
|
+
s.seek(o1, w1)
|
|
72
|
+
s.seek(o2, w2)
|
|
73
|
+
return s.tell()
|
|
74
|
+
|
|
75
|
+
return compare_returns(seek_seek, s, o1, w1, o2, w2)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def check_stringio_seek_write(s: StringIO, o1: int, w1: int, ws: str):
|
|
79
|
+
"""post: _"""
|
|
80
|
+
# crosshair: max_uninteresting_iterations=15
|
|
81
|
+
|
|
82
|
+
def seek_write(s, o1: int, w1: int, ws: str) -> Tuple[int, int, str]:
|
|
83
|
+
s.seek(o1, w1)
|
|
84
|
+
retval = s.write(ws)
|
|
85
|
+
return (retval, s.tell(), s.getvalue())
|
|
86
|
+
|
|
87
|
+
return compare_returns(seek_write, s, o1, w1, ws)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def check_stringio_write_newlines(newline_mode: str, ws: str):
|
|
91
|
+
"""post: _"""
|
|
92
|
+
|
|
93
|
+
def write_newlines(newline_mode: Optional[str], ws: str):
|
|
94
|
+
if newline_mode in (None, "", "\n", "\r", "\r\n"):
|
|
95
|
+
s = BackedStringIO("", newline_mode)
|
|
96
|
+
s.write(ws)
|
|
97
|
+
return s.newlines
|
|
98
|
+
|
|
99
|
+
return compare_returns(write_newlines, newline_mode, ws)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def check_stringio_simple_operation(s: StringIO, opname: str):
|
|
103
|
+
"""post: _"""
|
|
104
|
+
|
|
105
|
+
def simple_operation(s, opname: str) -> object:
|
|
106
|
+
return _do_something(s, opname)
|
|
107
|
+
|
|
108
|
+
return compare_returns(simple_operation, s, opname)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def check_stringio_operation_while_closed(s: StringIO, opname: str):
|
|
112
|
+
"""post: _"""
|
|
113
|
+
|
|
114
|
+
def closed_operation(s, opname: str) -> object:
|
|
115
|
+
s.close()
|
|
116
|
+
return _do_something(s, opname)
|
|
117
|
+
|
|
118
|
+
return compare_returns(closed_operation, s, opname)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
# This is the only real test definition.
|
|
122
|
+
# It runs crosshair on each of the "check" functions defined above.
|
|
123
|
+
@pytest.mark.parametrize("fn_name", [fn for fn in dir() if fn.startswith("check_")])
|
|
124
|
+
def test_builtin(fn_name: str) -> None:
|
|
125
|
+
fn = getattr(sys.modules[__name__], fn_name)
|
|
126
|
+
messages = run_checkables(analyze_function(fn))
|
|
127
|
+
errors = [m for m in messages if m.state > MessageType.PRE_UNSAT]
|
|
128
|
+
assert errors == []
|