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.
Files changed (176) hide show
  1. _crosshair_tracers.cpython-312-darwin.so +0 -0
  2. crosshair/__init__.py +42 -0
  3. crosshair/__main__.py +8 -0
  4. crosshair/_mark_stacks.h +790 -0
  5. crosshair/_preliminaries_test.py +18 -0
  6. crosshair/_tracers.h +94 -0
  7. crosshair/_tracers_pycompat.h +522 -0
  8. crosshair/_tracers_test.py +138 -0
  9. crosshair/abcstring.py +245 -0
  10. crosshair/auditwall.py +190 -0
  11. crosshair/auditwall_test.py +77 -0
  12. crosshair/codeconfig.py +113 -0
  13. crosshair/codeconfig_test.py +117 -0
  14. crosshair/condition_parser.py +1237 -0
  15. crosshair/condition_parser_test.py +497 -0
  16. crosshair/conftest.py +30 -0
  17. crosshair/copyext.py +155 -0
  18. crosshair/copyext_test.py +84 -0
  19. crosshair/core.py +1763 -0
  20. crosshair/core_and_libs.py +149 -0
  21. crosshair/core_regestered_types_test.py +82 -0
  22. crosshair/core_test.py +1316 -0
  23. crosshair/diff_behavior.py +314 -0
  24. crosshair/diff_behavior_test.py +261 -0
  25. crosshair/dynamic_typing.py +346 -0
  26. crosshair/dynamic_typing_test.py +210 -0
  27. crosshair/enforce.py +282 -0
  28. crosshair/enforce_test.py +182 -0
  29. crosshair/examples/PEP316/__init__.py +1 -0
  30. crosshair/examples/PEP316/bugs_detected/__init__.py +0 -0
  31. crosshair/examples/PEP316/bugs_detected/getattr_magic.py +16 -0
  32. crosshair/examples/PEP316/bugs_detected/hash_consistent_with_equals.py +31 -0
  33. crosshair/examples/PEP316/bugs_detected/shopping_cart.py +24 -0
  34. crosshair/examples/PEP316/bugs_detected/showcase.py +39 -0
  35. crosshair/examples/PEP316/correct_code/__init__.py +0 -0
  36. crosshair/examples/PEP316/correct_code/arith.py +60 -0
  37. crosshair/examples/PEP316/correct_code/chess.py +77 -0
  38. crosshair/examples/PEP316/correct_code/nesting_inference.py +17 -0
  39. crosshair/examples/PEP316/correct_code/numpy_examples.py +132 -0
  40. crosshair/examples/PEP316/correct_code/rolling_average.py +35 -0
  41. crosshair/examples/PEP316/correct_code/showcase.py +104 -0
  42. crosshair/examples/__init__.py +0 -0
  43. crosshair/examples/check_examples_test.py +146 -0
  44. crosshair/examples/deal/__init__.py +1 -0
  45. crosshair/examples/icontract/__init__.py +1 -0
  46. crosshair/examples/icontract/bugs_detected/__init__.py +0 -0
  47. crosshair/examples/icontract/bugs_detected/showcase.py +41 -0
  48. crosshair/examples/icontract/bugs_detected/wrong_sign.py +8 -0
  49. crosshair/examples/icontract/correct_code/__init__.py +0 -0
  50. crosshair/examples/icontract/correct_code/arith.py +51 -0
  51. crosshair/examples/icontract/correct_code/showcase.py +94 -0
  52. crosshair/fnutil.py +391 -0
  53. crosshair/fnutil_test.py +75 -0
  54. crosshair/fuzz_core_test.py +516 -0
  55. crosshair/libimpl/__init__.py +0 -0
  56. crosshair/libimpl/arraylib.py +161 -0
  57. crosshair/libimpl/binascii_ch_test.py +30 -0
  58. crosshair/libimpl/binascii_test.py +67 -0
  59. crosshair/libimpl/binasciilib.py +150 -0
  60. crosshair/libimpl/bisectlib_test.py +23 -0
  61. crosshair/libimpl/builtinslib.py +5228 -0
  62. crosshair/libimpl/builtinslib_ch_test.py +1191 -0
  63. crosshair/libimpl/builtinslib_test.py +3735 -0
  64. crosshair/libimpl/codecslib.py +86 -0
  65. crosshair/libimpl/codecslib_test.py +86 -0
  66. crosshair/libimpl/collectionslib.py +264 -0
  67. crosshair/libimpl/collectionslib_ch_test.py +252 -0
  68. crosshair/libimpl/collectionslib_test.py +332 -0
  69. crosshair/libimpl/copylib.py +23 -0
  70. crosshair/libimpl/copylib_test.py +18 -0
  71. crosshair/libimpl/datetimelib.py +2559 -0
  72. crosshair/libimpl/datetimelib_ch_test.py +354 -0
  73. crosshair/libimpl/datetimelib_test.py +112 -0
  74. crosshair/libimpl/decimallib.py +5257 -0
  75. crosshair/libimpl/decimallib_ch_test.py +78 -0
  76. crosshair/libimpl/decimallib_test.py +76 -0
  77. crosshair/libimpl/encodings/__init__.py +23 -0
  78. crosshair/libimpl/encodings/_encutil.py +187 -0
  79. crosshair/libimpl/encodings/ascii.py +44 -0
  80. crosshair/libimpl/encodings/latin_1.py +40 -0
  81. crosshair/libimpl/encodings/utf_8.py +93 -0
  82. crosshair/libimpl/encodings_ch_test.py +83 -0
  83. crosshair/libimpl/fractionlib.py +16 -0
  84. crosshair/libimpl/fractionlib_test.py +80 -0
  85. crosshair/libimpl/functoolslib.py +34 -0
  86. crosshair/libimpl/functoolslib_test.py +56 -0
  87. crosshair/libimpl/hashliblib.py +30 -0
  88. crosshair/libimpl/hashliblib_test.py +18 -0
  89. crosshair/libimpl/heapqlib.py +47 -0
  90. crosshair/libimpl/heapqlib_test.py +21 -0
  91. crosshair/libimpl/importliblib.py +18 -0
  92. crosshair/libimpl/importliblib_test.py +38 -0
  93. crosshair/libimpl/iolib.py +216 -0
  94. crosshair/libimpl/iolib_ch_test.py +128 -0
  95. crosshair/libimpl/iolib_test.py +19 -0
  96. crosshair/libimpl/ipaddresslib.py +8 -0
  97. crosshair/libimpl/itertoolslib.py +44 -0
  98. crosshair/libimpl/itertoolslib_test.py +44 -0
  99. crosshair/libimpl/jsonlib.py +984 -0
  100. crosshair/libimpl/jsonlib_ch_test.py +42 -0
  101. crosshair/libimpl/jsonlib_test.py +51 -0
  102. crosshair/libimpl/mathlib.py +179 -0
  103. crosshair/libimpl/mathlib_ch_test.py +44 -0
  104. crosshair/libimpl/mathlib_test.py +67 -0
  105. crosshair/libimpl/oslib.py +7 -0
  106. crosshair/libimpl/pathliblib_test.py +10 -0
  107. crosshair/libimpl/randomlib.py +178 -0
  108. crosshair/libimpl/randomlib_test.py +120 -0
  109. crosshair/libimpl/relib.py +846 -0
  110. crosshair/libimpl/relib_ch_test.py +169 -0
  111. crosshair/libimpl/relib_test.py +493 -0
  112. crosshair/libimpl/timelib.py +72 -0
  113. crosshair/libimpl/timelib_test.py +82 -0
  114. crosshair/libimpl/typeslib.py +15 -0
  115. crosshair/libimpl/typeslib_test.py +36 -0
  116. crosshair/libimpl/unicodedatalib.py +75 -0
  117. crosshair/libimpl/unicodedatalib_test.py +42 -0
  118. crosshair/libimpl/urlliblib.py +23 -0
  119. crosshair/libimpl/urlliblib_test.py +19 -0
  120. crosshair/libimpl/weakreflib.py +13 -0
  121. crosshair/libimpl/weakreflib_test.py +69 -0
  122. crosshair/libimpl/zliblib.py +15 -0
  123. crosshair/libimpl/zliblib_test.py +13 -0
  124. crosshair/lsp_server.py +261 -0
  125. crosshair/lsp_server_test.py +30 -0
  126. crosshair/main.py +973 -0
  127. crosshair/main_test.py +543 -0
  128. crosshair/objectproxy.py +376 -0
  129. crosshair/objectproxy_test.py +41 -0
  130. crosshair/opcode_intercept.py +601 -0
  131. crosshair/opcode_intercept_test.py +304 -0
  132. crosshair/options.py +218 -0
  133. crosshair/options_test.py +10 -0
  134. crosshair/patch_equivalence_test.py +75 -0
  135. crosshair/path_cover.py +209 -0
  136. crosshair/path_cover_test.py +138 -0
  137. crosshair/path_search.py +161 -0
  138. crosshair/path_search_test.py +52 -0
  139. crosshair/pathing_oracle.py +271 -0
  140. crosshair/pathing_oracle_test.py +21 -0
  141. crosshair/pure_importer.py +27 -0
  142. crosshair/pure_importer_test.py +16 -0
  143. crosshair/py.typed +0 -0
  144. crosshair/register_contract.py +273 -0
  145. crosshair/register_contract_test.py +190 -0
  146. crosshair/simplestructs.py +1165 -0
  147. crosshair/simplestructs_test.py +283 -0
  148. crosshair/smtlib.py +24 -0
  149. crosshair/smtlib_test.py +14 -0
  150. crosshair/statespace.py +1199 -0
  151. crosshair/statespace_test.py +108 -0
  152. crosshair/stubs_parser.py +352 -0
  153. crosshair/stubs_parser_test.py +43 -0
  154. crosshair/test_util.py +329 -0
  155. crosshair/test_util_test.py +26 -0
  156. crosshair/tools/__init__.py +0 -0
  157. crosshair/tools/check_help_in_doc.py +264 -0
  158. crosshair/tools/check_init_and_setup_coincide.py +119 -0
  159. crosshair/tools/generate_demo_table.py +127 -0
  160. crosshair/tracers.py +544 -0
  161. crosshair/tracers_test.py +154 -0
  162. crosshair/type_repo.py +151 -0
  163. crosshair/unicode_categories.py +589 -0
  164. crosshair/unicode_categories_test.py +27 -0
  165. crosshair/util.py +741 -0
  166. crosshair/util_test.py +173 -0
  167. crosshair/watcher.py +307 -0
  168. crosshair/watcher_test.py +107 -0
  169. crosshair/z3util.py +76 -0
  170. crosshair/z3util_test.py +11 -0
  171. crosshair_tool-0.0.99.dist-info/METADATA +144 -0
  172. crosshair_tool-0.0.99.dist-info/RECORD +176 -0
  173. crosshair_tool-0.0.99.dist-info/WHEEL +6 -0
  174. crosshair_tool-0.0.99.dist-info/entry_points.txt +3 -0
  175. crosshair_tool-0.0.99.dist-info/licenses/LICENSE +93 -0
  176. crosshair_tool-0.0.99.dist-info/top_level.txt +2 -0
