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
crosshair/util.py ADDED
@@ -0,0 +1,741 @@
1
+ import builtins
2
+ import collections
3
+ import collections.abc
4
+ import contextlib
5
+ import functools
6
+ import importlib.util
7
+ import math
8
+ import os
9
+ import pathlib
10
+ import re
11
+ import sys
12
+ import threading
13
+ import traceback
14
+ import types
15
+ from array import array
16
+ from dataclasses import dataclass
17
+ from enum import Enum
18
+ from inspect import (
19
+ BoundArguments,
20
+ Parameter,
21
+ getmodulename,
22
+ getsourcefile,
23
+ getsourcelines,
24
+ isdatadescriptor,
25
+ isfunction,
26
+ )
27
+ from time import monotonic
28
+ from types import BuiltinFunctionType, FunctionType, MethodDescriptorType, TracebackType
29
+ from typing import (
30
+ Any,
31
+ Callable,
32
+ Dict,
33
+ Generator,
34
+ Generic,
35
+ List,
36
+ Mapping,
37
+ MutableMapping,
38
+ Optional,
39
+ Sequence,
40
+ Set,
41
+ TextIO,
42
+ Tuple,
43
+ Type,
44
+ TypeVar,
45
+ Union,
46
+ cast,
47
+ )
48
+
49
+ import typing_inspect # type: ignore
50
+
51
+ from crosshair.auditwall import opened_auditwall
52
+ from crosshair.tracers import COMPOSITE_TRACER, NoTracing, ResumedTracing, is_tracing
53
+
54
+ _DEBUG_STREAM: Optional[TextIO] = None
55
+
56
+
57
+ # NOTE: many of these is_* functions should use a TypeGuard in 3.10 (or even TypeIs in 3.13)
58
+
59
+ if sys.version_info >= (3, 12):
60
+ from collections.abc import Buffer
61
+
62
+ def is_bytes_like(obj: object) -> bool:
63
+ return isinstance(obj, Buffer)
64
+
65
+ else:
66
+ from collections.abc import ByteString
67
+
68
+ def is_bytes_like(obj: object) -> bool:
69
+ return isinstance(obj, (ByteString, array))
70
+
71
+
72
+ def is_iterable(o: object) -> bool:
73
+ try:
74
+ iter(o) # type: ignore
75
+ return True
76
+ except TypeError:
77
+ return False
78
+
79
+
80
+ def is_hashable(o: object) -> bool:
81
+ return getattr(type(o), "__hash__", None) is not None
82
+
83
+
84
+ def is_pure_python(obj: object) -> bool:
85
+ if isinstance(obj, type):
86
+ return True if "__dict__" in dir(obj) else hasattr(obj, "__slots__")
87
+ elif callable(obj):
88
+ return isfunction(obj) # isfunction selects "user-defined" functions only
89
+ else:
90
+ return True
91
+
92
+
93
+ def memo(f):
94
+ """Decorate a function taking a single argument with a memoization decorator."""
95
+ saved = {}
96
+
97
+ @functools.wraps(f)
98
+ def memo_wrapper(a):
99
+ if a not in saved:
100
+ saved[a] = f(a)
101
+ return saved[a]
102
+
103
+ return memo_wrapper
104
+
105
+
106
+ # Valid smtlib identifier chars: ~ ! @ $ % ^ & * _ - + = < > . ? /
107
+ # See the section on "symbols" here:
108
+ # https://smtlib.cs.uiowa.edu/papers/smt-lib-reference-v2.6-r2017-07-18.pdf
109
+ _SMTLIB_TRANSLATION = str.maketrans("[],", "<>.", " ")
110
+
111
+
112
+ def smtlib_typename(typ: Type) -> str:
113
+ return name_of_type(typ).translate(_SMTLIB_TRANSLATION)
114
+
115
+
116
+ def name_of_type(typ: Type) -> str:
117
+ return typ.__name__ if hasattr(typ, "__name__") else str(typ).split(".")[-1]
118
+
119
+
120
+ def samefile(f1: Optional[str], f2: Optional[str]) -> bool:
121
+ try:
122
+ return f1 is not None and f2 is not None and os.path.samefile(f1, f2)
123
+ except FileNotFoundError:
124
+ return False
125
+
126
+
127
+ def true_type(obj: object) -> Type:
128
+ with NoTracing():
129
+ return type(obj)
130
+
131
+
132
+ CROSSHAIR_EXTRA_ASSERTS = os.environ.get("CROSSHAIR_EXTRA_ASSERTS", "0") == "1"
133
+
134
+ if CROSSHAIR_EXTRA_ASSERTS:
135
+
136
+ def assert_tracing(should_be_tracing):
137
+ def decorator(fn):
138
+ fn_name = fn.__qualname__
139
+
140
+ @functools.wraps(fn)
141
+ def check_tracing(*a, **kw):
142
+ if is_tracing() != should_be_tracing:
143
+ with NoTracing():
144
+ if should_be_tracing:
145
+ raise CrossHairInternal(
146
+ f"should be tracing when calling {fn_name}, but isn't"
147
+ )
148
+ else:
149
+ raise CrossHairInternal(
150
+ f"should not be tracing when calling {fn_name}, but is"
151
+ )
152
+ return fn(*a, **kw)
153
+
154
+ return check_tracing
155
+
156
+ return decorator
157
+
158
+ else:
159
+
160
+ def assert_tracing(should_be_tracing):
161
+ def decorator(fn):
162
+ return fn
163
+
164
+ return decorator
165
+
166
+
167
+ class IdKeyedDict(collections.abc.MutableMapping):
168
+ def __init__(self) -> None:
169
+ # Confusingly, we hold both the key object and value object in
170
+ # our inner dict. Holding the key object ensures that we don't
171
+ # GC the key object, which could lead to reusing the same id()
172
+ # for a different object.
173
+ self.inner: Dict[int, Tuple[object, object]] = {}
174
+
175
+ def __getitem__(self, k):
176
+ return self.inner.__getitem__(id(k))[1]
177
+
178
+ def __setitem__(self, k, v):
179
+ return self.inner.__setitem__(id(k), (k, v))
180
+
181
+ def __delitem__(self, k):
182
+ return self.inner.__delitem__(id(k))
183
+
184
+ def __iter__(self):
185
+ raise NotImplementedError
186
+ # No use cases for this yet, but we could do something like this:
187
+ # return (actual_key_object for actual_key_object, _ in self.inner.values())
188
+
189
+ def __len__(self):
190
+ return len(self.inner)
191
+
192
+
193
+ _SOURCE_CACHE: MutableMapping[object, Tuple[str, int, Tuple[str, ...]]] = IdKeyedDict()
194
+
195
+
196
+ def sourcelines(thing: object) -> Tuple[str, int, Tuple[str, ...]]:
197
+ # If it's a bound method, pull the function out:
198
+ while hasattr(thing, "__func__"):
199
+ thing = thing.__func__ # type: ignore
200
+ # Unwrap decorators as necessary:
201
+ while hasattr(thing, "__wrapped__"):
202
+ thing = thing.__wrapped__ # type: ignore
203
+ filename, start_line, lines = "<unknown file>", 0, ()
204
+ ret = _SOURCE_CACHE.get(thing, None)
205
+ if ret is None:
206
+ try:
207
+ filename = getsourcefile(thing) # type: ignore
208
+ (lines, start_line) = getsourcelines(thing) # type: ignore
209
+ except (OSError, TypeError):
210
+ pass
211
+ ret = (filename, start_line, tuple(lines))
212
+ _SOURCE_CACHE[thing] = ret
213
+ return ret
214
+
215
+
216
+ def frame_summary_for_fn(
217
+ fn: Callable, frames: traceback.StackSummary
218
+ ) -> Tuple[Optional[str], int]:
219
+ fn_name = fn.__name__
220
+ try:
221
+ fn_file = getsourcefile(fn) # Can return None OR raise TypeError
222
+ except TypeError:
223
+ fn_file = None
224
+ if fn_file is None:
225
+ return (None, 0)
226
+ for frame in reversed(frames):
227
+ if frame.name == fn_name and samefile(frame.filename, fn_file):
228
+ return (frame.filename, frame.lineno or 1)
229
+ return sourcelines(fn)[:2]
230
+
231
+
232
+ def set_debug(new_debug: bool, output: TextIO = sys.stderr):
233
+ global _DEBUG_STREAM
234
+ if new_debug:
235
+ _DEBUG_STREAM = output
236
+ else:
237
+ _DEBUG_STREAM = None
238
+
239
+
240
+ def in_debug() -> bool:
241
+ return bool(_DEBUG_STREAM)
242
+
243
+
244
+ def debug(*a):
245
+ """
246
+ Print debugging information in CrossHair's nested log output.
247
+
248
+ Arguments are serialized with ``str()`` and printed when running in CrossHair's
249
+ verbose mode.
250
+
251
+ Avoid passing symbolic values, as taking the string of a
252
+ symbolic will change the path exploration that CrossHair normally takes, leading to
253
+ different outcomes in verbose and non-verbose mode.
254
+ """
255
+ if not _DEBUG_STREAM:
256
+ return
257
+ with NoTracing():
258
+ stack = traceback.extract_stack()
259
+ frame = stack[-2]
260
+ indent = len(stack) - 3
261
+ print(
262
+ "{:06.3f}|{}|{}() {}".format(
263
+ monotonic(), " " * indent, frame.name, " ".join(map(str, a))
264
+ ),
265
+ file=_DEBUG_STREAM,
266
+ )
267
+
268
+
269
+ def warn(*a):
270
+ """
271
+ Display a warning to the user.
272
+
273
+ It currently does not do more than printing `WARNING:`, followed by the arguments
274
+ serialized with `str` to the `stderr` stream.
275
+ """
276
+ debug("WARNING:", *a)
277
+
278
+
279
+ TracebackLike = Union[None, TracebackType, Sequence[traceback.FrameSummary]]
280
+
281
+
282
+ def ch_stack(
283
+ tb: TracebackLike = None,
284
+ last_n_frames: int = sys.maxsize,
285
+ currently_handling: Optional[BaseException] = None,
286
+ ) -> str:
287
+ with NoTracing():
288
+ if currently_handling:
289
+ if tb is not None:
290
+ raise CrossHairInternal
291
+ lower_frames = traceback.extract_tb(currently_handling.__traceback__)
292
+ higher_frames = traceback.extract_stack()[:-2]
293
+ frames: Sequence[traceback.FrameSummary] = higher_frames + lower_frames
294
+ elif tb is None:
295
+ frames = traceback.extract_stack()[:-1]
296
+ elif isinstance(tb, TracebackType):
297
+ frames = traceback.extract_tb(tb)
298
+ else:
299
+ frames = tb
300
+ output: List[str] = []
301
+ for frame in frames[-last_n_frames:]:
302
+ filename = os.path.split(frame.filename)[1]
303
+ output.append(f"({frame.name} {filename}:{frame.lineno})")
304
+ return " ".join(output)
305
+
306
+
307
+ class ErrorDuringImport(Exception):
308
+ pass
309
+
310
+
311
+ @contextlib.contextmanager
312
+ def add_to_pypath(*paths: Union[str, pathlib.Path]) -> Generator:
313
+ old_path = sys.path[:]
314
+ for path in paths:
315
+ sys.path.insert(0, str(path))
316
+ try:
317
+ yield
318
+ finally:
319
+ sys.path[:] = old_path
320
+
321
+
322
+ class _TypingAccessDetector:
323
+ accessed = False
324
+
325
+ def __bool__(self):
326
+ self.accessed = True
327
+ return False
328
+
329
+
330
+ def import_module(module_name):
331
+ # Some packages like to write tmp files on import,
332
+ # e.g. https://github.com/pschanely/CrossHair/issues/172
333
+ with opened_auditwall():
334
+ orig_modules = set(sys.modules.values())
335
+ result_module = importlib.import_module(module_name)
336
+
337
+ return result_module
338
+
339
+
340
+ def load_file(filename: str) -> types.ModuleType:
341
+ """
342
+ Load a module from a file.
343
+
344
+ :raises ErrorDuringImport: if the file cannot be imported
345
+ """
346
+ try:
347
+ root_path, module_name = extract_module_from_file(filename)
348
+ with add_to_pypath(root_path):
349
+ return import_module(module_name)
350
+ except Exception as e:
351
+ raise ErrorDuringImport from e
352
+
353
+
354
+ @contextlib.contextmanager
355
+ def imported_alternative(name: str, suppress: Tuple[str, ...] = ()):
356
+ """Load an alternative version of a module with some modules suppressed."""
357
+ modules = sys.modules
358
+ orig_module = importlib.import_module(name) # Ensure the regular version is loaded
359
+ modules.update({k: None for k in suppress}) # type: ignore
360
+ alternative = importlib.reload(orig_module)
361
+ try:
362
+ yield
363
+ finally:
364
+ for k in suppress:
365
+ del modules[k]
366
+ importlib.reload(alternative)
367
+
368
+
369
+ def format_boundargs_as_dictionary(bound_args: BoundArguments) -> str:
370
+ body = ", ".join(f'"{k}": {repr(v)}' for k, v in bound_args.arguments.items())
371
+ return "{" + body + "}"
372
+
373
+
374
+ def format_boundargs(bound_args: BoundArguments) -> str:
375
+ arg_strings: List[str] = []
376
+ for name, param in bound_args.signature.parameters.items():
377
+ param_kind = param.kind
378
+ vals = bound_args.arguments.get(name, param.default)
379
+ if param_kind == Parameter.VAR_POSITIONAL:
380
+ arg_strings.extend(map(repr, vals))
381
+ elif param_kind == Parameter.VAR_KEYWORD:
382
+ arg_strings.extend(f"{k}={repr(v)}" for k, v in vals.items())
383
+ else:
384
+ if param_kind == Parameter.POSITIONAL_ONLY:
385
+ use_keyword = False
386
+ elif param_kind == Parameter.KEYWORD_ONLY:
387
+ use_keyword = True
388
+ else:
389
+ use_keyword = param.default is not Parameter.empty
390
+ if use_keyword:
391
+ arg_strings.append(f"{name}={repr(vals)}")
392
+ else:
393
+ arg_strings.append(repr(vals))
394
+ return ", ".join(arg_strings)
395
+
396
+
397
+ UNABLE_TO_REPR_TEXT = "<unable to repr>"
398
+
399
+
400
+ def eval_friendly_repr(obj: object) -> str:
401
+ assert not is_tracing()
402
+ with ResumedTracing(), EvalFriendlyReprContext() as ctx:
403
+ try:
404
+ # TODO: probably only the repr should have tracing enabled
405
+ return ctx.cleanup(repr(obj))
406
+ except Exception as e:
407
+ if isinstance(e, (IgnoreAttempt, UnexploredPath)):
408
+ raise
409
+ debug("Repr failed, ", type(e), ":", str(e))
410
+ debug("Repr failed at:", ch_stack(currently_handling=e))
411
+ return UNABLE_TO_REPR_TEXT
412
+
413
+
414
+ @dataclass(frozen=True)
415
+ class ReferencedIdentifier:
416
+ modulename: str
417
+ qualname: str
418
+
419
+ def __str__(self):
420
+ if self.modulename in ("builtins", ""):
421
+ return self.qualname
422
+ else:
423
+ return f"{self.modulename}.{self.qualname}"
424
+
425
+
426
+ def callable_identifier(cls: Callable) -> ReferencedIdentifier:
427
+ return ReferencedIdentifier(cls.__module__, cls.__qualname__)
428
+
429
+
430
+ def method_identifier(fn: Callable) -> ReferencedIdentifier:
431
+ if getattr(fn, "__objclass__", None):
432
+ clsref = callable_identifier(fn.__objclass__) # type: ignore
433
+ return ReferencedIdentifier(
434
+ clsref.modulename, f"{clsref.qualname}.{fn.__name__}"
435
+ )
436
+ return callable_identifier(fn)
437
+
438
+
439
+ # Objects of these types are known to always be *deeply* immutable:
440
+ ATOMIC_IMMUTABLE_TYPES = (
441
+ type(None),
442
+ bool,
443
+ int,
444
+ str,
445
+ float,
446
+ complex,
447
+ types.FunctionType,
448
+ types.BuiltinFunctionType,
449
+ types.LambdaType,
450
+ types.MethodType,
451
+ types.BuiltinMethodType,
452
+ )
453
+
454
+
455
+ class EvalFriendlyReprContext:
456
+ """
457
+ Monkey-patch repr() to make some cases more ammenible to eval().
458
+
459
+ In particular:
460
+ * object instances repr as "object()" rather than "<object object at ...>"
461
+ * non-finite floats like inf repr as 'float("inf")' rather than just 'inf'
462
+ * functions repr as their fully qualified names
463
+ * enums repr like "Color.RED" instead of "<Color.RED: 0>"
464
+ * uses the walrus (:=) operator to faithfully represent aliased values
465
+
466
+ Use the cleanup method to strip unnecessary assignments from the output.
467
+
468
+ >>> with EvalFriendlyReprContext() as ctx:
469
+ ... ctx.cleanup(repr(object()))
470
+ 'object()'
471
+ >>> with EvalFriendlyReprContext() as ctx:
472
+ ... ctx.cleanup(repr(float("nan")))
473
+ 'float("nan")'
474
+
475
+ The same context can be re-used to perform aliasing across multiple calls to repr:
476
+
477
+ >>> lst = []
478
+ >>> ctx = EvalFriendlyReprContext()
479
+ >>> with ctx:
480
+ ... part1 = repr(lst)
481
+ >>> with ctx:
482
+ ... part2 = repr(lst)
483
+ >>> ctx.cleanup(part1 + " and also " + part2)
484
+ 'v1:=[] and also v1'
485
+ """
486
+
487
+ def __init__(self, instance_overrides: Optional[IdKeyedDict] = None):
488
+ self.instance_overrides = (
489
+ IdKeyedDict() if instance_overrides is None else instance_overrides
490
+ )
491
+ self.repr_references: Set[ReferencedIdentifier] = set()
492
+
493
+ def __enter__(self):
494
+ if not is_tracing():
495
+ raise CrossHairInternal
496
+ OVERRIDES: Dict[type, Callable[[Any], Union[str, ReferencedIdentifier]]] = {
497
+ object: lambda o: "object()",
498
+ list: lambda o: f"[{', '.join(map(repr, o))}]", # (de-optimize C-level repr)
499
+ memoryview: lambda o: f"memoryview({repr(o.obj)})",
500
+ FunctionType: callable_identifier,
501
+ BuiltinFunctionType: callable_identifier,
502
+ MethodDescriptorType: method_identifier,
503
+ }
504
+ instance_overrides = self.instance_overrides
505
+
506
+ @functools.wraps(builtins.repr)
507
+ def _eval_friendly_repr(obj) -> str:
508
+ oid = id(obj)
509
+ typ = type(obj)
510
+ if obj in instance_overrides:
511
+ repr_fn: Callable[[Any], Union[str, ReferencedIdentifier]] = (
512
+ instance_overrides[obj]
513
+ )
514
+ elif typ == float:
515
+ if math.isfinite(obj):
516
+ repr_fn = repr
517
+ else:
518
+ repr_fn = lambda o: f'float("{o}")'
519
+ elif typ in OVERRIDES:
520
+ repr_fn = OVERRIDES[typ]
521
+ elif isinstance(obj, Enum) and obj in typ:
522
+ repr_fn = lambda _: ReferencedIdentifier(
523
+ typ.__module__, f"{typ.__qualname__}.{obj.name}"
524
+ )
525
+ elif isinstance(obj, type):
526
+ repr_fn = callable_identifier
527
+ else:
528
+ repr_fn = repr
529
+ str_or_ref = repr_fn(obj)
530
+ if isinstance(str_or_ref, ReferencedIdentifier):
531
+ self.repr_references.add(str_or_ref)
532
+ return str_or_ref.qualname
533
+ value_str = str_or_ref
534
+ if isinstance(obj, (ATOMIC_IMMUTABLE_TYPES, Enum)):
535
+ return value_str
536
+ name = f"_ch_efr_{oid}_"
537
+ instance_overrides[obj] = lambda _: name
538
+ return value_str if value_str == name else f"{name}:={value_str}"
539
+
540
+ self.patches = {repr: _eval_friendly_repr}
541
+ COMPOSITE_TRACER.patching_module.add(self.patches)
542
+ return self
543
+
544
+ def __exit__(self, exc_type, exc_val, exc_tb):
545
+ COMPOSITE_TRACER.patching_module.pop(self.patches)
546
+
547
+ def cleanup(self, output: str) -> str:
548
+ counts = collections.Counter(re.compile(r"\b_ch_efr_\d+_\b").findall(output))
549
+ assignment_remaps = {}
550
+ nextvarnum = 1
551
+ for varname, count in counts.items():
552
+ if count > 1:
553
+ assignment_remaps[varname + ":="] = f"v{nextvarnum}:="
554
+ assignment_remaps[varname] = f"v{nextvarnum}"
555
+ nextvarnum += 1
556
+ return re.compile(r"\b(_ch_efr_\d+_)\b(\:\=)?").sub(
557
+ lambda match: assignment_remaps.get(match.group(), ""), output
558
+ )
559
+
560
+
561
+ def extract_module_from_file(filename: str) -> Tuple[str, str]:
562
+ module_name = getmodulename(filename)
563
+ dirs = []
564
+ if module_name and module_name != "__init__":
565
+ dirs.append(module_name)
566
+ path = os.path.split(os.path.realpath(filename))[0]
567
+ while os.path.exists(os.path.join(path, "__init__.py")):
568
+ path, cur = os.path.split(path)
569
+ dirs.append(cur)
570
+ dirs.reverse()
571
+ module = ".".join(dirs)
572
+ return path, module
573
+
574
+
575
+ def renamed_function(fn: FunctionType, new_name: str):
576
+ """Produced a completely renamed function"""
577
+ return FunctionType(
578
+ fn.__code__.replace(co_name=new_name, co_filename=new_name + ".py"),
579
+ fn.__globals__,
580
+ new_name,
581
+ fn.__defaults__,
582
+ fn.__closure__,
583
+ )
584
+
585
+
586
+ _T = TypeVar("_T")
587
+
588
+
589
+ class DynamicScopeVar(Generic[_T]):
590
+ """
591
+ Manage a hidden value that can get passed through the callstack.
592
+
593
+ This has similar downsides to threadlocals/globals; it should be
594
+ used sparingly.
595
+
596
+ >>> _VAR = DynamicScopeVar(int)
597
+ >>> with _VAR.open(42):
598
+ ... _VAR.get()
599
+ 42
600
+ """
601
+
602
+ def __init__(self, typ: Type[_T], name_for_debugging: str = ""):
603
+ self._local = threading.local()
604
+ self._name = name_for_debugging
605
+
606
+ @contextlib.contextmanager
607
+ def open(self, value: _T, reentrant: bool = True):
608
+ _local = self._local
609
+ old_value = getattr(_local, "value", None)
610
+ if not reentrant:
611
+ assert old_value is None, f"Already in a {self._name} context"
612
+ _local.value = value
613
+ try:
614
+ yield value
615
+ finally:
616
+ assert getattr(_local, "value", None) is value
617
+ _local.value = old_value
618
+
619
+ def get(self, default: Optional[_T] = None) -> _T:
620
+ ret = getattr(self._local, "value", None)
621
+ if ret is not None:
622
+ return ret
623
+ if default is not None:
624
+ return default
625
+ assert False, f"Not in a {self._name} context"
626
+
627
+ def get_if_in_scope(self) -> Optional[_T]:
628
+ return getattr(self._local, "value", None)
629
+
630
+
631
+ class AttributeHolder:
632
+ def __init__(self, attrs: Mapping[str, object]):
633
+ for k, v in attrs.items():
634
+ self.__dict__[k] = v
635
+
636
+
637
+ class CrossHairValue:
638
+ """Base class for values that are pretending to be other values."""
639
+
640
+ pass
641
+
642
+
643
+ class ControlFlowException(BaseException):
644
+ # CrossHair sometimes uses exceptions to abort a path mid-execution.
645
+ # We extend such exceptions from BaseException instead of Exception,
646
+ # because expect that user code will usually only handle Exception.
647
+ pass
648
+
649
+
650
+ class CrossHairInternal(ControlFlowException):
651
+ def __init__(self, *a):
652
+ ControlFlowException.__init__(self, *a)
653
+ if in_debug():
654
+ debug("CrossHairInternal:", str(self))
655
+ debug("CrossHairInternal stack trace:")
656
+ for entry in traceback.format_stack()[:-1]:
657
+ for line in entry.splitlines():
658
+ debug("", line)
659
+
660
+
661
+ class UnexploredPath(ControlFlowException):
662
+ pass
663
+
664
+
665
+ class UnknownSatisfiability(UnexploredPath):
666
+ def __init__(self, *a):
667
+ UnexploredPath.__init__(self, *a)
668
+ debug("UnknownSatisfiability", str(self))
669
+
670
+
671
+ class NotDeterministic(Exception):
672
+ pass
673
+
674
+
675
+ class PathTimeout(UnexploredPath):
676
+ pass
677
+
678
+
679
+ class CrosshairUnsupported(UnexploredPath):
680
+ def __init__(self, *a):
681
+ debug("CrosshairUnsupported: ", str(self))
682
+ debug(" Stack trace:\n" + "".join(traceback.format_stack()))
683
+
684
+
685
+ class IgnoreAttempt(ControlFlowException):
686
+ def __init__(self, *a):
687
+ if in_debug():
688
+ debug(f"IgnoreAttempt", *a)
689
+ debug("IgnoreAttempt stack:", ch_stack())
690
+
691
+
692
+ if (3, 10) <= sys.version_info < (3, 14):
693
+ # Specialize some definitions for the Python versions where
694
+ # typing.Union != types.UnionType:
695
+
696
+ def origin_of(typ: Type) -> Type:
697
+ if hasattr(typ, "__origin__"):
698
+ return typ.__origin__
699
+ elif isinstance(typ, types.UnionType):
700
+ return cast(Type, Union)
701
+ else:
702
+ return typ
703
+
704
+ def type_args_of(typ: Type) -> Tuple[Type, ...]:
705
+ if getattr(typ, "__args__", None):
706
+ if isinstance(typ, types.UnionType):
707
+ return typ.__args__
708
+ return typing_inspect.get_args(typ, evaluate=True)
709
+ else:
710
+ return ()
711
+
712
+ else:
713
+
714
+ def type_args_of(typ: Type) -> Tuple[Type, ...]:
715
+ if getattr(typ, "__args__", None):
716
+ return typing_inspect.get_args(typ, evaluate=True)
717
+ else:
718
+ return ()
719
+
720
+ def origin_of(typ: Type) -> Type:
721
+ origin = getattr(typ, "__origin__", None)
722
+ # 3.14 unifies typing.Union and types.Union, so that's good!
723
+ # But a of 3.14.0a6, types.Union.__origin__ yields a data descriptor, so we need to check that.
724
+ return typ if origin is None or isdatadescriptor(origin) else origin
725
+
726
+
727
+ def type_arg_of(typ: Type, index: int) -> Type:
728
+ args = type_args_of(typ)
729
+ return args[index] if index < len(args) else object
730
+
731
+
732
+ def mem_usage_kb():
733
+ try:
734
+ import resource
735
+ except ImportError:
736
+ return 0 # do not bother monitoring memory on windows
737
+ usage = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
738
+ if sys.platform == "darwin":
739
+ return usage / 1024 # (bytes on osx)
740
+ else:
741
+ return usage # (kb)