crosshair-tool 0.0.95__tar.gz → 0.0.96__tar.gz

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.

Potentially problematic release.


This version of crosshair-tool might be problematic. Click here for more details.

Files changed (181) hide show
  1. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/PKG-INFO +1 -1
  2. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/__init__.py +1 -1
  3. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/condition_parser_test.py +0 -2
  4. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/core.py +2 -1
  5. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/core_test.py +2 -3
  6. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/diff_behavior_test.py +0 -2
  7. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/builtinslib.py +65 -16
  8. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/builtinslib_ch_test.py +12 -2
  9. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/builtinslib_test.py +36 -0
  10. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/collectionslib_test.py +4 -4
  11. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/functoolslib.py +8 -2
  12. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/functoolslib_test.py +22 -6
  13. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/opcode_intercept.py +9 -17
  14. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/path_cover.py +5 -1
  15. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/pathing_oracle.py +41 -4
  16. crosshair_tool-0.0.96/crosshair/pathing_oracle_test.py +21 -0
  17. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/statespace.py +73 -18
  18. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/statespace_test.py +16 -0
  19. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair_tool.egg-info/PKG-INFO +1 -1
  20. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair_tool.egg-info/SOURCES.txt +1 -0
  21. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/setup.py +1 -1
  22. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/LICENSE +0 -0
  23. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/README.md +0 -0
  24. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/__main__.py +0 -0
  25. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/_mark_stacks.h +0 -0
  26. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/_preliminaries_test.py +0 -0
  27. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/_tracers.c +0 -0
  28. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/_tracers.h +0 -0
  29. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/_tracers_pycompat.h +0 -0
  30. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/_tracers_test.py +0 -0
  31. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/abcstring.py +0 -0
  32. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/auditwall.py +0 -0
  33. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/auditwall_test.py +0 -0
  34. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/codeconfig.py +0 -0
  35. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/codeconfig_test.py +0 -0
  36. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/condition_parser.py +0 -0
  37. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/conftest.py +0 -0
  38. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/copyext.py +0 -0
  39. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/copyext_test.py +0 -0
  40. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/core_and_libs.py +0 -0
  41. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/core_regestered_types_test.py +0 -0
  42. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/diff_behavior.py +0 -0
  43. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/dynamic_typing.py +0 -0
  44. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/dynamic_typing_test.py +0 -0
  45. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/enforce.py +0 -0
  46. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/enforce_test.py +0 -0
  47. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/examples/PEP316/__init__.py +0 -0
  48. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/examples/PEP316/bugs_detected/__init__.py +0 -0
  49. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/examples/PEP316/bugs_detected/getattr_magic.py +0 -0
  50. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/examples/PEP316/bugs_detected/hash_consistent_with_equals.py +0 -0
  51. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/examples/PEP316/bugs_detected/shopping_cart.py +0 -0
  52. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/examples/PEP316/bugs_detected/showcase.py +0 -0
  53. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/examples/PEP316/correct_code/__init__.py +0 -0
  54. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/examples/PEP316/correct_code/arith.py +0 -0
  55. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/examples/PEP316/correct_code/chess.py +0 -0
  56. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/examples/PEP316/correct_code/nesting_inference.py +0 -0
  57. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/examples/PEP316/correct_code/numpy_examples.py +0 -0
  58. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/examples/PEP316/correct_code/rolling_average.py +0 -0
  59. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/examples/PEP316/correct_code/showcase.py +0 -0
  60. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/examples/__init__.py +0 -0
  61. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/examples/check_examples_test.py +0 -0
  62. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/examples/deal/__init__.py +0 -0
  63. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/examples/icontract/__init__.py +0 -0
  64. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/examples/icontract/bugs_detected/__init__.py +0 -0
  65. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/examples/icontract/bugs_detected/showcase.py +0 -0
  66. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/examples/icontract/bugs_detected/wrong_sign.py +0 -0
  67. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/examples/icontract/correct_code/__init__.py +0 -0
  68. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/examples/icontract/correct_code/arith.py +0 -0
  69. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/examples/icontract/correct_code/showcase.py +0 -0
  70. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/fnutil.py +0 -0
  71. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/fnutil_test.py +0 -0
  72. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/fuzz_core_test.py +0 -0
  73. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/__init__.py +0 -0
  74. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/arraylib.py +0 -0
  75. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/binascii_ch_test.py +0 -0
  76. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/binascii_test.py +0 -0
  77. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/binasciilib.py +0 -0
  78. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/bisectlib_test.py +0 -0
  79. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/codecslib.py +0 -0
  80. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/codecslib_test.py +0 -0
  81. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/collectionslib.py +0 -0
  82. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/collectionslib_ch_test.py +0 -0
  83. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/copylib.py +0 -0
  84. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/copylib_test.py +0 -0
  85. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/datetimelib.py +0 -0
  86. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/datetimelib_ch_test.py +0 -0
  87. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/datetimelib_test.py +0 -0
  88. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/decimallib.py +0 -0
  89. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/decimallib_ch_test.py +0 -0
  90. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/decimallib_test.py +0 -0
  91. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/encodings/__init__.py +0 -0
  92. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/encodings/_encutil.py +0 -0
  93. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/encodings/ascii.py +0 -0
  94. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/encodings/latin_1.py +0 -0
  95. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/encodings/utf_8.py +0 -0
  96. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/encodings_ch_test.py +0 -0
  97. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/fractionlib.py +0 -0
  98. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/fractionlib_test.py +0 -0
  99. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/hashliblib.py +0 -0
  100. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/hashliblib_test.py +0 -0
  101. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/heapqlib.py +0 -0
  102. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/heapqlib_test.py +0 -0
  103. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/importliblib.py +0 -0
  104. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/importliblib_test.py +0 -0
  105. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/iolib.py +0 -0
  106. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/iolib_ch_test.py +0 -0
  107. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/iolib_test.py +0 -0
  108. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/ipaddresslib.py +0 -0
  109. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/itertoolslib.py +0 -0
  110. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/itertoolslib_test.py +0 -0
  111. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/jsonlib.py +0 -0
  112. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/jsonlib_ch_test.py +0 -0
  113. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/jsonlib_test.py +0 -0
  114. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/mathlib.py +0 -0
  115. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/mathlib_ch_test.py +0 -0
  116. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/mathlib_test.py +0 -0
  117. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/oslib.py +0 -0
  118. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/pathliblib_test.py +0 -0
  119. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/randomlib.py +0 -0
  120. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/randomlib_test.py +0 -0
  121. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/relib.py +0 -0
  122. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/relib_ch_test.py +0 -0
  123. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/relib_test.py +0 -0
  124. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/timelib.py +0 -0
  125. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/timelib_test.py +0 -0
  126. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/typeslib.py +0 -0
  127. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/typeslib_test.py +0 -0
  128. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/unicodedatalib.py +0 -0
  129. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/unicodedatalib_test.py +0 -0
  130. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/urlliblib.py +0 -0
  131. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/urlliblib_test.py +0 -0
  132. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/weakreflib.py +0 -0
  133. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/weakreflib_test.py +0 -0
  134. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/zliblib.py +0 -0
  135. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/libimpl/zliblib_test.py +0 -0
  136. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/lsp_server.py +0 -0
  137. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/lsp_server_test.py +0 -0
  138. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/main.py +0 -0
  139. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/main_test.py +0 -0
  140. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/objectproxy.py +0 -0
  141. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/objectproxy_test.py +0 -0
  142. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/opcode_intercept_test.py +0 -0
  143. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/options.py +0 -0
  144. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/options_test.py +0 -0
  145. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/patch_equivalence_test.py +0 -0
  146. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/path_cover_test.py +0 -0
  147. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/path_search.py +0 -0
  148. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/path_search_test.py +0 -0
  149. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/pure_importer.py +0 -0
  150. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/pure_importer_test.py +0 -0
  151. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/py.typed +0 -0
  152. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/register_contract.py +0 -0
  153. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/register_contract_test.py +0 -0
  154. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/simplestructs.py +0 -0
  155. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/simplestructs_test.py +0 -0
  156. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/smtlib.py +0 -0
  157. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/smtlib_test.py +0 -0
  158. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/stubs_parser.py +0 -0
  159. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/stubs_parser_test.py +0 -0
  160. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/test_util.py +0 -0
  161. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/test_util_test.py +0 -0
  162. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/tools/__init__.py +0 -0
  163. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/tools/check_help_in_doc.py +0 -0
  164. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/tools/check_init_and_setup_coincide.py +0 -0
  165. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/tools/generate_demo_table.py +0 -0
  166. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/tracers.py +0 -0
  167. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/tracers_test.py +0 -0
  168. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/type_repo.py +0 -0
  169. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/unicode_categories.py +0 -0
  170. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/unicode_categories_test.py +0 -0
  171. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/util.py +0 -0
  172. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/util_test.py +0 -0
  173. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/watcher.py +0 -0
  174. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/watcher_test.py +0 -0
  175. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/z3util.py +0 -0
  176. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair/z3util_test.py +0 -0
  177. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair_tool.egg-info/dependency_links.txt +0 -0
  178. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair_tool.egg-info/entry_points.txt +0 -0
  179. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair_tool.egg-info/requires.txt +0 -0
  180. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/crosshair_tool.egg-info/top_level.txt +0 -0
  181. {crosshair_tool-0.0.95 → crosshair_tool-0.0.96}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: crosshair-tool
