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/enforce.py ADDED
@@ -0,0 +1,282 @@
1
+ import contextlib
2
+ import copy
3
+ import functools
4
+ import os
5
+ from types import FrameType
6
+ from typing import Callable, Dict, Mapping, Optional, Set, Tuple
7
+
8
+ from crosshair.condition_parser import (
9
+ ConditionParser,
10
+ Conditions,
11
+ NoEnforce,
12
+ fn_globals,
13
+ get_current_parser,
14
+ )
15
+ from crosshair.fnutil import FunctionInfo
16
+ from crosshair.statespace import prefer_true
17
+ from crosshair.tracers import COMPOSITE_TRACER, NoTracing, ResumedTracing, TracingModule
18
+ from crosshair.util import AttributeHolder
19
+
20
+ # [Pre|Post]conditionFailed exceptions extend BaseException just to reduce the
21
+ # possibility that end-user code accidentally handles them.
22
+
23
+
24
+ class PreconditionFailed(BaseException):
25
+ pass
26
+
27
+
28
+ class PostconditionFailed(BaseException):
29
+ pass
30
+
31
+
32
+ def WithEnforcement(fn: Callable) -> Callable:
33
+ """
34
+ Ensure conditions are enforced on the given callable.
35
+
36
+ Enforcement is normally disabled when calling from some internal files, for
37
+ performance reasons. Use WithEnforcement to ensure it is enabled anywhere.
38
+ """
39
+
40
+ # This local function has a special name that we look for while tracing
41
+ # (see the wants_codeobj method below):
42
+ def _crosshair_with_enforcement(*a, **kw):
43
+ return fn(*a, **kw)
44
+
45
+ return _crosshair_with_enforcement
46
+
47
+
48
+ def manual_constructor(typ: type):
49
+ def manually_construct(*a, **kw):
50
+ obj = WithEnforcement(typ.__new__)(typ, *a, **kw) # object.__new__(typ)
51
+ with NoTracing():
52
+ # Python does not invoke __init__ if __new__ returns an object of another type
53
+ # https://docs.python.org/3/reference/datamodel.html#object.__new__
54
+ if isinstance(obj, typ):
55
+ with ResumedTracing():
56
+ WithEnforcement(obj.__init__)(*a, **kw) # type: ignore
57
+ return obj
58
+
59
+ return manually_construct
60
+
61
+
62
+ _MISSING = object()
63
+
64
+
65
+ def EnforcementWrapper(
66
+ fn: Callable,
67
+ conditions: Conditions,
68
+ enforced: "EnforcedConditions",
69
+ first_arg: object,
70
+ ) -> Callable:
71
+ signature = conditions.sig
72
+
73
+ def _crosshair_wrapper(*a, **kw):
74
+ with NoTracing():
75
+ fns_enforcing = enforced.fns_enforcing
76
+ if fns_enforcing is None or fn in fns_enforcing:
77
+ with ResumedTracing():
78
+ return fn(*a, **kw)
79
+ with enforced.currently_enforcing(fn):
80
+ # debug("Calling enforcement wrapper ", fn.__name__, signature)
81
+ bound_args = signature.bind(*a, **kw)
82
+ bound_args.apply_defaults()
83
+ old = {}
84
+ mutable_args = conditions.mutable_args
85
+ mutable_args_remaining = (
86
+ set(mutable_args) if mutable_args is not None else set()
87
+ )
88
+ for argname, argval in bound_args.arguments.items():
89
+ try:
90
+ # TODO: reduce the type realizations when argval is a SymbolicObject
91
+ old[argname] = copy.copy(argval)
92
+ except TypeError as exc: # for uncopyables
93
+ pass
94
+ if argname in mutable_args_remaining:
95
+ mutable_args_remaining.remove(argname)
96
+ if mutable_args_remaining:
97
+ raise PostconditionFailed(
98
+ 'Unrecognized mutable argument(s) in postcondition: "{}"'.format(
99
+ ",".join(mutable_args_remaining)
100
+ )
101
+ )
102
+ for precondition in conditions.pre:
103
+ # debug(' precondition eval ', precondition.expr_source)
104
+ # TODO: is fn_globals required here?
105
+ with ResumedTracing():
106
+ if not precondition.evaluate(bound_args.arguments):
107
+ raise PreconditionFailed(
108
+ f'Precondition "{precondition.expr_source}" was not satisfied '
109
+ f'before calling "{fn.__name__}"'
110
+ )
111
+ with ResumedTracing():
112
+ ret = fn(*a, **kw)
113
+ with enforced.currently_enforcing(fn):
114
+ if fn.__name__ in ("__init__", "__new__"):
115
+ old["self"] = a[0]
116
+ lcls = {
117
+ **bound_args.arguments,
118
+ "__return__": ret,
119
+ "_": ret,
120
+ "__old__": AttributeHolder(old),
121
+ }
122
+ args = {**fn_globals(fn), **lcls}
123
+ for postcondition in conditions.post:
124
+ # debug('Checking postcondition ', postcondition.expr_source, ' on ', fn)
125
+ if not postcondition.evaluate:
126
+ continue
127
+ with ResumedTracing():
128
+ postcondition_ok = postcondition.evaluate(args)
129
+ if not prefer_true(postcondition_ok):
130
+ raise PostconditionFailed(
131
+ "Postcondition failed at {}:{}".format(
132
+ postcondition.filename, postcondition.line
133
+ )
134
+ )
135
+ # debug("Completed enforcement wrapper ", fn)
136
+ return ret
137
+
138
+ functools.update_wrapper(_crosshair_wrapper, fn)
139
+ return _crosshair_wrapper
140
+
141
+
142
+ _MISSING = object()
143
+
144
+
145
+ _FILE_SUFFIXES_WITHOUT_ENFORCEMENT: Tuple[str, ...] = (
146
+ "/ast.py",
147
+ "/crosshair/libimpl/builtinslib.py",
148
+ "/crosshair/core.py",
149
+ "/crosshair/condition_parser.py",
150
+ "/crosshair/enforce.py",
151
+ "/crosshair/util.py",
152
+ "/crosshair/fnutil.py",
153
+ "/crosshair/statespace.py",
154
+ "/crosshair/tracers.py",
155
+ "/z3.py",
156
+ "/z3core.py",
157
+ "/z3printer.py",
158
+ "/z3types.py",
159
+ "/copy.py",
160
+ "/inspect.py",
161
+ "/re.py",
162
+ "/copyreg.py",
163
+ "/sre_parse.py",
164
+ "/sre_compile.py",
165
+ "/traceback.py",
166
+ "/contextlib.py",
167
+ "/linecache.py",
168
+ "/collections/__init__.py",
169
+ "/enum.py",
170
+ "/typing.py",
171
+ )
172
+
173
+ if os.name == "nt":
174
+ # Hacky platform-independence for performance reasons.
175
+ # (not sure whether there are landmines here?)
176
+ _FILE_SUFFIXES_WITHOUT_ENFORCEMENT = tuple(
177
+ p.replace("/", "\\") for p in _FILE_SUFFIXES_WITHOUT_ENFORCEMENT
178
+ )
179
+
180
+
181
+ class EnforcedConditions(TracingModule):
182
+ def __init__(
183
+ self,
184
+ condition_parser: Optional[ConditionParser] = None,
185
+ interceptor=lambda x: x,
186
+ ):
187
+ super().__init__()
188
+ self.condition_parser = (
189
+ get_current_parser() if condition_parser is None else condition_parser
190
+ )
191
+ self.interceptor = interceptor
192
+ self.fns_enforcing: Optional[Set[Callable]] = None
193
+ self.codeobj_cache: Dict[object, bool] = {}
194
+
195
+ @contextlib.contextmanager
196
+ def currently_enforcing(self, fn: Callable):
197
+ if self.fns_enforcing is None:
198
+ yield None
199
+ else:
200
+ self.fns_enforcing.add(fn)
201
+ try:
202
+ yield None
203
+ finally:
204
+ self.fns_enforcing.remove(fn)
205
+
206
+ # TODO: replace this with PushedModule(EnforcedConditions)?
207
+ @contextlib.contextmanager
208
+ def enabled_enforcement(self):
209
+ prev = self.fns_enforcing
210
+ assert prev is None
211
+ self.fns_enforcing = set()
212
+ COMPOSITE_TRACER.push_module(self)
213
+
214
+ try:
215
+ yield None
216
+ finally:
217
+ self.fns_enforcing = prev
218
+ COMPOSITE_TRACER.pop_config(self)
219
+
220
+ def wants_codeobj(self, codeobj) -> bool:
221
+ name = codeobj.co_name
222
+ if name == "_crosshair_with_enforcement":
223
+ return True
224
+ fname = codeobj.co_filename
225
+ if fname.endswith(_FILE_SUFFIXES_WITHOUT_ENFORCEMENT):
226
+ return False
227
+ if name == "_crosshair_wrapper":
228
+ return False
229
+ return True
230
+
231
+ def cached_wants_codeobj(self, codeobj) -> bool:
232
+ cache = self.codeobj_cache
233
+ cachedval = cache.get(codeobj)
234
+ if cachedval is None:
235
+ cachedval = self.wants_codeobj(codeobj)
236
+ cache[codeobj] = cachedval
237
+ return cachedval
238
+
239
+ def trace_call(
240
+ self,
241
+ frame: FrameType,
242
+ fn: Callable,
243
+ binding_target: object,
244
+ ) -> Optional[Callable]:
245
+ caller_code = frame.f_code
246
+ if not self.cached_wants_codeobj(caller_code):
247
+ return None
248
+ try:
249
+ target_name = object.__getattribute__(fn, "__name__")
250
+ except AttributeError:
251
+ target_name = ""
252
+ if target_name.endswith((">", "_crosshair_wrapper")):
253
+ return None
254
+ if isinstance(fn, NoEnforce):
255
+ return fn.fn
256
+ if isinstance(fn, type) and fn not in (super, type):
257
+ return manual_constructor(fn)
258
+
259
+ parser = self.condition_parser
260
+ conditions = None
261
+ if binding_target is None:
262
+ conditions = parser.get_fn_conditions(FunctionInfo(None, "", fn)) # type: ignore
263
+ else:
264
+ # Method call.
265
+ # We normally expect to look up contracts on `type(binding_target)`, but
266
+ # if it's a `@classmethod`, we'll find it directly on `binding_target`.
267
+ # TODO: test contracts on metaclass methods
268
+ if isinstance(binding_target, type):
269
+ instance_methods = parser.get_class_conditions(binding_target).methods
270
+ conditions = instance_methods.get(target_name)
271
+ if conditions is None or not conditions.has_any():
272
+ type_methods = parser.get_class_conditions(type(binding_target)).methods
273
+ conditions = type_methods.get(target_name)
274
+
275
+ if conditions is not None and not conditions.has_any():
276
+ conditions = None
277
+ if conditions is None:
278
+ return None
279
+ # debug("Enforcing conditions on", fn, " type(binding)=", type(binding_target))
280
+ fn = self.interceptor(fn) # conditions.fn) # type: ignore
281
+ wrapper = EnforcementWrapper(fn, conditions, self, binding_target) # type: ignore
282
+ return wrapper
@@ -0,0 +1,182 @@
1
+ import abc
2
+ import sys
3
+ from contextlib import ExitStack
4
+
5
+ import pytest
6
+
7
+ from crosshair.condition_parser import Pep316Parser
8
+ from crosshair.enforce import (
9
+ EnforcedConditions,
10
+ PostconditionFailed,
11
+ PreconditionFailed,
12
+ manual_constructor,
13
+ )
14
+ from crosshair.tracers import COMPOSITE_TRACER
15
+ from crosshair.util import set_debug
16
+
17
+
18
+ def foo(x: int) -> int:
19
+ """
20
+ pre: 0 <= x <= 100
21
+ post: _ > x
22
+ """
23
+ return x * 2
24
+
25
+
26
+ class Pokeable:
27
+ """
28
+ inv: self.x >= 0
29
+ """
30
+
31
+ x: int = 1
32
+
33
+ def poke(self) -> None:
34
+ self.x += 1
35
+
36
+ def pokeby(self, amount: int) -> None:
37
+ """
38
+ pre: amount >= 0
39
+ """
40
+ self.x += amount
41
+
42
+
43
+ def same_thing(thing: object) -> object:
44
+ """post: __old__.thing == _"""
45
+ # If `thing` isn't copyable, it won't be available in `__old__`.
46
+ # In this case, enforcement will fail with an AttributeError.
47
+ return thing
48
+
49
+
50
+ class Enforcement(ExitStack):
51
+ def __enter__(self):
52
+ super().__enter__()
53
+ enforced_conditions = EnforcedConditions(Pep316Parser())
54
+ self.enter_context(COMPOSITE_TRACER)
55
+ self.enter_context(enforced_conditions.enabled_enforcement())
56
+ COMPOSITE_TRACER.trace_caller()
57
+
58
+
59
+ class TestCore:
60
+ def test_enforce_conditions(self) -> None:
61
+ assert foo(-1) == -2 # unchecked
62
+ with Enforcement():
63
+ assert foo(50) == 100
64
+ with pytest.raises(PreconditionFailed):
65
+ foo(-1)
66
+ with pytest.raises(PostconditionFailed):
67
+ foo(0)
68
+
69
+ def test_class_enforce(self) -> None:
70
+ Pokeable().pokeby(-1) # no exception (yet!)
71
+ with Enforcement():
72
+ Pokeable().poke()
73
+ with pytest.raises(PreconditionFailed):
74
+ Pokeable().pokeby(-1)
75
+
76
+ def test_enforce_on_uncopyable_value(self) -> None:
77
+ class NotCopyable:
78
+ def __copy__(self):
79
+ raise TypeError("not copyable")
80
+
81
+ not_copyable = NotCopyable()
82
+ with Enforcement():
83
+ with pytest.raises(AttributeError):
84
+ same_thing(not_copyable)
85
+
86
+
87
+ class BaseFooable:
88
+ def foo(self, x: int):
89
+ """pre: x > 100"""
90
+
91
+ def foo_only_in_super(self, x: int):
92
+ """pre: x > 100"""
93
+
94
+ @classmethod
95
+ def class_foo(cls, x: int):
96
+ """pre: x > 100"""
97
+
98
+ @staticmethod
99
+ def static_foo(x: int):
100
+ """pre: x > 100"""
101
+
102
+
103
+ class DerivedFooable(BaseFooable):
104
+ def foo(self, x: int):
105
+ """pre: x > 0"""
106
+
107
+ @classmethod
108
+ def class_foo(cls, x: int):
109
+ """pre: x > 0"""
110
+
111
+ @staticmethod
112
+ def static_foo(x: int):
113
+ """pre: x > 0"""
114
+
115
+
116
+ class TestTrickyCases:
117
+ def test_attrs_restored_properly(self) -> None:
118
+ orig_class_dict = DerivedFooable.__dict__.copy()
119
+ with Enforcement():
120
+ pass
121
+ for k, v in orig_class_dict.items():
122
+ assert (
123
+ DerivedFooable.__dict__[k] is v
124
+ ), f'member "{k}" changed afer encforcement'
125
+
126
+ def test_enforcement_of_class_methods(self) -> None:
127
+ with Enforcement():
128
+ with pytest.raises(PreconditionFailed):
129
+ BaseFooable.class_foo(50)
130
+ with Enforcement():
131
+ DerivedFooable.class_foo(50)
132
+
133
+ def test_enforcement_of_static_methods(self) -> None:
134
+ with Enforcement():
135
+ DerivedFooable.static_foo(50)
136
+ with pytest.raises(PreconditionFailed):
137
+ BaseFooable.static_foo(50)
138
+
139
+ def test_super_method_enforced(self) -> None:
140
+ with Enforcement():
141
+ with pytest.raises(PreconditionFailed):
142
+ DerivedFooable().foo_only_in_super(50)
143
+ with pytest.raises(PreconditionFailed):
144
+ DerivedFooable().foo(-1)
145
+ # Derived class has a weaker precondition, so this is OK:
146
+ DerivedFooable().foo(50)
147
+
148
+
149
+ class WithMetaclass(metaclass=abc.ABCMeta):
150
+ """inv: x != 55"""
151
+
152
+ def __init__(self, x):
153
+ """pre: x != 22"""
154
+ self.x = x
155
+
156
+
157
+ def test_skip_init_when_new_returns_different_type():
158
+ COUNTER = [0]
159
+
160
+ class ClassWithInit:
161
+ def __init__(self):
162
+ COUNTER[0] += 1
163
+
164
+ objwithinit = ClassWithInit()
165
+ assert COUNTER[0] == 1
166
+
167
+ class ClassWithNew:
168
+ def __new__(self):
169
+ return objwithinit
170
+
171
+ assert manual_constructor(ClassWithNew)() is objwithinit
172
+
173
+ assert COUNTER[0] == 1 # ensure we did not call __init__ again
174
+
175
+
176
+ def test_enforcement_init_on_abcmeta() -> None:
177
+ with Enforcement():
178
+ with pytest.raises(PreconditionFailed):
179
+ WithMetaclass(22)
180
+ with pytest.raises(PostconditionFailed):
181
+ WithMetaclass(55)
182
+ WithMetaclass(99)
@@ -0,0 +1 @@
1
+ # crosshair: analysis_kind=PEP316
File without changes
@@ -0,0 +1,16 @@
1
+ class Farm:
2
+ def visit_chickens(self) -> str:
3
+ return "cluck"
4
+
5
+ def visit_cows(self) -> str:
6
+ return "moo"
7
+
8
+
9
+ def visit_animals(animal: str) -> str:
10
+ """
11
+ post: __return__ != "moo"
12
+ """
13
+ try:
14
+ return getattr(Farm(), "visit_" + animal)()
15
+ except BaseException:
16
+ return ""
@@ -0,0 +1,31 @@
1
+ import dataclasses
2
+
3
+
4
+ class HasConsistentHash:
5
+ """
6
+ A mixin to enforce that classes have hash methods that are consistent
7
+ with their equality checks.
8
+ """
9
+
10
+ def __eq__(self, other: object) -> bool:
11
+ """
12
+ post: implies(__return__, hash(self) == hash(other))
13
+ """
14
+ raise NotImplementedError
15
+
16
+
17
+ @dataclasses.dataclass
18
+ class Apples(HasConsistentHash):
19
+ """
20
+ Uses HasConsistentHash to discover that the __eq__ method is
21
+ missing a test for the `count` attribute.
22
+ """
23
+
24
+ count: int
25
+ kind: str
26
+
27
+ def __hash__(self):
28
+ return self.count + hash(self.kind)
29
+
30
+ def __eq__(self, other: object) -> bool:
31
+ return isinstance(other, Apples) and self.kind == other.kind
@@ -0,0 +1,24 @@
1
+ from typing import Dict, List, Optional, Tuple
2
+
3
+
4
+ class ShoppingCart:
5
+ """
6
+ inv: all(quantity > 0 for (_, quantity) in self.items)
7
+ """
8
+
9
+ def __init__(self, items: Optional[List[Tuple[str, int]]] = None):
10
+ self.items = items or []
11
+
12
+
13
+ def compute_total(cart: ShoppingCart, prices: Dict[str, float]) -> float:
14
+ """
15
+ pre: len(cart.items) > 0
16
+ pre: all(pid in prices for (pid, _) in cart.items)
17
+
18
+ We try to ensure that you can't check out with a zero (or less) total.
19
+ However, we forgot a precondition; namely that the `prices` dictionary only has
20
+ prices that are greater than zero.
21
+
22
+ post: __return__ > 0
23
+ """
24
+ return sum(prices[pid] * quantity for (pid, quantity) in cart.items)
@@ -0,0 +1,39 @@
1
+ from typing import Callable, Dict, List, Sequence, Tuple, TypeVar
2
+
3
+ T = TypeVar("T")
4
+
5
+
6
+ def list_to_dict(s: Sequence[T]) -> Dict[T, T]:
7
+ """
8
+ post: len(__return__) == len(s)
9
+ # False; CrossHair finds a counterexample with duplicate values in the input.
10
+ """
11
+ return dict(zip(s, s))
12
+
13
+
14
+ def consecutive_pairs(x: List[T]) -> List[Tuple[T, T]]:
15
+ """
16
+ post: len(__return__) == len(x) - 1
17
+ # False (on an empty input list)
18
+ """
19
+ return [(x[i], x[i + 1]) for i in range(len(x) - 1)]
20
+
21
+
22
+ def higher_order(fn: Callable[[int], int]) -> int:
23
+ """
24
+ Crosshair can find models for pure callables over atomic types.
25
+
26
+ post: _ != 42
27
+ # False (when given something like lambda a: 42 if (a == 0) else 0)
28
+ """
29
+ return fn(fn(100))
30
+
31
+
32
+ def append_fourtytwo_to_each(lists: List[List[int]]):
33
+ """
34
+ pre: len(lists) >= 2
35
+ post: all(len(x) == len(__old__.lists[i]) + 1 for i, x in enumerate(lists))
36
+ # False when two elements of the input are the SAME list!
37
+ """
38
+ for ls in lists:
39
+ ls.append(42)
File without changes
@@ -0,0 +1,60 @@
1
+ from typing import List, Optional, Tuple
2
+
3
+
4
+ def perimiter_length(length: int, width: int) -> int:
5
+ """
6
+ pre: l > 0 and w > 0
7
+
8
+ The perimeter of a rectangle is longer than any single side:
9
+ post: _ > l and _ > w
10
+ """
11
+ return 2 * length + 2 * width
12
+
13
+
14
+ def swap(things: Tuple[int, int]) -> Tuple[int, int]:
15
+ """
16
+ Swap the arguments.
17
+
18
+ post: _[0] == things[1]
19
+ post: _[1] == things[0]
20
+ """
21
+ return (things[1], things[0])
22
+
23
+
24
+ # NOTE: To perform additional testing, you can write extra private functions like this one:
25
+ def _assert_double_swap_does_nothing(things: Tuple[int, int]) -> Tuple[int, int]:
26
+ """
27
+ post: _ == things
28
+ """
29
+ ret = swap(swap(things))
30
+ return ret
31
+
32
+
33
+ def double(items: List[str]) -> List[str]:
34
+ """
35
+ Return a new list that is the input list, repeated twice.
36
+
37
+ post: len(_) == len(items) * 2
38
+ """
39
+ return items + items
40
+
41
+
42
+ # NOTE: This is an example of contracts on recursive functions.
43
+ def smallest_two(numbers: Tuple[int, ...]) -> Tuple[Optional[int], Optional[int]]:
44
+ """
45
+ Find the two smallest numbers.
46
+
47
+ pre: len(numbers) > 0
48
+ # The left return value is always the smallest
49
+ post: _[0] == min(numbers)
50
+ """
51
+ if len(numbers) == 1:
52
+ return (numbers[0], None)
53
+ (smallest, second) = smallest_two(numbers[1:])
54
+ n = numbers[0]
55
+ if smallest is None or n < smallest:
56
+ return (n, smallest)
57
+ elif second is None or n < second:
58
+ return (smallest, n)
59
+ else:
60
+ return (smallest, second)