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
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import copy
|
|
2
2
|
import itertools
|
|
3
|
+
import operator
|
|
3
4
|
import re
|
|
5
|
+
import sys
|
|
4
6
|
from dataclasses import dataclass
|
|
5
7
|
from typing import Callable, List, Mapping, Optional, Sequence, Tuple
|
|
6
8
|
|
|
@@ -9,7 +11,7 @@ import pytest # type: ignore
|
|
|
9
11
|
from crosshair.core import _PATCH_REGISTRATIONS
|
|
10
12
|
from crosshair.core_and_libs import standalone_statespace
|
|
11
13
|
from crosshair.test_util import ExecutionResult, summarize_execution
|
|
12
|
-
from crosshair.util import
|
|
14
|
+
from crosshair.util import ch_stack, debug
|
|
13
15
|
|
|
14
16
|
"""
|
|
15
17
|
Tests that the builtin and standard library patches behave like their
|
|
@@ -38,6 +40,7 @@ possible_args = [
|
|
|
38
40
|
(42, int), # isinstance
|
|
39
41
|
(re.compile("(ab|a|b)"), r"\n", ""), # re methods
|
|
40
42
|
(bool, [1, 1, 0]), # itertools.takewhile and friends
|
|
43
|
+
(operator.add, [1, 0], [1, 1]), # multi-iterable map
|
|
41
44
|
([(1, 2), (3, 4)]), # key-value pairs
|
|
42
45
|
([(1, 2), ([], 4)]), # key-value pairs w/ unhashable key
|
|
43
46
|
]
|
|
@@ -56,22 +59,6 @@ for native_fn, patched_fn in _PATCH_REGISTRATIONS.items():
|
|
|
56
59
|
)
|
|
57
60
|
|
|
58
61
|
|
|
59
|
-
@dataclass(init=False)
|
|
60
|
-
class ExecutionResultWithTb:
|
|
61
|
-
ret: object # return value
|
|
62
|
-
exc: Optional[BaseException]
|
|
63
|
-
tb: Optional[str]
|
|
64
|
-
post_args: Sequence
|
|
65
|
-
post_kwargs: Mapping[str, object]
|
|
66
|
-
|
|
67
|
-
def __init__(self, result: ExecutionResult):
|
|
68
|
-
self.ret = result.ret
|
|
69
|
-
self.exc = result.exc
|
|
70
|
-
self.tb = test_stack(self.exc.__traceback__) if self.exc else None
|
|
71
|
-
self.post_args = result.post_args
|
|
72
|
-
self.post_kwargs = result.post_kwargs
|
|
73
|
-
|
|
74
|
-
|
|
75
62
|
@pytest.mark.parametrize("native_fn,patched_fn", comparisons)
|
|
76
63
|
@pytest.mark.parametrize(
|
|
77
64
|
"args", possible_args, ids=lambda t: re.sub(r"[\W_]", "_", str(t))
|
|
@@ -85,7 +72,4 @@ def test_patch(native_fn: Callable, patched_fn: Callable, args: Sequence[object]
|
|
|
85
72
|
with standalone_statespace:
|
|
86
73
|
patched_result = summarize_execution(patched_fn, args2, {}, detach_path=False)
|
|
87
74
|
debug("Patched result: ", patched_result)
|
|
88
|
-
|
|
89
|
-
assert ExecutionResultWithTb(native_result) == ExecutionResultWithTb(
|
|
90
|
-
patched_result
|
|
91
|
-
)
|
|
75
|
+
assert native_result == patched_result
|
crosshair/path_cover.py
CHANGED
|
@@ -26,10 +26,10 @@ from crosshair.tracers import (
|
|
|
26
26
|
)
|
|
27
27
|
from crosshair.util import (
|
|
28
28
|
ReferencedIdentifier,
|
|
29
|
+
ch_stack,
|
|
29
30
|
debug,
|
|
30
31
|
format_boundargs,
|
|
31
32
|
name_of_type,
|
|
32
|
-
test_stack,
|
|
33
33
|
)
|
|
34
34
|
|
|
35
35
|
|
|
@@ -95,9 +95,7 @@ def path_cover(
|
|
|
95
95
|
cov = coverage.get_results(fn)
|
|
96
96
|
debug("Realized args:", formatted_pre_args)
|
|
97
97
|
if exc is not None:
|
|
98
|
-
debug(
|
|
99
|
-
"user-level exception found", type(exc), exc, test_stack(exc_stack)
|
|
100
|
-
)
|
|
98
|
+
debug("user-level exception found", type(exc), exc, ch_stack(exc_stack))
|
|
101
99
|
exc_message = realize(str(exc)) if len(exc.args) > 0 else None
|
|
102
100
|
paths.append(
|
|
103
101
|
PathSummary(
|
|
@@ -135,7 +133,11 @@ def path_cover(
|
|
|
135
133
|
selected: List[PathSummary] = []
|
|
136
134
|
while paths:
|
|
137
135
|
next_best = max(
|
|
138
|
-
paths,
|
|
136
|
+
paths,
|
|
137
|
+
key=lambda p: (
|
|
138
|
+
len(p.coverage.offsets_covered - opcodes_found), # high coverage
|
|
139
|
+
-len(p.formatted_args), # with small input size
|
|
140
|
+
),
|
|
139
141
|
)
|
|
140
142
|
cur_offsets = next_best.coverage.offsets_covered
|
|
141
143
|
if coverage_type == CoverageType.OPCODE:
|
crosshair/path_search.py
CHANGED
|
@@ -12,15 +12,17 @@ from crosshair.options import AnalysisOptions
|
|
|
12
12
|
from crosshair.statespace import RootNode, StateSpace, context_statespace
|
|
13
13
|
from crosshair.tracers import CoverageResult, NoTracing, ResumedTracing
|
|
14
14
|
from crosshair.util import (
|
|
15
|
-
|
|
15
|
+
CrossHairInternal,
|
|
16
|
+
ch_stack,
|
|
16
17
|
debug,
|
|
17
18
|
format_boundargs_as_dictionary,
|
|
18
|
-
test_stack,
|
|
19
19
|
)
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
class OptimizationKind(enum.Enum):
|
|
23
23
|
SIMPLIFY = "SIMPLIFY"
|
|
24
|
+
# TODO: simplify mode is quite dumb atm; partly because eval_friendly_format
|
|
25
|
+
# realizes prior to generating the string.
|
|
24
26
|
NONE = "NONE"
|
|
25
27
|
MINIMIZE_INT = "MINIMIZE_INT"
|
|
26
28
|
|
|
@@ -57,7 +59,7 @@ def path_search(
|
|
|
57
59
|
try:
|
|
58
60
|
return argument_formatter(args)
|
|
59
61
|
except Exception as exc:
|
|
60
|
-
raise
|
|
62
|
+
raise CrossHairInternal(str(exc)) from exc
|
|
61
63
|
|
|
62
64
|
if optimization_kind == OptimizationKind.SIMPLIFY:
|
|
63
65
|
assert optimize_fn is None
|
|
@@ -99,7 +101,7 @@ def path_search(
|
|
|
99
101
|
"Aborting path, hit exception",
|
|
100
102
|
type(exc),
|
|
101
103
|
exc,
|
|
102
|
-
|
|
104
|
+
ch_stack(exc_stack),
|
|
103
105
|
)
|
|
104
106
|
return False
|
|
105
107
|
debug("Path succeeded")
|
crosshair/path_search_test.py
CHANGED
|
@@ -34,8 +34,7 @@ def do_path_search(
|
|
|
34
34
|
|
|
35
35
|
|
|
36
36
|
def test_optimize_options() -> None:
|
|
37
|
-
|
|
38
|
-
opts = DEFAULT_OPTIONS
|
|
37
|
+
opts = DEFAULT_OPTIONS.overlay(AnalysisOptionSet(max_uninteresting_iterations=10))
|
|
39
38
|
ret = do_path_search(
|
|
40
39
|
ten_over_difference, opts, None, optimization_kind=OptimizationKind.SIMPLIFY
|
|
41
40
|
)
|
crosshair/pathing_oracle.py
CHANGED
|
@@ -6,13 +6,16 @@ from z3 import ExprRef # type: ignore
|
|
|
6
6
|
|
|
7
7
|
from crosshair.statespace import (
|
|
8
8
|
AbstractPathingOracle,
|
|
9
|
+
DetachedPathNode,
|
|
9
10
|
ModelValueNode,
|
|
10
11
|
NodeLike,
|
|
11
12
|
RootNode,
|
|
12
13
|
SearchTreeNode,
|
|
14
|
+
StateSpace,
|
|
13
15
|
WorstResultNode,
|
|
14
16
|
)
|
|
15
|
-
from crosshair.util import
|
|
17
|
+
from crosshair.util import CrossHairInternal, debug, in_debug
|
|
18
|
+
from crosshair.z3util import z3And, z3Not, z3Or
|
|
16
19
|
|
|
17
20
|
CodeLoc = Tuple[str, ...]
|
|
18
21
|
|
|
@@ -54,9 +57,13 @@ class CoveragePathingOracle(AbstractPathingOracle):
|
|
|
54
57
|
{},
|
|
55
58
|
)
|
|
56
59
|
|
|
60
|
+
# TODO: This falls apart for moderately sized with_equal_probabilities
|
|
61
|
+
# because that has many small probability decisions.
|
|
62
|
+
# (even just a 10% change could be much larger than it would be otherwise)
|
|
57
63
|
_delta_probabilities = {-1: 0.1, 0: 0.25, 1: 0.9}
|
|
58
64
|
|
|
59
|
-
def pre_path_hook(self,
|
|
65
|
+
def pre_path_hook(self, space: StateSpace) -> None:
|
|
66
|
+
root = space._root
|
|
60
67
|
visits = self.visits
|
|
61
68
|
_delta_probabilities = self._delta_probabilities
|
|
62
69
|
|
|
@@ -110,15 +117,17 @@ class CoveragePathingOracle(AbstractPathingOracle):
|
|
|
110
117
|
def post_path_hook(self, path: Sequence[SearchTreeNode]) -> None:
|
|
111
118
|
leading_locs = []
|
|
112
119
|
leading_conditions: List[int] = []
|
|
113
|
-
for step, node in enumerate(path):
|
|
120
|
+
for step, node in enumerate(path[:-1]):
|
|
114
121
|
if not isinstance(node, NodeLike):
|
|
115
122
|
continue
|
|
116
|
-
node = node.simplify() # type: ignore
|
|
117
123
|
if isinstance(node, WorstResultNode):
|
|
118
124
|
key = node.stacktail
|
|
119
125
|
if (key not in leading_locs) and (not isinstance(node, ModelValueNode)):
|
|
120
126
|
self.summarized_positions[key] += Counter(leading_conditions)
|
|
121
127
|
leading_locs.append(key)
|
|
128
|
+
next_node = path[step + 1]
|
|
129
|
+
if isinstance(next_node, DetachedPathNode):
|
|
130
|
+
break
|
|
122
131
|
if step + 1 < len(path):
|
|
123
132
|
(is_positive, root_expr) = node.normalized_expr
|
|
124
133
|
expr_signature = (
|
|
@@ -126,13 +135,13 @@ class CoveragePathingOracle(AbstractPathingOracle):
|
|
|
126
135
|
if is_positive
|
|
127
136
|
else -self.internalize(root_expr)
|
|
128
137
|
)
|
|
129
|
-
if
|
|
138
|
+
if next_node == node.positive:
|
|
130
139
|
leading_conditions.append(expr_signature)
|
|
131
|
-
elif
|
|
140
|
+
elif next_node == node.negative:
|
|
132
141
|
leading_conditions.append(-expr_signature)
|
|
133
142
|
else:
|
|
134
|
-
raise
|
|
135
|
-
f"{type(path[step])}{type(path[step+1])}"
|
|
143
|
+
raise CrossHairInternal(
|
|
144
|
+
f"{type(path[step])} was followed by {type(path[step+1])}"
|
|
136
145
|
)
|
|
137
146
|
visits = self.visits
|
|
138
147
|
prev_len = len(visits)
|
|
@@ -207,16 +216,50 @@ class PreferNegativeOracle(AbstractPathingOracle):
|
|
|
207
216
|
return 0.25
|
|
208
217
|
|
|
209
218
|
|
|
219
|
+
class ConstrainedOracle(AbstractPathingOracle):
|
|
220
|
+
"""
|
|
221
|
+
A pathing oracle that prefers to take a path that satisfies
|
|
222
|
+
explicitly provided constraints.
|
|
223
|
+
"""
|
|
224
|
+
|
|
225
|
+
def __init__(self, inner_oracle: AbstractPathingOracle):
|
|
226
|
+
self.inner_oracle = inner_oracle
|
|
227
|
+
self.exprs: List[ExprRef] = []
|
|
228
|
+
|
|
229
|
+
def prefer(self, expr: ExprRef):
|
|
230
|
+
self.exprs.append(expr)
|
|
231
|
+
|
|
232
|
+
def pre_path_hook(self, space: StateSpace) -> None:
|
|
233
|
+
self.space = space
|
|
234
|
+
self.exprs = []
|
|
235
|
+
self.inner_oracle.pre_path_hook(space)
|
|
236
|
+
|
|
237
|
+
def post_path_hook(self, path: Sequence["SearchTreeNode"]) -> None:
|
|
238
|
+
self.inner_oracle.post_path_hook(path)
|
|
239
|
+
|
|
240
|
+
def decide(
|
|
241
|
+
self, root, node: "WorstResultNode", engine_probability: Optional[float]
|
|
242
|
+
) -> float:
|
|
243
|
+
# We always run the inner oracle in case it's tracking something about the path.
|
|
244
|
+
default_probability = self.inner_oracle.decide(root, node, engine_probability)
|
|
245
|
+
if not self.space.is_possible(z3And(*[node.expr, *self.exprs])):
|
|
246
|
+
return 0.0
|
|
247
|
+
elif not self.space.is_possible(z3And(*[z3Not(node.expr), *self.exprs])):
|
|
248
|
+
return 1.0
|
|
249
|
+
else:
|
|
250
|
+
return default_probability
|
|
251
|
+
|
|
252
|
+
|
|
210
253
|
class RotatingOracle(AbstractPathingOracle):
|
|
211
254
|
def __init__(self, oracles: List[AbstractPathingOracle]):
|
|
212
255
|
self.oracles = oracles
|
|
213
256
|
self.index = -1
|
|
214
257
|
|
|
215
|
-
def pre_path_hook(self,
|
|
258
|
+
def pre_path_hook(self, space: StateSpace) -> None:
|
|
216
259
|
oracles = self.oracles
|
|
217
260
|
self.index = (self.index + 1) % len(oracles)
|
|
218
261
|
for oracle in oracles:
|
|
219
|
-
oracle.pre_path_hook(
|
|
262
|
+
oracle.pre_path_hook(space)
|
|
220
263
|
|
|
221
264
|
def post_path_hook(self, path: Sequence["SearchTreeNode"]) -> None:
|
|
222
265
|
for oracle in self.oracles:
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import random
|
|
2
|
+
|
|
3
|
+
import z3 # type: ignore
|
|
4
|
+
|
|
5
|
+
from crosshair.pathing_oracle import ConstrainedOracle, PreferNegativeOracle
|
|
6
|
+
from crosshair.statespace import RootNode, SimpleStateSpace, WorstResultNode
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def test_constrained_oracle():
|
|
10
|
+
oracle = ConstrainedOracle(PreferNegativeOracle())
|
|
11
|
+
x = z3.Int("x")
|
|
12
|
+
root = RootNode()
|
|
13
|
+
space = SimpleStateSpace()
|
|
14
|
+
oracle.pre_path_hook(space)
|
|
15
|
+
oracle.prefer(x >= 7)
|
|
16
|
+
rand = random.Random()
|
|
17
|
+
assert oracle.decide(root, WorstResultNode(rand, x < 7, space.solver), None) == 0.0
|
|
18
|
+
assert oracle.decide(root, WorstResultNode(rand, x >= 3, space.solver), None) == 1.0
|
|
19
|
+
assert (
|
|
20
|
+
oracle.decide(root, WorstResultNode(rand, x == 7, space.solver), None) == 0.25
|
|
21
|
+
)
|
crosshair/pure_importer_test.py
CHANGED
|
@@ -7,26 +7,10 @@ import pytest
|
|
|
7
7
|
from crosshair.pure_importer import prefer_pure_python_imports
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
@
|
|
11
|
-
|
|
12
|
-
saved = {}
|
|
13
|
-
for k in list(sys.modules):
|
|
14
|
-
if k.startswith("pydantic"):
|
|
15
|
-
saved[k] = sys.modules.pop(k)
|
|
16
|
-
try:
|
|
17
|
-
yield
|
|
18
|
-
finally:
|
|
19
|
-
sys.modules.update(saved)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
@pytest.mark.skipif(
|
|
23
|
-
sys.version_info >= (3, 10), reason="no compiled pydantic on 3.10 (yet)"
|
|
10
|
+
@pytest.mark.skip(
|
|
11
|
+
reason="We used to test pydantic here, but current version doesn't use Cython"
|
|
24
12
|
)
|
|
25
13
|
def test_prefer_pure_python_imports():
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
with _pydantic_unloaded():
|
|
30
|
-
with prefer_pure_python_imports():
|
|
31
|
-
pydantic = importlib.import_module("pydantic")
|
|
32
|
-
assert not pydantic.compiled
|
|
14
|
+
with prefer_pure_python_imports():
|
|
15
|
+
pydantic = importlib.import_module("pydantic")
|
|
16
|
+
assert not pydantic.compiled
|
crosshair/register_contract.py
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"""API for registering contracts for external libraries."""
|
|
2
|
+
|
|
2
3
|
from dataclasses import dataclass
|
|
3
4
|
from inspect import Parameter, Signature, getmodule, ismethod, signature
|
|
4
5
|
from types import MethodDescriptorType, ModuleType, WrapperDescriptorType
|
|
5
|
-
from typing import Callable, Dict, List, Optional, Set, Union
|
|
6
|
+
from typing import Callable, Dict, FrozenSet, List, Optional, Set, Union
|
|
6
7
|
from weakref import ReferenceType
|
|
7
8
|
|
|
8
9
|
from crosshair.fnutil import resolve_signature
|
|
@@ -27,11 +28,13 @@ REGISTERED_CONTRACTS: Dict[Callable, ContractOverride] = {}
|
|
|
27
28
|
REGISTERED_MODULES: Set[ModuleType] = set()
|
|
28
29
|
|
|
29
30
|
# Don't automatically register those functions.
|
|
30
|
-
_NO_AUTO_REGISTER:
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
_NO_AUTO_REGISTER: FrozenSet[str] = frozenset(
|
|
32
|
+
{
|
|
33
|
+
"__init__",
|
|
34
|
+
"__init_subclass__",
|
|
35
|
+
"__new__",
|
|
36
|
+
}
|
|
37
|
+
)
|
|
35
38
|
|
|
36
39
|
|
|
37
40
|
def required_param_names(sig: Signature) -> Set[str]:
|
|
@@ -200,6 +203,13 @@ def register_contract(
|
|
|
200
203
|
return _internal_register_contract(fn, pre, post, sig, skip_body)
|
|
201
204
|
|
|
202
205
|
|
|
206
|
+
def clear_contract_registrations():
|
|
207
|
+
global REGISTERED_CONTRACTS
|
|
208
|
+
REGISTERED_CONTRACTS.clear()
|
|
209
|
+
global REGISTERED_MODULES
|
|
210
|
+
REGISTERED_MODULES.clear()
|
|
211
|
+
|
|
212
|
+
|
|
203
213
|
def get_contract(fn: Callable) -> Optional[ContractOverride]:
|
|
204
214
|
"""
|
|
205
215
|
Get the contract associated to the given function, it the function was registered.
|
|
@@ -63,16 +63,6 @@ def test_register_randint():
|
|
|
63
63
|
return randint(x, 10)
|
|
64
64
|
|
|
65
65
|
randint_sig = None
|
|
66
|
-
# Stub parser is not available for python < 3.8.
|
|
67
|
-
if sys.version_info < (3, 8):
|
|
68
|
-
randint_sig = Signature(
|
|
69
|
-
parameters=[
|
|
70
|
-
Parameter("self", Parameter.POSITIONAL_OR_KEYWORD, annotation=Random),
|
|
71
|
-
Parameter("a", Parameter.POSITIONAL_OR_KEYWORD, annotation=int),
|
|
72
|
-
Parameter("b", Parameter.POSITIONAL_OR_KEYWORD, annotation=int),
|
|
73
|
-
],
|
|
74
|
-
return_annotation=int,
|
|
75
|
-
)
|
|
76
66
|
register_contract(
|
|
77
67
|
Random.randint,
|
|
78
68
|
pre=lambda a, b: a <= b,
|
|
@@ -109,12 +99,10 @@ def test_register_numpy_randint():
|
|
|
109
99
|
|
|
110
100
|
def test_register_overload():
|
|
111
101
|
@overload
|
|
112
|
-
def overld(a: int) -> int:
|
|
113
|
-
...
|
|
102
|
+
def overld(a: int) -> int: ...
|
|
114
103
|
|
|
115
104
|
@overload
|
|
116
|
-
def overld(a: str) -> str:
|
|
117
|
-
...
|
|
105
|
+
def overld(a: str) -> str: ...
|
|
118
106
|
|
|
119
107
|
def overld(a: Union[int, str]) -> Union[int, str]:
|
|
120
108
|
if isinstance(a, int):
|