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,516 @@
1
+ import builtins
2
+ import copy
3
+ import enum
4
+ import random
5
+ import re
6
+ import time
7
+ import traceback
8
+ from collections.abc import Hashable, Mapping, Sized
9
+ from inspect import (
10
+ Parameter,
11
+ Signature,
12
+ getmembers,
13
+ isbuiltin,
14
+ isfunction,
15
+ ismethoddescriptor,
16
+ )
17
+ from types import ModuleType
18
+ from typing import (
19
+ Callable,
20
+ Dict,
21
+ FrozenSet,
22
+ Iterable,
23
+ List,
24
+ Optional,
25
+ Sequence,
26
+ Set,
27
+ Tuple,
28
+ Type,
29
+ TypeVar,
30
+ )
31
+
32
+ import pytest
33
+
34
+ import crosshair.core_and_libs # ensure patches/plugins are loaded
35
+ from crosshair.abcstring import AbcString
36
+ from crosshair.core import Patched, deep_realize, proxy_for_type, realize
37
+ from crosshair.fnutil import resolve_signature
38
+ from crosshair.libimpl.builtinslib import origin_of
39
+ from crosshair.statespace import (
40
+ CallAnalysis,
41
+ CrossHairInternal,
42
+ IgnoreAttempt,
43
+ RootNode,
44
+ StateSpace,
45
+ StateSpaceContext,
46
+ )
47
+ from crosshair.stubs_parser import signature_from_stubs
48
+ from crosshair.test_util import flexible_equal
49
+ from crosshair.tracers import COMPOSITE_TRACER, NoTracing, ResumedTracing
50
+ from crosshair.util import CrosshairUnsupported, debug, is_iterable, type_args_of
51
+
52
+ FUZZ_SEED = 1348
53
+
54
+ T = TypeVar("T")
55
+
56
+
57
+ def simple_name(value: object) -> str:
58
+ return re.sub(r"[\W_]+", "_", str(value))
59
+
60
+
61
+ IMMUTABLE_BASE_TYPES = [bool, int, float, str, frozenset]
62
+ ALL_BASE_TYPES = IMMUTABLE_BASE_TYPES + [set, dict, list]
63
+
64
+
65
+ def gen_type(r: random.Random, type_root: Type) -> type:
66
+ if type_root is Hashable:
67
+ base = r.choice(IMMUTABLE_BASE_TYPES)
68
+ elif type_root is object:
69
+ base = r.choice(ALL_BASE_TYPES)
70
+ else:
71
+ base = type_root
72
+ if base is dict:
73
+ kt = gen_type(r, Hashable)
74
+ vt = gen_type(r, object)
75
+ return Dict[kt, vt] # type: ignore
76
+ elif base is list:
77
+ return List[gen_type(r, object)] # type: ignore
78
+ elif base is set:
79
+ return Set[gen_type(r, Hashable)] # type: ignore
80
+ elif base is frozenset:
81
+ return FrozenSet[gen_type(r, Hashable)] # type: ignore
82
+ else:
83
+ return base
84
+
85
+
86
+ # TODO: consider replacing this with typeshed someday!
87
+ _SIGNATURE_OVERRIDES = {
88
+ getattr: Signature(
89
+ [
90
+ Parameter("obj", Parameter.POSITIONAL_ONLY, annotation=object),
91
+ Parameter("attr", Parameter.POSITIONAL_ONLY, annotation=str),
92
+ Parameter("default", Parameter.POSITIONAL_ONLY, annotation=object),
93
+ ]
94
+ ),
95
+ dict.items: Signature(),
96
+ dict.keys: Signature(),
97
+ # TODO: fuzz test values() somehow. items() and keys() are sets and
98
+ # therefore comparable -- not values() though:
99
+ # dict.values: Signature(),
100
+ dict.clear: Signature(),
101
+ dict.copy: Signature(),
102
+ dict.pop: Signature(
103
+ [
104
+ Parameter("k", Parameter.POSITIONAL_ONLY, annotation=object),
105
+ Parameter("d", Parameter.POSITIONAL_ONLY, annotation=object),
106
+ ]
107
+ ),
108
+ dict.update: Signature(
109
+ [Parameter("d", Parameter.POSITIONAL_ONLY, annotation=dict)]
110
+ ),
111
+ }
112
+
113
+
114
+ def value_for_type(typ: Type, r: random.Random) -> object:
115
+ """
116
+ post: isinstance(_, typ)
117
+ """
118
+ origin = origin_of(typ)
119
+ type_args = type_args_of(typ)
120
+ if typ is bool:
121
+ return r.choice([True, False])
122
+ elif typ is int:
123
+ return r.choice([-1, 0, 1, 2, 10])
124
+ elif typ is float:
125
+ return r.choice([-1.0, 0.0, 1.0, 2.0, 10.0]) # TODO: Inf, NaN
126
+ elif typ is str:
127
+ return r.choice(
128
+ ["", "x", "0", "xyz"]
129
+ ) # , '\0']) # TODO: null does not work properly yet
130
+ elif typ is bytes:
131
+ return r.choice([b"", b"ab", b"abc", b"\x00"])
132
+ elif origin in (list, set, frozenset):
133
+ (item_type,) = type_args
134
+ items = []
135
+ for _ in range(r.choice([0, 0, 0, 1, 1, 2])):
136
+ items.append(value_for_type(item_type, r))
137
+ return origin(items)
138
+ elif origin is dict:
139
+ (key_type, val_type) = type_args
140
+ ret = {}
141
+ for _ in range(r.choice([0, 0, 0, 1, 1, 1, 2])):
142
+ ret[value_for_type(key_type, r)] = value_for_type(val_type, r) # type: ignore
143
+ return ret
144
+ raise NotImplementedError
145
+
146
+
147
+ def get_signature(method) -> Optional[Signature]:
148
+ override_sig = _SIGNATURE_OVERRIDES.get(method, None)
149
+ if override_sig:
150
+ return override_sig
151
+ sig = resolve_signature(method)
152
+ if isinstance(sig, Signature):
153
+ return sig
154
+ stub_sigs, stub_sigs_valid = signature_from_stubs(method)
155
+ if stub_sigs_valid and len(stub_sigs) == 1:
156
+ debug("using signature from stubs:", stub_sigs[0])
157
+ return stub_sigs[0]
158
+ return None
159
+
160
+
161
+ def get_testable_members(classes: Iterable[type]) -> Iterable[Tuple[type, str]]:
162
+ for cls in classes:
163
+ for method_name, method in getmembers(cls):
164
+ if method_name.startswith("__"):
165
+ continue
166
+ if not (isfunction(method) or ismethoddescriptor(method)):
167
+ # TODO: fuzz test class/staticmethods with symbolic args
168
+ continue
169
+ yield cls, method_name
170
+
171
+
172
+ class TrialStatus(enum.Enum):
173
+ NORMAL = 0
174
+ UNSUPPORTED = 1
175
+
176
+
177
+ class FuzzTester:
178
+ r: random.Random
179
+
180
+ def __init__(self, seed):
181
+ self.r = random.Random(seed)
182
+
183
+ def symbolic_run(
184
+ self,
185
+ fn: Callable[[StateSpace, Dict[str, object]], object],
186
+ typed_args: Dict[str, type],
187
+ ) -> Tuple[
188
+ object, # return value
189
+ Optional[Dict[str, object]], # arguments after execution
190
+ Optional[BaseException], # exception thrown, if any
191
+ StateSpace,
192
+ ]:
193
+ search_root = RootNode()
194
+ with COMPOSITE_TRACER, NoTracing():
195
+ for itr in range(1, 200):
196
+ debug("iteration", itr)
197
+ space = StateSpace(
198
+ time.monotonic() + 30.0, 3.0, search_root=search_root
199
+ )
200
+ symbolic_args = {}
201
+ try:
202
+ with Patched(), StateSpaceContext(space):
203
+ symbolic_args = {
204
+ name: proxy_for_type(typ, name)
205
+ for name, typ in typed_args.items()
206
+ }
207
+ with ResumedTracing():
208
+ ret = fn(space, symbolic_args)
209
+ ret = (deep_realize(ret), symbolic_args, None, space)
210
+ space.detach_path()
211
+ return ret
212
+ except IgnoreAttempt as e:
213
+ debug("ignore iteration attempt: ", str(e))
214
+ except Exception as e:
215
+ debug(
216
+ "exception during symbolic execution:", traceback.format_exc()
217
+ )
218
+ return (None, symbolic_args, e, space)
219
+ top_analysis, space_exhausted = space.bubble_status(CallAnalysis())
220
+ if space_exhausted:
221
+ return (
222
+ None,
223
+ symbolic_args,
224
+ CrossHairInternal(f"exhausted after {itr} iterations"),
225
+ space,
226
+ )
227
+ raise CrossHairInternal("Unable to find a successful symbolic execution")
228
+
229
+ def runexpr(self, expr, bindings):
230
+ try:
231
+ return (eval(expr, {}, bindings), None)
232
+ except Exception as e:
233
+ debug(f'eval of "{expr}" produced exception {type(e)}: {e}')
234
+ return (None, e)
235
+
236
+ def run_function_trials(
237
+ self, fns: Sequence[Tuple[str, Callable]], num_trials: int
238
+ ) -> None:
239
+ for fn_name, fn in fns:
240
+ debug("Checking function", fn_name)
241
+ sig = get_signature(fn)
242
+ if not sig:
243
+ debug("Skipping", fn_name, " - unable to inspect signature")
244
+ continue
245
+ arg_names = [chr(ord("a") + i) for i in range(len(sig.parameters))]
246
+ arg_expr_strings = [
247
+ (a if p.kind != Parameter.KEYWORD_ONLY else f"{p.name}={a}")
248
+ for a, p in zip(arg_names, list(sig.parameters.values()))
249
+ ]
250
+ expr_str = fn_name + "(" + ",".join(arg_expr_strings) + ")"
251
+ arg_type_roots = {name: object for name in arg_names}
252
+ for trial_num in range(num_trials):
253
+ self.run_trial(expr_str, arg_type_roots)
254
+
255
+ def run_trial(self, expr_str: str, arg_type_roots: Dict[str, Type]) -> TrialStatus:
256
+ expr = expr_str.format(*arg_type_roots.keys())
257
+ typed_args = {
258
+ name: gen_type(self.r, type_root)
259
+ for name, type_root in arg_type_roots.items()
260
+ }
261
+ literal_args = {
262
+ name: value_for_type(typ, self.r) for name, typ in typed_args.items()
263
+ }
264
+
265
+ def symbolic_checker(
266
+ space: StateSpace, symbolic_args: Dict[str, object]
267
+ ) -> object:
268
+ for name in typed_args.keys():
269
+ literal, symbolic = literal_args[name], symbolic_args[name]
270
+ with NoTracing():
271
+ # TODO: transition into general purpose SMT expr extractor
272
+ # for equality with constant
273
+ if hasattr(symbolic, "_smt_promote_literal"):
274
+ symbolic.var = symbolic._smt_promote_literal(literal) # type: ignore
275
+ elif is_iterable(literal) and is_iterable(symbolic):
276
+ with ResumedTracing():
277
+ space.add(len(literal) == len(symbolic)) # type: ignore
278
+ if literal != symbolic:
279
+ raise IgnoreAttempt(
280
+ f'symbolic "{name}" not equal to literal "{name}"'
281
+ )
282
+ if repr(literal) != repr(symbolic):
283
+ # dict/set ordering, -0.0 vs 0.0, etc
284
+ raise IgnoreAttempt(
285
+ f'symbolic "{name}" not repr-equal to literal "{name}"'
286
+ )
287
+ return eval(expr, symbolic_args.copy())
288
+
289
+ debug(f" ===== {expr} with {literal_args} ===== ")
290
+ compile(expr, "<string>", "eval")
291
+ postexec_literal_args = copy.deepcopy(literal_args)
292
+ literal_ret, literal_exc = self.runexpr(expr, postexec_literal_args)
293
+ (
294
+ symbolic_ret,
295
+ postexec_symbolic_args,
296
+ symbolic_exc,
297
+ space,
298
+ ) = self.symbolic_run(symbolic_checker, typed_args)
299
+ if isinstance(symbolic_exc, CrosshairUnsupported):
300
+ return TrialStatus.UNSUPPORTED
301
+ with Patched(), StateSpaceContext(space), COMPOSITE_TRACER, NoTracing():
302
+ # compare iterators as the values they produce:
303
+ with ResumedTracing():
304
+ if isinstance(literal_ret, Iterable) and isinstance(
305
+ symbolic_ret, Iterable
306
+ ):
307
+ literal_ret = list(literal_ret)
308
+ symbolic_ret = list(symbolic_ret)
309
+ postexec_symbolic_args = deep_realize(postexec_symbolic_args)
310
+ symbolic_ret = deep_realize(symbolic_ret)
311
+ symbolic_exc = deep_realize(symbolic_exc)
312
+ rets_differ = not realize(flexible_equal(literal_ret, symbolic_ret))
313
+ postexec_args_differ = realize(
314
+ bool(postexec_literal_args != postexec_symbolic_args)
315
+ )
316
+ if (
317
+ rets_differ
318
+ or postexec_args_differ
319
+ or type(literal_exc) != type(symbolic_exc)
320
+ ):
321
+ debug(f" ***** BEGIN FAILURE FOR {expr} WITH {literal_args} ***** ")
322
+ debug(
323
+ f" ***** Expected return: {literal_ret} (exc: {type(literal_exc)} {literal_exc})"
324
+ )
325
+ debug(f" ***** postexec args: {postexec_literal_args}")
326
+ debug(
327
+ f" ***** Symbolic return: {symbolic_ret} (exc: {type(symbolic_exc)} {symbolic_exc})"
328
+ )
329
+ debug(f" ***** postexec args: {postexec_symbolic_args}")
330
+ debug(f" ***** END FAILURE FOR {expr} ***** ")
331
+ assert literal_ret == symbolic_ret
332
+ assert False, f"Mismatch while evaluating {expr} with {literal_args}"
333
+ debug(" OK ret= ", literal_ret, "vs", symbolic_ret)
334
+ debug(
335
+ " OK exc= ",
336
+ type(literal_exc),
337
+ literal_exc,
338
+ "vs",
339
+ type(symbolic_exc),
340
+ symbolic_exc,
341
+ )
342
+ return TrialStatus.NORMAL
343
+
344
+ def fuzz_function(self, module: ModuleType, method_name: str):
345
+ method = getattr(module, method_name)
346
+ sig = get_signature(method)
347
+ if not sig:
348
+ return
349
+ arg_names = [chr(ord("a") + i) for i in range(len(sig.parameters))]
350
+ arg_expr_strings = [
351
+ (a if p.kind != Parameter.KEYWORD_ONLY else f"{p.name}={a}")
352
+ for a, p in zip(arg_names, list(sig.parameters.values())[1:])
353
+ ]
354
+ expr_str = method_name + "(" + ",".join(arg_expr_strings) + ")"
355
+ arg_type_roots = {name: object for name in arg_names}
356
+ self.run_trial(expr_str, arg_type_roots)
357
+
358
+ def fuzz_method(self, cls: type, method_name: str):
359
+ method = getattr(cls, method_name)
360
+ sig = get_signature(method)
361
+ if not sig:
362
+ return
363
+ arg_names = [chr(ord("a") + i - 1) for i in range(1, len(sig.parameters))]
364
+ arg_expr_strings = [
365
+ (a if p.kind != Parameter.KEYWORD_ONLY else f"{p.name}={a}")
366
+ for a, p in zip(arg_names, list(sig.parameters.values())[1:])
367
+ ]
368
+ expr_str = "self." + method_name + "(" + ",".join(arg_expr_strings) + ")"
369
+ arg_type_roots: Dict[str, type] = {name: object for name in arg_names}
370
+ arg_type_roots["self"] = cls
371
+ self.run_trial(expr_str, arg_type_roots)
372
+
373
+
374
+ @pytest.mark.parametrize("seed", range(25))
375
+ @pytest.mark.parametrize(
376
+ "expr_str",
377
+ [
378
+ "iter({})",
379
+ "reversed({})",
380
+ "len({})",
381
+ # "repr({})", # false positive with unstable set ordering
382
+ "str({})",
383
+ "+{}",
384
+ "-{}",
385
+ "~{}",
386
+ # TODO: we aren't `dir()`-compatable right now.
387
+ ],
388
+ ids=simple_name,
389
+ )
390
+ def test_unary_ops(expr_str, seed) -> None:
391
+ tester = FuzzTester(seed)
392
+ arg_type_roots = {"a": object}
393
+ tester.run_trial(expr_str, arg_type_roots)
394
+
395
+
396
+ @pytest.mark.parametrize("seed", set(range(25)) - {17})
397
+ @pytest.mark.parametrize(
398
+ "expr_str",
399
+ [
400
+ "{} + {}",
401
+ "{} - {}",
402
+ "{} * {}",
403
+ "{} / {}",
404
+ "{} < {}",
405
+ "{} <= {}",
406
+ "{} >= {}",
407
+ "{} > {}",
408
+ "{} == {}",
409
+ "{}[{}]",
410
+ "{}.__delitem__({})",
411
+ "{} in {}",
412
+ "{} & {}",
413
+ "{} | {}",
414
+ "{} ^ {}",
415
+ "{} and {}",
416
+ "{} or {}",
417
+ "{} // {}",
418
+ "{} ** {}",
419
+ "{} % {}",
420
+ ],
421
+ ids=simple_name,
422
+ )
423
+ def test_binary_ops(expr_str, seed) -> None:
424
+ tester = FuzzTester(seed)
425
+ arg_type_roots = {"a": object, "b": object}
426
+ tester.run_trial(expr_str, arg_type_roots)
427
+
428
+
429
+ IGNORED_BUILTINS = [
430
+ "breakpoint",
431
+ "copyright",
432
+ "credits",
433
+ "dir",
434
+ "exit",
435
+ "help",
436
+ "id",
437
+ "input",
438
+ "license",
439
+ "locals",
440
+ "object",
441
+ "open",
442
+ "property",
443
+ "quit",
444
+ # TODO: debug and un-ignore the following:
445
+ "isinstance",
446
+ "issubclass",
447
+ "float",
448
+ ]
449
+
450
+
451
+ @pytest.mark.parametrize("seed", range(4))
452
+ @pytest.mark.parametrize(
453
+ "module,method_name",
454
+ [
455
+ (builtins, name)
456
+ for (name, _) in getmembers(builtins, isbuiltin)
457
+ if not name.startswith("_")
458
+ ],
459
+ ids=simple_name,
460
+ )
461
+ def test_builtin_functions(seed, module, method_name) -> None:
462
+ if method_name not in IGNORED_BUILTINS:
463
+ FuzzTester(seed).fuzz_function(module, method_name)
464
+ # fns = [
465
+ # (name, fn)
466
+ # for name, fn in getmembers(builtins)
467
+ # if (hasattr(fn, "__call__") and not name.startswith("_") and name not in ignore)
468
+ # ]
469
+
470
+
471
+ @pytest.mark.parametrize("seed", range(4))
472
+ @pytest.mark.parametrize(
473
+ "cls,method_name",
474
+ # we don't inspect str directly, because many signature() fails on several members:
475
+ get_testable_members([AbcString]),
476
+ ids=simple_name,
477
+ )
478
+ def test_str_methods(seed, cls, method_name) -> None:
479
+ FuzzTester(seed).fuzz_method(str, method_name)
480
+ # # we don't inspect str directly, because many signature() fails on several members:
481
+ # # TODO test maketrans()
482
+
483
+
484
+ @pytest.mark.parametrize("seed", range(5))
485
+ @pytest.mark.parametrize(
486
+ "cls,method_name",
487
+ get_testable_members([list, dict]),
488
+ ids=simple_name,
489
+ )
490
+ def test_container_methods(seed, cls, method_name) -> None:
491
+ FuzzTester(seed).fuzz_method(cls, method_name)
492
+
493
+
494
+ # TODO: deal with iteration order (and then increase repeat count)
495
+ @pytest.mark.parametrize("seed", range(3))
496
+ @pytest.mark.parametrize(
497
+ "cls,method_name",
498
+ get_testable_members([set, frozenset]),
499
+ ids=simple_name,
500
+ )
501
+ def test_set_methods(seed, cls, method_name) -> None:
502
+ FuzzTester(seed).fuzz_method(cls, method_name)
503
+
504
+
505
+ @pytest.mark.parametrize("seed", range(10))
506
+ @pytest.mark.parametrize(
507
+ "cls,method_name",
508
+ get_testable_members([int, float]),
509
+ ids=simple_name,
510
+ )
511
+ def test_numeric_methods(seed, cls, method_name) -> None:
512
+ FuzzTester(seed).fuzz_method(cls, method_name)
513
+ # TODO test int properties: real, imag, numerator, denominator
514
+ # TODO test int.conjugate()
515
+ # TODO test float properties: real, imag
516
+ # TODO test float.conjugate()
File without changes
@@ -0,0 +1,161 @@
1
+ import sys
2
+ from array import array
3
+ from typing import BinaryIO, Dict, Iterable, List, Sequence, Tuple
4
+
5
+ import z3 # type: ignore
6
+
7
+ from crosshair import SymbolicFactory, realize, register_patch
8
+ from crosshair.core import register_type
9
+ from crosshair.libimpl.builtinslib import SymbolicArrayBasedUniformTuple
10
+ from crosshair.simplestructs import ShellMutableSequence
11
+ from crosshair.statespace import StateSpace
12
+ from crosshair.tracers import NoTracing
13
+ from crosshair.util import CrossHairValue
14
+
15
+ INT_TYPE_BOUNDS: Dict[str, Tuple[int, int]] = {
16
+ # (min, max) ranges - inclusive on min, exclusive on max.
17
+ # Order is significant - we choose earlier codes more readily.
18
+ "L": (0, 1 << 32),
19
+ "B": (0, 1 << 8),
20
+ "l": (-(1 << 31), (1 << 31)),
21
+ "b": (-(1 << 7), (1 << 7)),
22
+ "Q": (0, 1 << 64),
23
+ "q": (-(1 << 63), (1 << 63)),
24
+ "I": (0, 1 << 16),
25
+ "i": (-(1 << 15), (1 << 15)),
26
+ "H": (0, 1 << 16),
27
+ "h": (-(1 << 15), (1 << 15)),
28
+ }
29
+
30
+ INT_TYPE_SIZE = {c: array(c).itemsize for c in INT_TYPE_BOUNDS.keys()}
31
+
32
+
33
+ def pick_code(space: StateSpace) -> Tuple[str, int, int]:
34
+ last_idx = len(INT_TYPE_BOUNDS) - 1
35
+ for idx, (code, rng) in enumerate(INT_TYPE_BOUNDS.items()):
36
+ if idx < last_idx:
37
+ if space.smt_fork(desc=f"not_{code}_array"):
38
+ continue
39
+ return (code, *rng)
40
+ assert False, "Not Reachable"
41
+
42
+
43
+ def make_array(creator: SymbolicFactory) -> object:
44
+ space = creator.space
45
+ code, minval, maxval = pick_code(space)
46
+ nums = SymbolicArrayBasedUniformTuple(creator.varname, Tuple[int, ...])
47
+ z3_array = nums._arr()
48
+ qvar = z3.Int("arrvar" + space.uniq())
49
+ space.add(z3.ForAll([qvar], minval <= z3.Select(z3_array, qvar)))
50
+ space.add(z3.ForAll([qvar], z3.Select(z3_array, qvar) < maxval))
51
+ return SymbolicArray(code, nums)
52
+
53
+
54
+ def check_int(item, minval, maxval):
55
+ if not (minval <= item < maxval):
56
+ raise OverflowError
57
+ return item
58
+
59
+
60
+ def _array(typecode: str, iterable: Iterable = ()):
61
+ realized_type = realize(typecode)
62
+ bounds = INT_TYPE_BOUNDS.get(typecode)
63
+ if bounds:
64
+ args = [check_int(x, *bounds) for x in iterable]
65
+ return SymbolicArray(realized_type, args)
66
+ return array(realized_type, realize(iterable))
67
+
68
+
69
+ class SymbolicArray(
70
+ ShellMutableSequence,
71
+ CrossHairValue,
72
+ ):
73
+ def __init__(self, typecode: str, items: Sequence = ()):
74
+ # All arguments are presumed valid here
75
+ self.typecode = typecode
76
+ self.itemsize = INT_TYPE_SIZE[typecode]
77
+ self._snapshots: List[array] = []
78
+ super().__init__(items)
79
+
80
+ def _realized_inner(self) -> array:
81
+ with NoTracing():
82
+ realized = self.__ch_realize__()
83
+ self.inner = realized
84
+ return realized
85
+
86
+ def _iter_checker(self, items: Iterable[int]) -> Iterable[int]:
87
+ bounds = INT_TYPE_BOUNDS.get(self.typecode)
88
+ if bounds:
89
+ return (check_int(i, *bounds) for i in items)
90
+ else:
91
+ return items
92
+
93
+ __hash__ = None # type: ignore
94
+
95
+ def __ch_realize__(self):
96
+ return array(self.typecode, self.inner)
97
+
98
+ def __ch_pytype__(self):
99
+ return array
100
+
101
+ def __eq__(self, other):
102
+ if not isinstance(other, array):
103
+ return False
104
+ return ShellMutableSequence.__eq__(self, other)
105
+
106
+ def _spawn(self, items: Sequence) -> ShellMutableSequence:
107
+ return SymbolicArray(self.typecode, items)
108
+
109
+ def append(self, value) -> None:
110
+ bounds = INT_TYPE_BOUNDS.get(self.typecode)
111
+ if bounds:
112
+ check_int(value, *bounds)
113
+ return super().append(value)
114
+
115
+ def buffer_info(self) -> Tuple[int, int]:
116
+ return self._realized_inner().buffer_info()
117
+
118
+ def byteswap(self) -> None:
119
+ self._realized_inner().byteswap()
120
+
121
+ # count() handled by superclass
122
+
123
+ def extend(self, nums: Iterable) -> None:
124
+ super().extend(self._iter_checker(nums))
125
+
126
+ def from_bytes(self, b: Sequence) -> None:
127
+ self.extend(b)
128
+
129
+ def fromfile(self, fd: BinaryIO, num_bytes: int) -> None:
130
+ self._realized_inner().fromfile(fd, num_bytes)
131
+
132
+ def fromlist(self, nums: List) -> None:
133
+ self.extend(nums)
134
+
135
+ def fromunicode(self, s: str) -> None:
136
+ self._realized_inner().fromunicode(s)
137
+
138
+ # index() handled by superclass
139
+ # insert() handled by superclass
140
+ # pop() handled by superclass
141
+ # remove() handled by superclass
142
+ # reverse() handled by superclass
143
+
144
+ def tobytes(self) -> bytes:
145
+ return self._realized_inner().tobytes()
146
+
147
+ def tofile(self, fh: BinaryIO) -> None:
148
+ self._realized_inner().tofile(fh)
149
+
150
+ def tolist(self) -> List:
151
+ return list(self.inner)
152
+
153
+ def tounicode(self) -> str:
154
+ return self._realized_inner().tounicode()
155
+
156
+ # TODO: test repr
157
+
158
+
159
+ def make_registrations():
160
+ register_type(array, make_array)
161
+ register_patch(array, _array)
@@ -0,0 +1,30 @@
1
+ import binascii
2
+ import sys
3
+
4
+ import pytest
5
+
6
+ from crosshair.core import analyze_function, run_checkables
7
+ from crosshair.statespace import MessageType
8
+ from crosshair.test_util import compare_results
9
+
10
+
11
+ def check_b2a_base64(byts: bytes, newline: bool):
12
+ """post: _"""
13
+ return compare_results(binascii.b2a_base64, byts, newline=newline)
14
+
15
+
16
+ def check_a2b_base64(byts: bytes, strict_mode: bool):
17
+ """post: _"""
18
+ kw = {"strict_mode": strict_mode} if sys.version_info >= (3, 11) else {}
19
+ return compare_results(binascii.a2b_base64, byts, **kw)
20
+
21
+
22
+ # This is the only real test definition.
23
+ # It runs crosshair on each of the "check" functions defined above.
24
+ @pytest.mark.parametrize("fn_name", [fn for fn in dir() if fn.startswith("check_")])
25
+ def test_builtin(fn_name: str) -> None:
26
+ this_module = sys.modules[__name__]
27
+ fn = getattr(this_module, fn_name)
28
+ messages = run_checkables(analyze_function(fn))
29
+ errors = [m for m in messages if m.state > MessageType.PRE_UNSAT]
30
+ assert errors == []