@@ -0,0 +1,209 @@
1
+ import builtins
2
+ import enum
3
+ import re
4
+ import traceback
5
+ from dataclasses import dataclass
6
+ from inspect import BoundArguments
7
+ from typing import Callable, List, Optional, Set, TextIO, Tuple, Type
8
+
9
+ from crosshair.condition_parser import condition_parser
10
+ from crosshair.core import (
11
+ ExceptionFilter,
12
+ LazyCreationRepr,
13
+ deep_realize,
14
+ explore_paths,
15
+ realize,
16
+ )
17
+ from crosshair.fnutil import FunctionInfo
18
+ from crosshair.options import AnalysisOptions
19
+ from crosshair.statespace import RootNode, StateSpace, context_statespace
20
+ from crosshair.tracers import (
21
+ COMPOSITE_TRACER,
22
+ CoverageResult,
23
+ CoverageTracingModule,
24
+ NoTracing,
25
+ PushedModule,
26
+ )
27
+ from crosshair.util import (
28
+ ReferencedIdentifier,
29
+ ch_stack,
30
+ debug,
31
+ format_boundargs,
32
+ name_of_type,
33
+ )
34
+
35
+
36
+ class CoverageType(enum.Enum):
37
+ OPCODE = "OPCODE"
38
+ PATH = "PATH"
39
+
40
+
41
+ @dataclass
42
+ class PathSummary:
43
+ args: BoundArguments
44
+ formatted_args: str
45
+ result: str
46
+ exc: Optional[Type[BaseException]]
47
+ exc_message: Optional[str]
48
+ post_args: BoundArguments
49
+ coverage: CoverageResult
50
+ references: Set[ReferencedIdentifier]
51
+
52
+
53
+ def path_cover(
54
+ ctxfn: FunctionInfo,
55
+ options: AnalysisOptions,
56
+ coverage_type: CoverageType,
57
+ arg_formatter: Callable[[BoundArguments], str] = format_boundargs,
58
+ ) -> List[PathSummary]:
59
+ fn, sig = ctxfn.callable()
60
+ while getattr(fn, "__wrapped__", None):
61
+ # Usually we don't want to run decorator code. (and we certainly don't want
62
+ # to measure coverage on the decorator rather than the real body) Unwrap:
63
+ fn = fn.__wrapped__ # type: ignore
64
+ search_root = RootNode()
65
+
66
+ paths: List[PathSummary] = []
67
+ coverage: CoverageTracingModule = CoverageTracingModule(fn)
68
+
69
+ def run_path(args: BoundArguments):
70
+ nonlocal coverage
71
+ with NoTracing():
72
+ coverage = CoverageTracingModule(fn)
73
+ with PushedModule(coverage):
74
+ return fn(*args.args, **args.kwargs)
75
+
76
+ def on_path_complete(
77
+ space: StateSpace,
78
+ pre_args: BoundArguments,
79
+ post_args: BoundArguments,
80
+ ret,
81
+ exc: Optional[BaseException],
82
+ exc_stack: Optional[traceback.StackSummary],
83
+ ) -> bool:
84
+ with ExceptionFilter() as efilter:
85
+ space.detach_path()
86
+
87
+ reprer = context_statespace().extra(LazyCreationRepr)
88
+ formatted_pre_args = reprer.eval_friendly_format(pre_args, arg_formatter)
89
+
90
+ pre_args = deep_realize(pre_args)
91
+ post_args = deep_realize(post_args)
92
+ ret = reprer.eval_friendly_format(ret, lambda x: builtins.repr(x))
93
+ references = reprer.repr_references
94
+
95
+ cov = coverage.get_results(fn)
96
+ debug("Realized args:", formatted_pre_args)
97
+ if exc is not None:
98
+ debug("user-level exception found", type(exc), exc, ch_stack(exc_stack))
99
+ exc_message = realize(str(exc)) if len(exc.args) > 0 else None
100
+ paths.append(
101
+ PathSummary(
102
+ pre_args,
103
+ formatted_pre_args,
104
+ ret,
105
+ type(exc),
106
+ exc_message,
107
+ post_args,
108
+ cov,
109
+ references,
110
+ )
111
+ )
112
+ else:
113
+ debug("Realized return:", ret)
114
+ paths.append(
115
+ PathSummary(
116
+ pre_args,
117
+ formatted_pre_args,
118
+ ret,
119
+ None,
120
+ None,
121
+ post_args,
122
+ cov,
123
+ references,
124
+ )
125
+ )
126
+ return False
127
+ debug("Skipping path (failed to realize values)", efilter.user_exc)
128
+ return False
129
+
130
+ explore_paths(run_path, sig, options, search_root, on_path_complete)
131
+
132
+ opcodes_found: Set[int] = set()
133
+ selected: List[PathSummary] = []
134
+ while paths:
135
+ next_best = max(
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
+ ),
141
+ )
142
+ cur_offsets = next_best.coverage.offsets_covered
143
+ if coverage_type == CoverageType.OPCODE:
144
+ debug("Next best path covers these opcode offsets:", cur_offsets)
145
+ if len(cur_offsets - opcodes_found) == 0:
146
+ break
147
+ selected.append(next_best)
148
+ opcodes_found |= cur_offsets
149
+ paths = [p for p in paths if p is not next_best]
150
+ return selected
151
+
152
+
153
+ def output_argument_dictionary_paths(
154
+ fn: Callable, paths: List[PathSummary], stdout: TextIO, stderr: TextIO
155
+ ):
156
+ for path in paths:
157
+ stdout.write(path.formatted_args + "\n")
158
+ stdout.flush()
159
+
160
+
161
+ def output_eval_exression_paths(
162
+ fn: Callable, paths: List[PathSummary], stdout: TextIO, stderr: TextIO
163
+ ):
164
+ for path in paths:
165
+ stdout.write(fn.__name__ + "(" + path.formatted_args + ")\n")
166
+ stdout.flush()
167
+
168
+
169
+ def import_statements_for_references(references: Set[ReferencedIdentifier]) -> Set[str]:
170
+ imports: Set[str] = set()
171
+ for ref in references:
172
+ if ref.modulename == "builtins":
173
+ continue
174
+ if "." in ref.qualname:
175
+ class_name, _ = ref.qualname.split(".", 1)
176
+ imports.add(f"from {ref.modulename} import {class_name}")
177
+ else:
178
+ imports.add(f"from {ref.modulename} import {ref.qualname}")
179
+ return imports
180
+
181
+
182
+ def output_pytest_paths(
183
+ fn: Callable, paths: List[PathSummary]
184
+ ) -> Tuple[Set[str], List[str]]:
185
+ fn_name = fn.__qualname__
186
+ lines: List[str] = []
187
+ references = {ReferencedIdentifier(fn.__module__, fn_name)}
188
+ imports: Set[str] = set()
189
+
190
+ name_with_underscores = fn_name.replace(".", "_")
191
+ for idx, path in enumerate(paths):
192
+ test_name_suffix = "" if idx == 0 else "_" + str(idx + 1)
193
+ exec_fn = f"{fn_name}({path.formatted_args})"
194
+ lines.append(f"def test_{name_with_underscores}{test_name_suffix}():")
195
+ if path.exc is None:
196
+ lines.append(f" assert {exec_fn} == {path.result}")
197
+ else:
198
+ imports.add("import pytest")
199
+ if path.exc_message is not None:
200
+ lines.append(
201
+ f" with pytest.raises({name_of_type(path.exc)}, match={repr(re.escape(path.exc_message))}):"
202
+ )
203
+ else:
204
+ lines.append(f" with pytest.raises({name_of_type(path.exc)}):")
205
+ lines.append(f" {exec_fn}")
206
+ lines.append("")
207
+ references |= path.references
208
+ imports |= import_statements_for_references(references)
209
+ return (imports, lines)
@@ -0,0 +1,138 @@
1
+ import functools
2
+ import re
3
+ import textwrap
4
+ from dataclasses import dataclass
5
+ from enum import Enum
6
+ from io import StringIO
7
+ from typing import Callable, Optional
8
+
9
+ from crosshair.fnutil import FunctionInfo
10
+ from crosshair.options import DEFAULT_OPTIONS
11
+ from crosshair.path_cover import (
12
+ CoverageType,
13
+ output_eval_exression_paths,
14
+ output_pytest_paths,
15
+ path_cover,
16
+ )
17
+ from crosshair.statespace import context_statespace
18
+ from crosshair.tracers import NoTracing
19
+
20
+
21
+ def _foo(x: int) -> int:
22
+ if x > 100:
23
+ return 100
24
+ return x
25
+
26
+
27
+ def _regex(x: str) -> bool:
28
+ compiled = re.compile("f(o)+")
29
+ return bool(compiled.fullmatch(x))
30
+
31
+
32
+ def _exceptionex(x: int) -> int:
33
+ if x == 42:
34
+ raise ValueError
35
+ return x
36
+
37
+
38
+ def _symbolic_exception_example(x: str) -> str:
39
+ if x == "foo'bar\"baz":
40
+ raise ValueError(x)
41
+ return x
42
+
43
+
44
+ def _has_no_successful_paths(x: int) -> None:
45
+ with NoTracing():
46
+ context_statespace().defer_assumption("fail", lambda: False)
47
+
48
+
49
+ @dataclass
50
+ class Train:
51
+ class Color(Enum):
52
+ RED = 0
53
+
54
+ color: Color
55
+
56
+
57
+ def _paint_train(train: Train, color: Train.Color) -> Train:
58
+ return Train(color=color)
59
+
60
+
61
+ OPTS = DEFAULT_OPTIONS.overlay(max_iterations=10)
62
+ foo = FunctionInfo.from_fn(_foo)
63
+ decorated_foo = FunctionInfo.from_fn(functools.lru_cache()(_foo))
64
+ regex = FunctionInfo.from_fn(_regex)
65
+ exceptionex = FunctionInfo.from_fn(_exceptionex)
66
+ symbolic_exception_example = FunctionInfo.from_fn(_symbolic_exception_example)
67
+ has_no_successful_paths = FunctionInfo.from_fn(_has_no_successful_paths)
68
+ paint_train = FunctionInfo.from_fn(_paint_train)
69
+
70
+
71
+ def test_path_cover_foo() -> None:
72
+ paths = list(path_cover(foo, OPTS, CoverageType.OPCODE))
73
+ assert len(paths) == 2
74
+ small, large = sorted(paths, key=lambda p: p.result) # type: ignore
75
+ assert large.result == "100"
76
+ assert large.args.arguments["x"] > 100
77
+ assert small.result == repr(small.args.arguments["x"])
78
+
79
+
80
+ def test_path_cover_decorated_foo() -> None:
81
+ paths = list(path_cover(decorated_foo, OPTS, CoverageType.OPCODE))
82
+ assert len(paths) == 2
83
+
84
+
85
+ def test_path_cover_regex() -> None:
86
+ paths = list(path_cover(regex, OPTS, CoverageType.OPCODE))
87
+ assert len(paths) == 1
88
+ paths = list(path_cover(regex, OPTS, CoverageType.PATH))
89
+ input_output = set((p.args.arguments["x"], p.result) for p in paths)
90
+ assert ("foo", "True") in input_output
91
+
92
+
93
+ def test_path_cover_exception_example() -> None:
94
+ paths = list(path_cover(exceptionex, OPTS, CoverageType.OPCODE))
95
+ out, err = StringIO(), StringIO()
96
+ output_eval_exression_paths(_exceptionex, paths, out, err)
97
+ assert "_exceptionex(42)" in out.getvalue()
98
+
99
+
100
+ def test_path_cover_symbolic_exception_message() -> None:
101
+ paths = list(path_cover(symbolic_exception_example, OPTS, CoverageType.OPCODE))
102
+ _imports, lines = output_pytest_paths(_symbolic_exception_example, paths)
103
+ expected = textwrap.dedent(
104
+ """\
105
+ def test__symbolic_exception_example():
106
+ with pytest.raises(ValueError, match='foo\\'bar"baz'):
107
+ _symbolic_exception_example('foo\\'bar"baz')"""
108
+ )
109
+ assert expected in "\n".join(lines)
110
+
111
+
112
+ def test_has_no_successful_paths() -> None:
113
+ assert list(path_cover(has_no_successful_paths, OPTS, CoverageType.OPCODE)) == []
114
+
115
+
116
+ def test_path_cover_lambda() -> None:
117
+ def lambdaFn(a: Optional[Callable[[int], int]]):
118
+ if a:
119
+ return a(2) + 4
120
+ else:
121
+ return "hello"
122
+
123
+ assert path_cover(FunctionInfo.from_fn(lambdaFn), OPTS, CoverageType.OPCODE)
124
+ # TODO: more detailed assert?
125
+
126
+
127
+ def test_path_cover_pytest_output() -> None:
128
+ paths = list(path_cover(paint_train, OPTS, CoverageType.OPCODE))
129
+ imports, lines = output_pytest_paths(_paint_train, paths)
130
+ assert lines == [
131
+ "def test__paint_train():",
132
+ " assert _paint_train(Train(Train.Color.RED), Train.Color.RED) == Train(color=Train.Color.RED)",
133
+ "",
134
+ ]
135
+ assert imports == {
136
+ "from crosshair.path_cover_test import _paint_train",
137
+ "from crosshair.path_cover_test import Train",
138
+ }
@@ -0,0 +1,161 @@
1
+ import enum
2
+ import traceback
3
+ from dataclasses import dataclass
4
+ from inspect import BoundArguments
5
+ from typing import Callable, Optional, Type
6
+
7
+ from crosshair.copyext import CopyMode, deepcopyext
8
+ from crosshair.core import ExceptionFilter, LazyCreationRepr, explore_paths
9
+ from crosshair.fnutil import FunctionInfo
10
+ from crosshair.libimpl.builtinslib import SymbolicInt
11
+ from crosshair.options import AnalysisOptions
12
+ from crosshair.statespace import RootNode, StateSpace, context_statespace
13
+ from crosshair.tracers import CoverageResult, NoTracing, ResumedTracing
14
+ from crosshair.util import (
15
+ CrossHairInternal,
16
+ ch_stack,
17
+ debug,
18
+ format_boundargs_as_dictionary,
19
+ )
20
+
21
+
22
+ class OptimizationKind(enum.Enum):
23
+ SIMPLIFY = "SIMPLIFY"
24
+ # TODO: simplify mode is quite dumb atm; partly because eval_friendly_format
25
+ # realizes prior to generating the string.
26
+ NONE = "NONE"
27
+ MINIMIZE_INT = "MINIMIZE_INT"
28
+
29
+
30
+ @dataclass
31
+ class PathSummary:
32
+ args: BoundArguments
33
+ result: object
34
+ exc: Optional[Type[BaseException]]
35
+ post_args: BoundArguments
36
+ coverage: CoverageResult
37
+
38
+
39
+ def path_search(
40
+ ctxfn: FunctionInfo,
41
+ options: AnalysisOptions,
42
+ argument_formatter: Optional[Callable[[BoundArguments], str]],
43
+ optimization_kind: OptimizationKind,
44
+ optimize_fn: Optional[Callable],
45
+ on_example: Callable[[str], None],
46
+ ) -> None:
47
+
48
+ if argument_formatter is None:
49
+ checked_format = (
50
+ lambda args: context_statespace()
51
+ .extra(LazyCreationRepr)
52
+ .eval_friendly_format(args, format_boundargs_as_dictionary)
53
+ )
54
+ else:
55
+
56
+ def checked_format(args: BoundArguments) -> str:
57
+ assert argument_formatter is not None
58
+ args = deepcopyext(args, CopyMode.REALIZE, {})
59
+ try:
60
+ return argument_formatter(args)
61
+ except Exception as exc:
62
+ raise CrossHairInternal(str(exc)) from exc
63
+
64
+ if optimization_kind == OptimizationKind.SIMPLIFY:
65
+ assert optimize_fn is None
66
+
67
+ def scorechar(codepoint: int):
68
+ if codepoint >= ord("a"):
69
+ return codepoint - ord("a")
70
+ elif codepoint >= ord("0"):
71
+ return 30 + codepoint - ord("0")
72
+ else:
73
+ return 100 + codepoint
74
+
75
+ def shrinkscore(ret, args: BoundArguments):
76
+ reprstr = checked_format(args)
77
+ return len(reprstr) * 1000 + sum(scorechar(ord(ch)) for ch in reprstr)
78
+
79
+ optimization_kind = OptimizationKind.MINIMIZE_INT
80
+ optimize_fn = shrinkscore
81
+
82
+ fn, sig = ctxfn.callable()
83
+ search_root = RootNode()
84
+
85
+ best_input: Optional[str] = None
86
+ best_score: Optional[int] = None
87
+ _optimize_fn = optimize_fn or (lambda _ret, args: fn(*args.args, **args.kwargs))
88
+
89
+ def on_path_complete(
90
+ space: StateSpace,
91
+ pre_args: BoundArguments,
92
+ post_args: BoundArguments,
93
+ ret,
94
+ exc: Optional[BaseException],
95
+ exc_stack: Optional[traceback.StackSummary],
96
+ ) -> bool:
97
+ nonlocal best_input, best_score
98
+ with NoTracing():
99
+ if exc is not None:
100
+ debug(
101
+ "Aborting path, hit exception",
102
+ type(exc),
103
+ exc,
104
+ ch_stack(exc_stack),
105
+ )
106
+ return False
107
+ debug("Path succeeded")
108
+ if optimization_kind == OptimizationKind.NONE:
109
+ with ResumedTracing():
110
+ best_input = checked_format(pre_args)
111
+ debug("Found input:", best_input)
112
+ on_example(best_input)
113
+ return True
114
+ with NoTracing(), ExceptionFilter() as efilter:
115
+ with ResumedTracing():
116
+ score = _optimize_fn(ret, pre_args)
117
+ space.detach_path()
118
+ smt_score = SymbolicInt._coerce_to_smt_sort(score)
119
+ if smt_score is None:
120
+ debug("non integer score returned; ignoring.")
121
+ return False
122
+ if space.smt_fork(smt_score < 0, probability_true=0.0):
123
+ debug("Score was leass than zero; ignoring.")
124
+ return False
125
+ if best_score is not None and space.smt_fork(
126
+ smt_score >= best_score, probability_true=0.0
127
+ ):
128
+ debug("Does not beat the best score of", best_score)
129
+ return False
130
+ known_min = 0
131
+ known_max = best_score
132
+ test = 1000 if known_max is None else (known_max // 2)
133
+ while True:
134
+ debug(known_min, test, known_max)
135
+ if space.smt_fork(smt_score < test, probability_true=1.0):
136
+ known_max = test - 1
137
+ else:
138
+ known_min = test
139
+ if known_max is None:
140
+ test = known_min * 100
141
+ continue
142
+ if known_min == known_max:
143
+ best_score = known_min
144
+ with ResumedTracing():
145
+ best_input = checked_format(pre_args)
146
+ break
147
+ test = (known_min + known_max + 1) // 2
148
+ debug("Minimized score to", best_score)
149
+ debug("For input:", best_input)
150
+ on_example(best_input)
151
+ return best_score == 0
152
+ debug("Skipping path (failure during scoring)", efilter.user_exc)
153
+ return False
154
+
155
+ explore_paths(
156
+ lambda ba: fn(*ba.args, **ba.kwargs),
157
+ sig,
158
+ options,
159
+ search_root,
160
+ on_path_complete,
161
+ )
@@ -0,0 +1,52 @@
1
+ import ast
2
+ from inspect import BoundArguments
3
+ from typing import Callable, Optional
4
+
5
+ from crosshair.fnutil import FunctionInfo
6
+ from crosshair.options import DEFAULT_OPTIONS, AnalysisOptions, AnalysisOptionSet
7
+ from crosshair.path_search import OptimizationKind, path_search
8
+
9
+
10
+ def ten_over_difference(x: int, y: int) -> int:
11
+ if x != 42:
12
+ return 10 // (x - y)
13
+ return 100
14
+
15
+
16
+ def do_path_search(
17
+ fn: Callable,
18
+ options: AnalysisOptions,
19
+ argument_formatter: Optional[Callable[[BoundArguments], str]],
20
+ optimization_kind: OptimizationKind,
21
+ optimize_fn: Optional[Callable] = None,
22
+ ) -> Optional[str]:
23
+ fninfo = FunctionInfo.from_fn(fn)
24
+ final_example: Optional[str] = None
25
+
26
+ def on_example(example: str) -> None:
27
+ nonlocal final_example
28
+ final_example = example
29
+
30
+ path_search(
31
+ fninfo, options, argument_formatter, optimization_kind, optimize_fn, on_example
32
+ )
33
+ return final_example
34
+
35
+
36
+ def test_optimize_options() -> None:
37
+ opts = DEFAULT_OPTIONS.overlay(AnalysisOptionSet(max_uninteresting_iterations=10))
38
+ ret = do_path_search(
39
+ ten_over_difference, opts, None, optimization_kind=OptimizationKind.SIMPLIFY
40
+ )
41
+ assert ret in ('{"x": 1, "y": 0}', '{"x": 0, "y": 1}')
42
+ ret = do_path_search(
43
+ ten_over_difference, opts, None, optimization_kind=OptimizationKind.MINIMIZE_INT
44
+ )
45
+ assert ret is not None
46
+ parsed_ret = ast.literal_eval(ret)
47
+ assert parsed_ret["x"] - parsed_ret["y"] > 10
48
+ ret = do_path_search(
49
+ ten_over_difference, opts, None, optimization_kind=OptimizationKind.NONE
50
+ )
51
+ assert ret is not None
52
+ ast.literal_eval(ret) # (just ensure the result is parseable)