crosshair-tool 0.0.83__tar.gz → 0.0.85__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.
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/PKG-INFO +2 -2
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/README.md +1 -1
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/__init__.py +1 -1
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/_mark_stacks.h +0 -25
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/_tracers.c +92 -15
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/_tracers.h +2 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/_tracers_test.py +8 -2
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/auditwall.py +0 -1
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/auditwall_test.py +5 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/condition_parser.py +5 -5
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/condition_parser_test.py +50 -63
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/copyext.py +23 -7
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/copyext_test.py +11 -1
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/core.py +23 -17
- crosshair-tool-0.0.85/crosshair/core_test.py +1290 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/diff_behavior_test.py +14 -21
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/dynamic_typing.py +90 -1
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/dynamic_typing_test.py +73 -1
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/enforce_test.py +15 -22
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/fnutil_test.py +4 -8
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/arraylib.py +2 -5
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/binasciilib.py +2 -3
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/builtinslib.py +28 -21
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/builtinslib_test.py +1 -8
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/collectionslib.py +18 -3
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/collectionslib_test.py +89 -15
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/encodings/_encutil.py +8 -3
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/mathlib_test.py +0 -7
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/relib_ch_test.py +2 -2
- crosshair-tool-0.0.85/crosshair/libimpl/timelib.py +72 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/timelib_test.py +12 -2
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/lsp_server.py +1 -1
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/main.py +3 -1
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/objectproxy_test.py +7 -11
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/opcode_intercept.py +24 -8
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/opcode_intercept_test.py +13 -2
- crosshair-tool-0.0.85/crosshair/tools/__init__.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/tracers.py +27 -9
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/type_repo.py +2 -2
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/unicode_categories.py +1 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/util.py +45 -16
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/watcher.py +2 -2
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair_tool.egg-info/PKG-INFO +2 -2
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair_tool.egg-info/SOURCES.txt +1 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/setup.cfg +2 -2
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/setup.py +2 -2
- crosshair-tool-0.0.83/crosshair/core_test.py +0 -1249
- crosshair-tool-0.0.83/crosshair/libimpl/timelib.py +0 -53
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/LICENSE +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/__main__.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/_preliminaries_test.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/_tracers_pycompat.h +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/abcstring.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/codeconfig.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/codeconfig_test.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/conftest.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/core_and_libs.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/core_regestered_types_test.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/diff_behavior.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/enforce.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/examples/PEP316/__init__.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/examples/PEP316/bugs_detected/__init__.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/examples/PEP316/bugs_detected/getattr_magic.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/examples/PEP316/bugs_detected/hash_consistent_with_equals.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/examples/PEP316/bugs_detected/shopping_cart.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/examples/PEP316/bugs_detected/showcase.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/examples/PEP316/correct_code/__init__.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/examples/PEP316/correct_code/arith.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/examples/PEP316/correct_code/chess.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/examples/PEP316/correct_code/nesting_inference.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/examples/PEP316/correct_code/numpy_examples.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/examples/PEP316/correct_code/rolling_average.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/examples/PEP316/correct_code/showcase.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/examples/__init__.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/examples/check_examples_test.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/examples/deal/__init__.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/examples/icontract/__init__.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/examples/icontract/bugs_detected/__init__.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/examples/icontract/bugs_detected/showcase.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/examples/icontract/bugs_detected/wrong_sign.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/examples/icontract/correct_code/__init__.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/examples/icontract/correct_code/arith.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/examples/icontract/correct_code/showcase.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/fnutil.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/fuzz_core_test.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/__init__.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/binascii_ch_test.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/binascii_test.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/bisectlib_test.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/builtinslib_ch_test.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/codecslib.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/codecslib_test.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/collectionslib_ch_test.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/copylib.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/datetimelib.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/datetimelib_ch_test.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/datetimelib_test.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/decimallib.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/decimallib_ch_test.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/decimallib_test.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/encodings/__init__.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/encodings/ascii.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/encodings/latin_1.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/encodings/utf_8.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/encodings_ch_test.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/fractionlib.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/fractionlib_test.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/functoolslib.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/functoolslib_test.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/hashliblib.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/hashliblib_test.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/heapqlib.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/heapqlib_test.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/importliblib.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/importliblib_test.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/iolib.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/iolib_ch_test.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/iolib_test.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/ipaddresslib.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/itertoolslib.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/itertoolslib_test.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/jsonlib.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/jsonlib_ch_test.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/jsonlib_test.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/mathlib.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/mathlib_ch_test.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/oslib.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/pathliblib_test.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/randomlib.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/randomlib_test.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/relib.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/relib_test.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/typeslib.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/typeslib_test.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/unicodedatalib.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/unicodedatalib_test.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/urlliblib.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/urlliblib_test.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/weakreflib.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/weakreflib_test.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/zliblib.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/zliblib_test.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/lsp_server_test.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/main_test.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/objectproxy.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/options.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/options_test.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/patch_equivalence_test.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/path_cover.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/path_cover_test.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/path_search.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/path_search_test.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/pathing_oracle.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/pure_importer.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/pure_importer_test.py +0 -0
- /crosshair-tool-0.0.83/crosshair/tools/__init__.py → /crosshair-tool-0.0.85/crosshair/py.typed +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/register_contract.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/register_contract_test.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/simplestructs.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/simplestructs_test.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/smtlib.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/smtlib_test.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/statespace.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/statespace_test.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/stubs_parser.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/stubs_parser_test.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/test_util.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/test_util_test.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/tools/check_help_in_doc.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/tools/check_init_and_setup_coincide.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/tools/generate_demo_table.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/tracers_test.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/unicode_categories_test.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/util_test.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/watcher_test.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/z3util.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/z3util_test.py +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair_tool.egg-info/dependency_links.txt +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair_tool.egg-info/entry_points.txt +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair_tool.egg-info/requires.txt +0 -0
- {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair_tool.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: crosshair-tool
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.85
|
|
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
|
|
@@ -30,7 +30,7 @@ License-File: LICENSE
|
|
|
30
30
|
# CrossHair
|
|
31
31
|
|
|
32
32
|
[](https://discord.gg/rUeTaYTWbb)
|
|
33
|
-
[](https://github.com/pschanely/CrossHair/actions?query=workflow%3ACheck+event%3Apush)
|
|
34
34
|
[](https://pepy.tech/project/crosshair-tool)
|
|
35
35
|
|
|
36
36
|
An analysis tool for Python that blurs the line between testing and
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# CrossHair
|
|
4
4
|
|
|
5
5
|
[](https://discord.gg/rUeTaYTWbb)
|
|
6
|
-
[](https://github.com/pschanely/CrossHair/actions?query=workflow%3ACheck+event%3Apush)
|
|
7
7
|
[](https://pepy.tech/project/crosshair-tool)
|
|
8
8
|
|
|
9
9
|
An analysis tool for Python that blurs the line between testing and
|
|
@@ -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.
|
|
18
|
+
__version__ = "0.0.85" # Do not forget to update in setup.py!
|
|
19
19
|
__author__ = "Phillip Schanely"
|
|
20
20
|
__license__ = "MIT"
|
|
21
21
|
__status__ = "Alpha"
|
|
@@ -538,31 +538,6 @@ static const uint8_t _ch_DE_INSTRUMENT[256] = {
|
|
|
538
538
|
#endif
|
|
539
539
|
#endif
|
|
540
540
|
|
|
541
|
-
static const uint8_t _ch_TRACABLE_INSTRUCTIONS[256] = {
|
|
542
|
-
// This must be manually kept in sync the the various
|
|
543
|
-
// instructions that we care about on the python side.
|
|
544
|
-
[MAP_ADD] = 1,
|
|
545
|
-
[BINARY_SUBSCR] = 1,
|
|
546
|
-
[BINARY_SLICE] = 1,
|
|
547
|
-
[CONTAINS_OP] = 1,
|
|
548
|
-
[BUILD_STRING] = 1,
|
|
549
|
-
#if PY_VERSION_HEX < 0x030D0000
|
|
550
|
-
// <= 3.12
|
|
551
|
-
[FORMAT_VALUE] = 1,
|
|
552
|
-
#elif PY_VERSION_HEX < 0x030E0000
|
|
553
|
-
// 3.13
|
|
554
|
-
[CALL_KW] = 1,
|
|
555
|
-
[CONVERT_VALUE] = 1,
|
|
556
|
-
#endif
|
|
557
|
-
[UNARY_NOT] = 1,
|
|
558
|
-
[SET_ADD] = 1,
|
|
559
|
-
[IS_OP] = 1,
|
|
560
|
-
[BINARY_OP] = 1,
|
|
561
|
-
[CALL] = 1,
|
|
562
|
-
[CALL_FUNCTION_EX] = 1,
|
|
563
|
-
};
|
|
564
|
-
|
|
565
|
-
|
|
566
541
|
/* Get the underlying opcode, stripping instrumentation */
|
|
567
542
|
int _ch_Py_GetBaseOpcode(PyCodeObject *code, int i)
|
|
568
543
|
{
|
|
@@ -17,20 +17,69 @@
|
|
|
17
17
|
#include <opcode.h>
|
|
18
18
|
|
|
19
19
|
#define Py_BUILD_CORE
|
|
20
|
+
#define NEED_OPCODE_METADATA
|
|
20
21
|
|
|
21
|
-
#if PY_VERSION_HEX >= 0x030C0000
|
|
22
|
-
#include "_mark_stacks.h"
|
|
23
|
-
#endif
|
|
24
22
|
|
|
25
23
|
#include "_tracers_pycompat.h"
|
|
24
|
+
#include "internal/pycore_code.h"
|
|
25
|
+
|
|
26
|
+
#if PY_VERSION_HEX >= 0x030D0000
|
|
27
|
+
// Python 3.13+
|
|
28
|
+
#include "internal/pycore_opcode_metadata.h"
|
|
29
|
+
#endif
|
|
30
|
+
|
|
26
31
|
#include "_tracers.h"
|
|
27
32
|
|
|
28
33
|
#include "frameobject.h"
|
|
29
34
|
|
|
30
35
|
#if PY_VERSION_HEX >= 0x030B0000
|
|
36
|
+
// Python 3.11+
|
|
31
37
|
#include "internal/pycore_frame.h"
|
|
32
38
|
#endif
|
|
33
39
|
|
|
40
|
+
|
|
41
|
+
#if PY_VERSION_HEX >= 0x030C0000
|
|
42
|
+
// Python 3.12+
|
|
43
|
+
const uint8_t _ch_TRACABLE_INSTRUCTIONS[256] = {
|
|
44
|
+
// This must be manually kept in sync the the various
|
|
45
|
+
// instructions that we care about on the python side.
|
|
46
|
+
[MAP_ADD] = 1,
|
|
47
|
+
[BINARY_SLICE] = 1,
|
|
48
|
+
[CONTAINS_OP] = 1,
|
|
49
|
+
[BUILD_STRING] = 1,
|
|
50
|
+
#if PY_VERSION_HEX < 0x030D0000
|
|
51
|
+
// <= 3.12
|
|
52
|
+
[FORMAT_VALUE] = 1,
|
|
53
|
+
[BINARY_SUBSCR] = 1,
|
|
54
|
+
#elif PY_VERSION_HEX < 0x030E0000
|
|
55
|
+
// == 3.13
|
|
56
|
+
[CALL_KW] = 1,
|
|
57
|
+
[CONVERT_VALUE] = 1,
|
|
58
|
+
[BINARY_SUBSCR] = 1,
|
|
59
|
+
#elif PY_VERSION_HEX < 0x030F0000
|
|
60
|
+
// == 3.14
|
|
61
|
+
[CALL_KW] = 1,
|
|
62
|
+
[CONVERT_VALUE] = 1,
|
|
63
|
+
#endif
|
|
64
|
+
[UNARY_NOT] = 1,
|
|
65
|
+
[SET_ADD] = 1,
|
|
66
|
+
[IS_OP] = 1,
|
|
67
|
+
[BINARY_OP] = 1,
|
|
68
|
+
[CALL] = 1,
|
|
69
|
+
[CALL_FUNCTION_EX] = 1,
|
|
70
|
+
};
|
|
71
|
+
#endif
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
#if PY_VERSION_HEX >= 0x030C0000
|
|
75
|
+
#if PY_VERSION_HEX < 0x030E0000
|
|
76
|
+
// Python 3.12 & 3.13
|
|
77
|
+
#define CH_STACK_COMPUTATION 1
|
|
78
|
+
#include "_mark_stacks.h"
|
|
79
|
+
#endif
|
|
80
|
+
#endif
|
|
81
|
+
|
|
82
|
+
|
|
34
83
|
static int
|
|
35
84
|
pyint_as_int(PyObject * pyint, int *pint)
|
|
36
85
|
{
|
|
@@ -92,8 +141,7 @@ CTracer_dealloc(CTracer *self)
|
|
|
92
141
|
Py_TYPE(self)->tp_free((PyObject*)self);
|
|
93
142
|
}
|
|
94
143
|
|
|
95
|
-
#if
|
|
96
|
-
// Python 3.12
|
|
144
|
+
#if CH_STACK_COMPUTATION
|
|
97
145
|
|
|
98
146
|
#define _CODE_STACK_CACHE_CAPACITY 64
|
|
99
147
|
static CodeAndStacks _CODE_STACK_CACHE[_CODE_STACK_CACHE_CAPACITY];
|
|
@@ -183,7 +231,7 @@ CTracer_push_module(CTracer *self, PyObject *args)
|
|
|
183
231
|
continue;
|
|
184
232
|
}
|
|
185
233
|
#if PY_VERSION_HEX >= 0x030C0000
|
|
186
|
-
// Python 3.12
|
|
234
|
+
// Python 3.12+
|
|
187
235
|
if (! _ch_TRACABLE_INSTRUCTIONS[opcode]) {
|
|
188
236
|
self->trace_all_opcodes = TRUE;
|
|
189
237
|
// sys.monitoring also will need to be reset, but that happens at the python layer above
|
|
@@ -293,12 +341,36 @@ static int
|
|
|
293
341
|
CTracer_handle_opcode(CTracer *self, PyCodeObject* pCode, int lasti)
|
|
294
342
|
{
|
|
295
343
|
|
|
296
|
-
#if
|
|
297
|
-
|
|
344
|
+
#if CH_STACK_COMPUTATION
|
|
345
|
+
if (!self->trace_all_opcodes) {
|
|
346
|
+
int64_t *stacks = _ch_get_stacks(pCode);
|
|
347
|
+
uint8_t at_enabled_position = stacks[lasti / 2] & 1;
|
|
348
|
+
if (!at_enabled_position) {
|
|
349
|
+
return RET_DISABLE_TRACING;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
#elif PY_VERSION_HEX >= 0x030E0000
|
|
353
|
+
// Python 3.14+
|
|
298
354
|
if (!self->trace_all_opcodes) {
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
355
|
+
PyBytesObject *code_bytes = PyCode_GetCode(pCode);
|
|
356
|
+
int opcode = code_bytes->ob_sval[lasti];
|
|
357
|
+
int last_opcode = 255;
|
|
358
|
+
uint8_t at_enabled_position = _ch_TRACABLE_INSTRUCTIONS[opcode];
|
|
359
|
+
uint8_t last_instr_enabled = 0;
|
|
360
|
+
if (lasti > 1) {
|
|
361
|
+
// TODO: This seems wrong; it doesn't account for extended args or cache entries;
|
|
362
|
+
last_opcode = code_bytes->ob_sval[lasti - 2];
|
|
363
|
+
last_instr_enabled = _ch_TRACABLE_INSTRUCTIONS[last_opcode];
|
|
364
|
+
}
|
|
365
|
+
// printf("lasti: %d, func: %s, opcode: %s, last opcode (%s) enabled: %d -> %d\n", lasti,
|
|
366
|
+
// PyUnicode_AsUTF8(pCode->co_name) ? PyUnicode_AsUTF8(pCode->co_name) : "<unknown>",
|
|
367
|
+
// _PyOpcode_OpName[opcode] ? _PyOpcode_OpName[opcode] : "<unknown>",
|
|
368
|
+
// _PyOpcode_OpName[last_opcode] ? _PyOpcode_OpName[last_opcode] : "<unknown>",
|
|
369
|
+
// last_instr_enabled,
|
|
370
|
+
// at_enabled_position
|
|
371
|
+
// );
|
|
372
|
+
|
|
373
|
+
if ((!last_instr_enabled) && (!at_enabled_position)) {
|
|
302
374
|
return RET_DISABLE_TRACING;
|
|
303
375
|
}
|
|
304
376
|
}
|
|
@@ -860,7 +932,12 @@ TraceSwapType = {
|
|
|
860
932
|
|
|
861
933
|
|
|
862
934
|
static PyObject **crosshair_tracers_stack_lookup(PyFrameObject *frame, int index) {
|
|
863
|
-
#if PY_VERSION_HEX >=
|
|
935
|
+
#if PY_VERSION_HEX >= 0x030E0000
|
|
936
|
+
// Python 3.14+
|
|
937
|
+
PyCodeObject* code = _PyFrame_GetCodeBorrow(frame);
|
|
938
|
+
_PyInterpreterFrame* interpreterFrame = frame->f_frame;
|
|
939
|
+
return &(interpreterFrame->stackpointer[index]);
|
|
940
|
+
#elif PY_VERSION_HEX >= 0x030C0000
|
|
864
941
|
// Python 3.12
|
|
865
942
|
PyCodeObject* code = _PyFrame_GetCodeBorrow(frame);
|
|
866
943
|
_PyInterpreterFrame* interpreterFrame = frame->f_frame;
|
|
@@ -900,7 +977,7 @@ static PyObject *crosshair_tracers_stack_read(PyObject *self, PyObject *args)
|
|
|
900
977
|
return NULL;
|
|
901
978
|
}
|
|
902
979
|
PyObject *ret = *retaddr;
|
|
903
|
-
if (ret == NULL) {
|
|
980
|
+
if (ret == NULL || ret == 1) { // starting in 3.14, we sometimes see a sentinal 0x1 pointer (?)
|
|
904
981
|
PyErr_SetString(PyExc_ValueError, "No stack value is present");
|
|
905
982
|
return NULL;
|
|
906
983
|
} else {
|
|
@@ -937,7 +1014,7 @@ static PyObject* crosshair_tracers_code_stack_depths(PyObject *self, PyObject *a
|
|
|
937
1014
|
return NULL;
|
|
938
1015
|
}
|
|
939
1016
|
|
|
940
|
-
#if
|
|
1017
|
+
#if CH_STACK_COMPUTATION
|
|
941
1018
|
// Python 3.12
|
|
942
1019
|
int64_t *stacks = _ch_get_stacks(code);
|
|
943
1020
|
int codelen = (int)Py_SIZE(code);
|
|
@@ -961,7 +1038,7 @@ static PyObject* crosshair_tracers_supported_opcodes(PyObject *self, PyObject *a
|
|
|
961
1038
|
return NULL;
|
|
962
1039
|
}
|
|
963
1040
|
#if PY_VERSION_HEX >= 0x030C0000
|
|
964
|
-
// Python 3.12
|
|
1041
|
+
// Python 3.12+
|
|
965
1042
|
PyObject* python_val = PyList_New(0);
|
|
966
1043
|
for (int i = 0; i < 256; i++) {
|
|
967
1044
|
if (_ch_TRACABLE_INSTRUCTIONS[i]) {
|
|
@@ -89,7 +89,10 @@ def _log_execution_stacks(fn, *a, **kw):
|
|
|
89
89
|
return stacks
|
|
90
90
|
|
|
91
91
|
|
|
92
|
-
@pytest.mark.skipif(
|
|
92
|
+
@pytest.mark.skipif(
|
|
93
|
+
sys.version_info < (3, 12) or sys.version_info >= (3, 14),
|
|
94
|
+
reason="stack depths only in 3.12 & 3.13",
|
|
95
|
+
)
|
|
93
96
|
def test_one_function_stack_depth():
|
|
94
97
|
_E = (TypeError, KeyboardInterrupt)
|
|
95
98
|
|
|
@@ -100,7 +103,10 @@ def test_one_function_stack_depth():
|
|
|
100
103
|
_log_execution_stacks(a, 4)
|
|
101
104
|
|
|
102
105
|
|
|
103
|
-
@pytest.mark.skipif(
|
|
106
|
+
@pytest.mark.skipif(
|
|
107
|
+
sys.version_info < (3, 12) or sys.version_info >= (3, 14),
|
|
108
|
+
reason="stack depths only in 3.12 & 3.13",
|
|
109
|
+
)
|
|
104
110
|
def test_stack_get():
|
|
105
111
|
def to_be_traced(x):
|
|
106
112
|
r = 8 - x
|
|
@@ -42,6 +42,10 @@ def test_popen_disallowed():
|
|
|
42
42
|
assert call([pyexec, __file__, "popen", "withwall"]) == 10
|
|
43
43
|
|
|
44
44
|
|
|
45
|
+
def test_chdir_allowed():
|
|
46
|
+
assert call([pyexec, __file__, "chdir", "withwall"]) == 0
|
|
47
|
+
|
|
48
|
+
|
|
45
49
|
@pytest.mark.skipif(sys.version_info < (3, 9), reason="Python 3.9+ required")
|
|
46
50
|
def test_popen_via_platform_allowed():
|
|
47
51
|
assert call([pyexec, __file__, "popen_via_platform", "withwall"]) == 0
|
|
@@ -58,6 +62,7 @@ _ACTIONS = {
|
|
|
58
62
|
"popen_via_platform": lambda: platform._syscmd_ver( # type: ignore
|
|
59
63
|
supported_platforms=(sys.platform,)
|
|
60
64
|
),
|
|
65
|
+
"chdir": lambda: os.chdir("."),
|
|
61
66
|
}
|
|
62
67
|
|
|
63
68
|
if __name__ == "__main__":
|
|
@@ -47,6 +47,7 @@ from crosshair.options import AnalysisKind
|
|
|
47
47
|
from crosshair.register_contract import get_contract
|
|
48
48
|
from crosshair.tracers import NoTracing
|
|
49
49
|
from crosshair.util import (
|
|
50
|
+
CrossHairInternal,
|
|
50
51
|
DynamicScopeVar,
|
|
51
52
|
EvalFriendlyReprContext,
|
|
52
53
|
IdKeyedDict,
|
|
@@ -485,6 +486,7 @@ class ConcreteConditionParser(ConditionParser):
|
|
|
485
486
|
method = cls.__dict__.get(method_name, None)
|
|
486
487
|
super_method_conditions = super_methods.get(method_name)
|
|
487
488
|
if super_method_conditions is not None:
|
|
489
|
+
# Re-type the super's `self` argument to be this class:
|
|
488
490
|
revised_sig = set_first_arg_type(super_method_conditions.sig, cls)
|
|
489
491
|
super_method_conditions = replace(
|
|
490
492
|
super_method_conditions, sig=revised_sig
|
|
@@ -511,17 +513,15 @@ class ConcreteConditionParser(ConditionParser):
|
|
|
511
513
|
final_pre = list(conditions.pre)
|
|
512
514
|
final_post = list(conditions.post)
|
|
513
515
|
if method_name in (
|
|
514
|
-
"__new__", #
|
|
516
|
+
"__new__", # a staticmethod, but not isinstance(staticmethod)
|
|
515
517
|
"__repr__", # is itself required for reporting problems with invariants.
|
|
516
518
|
# [set/del]attr can do anything; we can't resonably enforce invariants:
|
|
517
519
|
"__setattr__",
|
|
518
520
|
"__delattr__",
|
|
521
|
+
"__replace__", # Will raise an exception with most arbitrary **kwargs.
|
|
522
|
+
"__annotate__", # a staticmethod, but not isinstance(staticmethod)
|
|
519
523
|
):
|
|
520
524
|
pass
|
|
521
|
-
elif method_name == "__replace__":
|
|
522
|
-
# TODO: remove this case when fixed in 3.13
|
|
523
|
-
# see https://github.com/python/cpython/issues/114198
|
|
524
|
-
pass
|
|
525
525
|
elif method_name == "__del__":
|
|
526
526
|
final_pre.extend(inv)
|
|
527
527
|
elif method_name == "__init__":
|
|
@@ -133,41 +133,32 @@ def test_parse_sphinx_raises() -> None:
|
|
|
133
133
|
assert parse_sphinx_raises(sphinx_raises) == {LocallyDefiendException}
|
|
134
134
|
|
|
135
135
|
|
|
136
|
-
class
|
|
136
|
+
class TestPep316Parser:
|
|
137
137
|
def test_class_parse(self) -> None:
|
|
138
138
|
class_conditions = Pep316Parser().get_class_conditions(Foo)
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
set(class_conditions.methods.keys()), set(["isready", "__init__"])
|
|
145
|
-
)
|
|
139
|
+
assert set([c.expr_source for c in class_conditions.inv]) == {
|
|
140
|
+
"self.x >= 0",
|
|
141
|
+
"self.y >= 0",
|
|
142
|
+
}
|
|
143
|
+
assert {"isready", "__init__"} <= set(class_conditions.methods.keys())
|
|
146
144
|
method = class_conditions.methods["isready"]
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
145
|
+
assert set([c.expr_source for c in method.pre]) == {
|
|
146
|
+
"self.x >= 0",
|
|
147
|
+
"self.y >= 0",
|
|
148
|
+
}
|
|
151
149
|
startlineno = inspect.getsourcelines(Foo)[1]
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
("self.y >= 0", startlineno + 12),
|
|
158
|
-
("__return__ == (self.x == 0)", startlineno + 24),
|
|
159
|
-
]
|
|
160
|
-
),
|
|
161
|
-
)
|
|
150
|
+
assert set([(c.expr_source, c.line) for c in method.post]) == {
|
|
151
|
+
("self.x >= 0", startlineno + 7),
|
|
152
|
+
("self.y >= 0", startlineno + 12),
|
|
153
|
+
("__return__ == (self.x == 0)", startlineno + 24),
|
|
154
|
+
}
|
|
162
155
|
|
|
163
156
|
def test_single_line_condition(self) -> None:
|
|
164
157
|
conditions = Pep316Parser().get_fn_conditions(
|
|
165
158
|
FunctionInfo.from_fn(single_line_condition)
|
|
166
159
|
)
|
|
167
160
|
assert conditions is not None
|
|
168
|
-
|
|
169
|
-
set([c.expr_source for c in conditions.post]), set(["__return__ >= x"])
|
|
170
|
-
)
|
|
161
|
+
assert set([c.expr_source for c in conditions.post]) == {"__return__ >= x"}
|
|
171
162
|
|
|
172
163
|
def test_implies_condition(self):
|
|
173
164
|
conditions = Pep316Parser().get_fn_conditions(
|
|
@@ -182,37 +173,35 @@ class Pep316ParserTest(unittest.TestCase):
|
|
|
182
173
|
FunctionInfo.from_fn(locally_defined_raises_condition)
|
|
183
174
|
)
|
|
184
175
|
assert conditions is not None
|
|
185
|
-
|
|
186
|
-
|
|
176
|
+
assert [] == list(conditions.syntax_messages())
|
|
177
|
+
assert set([LocallyDefiendException]) == conditions.raises
|
|
187
178
|
|
|
188
179
|
def test_tricky_raises_condition(self) -> None:
|
|
189
180
|
conditions = Pep316Parser().get_fn_conditions(
|
|
190
181
|
FunctionInfo.from_fn(tricky_raises_condition)
|
|
191
182
|
)
|
|
192
183
|
assert conditions is not None
|
|
193
|
-
|
|
194
|
-
|
|
184
|
+
assert [] == list(conditions.syntax_messages())
|
|
185
|
+
assert conditions.raises == {KeyError, json.JSONDecodeError}
|
|
195
186
|
|
|
196
187
|
def test_invariant_is_inherited(self) -> None:
|
|
197
188
|
class_conditions = Pep316Parser().get_class_conditions(SubClassExample)
|
|
198
|
-
|
|
189
|
+
assert set(class_conditions.methods.keys()) == {"foo", "__init__"}
|
|
199
190
|
method = class_conditions.methods["foo"]
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
set([c.expr_source for c in method.post]), set(["True", "False"])
|
|
205
|
-
)
|
|
191
|
+
assert len(method.pre) == 1
|
|
192
|
+
assert set([c.expr_source for c in method.pre]) == {"True"}
|
|
193
|
+
assert len(method.post) == 2
|
|
194
|
+
assert set([c.expr_source for c in method.post]) == {"True", "False"}
|
|
206
195
|
|
|
207
196
|
def test_invariant_applies_to_init(self) -> None:
|
|
208
197
|
class_conditions = Pep316Parser().get_class_conditions(BaseClassExample)
|
|
209
|
-
|
|
198
|
+
assert set(class_conditions.methods.keys()) == {"__init__", "foo"}
|
|
210
199
|
|
|
211
200
|
@pytest.mark.skipif(
|
|
212
201
|
sys.version_info >= (3, 13), reason="builtins have signatures in 3.13"
|
|
213
202
|
)
|
|
214
203
|
def test_builtin_conditions_are_null(self) -> None:
|
|
215
|
-
|
|
204
|
+
assert Pep316Parser().get_fn_conditions(FunctionInfo.from_fn(zip)) is None
|
|
216
205
|
|
|
217
206
|
def test_conditions_with_closure_references_and_string_type(self) -> None:
|
|
218
207
|
# This is a function that refers to something in its closure.
|
|
@@ -228,7 +217,7 @@ class Pep316ParserTest(unittest.TestCase):
|
|
|
228
217
|
|
|
229
218
|
|
|
230
219
|
@pytest.mark.skipif(not icontract, reason="icontract is not installed")
|
|
231
|
-
class
|
|
220
|
+
class TestIcontractParser:
|
|
232
221
|
def test_simple_parse(self):
|
|
233
222
|
@icontract.require(lambda ls: len(ls) > 0)
|
|
234
223
|
@icontract.ensure(lambda ls, result: min(ls) <= result <= max(ls))
|
|
@@ -237,17 +226,17 @@ class IcontractParserTest(unittest.TestCase):
|
|
|
237
226
|
|
|
238
227
|
conditions = IcontractParser().get_fn_conditions(FunctionInfo.from_fn(avg))
|
|
239
228
|
assert conditions is not None
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
229
|
+
assert len(conditions.pre) == 1
|
|
230
|
+
assert len(conditions.post) == 1
|
|
231
|
+
assert conditions.pre[0].evaluate({"ls": []}) is False
|
|
243
232
|
post_args = {
|
|
244
233
|
"ls": [42, 43],
|
|
245
234
|
"__old__": AttributeHolder({}),
|
|
246
235
|
"__return__": 40,
|
|
247
236
|
"_": 40,
|
|
248
237
|
}
|
|
249
|
-
|
|
250
|
-
|
|
238
|
+
assert conditions.post[0].evaluate(post_args) is False
|
|
239
|
+
assert len(post_args) == 4 # (check args are unmodified)
|
|
251
240
|
|
|
252
241
|
def test_simple_class_parse(self):
|
|
253
242
|
@icontract.invariant(lambda self: self.i >= 0)
|
|
@@ -268,14 +257,14 @@ class IcontractParserTest(unittest.TestCase):
|
|
|
268
257
|
self.i -= 1
|
|
269
258
|
|
|
270
259
|
conditions = IcontractParser().get_class_conditions(Counter)
|
|
271
|
-
|
|
260
|
+
assert len(conditions.inv) == 1
|
|
272
261
|
|
|
273
262
|
decr_conditions = conditions.methods["decr"]
|
|
274
|
-
|
|
263
|
+
assert len(decr_conditions.pre) == 2
|
|
275
264
|
# decr() precondition: count > 0
|
|
276
|
-
|
|
265
|
+
assert decr_conditions.pre[0].evaluate({"self": Counter()}) is False
|
|
277
266
|
# invariant: count >= 0
|
|
278
|
-
|
|
267
|
+
assert decr_conditions.pre[1].evaluate({"self": Counter()}) is True
|
|
279
268
|
|
|
280
269
|
class TruncatedCounter(Counter):
|
|
281
270
|
@icontract.require(
|
|
@@ -287,21 +276,19 @@ class IcontractParserTest(unittest.TestCase):
|
|
|
287
276
|
|
|
288
277
|
conditions = IcontractParser().get_class_conditions(TruncatedCounter)
|
|
289
278
|
decr_conditions = conditions.methods["decr"]
|
|
290
|
-
|
|
291
|
-
decr_conditions.pre[0].evaluate({"self": TruncatedCounter()}), True
|
|
292
|
-
)
|
|
279
|
+
assert decr_conditions.pre[0].evaluate({"self": TruncatedCounter()}) is True
|
|
293
280
|
|
|
294
281
|
# check the weakened precondition
|
|
295
|
-
|
|
296
|
-
len(decr_conditions.pre)
|
|
282
|
+
assert (
|
|
283
|
+
len(decr_conditions.pre) == 2
|
|
297
284
|
) # one for the invariant, one for the disjunction
|
|
298
285
|
ctr = TruncatedCounter()
|
|
299
286
|
ctr.i = 1
|
|
300
|
-
|
|
301
|
-
|
|
287
|
+
assert decr_conditions.pre[1].evaluate({"self": ctr}) is True
|
|
288
|
+
assert decr_conditions.pre[0].evaluate({"self": ctr}) is True
|
|
302
289
|
ctr.i = 0
|
|
303
|
-
|
|
304
|
-
|
|
290
|
+
assert decr_conditions.pre[1].evaluate({"self": ctr}) is True
|
|
291
|
+
assert decr_conditions.pre[0].evaluate({"self": ctr}) is True
|
|
305
292
|
|
|
306
293
|
|
|
307
294
|
@pytest.mark.skipif(not deal, reason="deal is not installed")
|
|
@@ -395,20 +382,20 @@ def fn_with_docstring_comments_and_assert(numbers: List[int]) -> None:
|
|
|
395
382
|
assert min(numbers) > smallest
|
|
396
383
|
|
|
397
384
|
|
|
398
|
-
class
|
|
385
|
+
class TestAssertsParser:
|
|
399
386
|
def tests_simple_parse(self) -> None:
|
|
400
387
|
conditions = AssertsParser().get_fn_conditions(
|
|
401
388
|
FunctionInfo.from_fn(avg_with_asserts)
|
|
402
389
|
)
|
|
403
390
|
assert conditions is not None
|
|
404
391
|
conditions.fn([])
|
|
405
|
-
|
|
406
|
-
with
|
|
392
|
+
assert conditions.fn([2.2]) == 2.2
|
|
393
|
+
with pytest.raises(AssertionError):
|
|
407
394
|
conditions.fn([9.2, 17.8])
|
|
408
395
|
|
|
409
396
|
def tests_empty_parse(self) -> None:
|
|
410
397
|
conditions = AssertsParser().get_fn_conditions(FunctionInfo.from_fn(debug))
|
|
411
|
-
|
|
398
|
+
assert conditions is None
|
|
412
399
|
|
|
413
400
|
def tests_extra_ast_nodes(self) -> None:
|
|
414
401
|
conditions = AssertsParser().get_fn_conditions(
|
|
@@ -422,10 +409,10 @@ class AssertsParserTest(unittest.TestCase):
|
|
|
422
409
|
# normal, passing case:
|
|
423
410
|
nums = [3, 1, 2]
|
|
424
411
|
conditions.fn(nums)
|
|
425
|
-
|
|
412
|
+
assert nums == [3, 2]
|
|
426
413
|
|
|
427
414
|
# Failing case (duplicate minimum values):
|
|
428
|
-
with
|
|
415
|
+
with pytest.raises(AssertionError):
|
|
429
416
|
nums = [3, 1, 1, 2]
|
|
430
417
|
conditions.fn(nums)
|
|
431
418
|
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
import sys
|
|
2
|
+
|
|
3
|
+
if sys.version_info >= (3, 14):
|
|
4
|
+
from copy import _atomic_types
|
|
5
|
+
else:
|
|
6
|
+
from copy import _deepcopy_atomic # type: ignore
|
|
2
7
|
from copy import _deepcopy_dict # type: ignore
|
|
3
8
|
from copy import _deepcopy_dispatch # type: ignore
|
|
4
9
|
from copy import _deepcopy_list # type: ignore
|
|
@@ -9,7 +14,7 @@ from copy import Error
|
|
|
9
14
|
from copyreg import dispatch_table # type: ignore
|
|
10
15
|
from enum import Enum
|
|
11
16
|
from types import MappingProxyType
|
|
12
|
-
from typing import Any, Dict, Tuple
|
|
17
|
+
from typing import Any, Callable, Dict, Tuple
|
|
13
18
|
|
|
14
19
|
from crosshair.tracers import ResumedTracing
|
|
15
20
|
from crosshair.util import (
|
|
@@ -85,17 +90,28 @@ def deepcopyext(obj: object, mode: CopyMode, memo: Dict) -> Any:
|
|
|
85
90
|
return cpy
|
|
86
91
|
|
|
87
92
|
|
|
93
|
+
if sys.version_info >= (3, 14):
|
|
94
|
+
|
|
95
|
+
def lookup_dispatch(cls: type) -> Callable:
|
|
96
|
+
if cls in _atomic_types:
|
|
97
|
+
return lambda obj, memo: obj
|
|
98
|
+
return _deepcopy_dispatch.get(cls)
|
|
99
|
+
|
|
100
|
+
else:
|
|
101
|
+
|
|
102
|
+
def lookup_dispatch(cls: type) -> Callable:
|
|
103
|
+
return _deepcopy_dispatch.get(cls)
|
|
104
|
+
|
|
105
|
+
|
|
88
106
|
def _deepconstruct(obj: object, mode: CopyMode, memo: Dict):
|
|
89
107
|
cls = type(obj)
|
|
90
108
|
|
|
91
109
|
def subdeepcopy(obj: object, memo: Dict):
|
|
92
110
|
return deepcopyext(obj, mode, memo)
|
|
93
111
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
if creator
|
|
97
|
-
return obj
|
|
98
|
-
elif creator in (_deepcopy_dict, _deepcopy_list, _deepcopy_tuple):
|
|
112
|
+
creator = lookup_dispatch(cls)
|
|
113
|
+
if creator is not None:
|
|
114
|
+
if creator in (_deepcopy_dict, _deepcopy_list, _deepcopy_tuple):
|
|
99
115
|
return creator(obj, memo, deepcopy=subdeepcopy)
|
|
100
116
|
else:
|
|
101
117
|
# TODO: We loose subdeepcopy in this case - won't
|
|
@@ -7,7 +7,7 @@ import pytest
|
|
|
7
7
|
from crosshair.copyext import CopyMode, deepcopyext
|
|
8
8
|
from crosshair.core_and_libs import proxy_for_type, standalone_statespace
|
|
9
9
|
from crosshair.libimpl.builtinslib import SymbolicInt
|
|
10
|
-
from crosshair.tracers import NoTracing
|
|
10
|
+
from crosshair.tracers import NoTracing
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
def test_deepcopyext_best_effort():
|
|
@@ -33,6 +33,16 @@ def test_deepcopyext_symbolic_set():
|
|
|
33
33
|
deepcopyext(s, CopyMode.REALIZE, {})
|
|
34
34
|
|
|
35
35
|
|
|
36
|
+
def test_deepcopyext_realize_simple(space):
|
|
37
|
+
x = SymbolicInt("x")
|
|
38
|
+
input = (x,)
|
|
39
|
+
output = deepcopyext(input, CopyMode.REALIZE, {})
|
|
40
|
+
assert input is not output
|
|
41
|
+
assert input[0] is not output[0]
|
|
42
|
+
assert type(input[0]) is SymbolicInt
|
|
43
|
+
assert type(output[0]) is int
|
|
44
|
+
|
|
45
|
+
|
|
36
46
|
def test_deepcopyext_realize(space):
|
|
37
47
|
x = SymbolicInt("x")
|
|
38
48
|
lock = RLock()
|