3
- Version: 0.0.95
3
+ Version: 0.0.96
4
4
  Summary: Analyze Python code for correctness using symbolic execution.
5
5
  Home-page: https://github.com/pschanely/CrossHair
6
6
  Author: Phillip Schanely
@@ -15,7 +15,7 @@ from crosshair.statespace import StateSpace
15
15
  from crosshair.tracers import NoTracing, ResumedTracing
16
16
  from crosshair.util import IgnoreAttempt, debug
17
17
 
18
- __version__ = "0.0.95" # Do not forget to update in setup.py!
18
+ __version__ = "0.0.96" # Do not forget to update in setup.py!
19
19
  __author__ = "Phillip Schanely"
20
20
  __license__ = "MIT"
21
21
  __status__ = "Alpha"
@@ -1,8 +1,6 @@
1
1
  import inspect
2
2
  import json
3
3
  import sys
4
- import textwrap
5
- import unittest
6
4
  from typing import List
7
5
 
8
6
  import pytest
@@ -377,7 +377,8 @@ def with_symbolic_self(symbolic_cls: Type, fn: Callable):
377
377
  elif any(isinstance(a, CrossHairValue) for a in args) or (
378
378
  kwargs and any(isinstance(a, CrossHairValue) for a in kwargs.values())
379
379
  ):
