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,42 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import sys
|
|
3
|
+
from typing import Dict, List, Tuple, Union
|
|
4
|
+
|
|
5
|
+
import pytest # type: ignore
|
|
6
|
+
|
|
7
|
+
from crosshair.core_and_libs import MessageType, analyze_function, run_checkables
|
|
8
|
+
from crosshair.options import AnalysisOptionSet
|
|
9
|
+
from crosshair.test_util import compare_results
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def check_decode(s: str):
|
|
13
|
+
"""post: _"""
|
|
14
|
+
return compare_results(json.loads, s)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def check_encode_atomics(obj: Union[bool, float, str, int]):
|
|
18
|
+
"""post: _"""
|
|
19
|
+
return compare_results(json.dumps, obj)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def check_encode_containers(obj: Union[Dict[float, bool], Tuple[int, bool], List[str]]):
|
|
23
|
+
"""post: _"""
|
|
24
|
+
return compare_results(json.dumps, obj)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def check_encode_decode_roundtrip(obj: Union[bool, int, str]):
|
|
28
|
+
"""post: _"""
|
|
29
|
+
return compare_results(lambda o: json.loads(json.dumps(o)), obj)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# TODO: Test customized encoding stuff
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# This is the only real test definition.
|
|
36
|
+
# It runs crosshair on each of the "check" functions defined above.
|
|
37
|
+
@pytest.mark.parametrize("fn_name", [fn for fn in dir() if fn.startswith("check_")])
|
|
38
|
+
def test_builtin(fn_name: str) -> None:
|
|
39
|
+
fn = getattr(sys.modules[__name__], fn_name)
|
|
40
|
+
messages = run_checkables(analyze_function(fn))
|
|
41
|
+
errors = [m for m in messages if m.state > MessageType.PRE_UNSAT]
|
|
42
|
+
assert errors == []
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from typing import List
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
from crosshair.core_and_libs import standalone_statespace
|
|
7
|
+
from crosshair.statespace import POST_FAIL
|
|
8
|
+
from crosshair.test_util import check_states
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_disallow_unicode_digits():
|
|
12
|
+
with standalone_statespace:
|
|
13
|
+
float("0E٠") # This is a valid float!
|
|
14
|
+
with pytest.raises(json.JSONDecodeError):
|
|
15
|
+
json.loads("0E٠") # But not a valid JSON float.
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@pytest.mark.demo("yellow")
|
|
19
|
+
def test_dumps():
|
|
20
|
+
def f(lst: List[int]):
|
|
21
|
+
"""
|
|
22
|
+
Can a JSON-serialized list be larger than 10 characters?
|
|
23
|
+
|
|
24
|
+
NOTE: Although this example is fast, most JSON serialization
|
|
25
|
+
tasks require at least a few minutes of analysis, and many may
|
|
26
|
+
not be solvable in any reasonable time frame.
|
|
27
|
+
|
|
28
|
+
post: len(_) <= 10
|
|
29
|
+
"""
|
|
30
|
+
return json.dumps(lst)
|
|
31
|
+
|
|
32
|
+
check_states(f, POST_FAIL)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@pytest.mark.demo("yellow")
|
|
36
|
+
def test_loads():
|
|
37
|
+
def f(s: str):
|
|
38
|
+
"""
|
|
39
|
+
Can we parse an empty JSON array out of a 3 character string?
|
|
40
|
+
|
|
41
|
+
NOTE: Although this example is fast, most JSON deserialization
|
|
42
|
+
tasks require at least a few minutes of analysis, and many may
|
|
43
|
+
not be solvable in any reasonable time frame.
|
|
44
|
+
|
|
45
|
+
pre: len(s) == 3
|
|
46
|
+
raises: json.JSONDecodeError
|
|
47
|
+
post: _ != []
|
|
48
|
+
"""
|
|
49
|
+
return json.loads(s)
|
|
50
|
+
|
|
51
|
+
check_states(f, POST_FAIL)
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import math
|
|
2
|
+
import sys
|
|
3
|
+
from numbers import Real
|
|
4
|
+
|
|
5
|
+
import z3 # type: ignore
|
|
6
|
+
|
|
7
|
+
from crosshair import NoTracing, register_patch
|
|
8
|
+
from crosshair.core import with_realized_args
|
|
9
|
+
from crosshair.libimpl.builtinslib import (
|
|
10
|
+
PreciseIeeeSymbolicFloat,
|
|
11
|
+
RealBasedSymbolicFloat,
|
|
12
|
+
SymbolicBool,
|
|
13
|
+
SymbolicIntable,
|
|
14
|
+
SymbolicValue,
|
|
15
|
+
smt_xor,
|
|
16
|
+
)
|
|
17
|
+
from crosshair.tracers import ResumedTracing
|
|
18
|
+
from crosshair.util import name_of_type
|
|
19
|
+
from crosshair.z3util import z3Not, z3Or
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _is_positive(x):
|
|
23
|
+
if isinstance(x, SymbolicValue):
|
|
24
|
+
if isinstance(x, PreciseIeeeSymbolicFloat):
|
|
25
|
+
return SymbolicBool(z3Not(z3.fpIsNegative(x.var)))
|
|
26
|
+
elif isinstance(x, RealBasedSymbolicFloat):
|
|
27
|
+
return SymbolicBool(x.var >= 0)
|
|
28
|
+
else:
|
|
29
|
+
with ResumedTracing():
|
|
30
|
+
return x >= 0
|
|
31
|
+
else:
|
|
32
|
+
return math.copysign(1, x) == 1
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _copysign(x, y):
|
|
36
|
+
if not isinstance(x, Real):
|
|
37
|
+
raise TypeError(f"must be real number, not {name_of_type(type(x))}")
|
|
38
|
+
if not isinstance(y, Real):
|
|
39
|
+
raise TypeError(f"must be real number, not {name_of_type(type(y))}")
|
|
40
|
+
with NoTracing():
|
|
41
|
+
x_is_positive = _is_positive(x)
|
|
42
|
+
y_is_positive = _is_positive(y)
|
|
43
|
+
# then invert as needed:
|
|
44
|
+
invert = smt_xor(x_is_positive, y_is_positive)
|
|
45
|
+
with NoTracing():
|
|
46
|
+
if isinstance(invert, SymbolicBool) and isinstance(
|
|
47
|
+
x, (PreciseIeeeSymbolicFloat, RealBasedSymbolicFloat)
|
|
48
|
+
):
|
|
49
|
+
return type(x)(z3.If(invert.var, -x.var, x.var))
|
|
50
|
+
with ResumedTracing():
|
|
51
|
+
return -x if invert else x
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
if sys.version_info >= (3, 9):
|
|
55
|
+
|
|
56
|
+
def _gcd(a=0, b=0):
|
|
57
|
+
while b:
|
|
58
|
+
a, b = b, a % b
|
|
59
|
+
return abs(a)
|
|
60
|
+
|
|
61
|
+
else: # (arguments were required in Python <= 3.8)
|
|
62
|
+
|
|
63
|
+
def _gcd(a, b):
|
|
64
|
+
while b:
|
|
65
|
+
a, b = b, a % b
|
|
66
|
+
return abs(a)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _isfinite(x):
|
|
70
|
+
with NoTracing():
|
|
71
|
+
if isinstance(x, (SymbolicIntable, RealBasedSymbolicFloat)):
|
|
72
|
+
return True
|
|
73
|
+
elif isinstance(x, PreciseIeeeSymbolicFloat):
|
|
74
|
+
return SymbolicBool(z3Not(z3Or(z3.fpIsNaN(x.var), z3.fpIsInf(x.var))))
|
|
75
|
+
else:
|
|
76
|
+
return math.isfinite(x)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _isnan(x):
|
|
80
|
+
with NoTracing():
|
|
81
|
+
if isinstance(x, (SymbolicIntable, RealBasedSymbolicFloat)):
|
|
82
|
+
return False
|
|
83
|
+
elif isinstance(x, PreciseIeeeSymbolicFloat):
|
|
84
|
+
return SymbolicBool(z3.fpIsNaN(x.var))
|
|
85
|
+
else:
|
|
86
|
+
return math.isnan(x)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _isinf(x):
|
|
90
|
+
with NoTracing():
|
|
91
|
+
if isinstance(x, (SymbolicIntable, RealBasedSymbolicFloat)):
|
|
92
|
+
return False
|
|
93
|
+
elif isinstance(x, PreciseIeeeSymbolicFloat):
|
|
94
|
+
return SymbolicBool(z3.fpIsInf(x.var))
|
|
95
|
+
else:
|
|
96
|
+
return math.isinf(x)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
_FUNCTIONS_WITH_REALIZATION = [
|
|
100
|
+
# TODO: we could attempt to implement some of these in the SMT solver
|
|
101
|
+
"acos",
|
|
102
|
+
"acosh",
|
|
103
|
+
"asin",
|
|
104
|
+
"asinh",
|
|
105
|
+
"atan",
|
|
106
|
+
"atan2",
|
|
107
|
+
"atanh",
|
|
108
|
+
"ceil",
|
|
109
|
+
"comb",
|
|
110
|
+
"cos",
|
|
111
|
+
"cosh",
|
|
112
|
+
"degrees",
|
|
113
|
+
"dist",
|
|
114
|
+
"erf",
|
|
115
|
+
"erfc",
|
|
116
|
+
"exp",
|
|
117
|
+
"expm1",
|
|
118
|
+
"fabs",
|
|
119
|
+
"factorial",
|
|
120
|
+
"floor",
|
|
121
|
+
"fmod",
|
|
122
|
+
"frexp",
|
|
123
|
+
"fsum",
|
|
124
|
+
"gamma",
|
|
125
|
+
"hypot",
|
|
126
|
+
"isclose",
|
|
127
|
+
"isqrt",
|
|
128
|
+
"ldexp",
|
|
129
|
+
"lgamma",
|
|
130
|
+
"log",
|
|
131
|
+
"log10",
|
|
132
|
+
"log1p",
|
|
133
|
+
"log2",
|
|
134
|
+
"modf",
|
|
135
|
+
"perm",
|
|
136
|
+
"pow",
|
|
137
|
+
"prod",
|
|
138
|
+
"radians",
|
|
139
|
+
"remainder",
|
|
140
|
+
"sin",
|
|
141
|
+
"sinh",
|
|
142
|
+
"sqrt",
|
|
143
|
+
"tan",
|
|
144
|
+
"tanh",
|
|
145
|
+
"trunc",
|
|
146
|
+
]
|
|
147
|
+
|
|
148
|
+
if sys.version_info >= (3, 9):
|
|
149
|
+
_FUNCTIONS_WITH_REALIZATION.extend(
|
|
150
|
+
[
|
|
151
|
+
"lcm",
|
|
152
|
+
"nextafter",
|
|
153
|
+
"ulp",
|
|
154
|
+
]
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
if sys.version_info >= (3, 11):
|
|
158
|
+
_FUNCTIONS_WITH_REALIZATION.extend(
|
|
159
|
+
[
|
|
160
|
+
"cbrt",
|
|
161
|
+
"exp2",
|
|
162
|
+
]
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
if sys.version_info >= (3, 12):
|
|
166
|
+
_FUNCTIONS_WITH_REALIZATION.append("sumprod")
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def make_registrations():
|
|
170
|
+
register_patch(math.copysign, _copysign)
|
|
171
|
+
register_patch(math.gcd, _gcd)
|
|
172
|
+
register_patch(math.isfinite, _isfinite)
|
|
173
|
+
register_patch(math.isnan, _isnan)
|
|
174
|
+
register_patch(math.isinf, _isinf)
|
|
175
|
+
for fn_name in _FUNCTIONS_WITH_REALIZATION:
|
|
176
|
+
fn = getattr(math, fn_name)
|
|
177
|
+
register_patch(
|
|
178
|
+
fn, with_realized_args(fn, deep=True)
|
|
179
|
+
) # deep realization needed for Fraction instances
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import math
|
|
2
|
+
import sys
|
|
3
|
+
from typing import Union
|
|
4
|
+
|
|
5
|
+
import pytest # type: ignore
|
|
6
|
+
|
|
7
|
+
from crosshair.core import realize
|
|
8
|
+
from crosshair.core_and_libs import MessageType, analyze_function, run_checkables
|
|
9
|
+
from crosshair.options import AnalysisOptionSet
|
|
10
|
+
from crosshair.test_util import compare_results
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def check_copysign(
|
|
14
|
+
a: Union[int, float], b: Union[int, float], realize_a: bool, realize_b: bool
|
|
15
|
+
):
|
|
16
|
+
"""post: _"""
|
|
17
|
+
if realize_a:
|
|
18
|
+
a = realize(a)
|
|
19
|
+
if realize_b:
|
|
20
|
+
b = realize(b)
|
|
21
|
+
return compare_results(math.copysign, a, b)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def check_gcd(a: int, b: int):
|
|
25
|
+
"""
|
|
26
|
+
pre: all([-10 < a, a < 10, -10 < b, b < 10]) # for performance
|
|
27
|
+
post: _
|
|
28
|
+
"""
|
|
29
|
+
return compare_results(math.gcd, a, b)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# This is the only real test definition.
|
|
33
|
+
# It runs crosshair on each of the "check" functions defined above.
|
|
34
|
+
@pytest.mark.parametrize("fn_name", [fn for fn in dir() if fn.startswith("check_")])
|
|
35
|
+
def test_builtin(fn_name: str) -> None:
|
|
36
|
+
this_module = sys.modules[__name__]
|
|
37
|
+
messages = run_checkables(
|
|
38
|
+
analyze_function(
|
|
39
|
+
getattr(this_module, fn_name),
|
|
40
|
+
AnalysisOptionSet(max_uninteresting_iterations=50),
|
|
41
|
+
)
|
|
42
|
+
)
|
|
43
|
+
errors = [m for m in messages if m.state > MessageType.PRE_UNSAT]
|
|
44
|
+
assert errors == []
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import math
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
from crosshair.core import proxy_for_type, standalone_statespace
|
|
7
|
+
from crosshair.libimpl.builtinslib import (
|
|
8
|
+
ModelingDirector,
|
|
9
|
+
PreciseIeeeSymbolicFloat,
|
|
10
|
+
RealBasedSymbolicFloat,
|
|
11
|
+
)
|
|
12
|
+
from crosshair.statespace import POST_FAIL
|
|
13
|
+
from crosshair.test_util import check_states
|
|
14
|
+
from crosshair.tracers import NoTracing, ResumedTracing
|
|
15
|
+
from crosshair.util import set_debug
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_copysign_e2e():
|
|
19
|
+
def can_find_minus_zero(x: float):
|
|
20
|
+
"""post: math.copysign(1, _) == 1"""
|
|
21
|
+
if x == 0:
|
|
22
|
+
return x
|
|
23
|
+
return 1
|
|
24
|
+
|
|
25
|
+
check_states(can_find_minus_zero, POST_FAIL)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@pytest.mark.parametrize(
|
|
29
|
+
"FloatType", [PreciseIeeeSymbolicFloat, RealBasedSymbolicFloat]
|
|
30
|
+
)
|
|
31
|
+
def test_copysign_symbolics(FloatType, space):
|
|
32
|
+
space.extra(ModelingDirector).global_representations[float] = FloatType
|
|
33
|
+
x = FloatType("x")
|
|
34
|
+
y = FloatType("y")
|
|
35
|
+
with ResumedTracing():
|
|
36
|
+
assert not space.is_possible(math.copysign(x, -0.0) > 0.0)
|
|
37
|
+
assert space.is_possible(math.copysign(x, y) > 0.0)
|
|
38
|
+
assert space.is_possible(math.copysign(x, y) < 0.0)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_isfinite():
|
|
42
|
+
with standalone_statespace:
|
|
43
|
+
with NoTracing():
|
|
44
|
+
x = RealBasedSymbolicFloat("symfloat")
|
|
45
|
+
assert math.isfinite(x)
|
|
46
|
+
assert math.isfinite(2.3)
|
|
47
|
+
assert not math.isfinite(float("nan"))
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_isinf():
|
|
51
|
+
with standalone_statespace:
|
|
52
|
+
with NoTracing():
|
|
53
|
+
x = RealBasedSymbolicFloat("symfloat")
|
|
54
|
+
assert not math.isinf(x)
|
|
55
|
+
assert not math.isinf(float("nan"))
|
|
56
|
+
assert math.isinf(float("-inf"))
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test_log():
|
|
60
|
+
with standalone_statespace as space:
|
|
61
|
+
with NoTracing():
|
|
62
|
+
i = proxy_for_type(int, "i")
|
|
63
|
+
f = proxy_for_type(float, "f")
|
|
64
|
+
space.add(i > 0)
|
|
65
|
+
space.add(f > 0)
|
|
66
|
+
math.log(i)
|
|
67
|
+
math.log(f)
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import random
|
|
2
|
+
from functools import partial
|
|
3
|
+
from inspect import Parameter, Signature
|
|
4
|
+
from typing import Any, Callable, List, Optional, Union
|
|
5
|
+
|
|
6
|
+
from crosshair import NoTracing, SymbolicFactory, register_type
|
|
7
|
+
from crosshair.libimpl.builtinslib import SymbolicInt
|
|
8
|
+
from crosshair.register_contract import register_contract
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ExplicitRandom(random.Random):
|
|
12
|
+
def __new__(cls, *a):
|
|
13
|
+
return super().__new__(ExplicitRandom, 0)
|
|
14
|
+
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
future_values: Optional[List[Union[int, float]]] = None,
|
|
18
|
+
idx: int = 0,
|
|
19
|
+
intgen: Callable[[int], int] = lambda c: 0,
|
|
20
|
+
floatgen: Callable[[], float] = lambda: 0.0,
|
|
21
|
+
):
|
|
22
|
+
self._future_values = future_values if future_values else []
|
|
23
|
+
self._idx = idx
|
|
24
|
+
self._intgen = intgen
|
|
25
|
+
self._floatgen = floatgen
|
|
26
|
+
super().__init__()
|
|
27
|
+
|
|
28
|
+
def __copy__(self):
|
|
29
|
+
# Just a regular copy. (otherwise, we'd be deferring to getstate/setstate)
|
|
30
|
+
cls = self.__class__
|
|
31
|
+
result = cls.__new__(cls)
|
|
32
|
+
result.__dict__.update(self.__dict__)
|
|
33
|
+
return result
|
|
34
|
+
|
|
35
|
+
def __ch_deepcopy__(self, memo):
|
|
36
|
+
# We pretend this is a deepcopy, but it isn't.
|
|
37
|
+
# That way, the values lazily added to _future_values will be the same in each
|
|
38
|
+
# instance, even though we don't know what they are at the time of the deep
|
|
39
|
+
# copy.
|
|
40
|
+
return self.__copy__()
|
|
41
|
+
|
|
42
|
+
def __repr__(self) -> str:
|
|
43
|
+
return f"crosshair.libimpl.randomlib.ExplicitRandom({self._future_values!r})"
|
|
44
|
+
|
|
45
|
+
def __reduce__(self):
|
|
46
|
+
return (ExplicitRandom, (self._future_values, self._idx))
|
|
47
|
+
|
|
48
|
+
def random(self) -> float:
|
|
49
|
+
idx = self._idx
|
|
50
|
+
future_values = self._future_values
|
|
51
|
+
if idx >= len(future_values):
|
|
52
|
+
future_values.append(self._floatgen())
|
|
53
|
+
intorfloat = self._future_values[idx]
|
|
54
|
+
if isinstance(intorfloat, int):
|
|
55
|
+
ret = 1.0 / abs(intorfloat) if intorfloat != 0 else 0.0
|
|
56
|
+
else:
|
|
57
|
+
ret = intorfloat
|
|
58
|
+
self._idx += 1
|
|
59
|
+
return ret
|
|
60
|
+
|
|
61
|
+
def _randbelow(self, cap: int) -> int:
|
|
62
|
+
idx = self._idx
|
|
63
|
+
future_values = self._future_values
|
|
64
|
+
if idx >= len(future_values):
|
|
65
|
+
future_values.append(self._intgen(cap))
|
|
66
|
+
intorfloat = future_values[idx]
|
|
67
|
+
if isinstance(intorfloat, float):
|
|
68
|
+
ret = abs(hash(intorfloat)) % cap
|
|
69
|
+
else:
|
|
70
|
+
ret = intorfloat
|
|
71
|
+
self._idx += 1
|
|
72
|
+
return ret
|
|
73
|
+
|
|
74
|
+
def getrandbits(self, k: int) -> int:
|
|
75
|
+
return self._randbelow(2**k)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def genint(factory: SymbolicFactory, cap: int):
|
|
79
|
+
# TODO: conditionally use SymbolicBoundedInt here and below
|
|
80
|
+
with NoTracing():
|
|
81
|
+
symbolic_int = SymbolicInt(factory.varname + factory.space.uniq(), int)
|
|
82
|
+
factory.space.add(0 <= symbolic_int.var)
|
|
83
|
+
factory.space.add(symbolic_int.var < SymbolicInt._coerce_to_smt_sort(cap))
|
|
84
|
+
return symbolic_int
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def genfloat(factory: SymbolicFactory):
|
|
88
|
+
with NoTracing():
|
|
89
|
+
symbolic_float: Any = factory(float)
|
|
90
|
+
factory.space.add(0.0 <= symbolic_float.var)
|
|
91
|
+
factory.space.add(symbolic_float.var < 1.0)
|
|
92
|
+
return symbolic_float
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def make_registrations() -> None:
|
|
96
|
+
register_type(
|
|
97
|
+
random.Random,
|
|
98
|
+
lambda p: ExplicitRandom([], 0, partial(genint, p), partial(genfloat, p)),
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
register_contract(
|
|
102
|
+
random.Random.random,
|
|
103
|
+
post=lambda __return__: 0.0 <= __return__ < 1.0,
|
|
104
|
+
sig=Signature(
|
|
105
|
+
parameters=[
|
|
106
|
+
Parameter("self", Parameter.POSITIONAL_OR_KEYWORD),
|
|
107
|
+
],
|
|
108
|
+
return_annotation=float,
|
|
109
|
+
),
|
|
110
|
+
)
|
|
111
|
+
register_contract(
|
|
112
|
+
random.Random.randint,
|
|
113
|
+
pre=lambda a, b: a <= b,
|
|
114
|
+
post=lambda __return__, a, b: a <= __return__ <= b,
|
|
115
|
+
sig=Signature(
|
|
116
|
+
parameters=[
|
|
117
|
+
Parameter("self", Parameter.POSITIONAL_OR_KEYWORD),
|
|
118
|
+
Parameter("a", Parameter.POSITIONAL_OR_KEYWORD, annotation=int),
|
|
119
|
+
Parameter("b", Parameter.POSITIONAL_OR_KEYWORD, annotation=int),
|
|
120
|
+
],
|
|
121
|
+
return_annotation=int,
|
|
122
|
+
),
|
|
123
|
+
)
|
|
124
|
+
register_contract(
|
|
125
|
+
random.Random.randrange,
|
|
126
|
+
pre=lambda start, stop, step: (
|
|
127
|
+
(step == 1 and start >= 1)
|
|
128
|
+
if stop is None
|
|
129
|
+
else (start != stop and step != 0 and (stop - start > 0) == (step > 0))
|
|
130
|
+
),
|
|
131
|
+
post=lambda __return__, start, stop, step, _int=int: (
|
|
132
|
+
(0 <= __return__ < start if stop is None else start <= __return__ < stop)
|
|
133
|
+
and (step == 1 or (__return__ - start) % step == 0)
|
|
134
|
+
),
|
|
135
|
+
sig=Signature(
|
|
136
|
+
parameters=[
|
|
137
|
+
Parameter("self", Parameter.POSITIONAL_OR_KEYWORD),
|
|
138
|
+
Parameter("start", Parameter.POSITIONAL_OR_KEYWORD, annotation=int),
|
|
139
|
+
Parameter(
|
|
140
|
+
"stop",
|
|
141
|
+
Parameter.POSITIONAL_OR_KEYWORD,
|
|
142
|
+
default=None,
|
|
143
|
+
annotation=Optional[int],
|
|
144
|
+
),
|
|
145
|
+
Parameter(
|
|
146
|
+
"step", Parameter.POSITIONAL_OR_KEYWORD, default=1, annotation=int
|
|
147
|
+
),
|
|
148
|
+
Parameter(
|
|
149
|
+
"_int", Parameter.POSITIONAL_OR_KEYWORD, default=int, annotation=int
|
|
150
|
+
),
|
|
151
|
+
],
|
|
152
|
+
return_annotation=int,
|
|
153
|
+
),
|
|
154
|
+
)
|
|
155
|
+
register_contract(
|
|
156
|
+
random.Random.uniform,
|
|
157
|
+
post=lambda __return__, a, b: min(a, b) <= __return__ <= max(a, b),
|
|
158
|
+
sig=Signature(
|
|
159
|
+
parameters=[
|
|
160
|
+
Parameter("self", Parameter.POSITIONAL_OR_KEYWORD),
|
|
161
|
+
Parameter("a", Parameter.POSITIONAL_OR_KEYWORD, annotation=float),
|
|
162
|
+
Parameter("b", Parameter.POSITIONAL_OR_KEYWORD, annotation=float),
|
|
163
|
+
],
|
|
164
|
+
return_annotation=float,
|
|
165
|
+
),
|
|
166
|
+
)
|
|
167
|
+
register_contract(
|
|
168
|
+
random.Random.getrandbits,
|
|
169
|
+
pre=lambda k: k >= 0,
|
|
170
|
+
post=lambda __return__, k: 0 <= __return__ < 2**k,
|
|
171
|
+
sig=Signature(
|
|
172
|
+
parameters=[
|
|
173
|
+
Parameter("self", Parameter.POSITIONAL_OR_KEYWORD),
|
|
174
|
+
Parameter("k", Parameter.POSITIONAL_OR_KEYWORD, annotation=int),
|
|
175
|
+
],
|
|
176
|
+
return_annotation=int,
|
|
177
|
+
),
|
|
178
|
+
)
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import random
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
from crosshair.core_and_libs import proxy_for_type, standalone_statespace
|
|
7
|
+
from crosshair.libimpl.randomlib import ExplicitRandom
|
|
8
|
+
from crosshair.statespace import CANNOT_CONFIRM, CONFIRMED, POST_FAIL, MessageType
|
|
9
|
+
from crosshair.test_util import check_states
|
|
10
|
+
from crosshair.tracers import ResumedTracing
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def test_ExplicitRandom():
|
|
14
|
+
rng = ExplicitRandom([1, 2])
|
|
15
|
+
assert rng.randrange(0, 10) == 1
|
|
16
|
+
assert rng.choice(["a", "b", "c"]) == "c"
|
|
17
|
+
assert rng.choice(["a", "b", "c"]) == "a"
|
|
18
|
+
assert rng.random() == 0.0
|
|
19
|
+
assert repr(rng) == "crosshair.libimpl.randomlib.ExplicitRandom([1, 2, 0, 0.0])"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_ExplicitRandom_copy():
|
|
23
|
+
rng = ExplicitRandom([1, 2])
|
|
24
|
+
(rng2,) = copy.deepcopy([rng])
|
|
25
|
+
assert rng.randint(0, 5) == 1
|
|
26
|
+
assert rng2.randint(0, 5) == 1
|
|
27
|
+
assert rng.randint(0, 5) == 2
|
|
28
|
+
assert rng2.randint(0, 5) == 2
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_proxy_random():
|
|
32
|
+
with standalone_statespace as space:
|
|
33
|
+
rng = proxy_for_type(random.Random, "rng")
|
|
34
|
+
i = rng.randrange(5, 10)
|
|
35
|
+
with ResumedTracing():
|
|
36
|
+
assert space.is_possible(i == 5)
|
|
37
|
+
assert space.is_possible(i == 9)
|
|
38
|
+
assert not space.is_possible(i == 4)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_global_randrange():
|
|
42
|
+
assert random.randrange(10, 20, 5) in (10, 15) # confirm we've got the args right
|
|
43
|
+
|
|
44
|
+
def f():
|
|
45
|
+
"""post: _ in (10, 15)"""
|
|
46
|
+
return random.randrange(10, 20, 5)
|
|
47
|
+
|
|
48
|
+
check_states(f, CONFIRMED)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_global_randrange_only_upperbound():
|
|
52
|
+
assert random.randrange(2) in (0, 1) # confirm we've got the args right
|
|
53
|
+
|
|
54
|
+
def f():
|
|
55
|
+
"""post: _ in (0, 1)"""
|
|
56
|
+
return random.randrange(2)
|
|
57
|
+
|
|
58
|
+
check_states(f, CONFIRMED)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@pytest.mark.demo
|
|
62
|
+
def test_randrange():
|
|
63
|
+
def f():
|
|
64
|
+
"""
|
|
65
|
+
Can random.randrange() produce the value at low end of the range?
|
|
66
|
+
|
|
67
|
+
NOTE: CrossHair's random generator can produce any possible valid
|
|
68
|
+
value.
|
|
69
|
+
The counterexample includes a monkey-patching context
|
|
70
|
+
manager that lets you reproduce the issue, e.g.:
|
|
71
|
+
with crosshair.patch_to_return({random.Random.randrange: [20]}):
|
|
72
|
+
f()
|
|
73
|
+
|
|
74
|
+
post: _ != 10
|
|
75
|
+
"""
|
|
76
|
+
return random.randrange(10, 20)
|
|
77
|
+
|
|
78
|
+
check_states(f, POST_FAIL)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@pytest.mark.demo
|
|
82
|
+
def test_uniform():
|
|
83
|
+
assert 10.0 <= random.uniform(10, 20) <= 20.0 # confirm we've got the args right
|
|
84
|
+
|
|
85
|
+
def f():
|
|
86
|
+
"""
|
|
87
|
+
Can random.uniform() produce the value at high end of the range?
|
|
88
|
+
|
|
89
|
+
NOTE: CrossHair's random generator can produce any possible valid
|
|
90
|
+
value.
|
|
91
|
+
The counterexample includes a monkey-patching context
|
|
92
|
+
manager that lets you reproduce the issue, e.g.:
|
|
93
|
+
with crosshair.patch_to_return({random.Random.uniform: [20.0]}):
|
|
94
|
+
f()
|
|
95
|
+
|
|
96
|
+
post: _ != 20.0
|
|
97
|
+
"""
|
|
98
|
+
return random.uniform(10, 20)
|
|
99
|
+
|
|
100
|
+
check_states(f, POST_FAIL)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def test_global_uniform_inverted_args():
|
|
104
|
+
assert -2.0 <= random.uniform(10, -2) <= 10.0 # confirm we've got the args right
|
|
105
|
+
|
|
106
|
+
def f():
|
|
107
|
+
"""post: -2.0 <= _ <= 10.0"""
|
|
108
|
+
return random.uniform(10, -2)
|
|
109
|
+
|
|
110
|
+
check_states(f, CONFIRMED)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def test_global_getrandbits():
|
|
114
|
+
assert 0 <= random.getrandbits(3) < 8
|
|
115
|
+
|
|
116
|
+
def f():
|
|
117
|
+
"""post: 0<= _ < 8"""
|
|
118
|
+
return random.getrandbits(3)
|
|
119
|
+
|
|
120
|
+
check_states(f, CONFIRMED)
|