380
- self = symbolic_cls._smt_promote_literal(self)
380
+ # NOTE: _ch_create_from_literal is suppoerted for very few types right now
381
+ self = symbolic_cls._ch_create_from_literal(self)
381
382
  target_fn = getattr(symbolic_cls, fn.__name__)
382
383
  else:
383
384
  args = map(realize, args)
@@ -5,7 +5,6 @@ import re
5
5
  import sys
6
6
  import time
7
7
  from typing import *
8
- from unittest import skipIf
9
8
 
10
9
  import pytest # type: ignore
11
10
 
@@ -736,7 +735,7 @@ def test_newtype() -> None:
736
735
  assert isinstance(x, SymbolicInt)
737
736
 
738
737
 
739
- @skipIf(sys.version_info < (3, 12), "type statements added in 3.12")
738
+ @pytest.mark.skipif(sys.version_info < (3, 12), reason="type statements added in 3.12")
740
739
  def test_type_statement() -> None:
741
740
  env: dict[str, Any] = {}
742
741
  exec("type MyIntNew = int\n", env)
@@ -747,7 +746,7 @@ def test_type_statement() -> None:
747
746
  assert isinstance(x, SymbolicInt)
748
747
 
749
748
 
750
- @skipIf(sys.version_info < (3, 12), "type statements added in 3.12")
749
+ @pytest.mark.skipif(sys.version_info < (3, 12), reason="type statements added in 3.12")
751
750
  def test_parameterized_type_statement() -> None:
752
751
  env: dict[str, Any] = {}
753
752
  exec("type Pair[A, B] = tuple[B, A]\n", env)
@@ -1,5 +1,3 @@
1
- import sys
2
- import unittest
3
1
  from typing import Callable, List, Optional
4
2
 
5
3
  from crosshair.diff_behavior import (
@@ -346,7 +346,7 @@ class AtomicSymbolicValue(SymbolicValue):
346
346
  raise CrossHairInternal(f"_pytype not implemented in {cls}")
347
347
 
348
348
  @classmethod
349
- def _smt_promote_literal(cls, val: object) -> Optional[z3.SortRef]:
349
+ def _smt_promote_literal(cls, literal: object) -> Optional[z3.ExprRef]:
350
350
  raise CrossHairInternal(f"_smt_promote_literal not implemented in {cls}")
351
351
 
352
352
  @classmethod
@@ -1120,7 +1120,7 @@ class SymbolicBool(SymbolicIntable, AtomicSymbolicValue):
1120
1120
  return bool
1121
1121
 
1122
1122
  @classmethod
1123
- def _smt_promote_literal(cls, literal) -> Optional[z3.SortRef]:
1123
+ def _smt_promote_literal(cls, literal) -> Optional[z3.ExprRef]:
1124
1124
  if isinstance(literal, bool):
1125
1125
  return z3.BoolVal(literal)
1126
1126
  return None
@@ -1189,7 +1189,7 @@ class SymbolicInt(SymbolicIntable, AtomicSymbolicValue):
1189
1189
  return int
1190
1190
 
1191
1191
  @classmethod
1192
- def _smt_promote_literal(cls, literal) -> Optional[z3.SortRef]:
1192
+ def _smt_promote_literal(cls, literal) -> Optional[z3.ExprRef]:
1193
1193
  if isinstance(literal, int):
1194
1194
  return z3IntVal(literal)
1195
1195
  return None
@@ -1410,7 +1410,7 @@ class PreciseIeeeSymbolicFloat(SymbolicFloat):
1410
1410
  return _PRECISE_IEEE_FLOAT_SORT
1411
1411
 
1412
1412
  @classmethod
1413
- def _smt_promote_literal(cls, literal) -> Optional[z3.SortRef]:
1413
+ def _smt_promote_literal(cls, literal) -> Optional[z3.ExprRef]:
1414
1414
  if isinstance(literal, float):
1415
1415
  return z3.FPVal(literal, cls._ch_smt_sort())
1416
1416
  return None
@@ -1533,7 +1533,7 @@ class RealBasedSymbolicFloat(SymbolicFloat):
1533
1533
  return z3.RealSort()
1534
1534
 
1535
1535
  @classmethod
1536
- def _smt_promote_literal(cls, literal) -> Optional[z3.SortRef]:
1536
+ def _smt_promote_literal(cls, literal) -> Optional[z3.ExprRef]:
1537
1537
  if isinstance(literal, float) and isfinite(literal):
1538
1538
  return z3.RealVal(literal)
1539
1539
  return None
@@ -2447,7 +2447,7 @@ class SymbolicType(AtomicSymbolicValue, SymbolicValue, Untracable):
2447
2447
  return type
2448
2448
 
2449
2449
  @classmethod
2450
- def _smt_promote_literal(cls, literal) -> Optional[z3.SortRef]:
2450
+ def _smt_promote_literal(cls, literal) -> Optional[z3.ExprRef]:
2451
2451
  if isinstance(literal, type):
2452
2452
  return context_statespace().extra(SymbolicTypeRepository).get_type(literal)
2453
2453
  return None
@@ -2668,10 +2668,25 @@ class SymbolicCallable:
2668
2668
  __annotations__: dict = {}
2669
2669
 
2670
2670
  def __init__(self, values: list):
2671
+ """
2672
+ A function that will ignore its arguments and produce return values
2673
+ from the list given.
2674
+ If the given list is exhausted, the function will just repeatedly
2675
+ return the final value in the list.
2676
+
2677
+ If `values` is concrete, it must be non-mepty.
2678
+ If `values` is a symbolic list, it will be forced to be non-empty
2679
+ (the caller must enure that's possible).
2680
+ """
2671
2681
  assert not is_tracing()
2672
2682
  with ResumedTracing():
2673
- if not values:
2674
- raise IgnoreAttempt
2683
+ has_values = len(values) > 0
2684
+ if isinstance(values, CrossHairValue):
2685
+ space = context_statespace()
2686
+ assert space.is_possible(has_values)
2687
+ space.add(has_values)
2688
+ else:
2689
+ assert has_values
2675
2690
  self.values = values
2676
2691
  self.idx = 0
2677
2692
 
@@ -2695,6 +2710,7 @@ class SymbolicCallable:
2695
2710
  if idx >= len(values):
2696
2711
  return values[-1]
2697
2712
  else:
2713
+ self.idx += 1
2698
2714
  return values[idx]
2699
2715
 
2700
2716
  def __bool__(self):
@@ -3406,6 +3422,26 @@ class AnySymbolicStr(AbcString):
3406
3422
  return "0" * fill_length + self
3407
3423
 
3408
3424
 
3425
+ def _unfindable_range(start: Optional[int], end: Optional[int], mylen: int) -> bool:
3426
+ """
3427
+ Emulates some preliminary checks that CPython makes before searching
3428
+ for substrings within some bounds. (in e.g. str.find, str.startswith, etc)
3429
+ """
3430
+ if start is None or start == 0 or start <= -mylen:
3431
+ return False
3432
+
3433
+ # At this point, we know that `start` is defined and points to an index after 0
3434
+ if end is None or end >= mylen:
3435
+ return start > mylen
3436
+
3437
+ # At this point, we know that `end` is defined and points to an index before the end of the string
3438
+ if start < 0:
3439
+ start += mylen
3440
+ if end < 0:
3441
+ end += mylen
3442
+ return end < start
3443
+
3444
+
3409
3445
  class LazyIntSymbolicStr(AnySymbolicStr, CrossHairValue):
3410
3446
  """
3411
3447
  A symbolic string that lazily generates SymbolicInt-based characters as needed.
@@ -3444,10 +3480,8 @@ class LazyIntSymbolicStr(AnySymbolicStr, CrossHairValue):
3444
3480
  codepoints = tuple(self._codepoints)
3445
3481
  return "".join(chr(realize(x)) for x in codepoints)
3446
3482
 
3447
- # This is normally an AtomicSymbolicValue method, but sometimes it's used in a
3448
- # duck-typing way.
3449
3483
  @classmethod
3450
- def _smt_promote_literal(cls, val: object) -> Optional[z3.SortRef]:
3484
+ def _ch_create_from_literal(cls, val: object) -> Optional[CrossHairValue]:
3451
3485
  if isinstance(val, str):
3452
3486
  return LazyIntSymbolicStr(list(map(ord, val)))
3453
3487
  return None
@@ -3481,6 +3515,10 @@ class LazyIntSymbolicStr(AnySymbolicStr, CrossHairValue):
3481
3515
  with NoTracing():
3482
3516
  if not isinstance(i, (Integral, slice)):
3483
3517
  raise TypeError(type(i))
3518
+ # This could/should? be symbolic by naming all the possibilities.
3519
+ # Note the slice case still must realize the return length.
3520
+ # Especially because we no longer explore realization trees except
3521
+ # as a last resort.
3484
3522
  i = deep_realize(i)
3485
3523
  with ResumedTracing():
3486
3524
  newcontents = self._codepoints[i]
@@ -3561,11 +3599,15 @@ class LazyIntSymbolicStr(AnySymbolicStr, CrossHairValue):
3561
3599
  return any(self.endswith(s, start, end) for s in substr)
3562
3600
  if not isinstance(substr, str):
3563
3601
  raise TypeError
3602
+ substrlen = len(substr)
3564
3603
  if start is None and end is None:
3565
3604
  matchable = self
3566
3605
  else:
3567
3606
  matchable = self[start:end]
3568
- return matchable[-len(substr) :] == substr
3607
+ if substrlen == 0:
3608
+ return not _unfindable_range(start, end, len(self))
3609
+ else:
3610
+ return matchable[-substrlen:] == substr
3569
3611
 
3570
3612
  def startswith(self, substr, start=None, end=None):
3571
3613
  if isinstance(substr, tuple):
@@ -3575,6 +3617,10 @@ class LazyIntSymbolicStr(AnySymbolicStr, CrossHairValue):
3575
3617
  if start is None and end is None:
3576
3618
  matchable = self
3577
3619
  else:
3620
+ # Wacky special case: the empty string is findable off the left
3621
+ # side but not the right!
3622
+ if _unfindable_range(start, end, len(self)):
3623
+ return False
3578
3624
  matchable = self[start:end]
3579
3625
  return matchable[: len(substr)] == substr
3580
3626
 
@@ -3615,7 +3661,7 @@ class LazyIntSymbolicStr(AnySymbolicStr, CrossHairValue):
3615
3661
  end += mylen
3616
3662
  matchstr = self[start:end] if start != 0 or end is not mylen else self
3617
3663
  if len(substr) == 0:
3618
- # Add oddity of CPython. We can find the empty string when over-slicing
3664
+ # An oddity of CPython. We can find the empty string when over-slicing
3619
3665
  # off the left side of the string, but not off the right:
3620
3666
  # ''.find('', 3, 4) == -1
3621
3667
  # ''.find('', -4, -3) == 0
@@ -4790,14 +4836,17 @@ def _str_format_map(self, map) -> Union[AnySymbolicStr, str]:
4790
4836
 
4791
4837
 
4792
4838
  def _str_startswith(self, substr, start=None, end=None) -> bool:
4793
- if not isinstance(self, str):
4794
- raise TypeError
4795
4839
  with NoTracing():
4840
+ if isinstance(self, LazyIntSymbolicStr):
4841
+ with ResumedTracing():
4842
+ return self.startswith(substr, start, end)
4843
+ elif not isinstance(self, str):
4844
+ raise TypeError
4796
4845
  # Handle native values with native implementation:
4797
4846
  if type(substr) is str:
4798
4847
  return self.startswith(substr, start, end)
4799
4848
  if type(substr) is tuple:
4800
- if all(type(i) is str for i in substr):
4849
+ if all(type(s) is str for s in substr):
4801
4850
  return self.startswith(substr, start, end)
4802
4851
  symbolic_self = LazyIntSymbolicStr([ord(c) for c in self])
4803
4852
  return symbolic_self.startswith(substr, start, end)
@@ -606,9 +606,14 @@ def check_str_endswith(
606
606
  string: str, suffix: str, start: Optional[int], end: Optional[int]
607
607
  ) -> ResultComparison:
608
608
  """post: _"""
609
+ # crosshair: max_uninteresting_iterations=100
610
+
611
+ for i in (len(string), len(suffix), start, end):
612
+ if i is not None and abs(i) >= 1:
613
+ pass
614
+
609
615
  return compare_results(
610
- lambda s, *a: s.endswith(*a),
611
- string,
616
+ lambda s, *a, **kw: s.endswith(*a, **kw), string, suffix, start, end
612
617
  )
613
618
 
614
619
 
@@ -825,6 +830,11 @@ def check_str_startswith(
825
830
  end: Optional[int],
826
831
  ) -> ResultComparison:
827
832
  """post: _"""
833
+ # crosshair: max_uninteresting_iterations=100
834
+
835
+ for i in (len(string), len(prefix), start, end):
836
+ if i is not None and abs(i) >= 1:
837
+ pass
828
838
  return compare_results(
829
839
  lambda s, *a, **kw: s.startswith(*a, **kw), string, prefix, start, end
830
840
  )
@@ -939,6 +939,31 @@ def test_str_replace_method() -> None:
939
939
  check_states(f, POST_FAIL)
940
940
 
941
941
 
942
+ def test_str_startswith(space) -> None:
943
+ symbolic_char = proxy_for_type(str, "x")
944
+ symbolic_empty = proxy_for_type(str, "y")
945
+ with ResumedTracing():
946
+ space.add(len(symbolic_char) == 1)
947
+ space.add(len(symbolic_empty) == 0)
948
+ assert symbolic_char.startswith(symbolic_empty)
949
+ assert symbolic_char.startswith(symbolic_char)
950
+ assert symbolic_char.startswith(("foo", symbolic_empty))
951
+ assert not symbolic_char.startswith(("foo", "bar"))
952
+ assert symbolic_char.startswith(("", "bar"))
953
+ assert symbolic_char.startswith("")
954
+ assert symbolic_char.startswith(symbolic_empty, 1)
955
+ assert symbolic_char.startswith(symbolic_empty, 1, 1)
956
+ assert str.startswith(symbolic_char, symbolic_empty)
957
+ assert "foo".startswith(symbolic_empty)
958
+ assert not "".startswith(symbolic_char)
959
+
960
+ # Yes, the empty string is findable off the left side but not the right
961
+ assert "x".startswith("", -10, -9)
962
+ assert symbolic_char.startswith(symbolic_empty, -10, -9)
963
+ assert not "x".startswith("", 9, 10)
964
+ assert not symbolic_char.startswith(symbolic_empty, 9, 10)
965
+
966
+
942
967
  @pytest.mark.demo
943
968
  def test_str_index_method() -> None:
944
969
  def f(a: str) -> int:
@@ -3134,6 +3159,17 @@ def test_callable_as_bool() -> None:
3134
3159
  check_states(f, CONFIRMED)
3135
3160
 
3136
3161
 
3162
+ def test_callable_can_return_different_values(space) -> None:
3163
+ fn = proxy_for_type(Callable[[], int], "fn")
3164
+ with ResumedTracing():
3165
+ first_return = fn()
3166
+ second_return = fn()
3167
+ returns_are_equal = first_return == second_return
3168
+ returns_are_not_equal = first_return != second_return
3169
+ assert space.is_possible(returns_are_equal)
3170
+ assert space.is_possible(returns_are_not_equal)
3171
+
3172
+
3137
3173
  @pytest.mark.smoke
3138
3174
  def test_callable_repr() -> None:
3139
3175
  def f(f1: Callable[[int], int]) -> int:
@@ -3,7 +3,7 @@ import sys
3
3
  from collections import Counter, defaultdict, deque, namedtuple
4
4
  from copy import deepcopy
5
5
  from inspect import Parameter, Signature
6
- from typing import Counter, DefaultDict, Deque, NamedTuple, Tuple
6
+ from typing import Callable, Counter, DefaultDict, Deque, Dict, NamedTuple, Tuple
7
7
 
8
8
  import pytest
9
9
 
@@ -14,7 +14,7 @@ from crosshair.core import (
14
14
  realize,
15
15
  standalone_statespace,
16
16
  )
17
- from crosshair.libimpl.collectionslib import ListBasedDeque
17
+ from crosshair.libimpl.collectionslib import ListBasedDeque, PureDefaultDict
18
18
  from crosshair.statespace import CANNOT_CONFIRM, CONFIRMED, POST_FAIL, MessageType
19
19
  from crosshair.test_util import check_states
20
20
  from crosshair.tracers import NoTracing, ResumedTracing
@@ -246,10 +246,10 @@ def test_defaultdict_default_fail(test_list) -> None:
246
246
 
247
247
 
248
248
  def test_defaultdict_default_ok(test_list) -> None:
249
- def f(a: DefaultDict[int, int], k1: int, k2: int) -> DefaultDict[int, int]:
249
+ def f(a: DefaultDict[int, int], k: int) -> DefaultDict[int, int]:
250
250
  """
251
251
  pre: len(a) == 0 and a.default_factory is not None
252
- post: _[k1] == _[k2]
252
+ post: _[k] == _[k]
253
253
  """
254
254
  return a
255
255
 
@@ -1,4 +1,4 @@
1
- from functools import _lru_cache_wrapper, partial, reduce
1
+ from functools import _lru_cache_wrapper, partial, reduce, update_wrapper, wraps
2
2
 
3
3
  from crosshair.core import register_patch
4
4
 
@@ -7,7 +7,13 @@ from crosshair.core import register_patch
7
7
 
8
8
  def _partial(func, *a1, **kw1):
9
9
  if callable(func):
10
- return partial(lambda *a2, **kw2: func(*a2, **kw2), *a1, **kw1)
10
+ # We make a do-nothing wrapper to ensure that the tracer has a crack
11
+ # at this function when it is called.
12
+ def wrapper(*a2, **kw2):
13
+ return func(*a2, **kw2)
14
+
15
+ update_wrapper(wrapper, func)
16
+ return partial(wrapper, *a1, **kw1)
11
17
  else:
12
18
  raise TypeError
13
19
 
@@ -1,20 +1,36 @@
1
1
  import functools
2
+ import inspect
2
3
 
3
4
  from crosshair.core import proxy_for_type, standalone_statespace
4
5
  from crosshair.libimpl.builtinslib import LazyIntSymbolicStr
5
- from crosshair.tracers import NoTracing
6
+ from crosshair.tracers import NoTracing, ResumedTracing
6
7
 
7
8
 
8
- def test_partial():
9
- with standalone_statespace as space:
10
- with NoTracing():
11
- abc = LazyIntSymbolicStr(list(map(ord, "abc")))
12
- xyz = LazyIntSymbolicStr(list(map(ord, "xyz")))
9
+ def test_partial(space):
10
+ abc = LazyIntSymbolicStr(list(map(ord, "abc")))
11
+ xyz = LazyIntSymbolicStr(list(map(ord, "xyz")))
12
+ with ResumedTracing():
13
13
  joiner = functools.partial(str.join, ",")
14
14
  ret = joiner([abc, xyz])
15
15
  assert ret == "abc,xyz"
16
16
 
17
17
 
18
+ def test_partial_is_interceptable(space):
19
+ x = proxy_for_type(str, "x")
20
+ y = proxy_for_type(str, "y")
21
+ with ResumedTracing():
22
+ joiner = functools.partial(str.startswith, x)
23
+ # Ensure we don't explode
24
+ list(map(joiner, ["foo", y]))
25
+
26
+
27
+ def test_partial_arg_is_inspectable(space):
28
+ with ResumedTracing():
29
+ joiner = functools.partial(str.join, ",")
30
+ assert isinstance(joiner, functools.partial)
31
+ assert inspect.getdoc(joiner.func) == inspect.getdoc(str.join)
32
+
33
+
18
34
  def test_reduce():
19
35
  with standalone_statespace as space:
20
36
  with NoTracing():
@@ -5,7 +5,9 @@ from collections import defaultdict
5
5
  from collections.abc import MutableMapping, Set
6
6
  from sys import version_info
7
7
  from types import CodeType, FrameType
8
- from typing import Any, Callable, Iterable, Mapping, Tuple, Union
8
+ from typing import Any, Callable, Iterable, List, Mapping, Tuple, Union
9
+
10
+ from z3 import ExprRef
9
11
 
10
12
  from crosshair.core import (
11
13
  ATOMIC_IMMUTABLE_TYPES,
@@ -83,13 +85,6 @@ class MultiSubscriptableContainer:
83
85
  if isinstance(container, Mapping):
84
86
  kv_pairs: Iterable[Tuple[Any, Any]] = container.items()
85
87
  else:
86
- in_bounds = space.smt_fork(
87
- z3Or(-len(container) <= key.var, key.var < len(container)),
88
- desc=f"index_in_bounds",
89
- probability_true=0.9,
90
- )
91
- if not in_bounds:
92
- raise IndexError
93
88
  kv_pairs = enumerate(container)
94
89
 
95
90
  values_by_type = defaultdict(list)
@@ -118,7 +113,7 @@ class MultiSubscriptableContainer:
118
113
  keys_by_value_id[id(cur_value)].append(cur_key)
119
114
  for value_type, cur_pairs in values_by_type.items():
120
115
  hypothetical_result = symbolic_for_pytype(value_type)(
121
- "item_at_" + space.uniq(), value_type
116
+ "item_" + space.uniq(), value_type
122
117
  )
123
118
  with ResumedTracing():
124
119
  condition_pairs = []
@@ -139,20 +134,17 @@ class MultiSubscriptableContainer:
139
134
  space.add(any([all(pair) for pair in condition_pairs]))
140
135
  return hypothetical_result
141
136
 
142
- for (value_id, value), probability_true in with_uniform_probabilities(
143
- values_by_id.items()
144
- ):
137
+ exprs_and_values: List[Tuple[ExprRef, object]] = []
138
+ for value_id, value in values_by_id.items():
145
139
  keys_for_value = keys_by_value_id[value_id]
146
140
  with ResumedTracing():
147
141
  is_match = any([key == k for k in keys_for_value])
148
142
  if isinstance(is_match, SymbolicBool):
149
- if space.smt_fork(
150
- is_match.var,
151
- probability_true=probability_true,
152
- ):
153
- return value
143
+ exprs_and_values.append((is_match.var, value))
154
144
  elif is_match:
155
145
  return value
146
+ if exprs_and_values:
147
+ return space.smt_fanout(exprs_and_values, desc="multi_subscript")
156
148
 
157
149
  if type(container) is dict:
158
150
  raise KeyError # ( f"Key {key} not found in dict")
@@ -133,7 +133,11 @@ def path_cover(
133
133
  selected: List[PathSummary] = []
134
134
  while paths:
135
135
  next_best = max(
136
- paths, key=lambda p: len(p.coverage.offsets_covered - opcodes_found)
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
+ ),
137
141
  )
138
142
  cur_offsets = next_best.coverage.offsets_covered
139
143
  if coverage_type == CoverageType.OPCODE:
@@ -2,7 +2,7 @@ import math
2
2
  from collections import defaultdict
3
3
  from typing import Counter, Dict, List, Optional, Sequence, Tuple
4
4
 
5
- from z3 import ExprRef # type: ignore
5
+ from z3 import ExprRef
6
6
 
7
7
  from crosshair.statespace import (
8
8
  AbstractPathingOracle,
@@ -11,9 +11,11 @@ from crosshair.statespace import (
11
11
  NodeLike,
12
12
  RootNode,
13
13
  SearchTreeNode,
14
+ StateSpace,
14
15
  WorstResultNode,
15
16
  )
16
17
  from crosshair.util import CrossHairInternal, debug, in_debug
18
+ from crosshair.z3util import z3And, z3Not, z3Or
17
19
 
18
20
  CodeLoc = Tuple[str, ...]
19
21
 
@@ -60,7 +62,8 @@ class CoveragePathingOracle(AbstractPathingOracle):
60
62
  # (even just a 10% change could be much larger than it would be otherwise)
61
63
  _delta_probabilities = {-1: 0.1, 0: 0.25, 1: 0.9}
62
64
 
63
- def pre_path_hook(self, root: RootNode) -> None:
65
+ def pre_path_hook(self, space: StateSpace) -> None:
66
+ root = space._root
64
67
  visits = self.visits
65
68
  _delta_probabilities = self._delta_probabilities
66
69
 
@@ -213,16 +216,50 @@ class PreferNegativeOracle(AbstractPathingOracle):
213
216
  return 0.25
214
217
 
215
218
 
219
+ class ConstrainedOracle(AbstractPathingOracle):
220
+ """
221
+ A pathing oracle that prefers to take a path that satisfies
222
+ explicitly provided constraints.
223
+ """
224
+
225
+ def __init__(self, inner_oracle: AbstractPathingOracle):
226
+ self.inner_oracle = inner_oracle
227
+ self.exprs: List[ExprRef] = []
228
+
229
+ def prefer(self, expr: ExprRef):
230
+ self.exprs.append(expr)
231
+
232
+ def pre_path_hook(self, space: StateSpace) -> None:
233
+ self.space = space
234
+ self.exprs = []
235
+ self.inner_oracle.pre_path_hook(space)
236
+
237
+ def post_path_hook(self, path: Sequence["SearchTreeNode"]) -> None:
238
+ self.inner_oracle.post_path_hook(path)
239
+
240
+ def decide(
241
+ self, root, node: "WorstResultNode", engine_probability: Optional[float]
242
+ ) -> float:
243
+ # We always run the inner oracle in case it's tracking something about the path.
244
+ default_probability = self.inner_oracle.decide(root, node, engine_probability)
245
+ if not self.space.is_possible(z3And(*[node.expr, *self.exprs])):
246
+ return 0.0
247
+ elif not self.space.is_possible(z3And(*[z3Not(node.expr), *self.exprs])):
248
+ return 1.0
249
+ else:
250
+ return default_probability
251
+
252
+
216
253
  class RotatingOracle(AbstractPathingOracle):
217
254
  def __init__(self, oracles: List[AbstractPathingOracle]):
218
255
  self.oracles = oracles
219
256
  self.index = -1
220
257
 
221
- def pre_path_hook(self, root: "RootNode") -> None:
258
+ def pre_path_hook(self, space: StateSpace) -> None:
222
259
  oracles = self.oracles
223
260
  self.index = (self.index + 1) % len(oracles)
224
261
  for oracle in oracles:
225
- oracle.pre_path_hook(root)
262
+ oracle.pre_path_hook(space)
226
263
 
227
264
  def post_path_hook(self, path: Sequence["SearchTreeNode"]) -> None:
228
265
  for oracle in self.oracles:
@@ -0,0 +1,21 @@
1
+ import random
2
+
3
+ import z3
4
+
5
+ from crosshair.pathing_oracle import ConstrainedOracle, PreferNegativeOracle
6
+ from crosshair.statespace import RootNode, SimpleStateSpace, WorstResultNode
7
+
8
+
9
+ def test_constrained_oracle():
10
+ oracle = ConstrainedOracle(PreferNegativeOracle())
11
+ x = z3.Int("x")
12
+ root = RootNode()
13
+ space = SimpleStateSpace()
14
+ oracle.pre_path_hook(space)
15
+ oracle.prefer(x >= 7)
16
+ rand = random.Random()
17
+ assert oracle.decide(root, WorstResultNode(rand, x < 7, space.solver), None) == 0.0
18
+ assert oracle.decide(root, WorstResultNode(rand, x >= 3, space.solver), None) == 1.0
19
+ assert (
20
+ oracle.decide(root, WorstResultNode(rand, x == 7, space.solver), None) == 0.25
21
+ )