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.
- _crosshair_tracers.cpython-312-darwin.so +0 -0
- crosshair/__init__.py +42 -0
- crosshair/__main__.py +8 -0
- crosshair/_mark_stacks.h +790 -0
- crosshair/_preliminaries_test.py +18 -0
- crosshair/_tracers.h +94 -0
- crosshair/_tracers_pycompat.h +522 -0
- crosshair/_tracers_test.py +138 -0
- crosshair/abcstring.py +245 -0
- crosshair/auditwall.py +190 -0
- crosshair/auditwall_test.py +77 -0
- crosshair/codeconfig.py +113 -0
- crosshair/codeconfig_test.py +117 -0
- crosshair/condition_parser.py +1237 -0
- crosshair/condition_parser_test.py +497 -0
- crosshair/conftest.py +30 -0
- crosshair/copyext.py +155 -0
- crosshair/copyext_test.py +84 -0
- crosshair/core.py +1763 -0
- crosshair/core_and_libs.py +149 -0
- crosshair/core_regestered_types_test.py +82 -0
- crosshair/core_test.py +1316 -0
- crosshair/diff_behavior.py +314 -0
- crosshair/diff_behavior_test.py +261 -0
- crosshair/dynamic_typing.py +346 -0
- crosshair/dynamic_typing_test.py +210 -0
- crosshair/enforce.py +282 -0
- crosshair/enforce_test.py +182 -0
- crosshair/examples/PEP316/__init__.py +1 -0
- crosshair/examples/PEP316/bugs_detected/__init__.py +0 -0
- crosshair/examples/PEP316/bugs_detected/getattr_magic.py +16 -0
- crosshair/examples/PEP316/bugs_detected/hash_consistent_with_equals.py +31 -0
- crosshair/examples/PEP316/bugs_detected/shopping_cart.py +24 -0
- crosshair/examples/PEP316/bugs_detected/showcase.py +39 -0
- crosshair/examples/PEP316/correct_code/__init__.py +0 -0
- crosshair/examples/PEP316/correct_code/arith.py +60 -0
- crosshair/examples/PEP316/correct_code/chess.py +77 -0
- crosshair/examples/PEP316/correct_code/nesting_inference.py +17 -0
- crosshair/examples/PEP316/correct_code/numpy_examples.py +132 -0
- crosshair/examples/PEP316/correct_code/rolling_average.py +35 -0
- crosshair/examples/PEP316/correct_code/showcase.py +104 -0
- crosshair/examples/__init__.py +0 -0
- crosshair/examples/check_examples_test.py +146 -0
- crosshair/examples/deal/__init__.py +1 -0
- crosshair/examples/icontract/__init__.py +1 -0
- crosshair/examples/icontract/bugs_detected/__init__.py +0 -0
- crosshair/examples/icontract/bugs_detected/showcase.py +41 -0
- crosshair/examples/icontract/bugs_detected/wrong_sign.py +8 -0
- crosshair/examples/icontract/correct_code/__init__.py +0 -0
- crosshair/examples/icontract/correct_code/arith.py +51 -0
- crosshair/examples/icontract/correct_code/showcase.py +94 -0
- crosshair/fnutil.py +391 -0
- crosshair/fnutil_test.py +75 -0
- crosshair/fuzz_core_test.py +516 -0
- crosshair/libimpl/__init__.py +0 -0
- crosshair/libimpl/arraylib.py +161 -0
- crosshair/libimpl/binascii_ch_test.py +30 -0
- crosshair/libimpl/binascii_test.py +67 -0
- crosshair/libimpl/binasciilib.py +150 -0
- crosshair/libimpl/bisectlib_test.py +23 -0
- crosshair/libimpl/builtinslib.py +5228 -0
- crosshair/libimpl/builtinslib_ch_test.py +1191 -0
- crosshair/libimpl/builtinslib_test.py +3735 -0
- crosshair/libimpl/codecslib.py +86 -0
- crosshair/libimpl/codecslib_test.py +86 -0
- crosshair/libimpl/collectionslib.py +264 -0
- crosshair/libimpl/collectionslib_ch_test.py +252 -0
- crosshair/libimpl/collectionslib_test.py +332 -0
- crosshair/libimpl/copylib.py +23 -0
- crosshair/libimpl/copylib_test.py +18 -0
- crosshair/libimpl/datetimelib.py +2559 -0
- crosshair/libimpl/datetimelib_ch_test.py +354 -0
- crosshair/libimpl/datetimelib_test.py +112 -0
- crosshair/libimpl/decimallib.py +5257 -0
- crosshair/libimpl/decimallib_ch_test.py +78 -0
- crosshair/libimpl/decimallib_test.py +76 -0
- crosshair/libimpl/encodings/__init__.py +23 -0
- crosshair/libimpl/encodings/_encutil.py +187 -0
- crosshair/libimpl/encodings/ascii.py +44 -0
- crosshair/libimpl/encodings/latin_1.py +40 -0
- crosshair/libimpl/encodings/utf_8.py +93 -0
- crosshair/libimpl/encodings_ch_test.py +83 -0
- crosshair/libimpl/fractionlib.py +16 -0
- crosshair/libimpl/fractionlib_test.py +80 -0
- crosshair/libimpl/functoolslib.py +34 -0
- crosshair/libimpl/functoolslib_test.py +56 -0
- crosshair/libimpl/hashliblib.py +30 -0
- crosshair/libimpl/hashliblib_test.py +18 -0
- crosshair/libimpl/heapqlib.py +47 -0
- crosshair/libimpl/heapqlib_test.py +21 -0
- crosshair/libimpl/importliblib.py +18 -0
- crosshair/libimpl/importliblib_test.py +38 -0
- crosshair/libimpl/iolib.py +216 -0
- crosshair/libimpl/iolib_ch_test.py +128 -0
- crosshair/libimpl/iolib_test.py +19 -0
- crosshair/libimpl/ipaddresslib.py +8 -0
- crosshair/libimpl/itertoolslib.py +44 -0
- crosshair/libimpl/itertoolslib_test.py +44 -0
- crosshair/libimpl/jsonlib.py +984 -0
- crosshair/libimpl/jsonlib_ch_test.py +42 -0
- crosshair/libimpl/jsonlib_test.py +51 -0
- crosshair/libimpl/mathlib.py +179 -0
- crosshair/libimpl/mathlib_ch_test.py +44 -0
- crosshair/libimpl/mathlib_test.py +67 -0
- crosshair/libimpl/oslib.py +7 -0
- crosshair/libimpl/pathliblib_test.py +10 -0
- crosshair/libimpl/randomlib.py +178 -0
- crosshair/libimpl/randomlib_test.py +120 -0
- crosshair/libimpl/relib.py +846 -0
- crosshair/libimpl/relib_ch_test.py +169 -0
- crosshair/libimpl/relib_test.py +493 -0
- crosshair/libimpl/timelib.py +72 -0
- crosshair/libimpl/timelib_test.py +82 -0
- crosshair/libimpl/typeslib.py +15 -0
- crosshair/libimpl/typeslib_test.py +36 -0
- crosshair/libimpl/unicodedatalib.py +75 -0
- crosshair/libimpl/unicodedatalib_test.py +42 -0
- crosshair/libimpl/urlliblib.py +23 -0
- crosshair/libimpl/urlliblib_test.py +19 -0
- crosshair/libimpl/weakreflib.py +13 -0
- crosshair/libimpl/weakreflib_test.py +69 -0
- crosshair/libimpl/zliblib.py +15 -0
- crosshair/libimpl/zliblib_test.py +13 -0
- crosshair/lsp_server.py +261 -0
- crosshair/lsp_server_test.py +30 -0
- crosshair/main.py +973 -0
- crosshair/main_test.py +543 -0
- crosshair/objectproxy.py +376 -0
- crosshair/objectproxy_test.py +41 -0
- crosshair/opcode_intercept.py +601 -0
- crosshair/opcode_intercept_test.py +304 -0
- crosshair/options.py +218 -0
- crosshair/options_test.py +10 -0
- crosshair/patch_equivalence_test.py +75 -0
- crosshair/path_cover.py +209 -0
- crosshair/path_cover_test.py +138 -0
- crosshair/path_search.py +161 -0
- crosshair/path_search_test.py +52 -0
- crosshair/pathing_oracle.py +271 -0
- crosshair/pathing_oracle_test.py +21 -0
- crosshair/pure_importer.py +27 -0
- crosshair/pure_importer_test.py +16 -0
- crosshair/py.typed +0 -0
- crosshair/register_contract.py +273 -0
- crosshair/register_contract_test.py +190 -0
- crosshair/simplestructs.py +1165 -0
- crosshair/simplestructs_test.py +283 -0
- crosshair/smtlib.py +24 -0
- crosshair/smtlib_test.py +14 -0
- crosshair/statespace.py +1199 -0
- crosshair/statespace_test.py +108 -0
- crosshair/stubs_parser.py +352 -0
- crosshair/stubs_parser_test.py +43 -0
- crosshair/test_util.py +329 -0
- crosshair/test_util_test.py +26 -0
- crosshair/tools/__init__.py +0 -0
- crosshair/tools/check_help_in_doc.py +264 -0
- crosshair/tools/check_init_and_setup_coincide.py +119 -0
- crosshair/tools/generate_demo_table.py +127 -0
- crosshair/tracers.py +544 -0
- crosshair/tracers_test.py +154 -0
- crosshair/type_repo.py +151 -0
- crosshair/unicode_categories.py +589 -0
- crosshair/unicode_categories_test.py +27 -0
- crosshair/util.py +741 -0
- crosshair/util_test.py +173 -0
- crosshair/watcher.py +307 -0
- crosshair/watcher_test.py +107 -0
- crosshair/z3util.py +76 -0
- crosshair/z3util_test.py +11 -0
- crosshair_tool-0.0.99.dist-info/METADATA +144 -0
- crosshair_tool-0.0.99.dist-info/RECORD +176 -0
- crosshair_tool-0.0.99.dist-info/WHEEL +6 -0
- crosshair_tool-0.0.99.dist-info/entry_points.txt +3 -0
- crosshair_tool-0.0.99.dist-info/licenses/LICENSE +93 -0
- crosshair_tool-0.0.99.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import operator
|
|
2
|
+
import sys
|
|
3
|
+
from decimal import BasicContext, Decimal, ExtendedContext, localcontext
|
|
4
|
+
from typing import Union
|
|
5
|
+
|
|
6
|
+
import pytest # type: ignore
|
|
7
|
+
|
|
8
|
+
from crosshair.core_and_libs import MessageType, analyze_function, run_checkables
|
|
9
|
+
from crosshair.test_util import ResultComparison, compare_results, compare_returns
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _binary_op_under_context(ctx, op):
|
|
13
|
+
def run_op(d1, d2):
|
|
14
|
+
with localcontext(ctx):
|
|
15
|
+
return op(d1, d2)
|
|
16
|
+
|
|
17
|
+
return run_op
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def check_division(
|
|
21
|
+
decimal1: Decimal, decimal2: Union[Decimal, int, float]
|
|
22
|
+
) -> ResultComparison:
|
|
23
|
+
"""post: _"""
|
|
24
|
+
return compare_returns(operator.truediv, decimal1, decimal2)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def check_pow(decimal1: Decimal, decimal2: Decimal) -> ResultComparison:
|
|
28
|
+
"""post: _"""
|
|
29
|
+
return compare_results(operator.pow, decimal1, decimal2)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def check_extended_context(
|
|
33
|
+
decimal1: Decimal, decimal2: Union[Decimal, int, float]
|
|
34
|
+
) -> ResultComparison:
|
|
35
|
+
"""post: _"""
|
|
36
|
+
return compare_results(
|
|
37
|
+
_binary_op_under_context(ExtendedContext, operator.truediv), decimal1, decimal2
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def check_basic_context(
|
|
42
|
+
decimal1: Decimal, decimal2: Union[Decimal, int, float]
|
|
43
|
+
) -> ResultComparison:
|
|
44
|
+
"""post: _"""
|
|
45
|
+
return compare_results(
|
|
46
|
+
_binary_op_under_context(BasicContext, operator.truediv), decimal1, decimal2
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def check_div_using_context_method(
|
|
51
|
+
decimal1: Decimal, decimal2: Union[Decimal, int, float]
|
|
52
|
+
) -> ResultComparison:
|
|
53
|
+
"""post: _"""
|
|
54
|
+
return compare_returns(BasicContext.divide, decimal1, decimal2)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def check_div_using_context_parameter(
|
|
58
|
+
decimal1: Decimal, decimal2: Union[Decimal, int, float]
|
|
59
|
+
) -> ResultComparison:
|
|
60
|
+
"""post: _"""
|
|
61
|
+
return compare_returns(
|
|
62
|
+
lambda d1, d2: d1.divide(d2, context=BasicContext), decimal1, decimal2
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def check_create_decimal_from_float(float_number: float):
|
|
67
|
+
"""post: _"""
|
|
68
|
+
return compare_results(BasicContext.create_decimal_from_float, float_number)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# This is the only real test definition.
|
|
72
|
+
# It runs crosshair on each of the "check" functions defined above.
|
|
73
|
+
@pytest.mark.parametrize("fn_name", [fn for fn in dir() if fn.startswith("check_")])
|
|
74
|
+
def test_builtin(fn_name: str) -> None:
|
|
75
|
+
this_module = sys.modules[__name__]
|
|
76
|
+
messages = run_checkables(analyze_function(getattr(this_module, fn_name)))
|
|
77
|
+
errors = [m for m in messages if m.state > MessageType.PRE_UNSAT]
|
|
78
|
+
assert errors == []
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
from decimal import (
|
|
2
|
+
Decimal,
|
|
3
|
+
DivisionByZero,
|
|
4
|
+
ExtendedContext,
|
|
5
|
+
InvalidOperation,
|
|
6
|
+
localcontext,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
|
|
11
|
+
from crosshair.core import proxy_for_type, standalone_statespace
|
|
12
|
+
from crosshair.libimpl.decimallib import Decimal as PyDecimal
|
|
13
|
+
from crosshair.tracers import NoTracing
|
|
14
|
+
from crosshair.util import debug
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def test_mixed_decimal_addition() -> None:
|
|
18
|
+
d1 = Decimal("1.05")
|
|
19
|
+
with standalone_statespace:
|
|
20
|
+
d2 = proxy_for_type(Decimal, "d2")
|
|
21
|
+
debug("type(d2)", type(d2))
|
|
22
|
+
d1 + d2
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_external_decimal_context() -> None:
|
|
26
|
+
with localcontext(ExtendedContext):
|
|
27
|
+
Decimal("43.4") / 0 # does not raise
|
|
28
|
+
with pytest.raises(DivisionByZero):
|
|
29
|
+
Decimal("43.4") / 0
|
|
30
|
+
with pytest.raises(InvalidOperation):
|
|
31
|
+
Decimal("0") / 0
|
|
32
|
+
with pytest.raises(DivisionByZero):
|
|
33
|
+
PyDecimal("43.4") / 0
|
|
34
|
+
with standalone_statespace as space:
|
|
35
|
+
with NoTracing():
|
|
36
|
+
d1 = proxy_for_type(Decimal, "d1")
|
|
37
|
+
if d1 == 0:
|
|
38
|
+
d1 += 1
|
|
39
|
+
with pytest.raises(DivisionByZero):
|
|
40
|
+
d1 / 0
|
|
41
|
+
with pytest.raises(InvalidOperation):
|
|
42
|
+
(d1 - d1) / 0
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_context_method_on_symbolic():
|
|
46
|
+
with standalone_statespace:
|
|
47
|
+
ExtendedContext.exp(proxy_for_type(Decimal, "d"))
|
|
48
|
+
ExtendedContext.divide_int(Decimal(12), proxy_for_type(Decimal, "d"))
|
|
49
|
+
ExtendedContext.divide_int(Decimal(12), Decimal(2))
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_precision():
|
|
53
|
+
"""post: _"""
|
|
54
|
+
d1, d2 = Decimal("3.4445"), Decimal("1.0023")
|
|
55
|
+
expected = Decimal("4.45")
|
|
56
|
+
assert d1 + d2 != expected
|
|
57
|
+
with standalone_statespace:
|
|
58
|
+
with localcontext() as ctx:
|
|
59
|
+
ctx.prec = 3
|
|
60
|
+
assert d1 + d2 == expected
|
|
61
|
+
with localcontext() as ctx:
|
|
62
|
+
ctx.prec = 1
|
|
63
|
+
assert +expected == Decimal("4")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
# Still working on this! (rn, issue with z3 int exponent vars becoming reals)
|
|
67
|
+
# def test_decimal_end_to_end():
|
|
68
|
+
# def add_tax(price: Decimal):
|
|
69
|
+
# """post: _ != Decimal('1.05')"""
|
|
70
|
+
# ctx = ExtendedContext.copy()
|
|
71
|
+
# ctx.prec = 3
|
|
72
|
+
# with localcontext(ctx):
|
|
73
|
+
# return price + Decimal("0.05")
|
|
74
|
+
# # return price * Decimal("1.05")
|
|
75
|
+
|
|
76
|
+
# check_states(add_tax, POST_FAIL)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import codecs
|
|
2
|
+
import importlib
|
|
3
|
+
from encodings import normalize_encoding
|
|
4
|
+
from encodings.aliases import aliases
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def codec_search(encoding: str) -> Optional[codecs.CodecInfo]:
|
|
9
|
+
enc_prefix = "crosshair_"
|
|
10
|
+
if not encoding.startswith(enc_prefix):
|
|
11
|
+
return None
|
|
12
|
+
|
|
13
|
+
encoding = normalize_encoding(encoding[len(enc_prefix) :])
|
|
14
|
+
encoding = aliases.get(encoding, encoding)
|
|
15
|
+
|
|
16
|
+
try:
|
|
17
|
+
module = importlib.import_module(f"crosshair.libimpl.encodings.{encoding}")
|
|
18
|
+
except ImportError:
|
|
19
|
+
pass
|
|
20
|
+
else:
|
|
21
|
+
if callable(getattr(module, "getregentry", None)):
|
|
22
|
+
return module.getregentry() # type: ignore
|
|
23
|
+
return None
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import codecs
|
|
2
|
+
import sys
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import List, Optional, Tuple, Type, Union
|
|
5
|
+
|
|
6
|
+
if sys.version_info >= (3, 12):
|
|
7
|
+
from collections.abc import Buffer
|
|
8
|
+
else:
|
|
9
|
+
from collections.abc import ByteString as Buffer
|
|
10
|
+
|
|
11
|
+
from crosshair.core import realize
|
|
12
|
+
from crosshair.libimpl.builtinslib import AnySymbolicStr, SymbolicBytes
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ChunkError:
|
|
16
|
+
def reason(self) -> str:
|
|
17
|
+
raise NotImplementedError
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class UnexpectedEndError(ChunkError):
|
|
21
|
+
def reason(self) -> str:
|
|
22
|
+
return "unexpected end of data"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class MidChunkError(ChunkError):
|
|
27
|
+
_reason: str
|
|
28
|
+
|
|
29
|
+
# _errlen: int = 1
|
|
30
|
+
def reason(self) -> str:
|
|
31
|
+
return self._reason
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class _UnicodeDecodeError(UnicodeDecodeError):
|
|
35
|
+
def __init__(self, enc, byts, start, end, reason):
|
|
36
|
+
UnicodeDecodeError.__init__(self, enc, b"", start, end, reason)
|
|
37
|
+
self.object = byts
|
|
38
|
+
|
|
39
|
+
def __ch_deep_realize__(self, memo) -> object:
|
|
40
|
+
enc, obj, reason = self.encoding, self.object, self.reason
|
|
41
|
+
start, end = self.start, self.end
|
|
42
|
+
return UnicodeDecodeError(
|
|
43
|
+
realize(enc), realize(obj), realize(start), realize(end), realize(reason)
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
def __repr__(self):
|
|
47
|
+
enc, obj, reason = self.encoding, self.object, self.reason
|
|
48
|
+
start, end = self.start, self.end
|
|
49
|
+
return f"UnicodeDecodeError({enc!r}, {obj!r}, {start!r}, {end!r}, {reason!r})"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class StemEncoder:
|
|
53
|
+
|
|
54
|
+
encoding_name: str
|
|
55
|
+
|
|
56
|
+
@classmethod
|
|
57
|
+
def _encode_chunk(
|
|
58
|
+
cls, intput: str, start: int
|
|
59
|
+
) -> Tuple[Union[bytes, SymbolicBytes], int, Optional[ChunkError]]:
|
|
60
|
+
raise NotImplementedError
|
|
61
|
+
|
|
62
|
+
@classmethod
|
|
63
|
+
def _decode_chunk(
|
|
64
|
+
cls, intput: bytes, start: int
|
|
65
|
+
) -> Tuple[Union[str, AnySymbolicStr], int, Optional[ChunkError]]:
|
|
66
|
+
raise NotImplementedError
|
|
67
|
+
|
|
68
|
+
@classmethod
|
|
69
|
+
def encode(
|
|
70
|
+
cls, input: str, errors: str = "strict"
|
|
71
|
+
) -> Tuple[Union[bytes, SymbolicBytes], int]:
|
|
72
|
+
if not (isinstance(input, str) and isinstance(errors, str)):
|
|
73
|
+
raise TypeError
|
|
74
|
+
parts: List[bytes] = []
|
|
75
|
+
idx = 0
|
|
76
|
+
inputlen = len(input)
|
|
77
|
+
while idx < inputlen:
|
|
78
|
+
out, idx, err = cls._encode_chunk(input, idx)
|
|
79
|
+
parts.append(out) # type: ignore
|
|
80
|
+
if err is not None:
|
|
81
|
+
realized_input = realize(input) # TODO: avoid realization here.
|
|
82
|
+
# (which possibly requires implementing the error handlers in python)
|
|
83
|
+
exc = UnicodeEncodeError(
|
|
84
|
+
cls.encoding_name, realized_input, idx, idx + 1, err.reason()
|
|
85
|
+
)
|
|
86
|
+
replacement, idx = codecs.lookup_error(errors)(exc)
|
|
87
|
+
if isinstance(replacement, str):
|
|
88
|
+
replacement = codecs.encode(replacement, cls.encoding_name)
|
|
89
|
+
parts.append(replacement)
|
|
90
|
+
return b"".join(parts), idx
|
|
91
|
+
|
|
92
|
+
@classmethod
|
|
93
|
+
def decode(
|
|
94
|
+
cls, input: bytes, errors: str = "strict"
|
|
95
|
+
) -> Tuple[Union[str, AnySymbolicStr], int]:
|
|
96
|
+
if not (isinstance(input, Buffer) and isinstance(errors, str)):
|
|
97
|
+
raise TypeError
|
|
98
|
+
parts: List[Union[str, AnySymbolicStr]] = []
|
|
99
|
+
idx = 0
|
|
100
|
+
inputlen = len(input)
|
|
101
|
+
while idx < inputlen:
|
|
102
|
+
out, idx, err = cls._decode_chunk(input, idx)
|
|
103
|
+
parts.append(out)
|
|
104
|
+
if err is not None:
|
|
105
|
+
# 1. Handle some well-known error modes directly:
|
|
106
|
+
if errors == "strict":
|
|
107
|
+
raise _UnicodeDecodeError(
|
|
108
|
+
cls.encoding_name, input, idx, idx + 1, err.reason()
|
|
109
|
+
)
|
|
110
|
+
# TODO: continuation after erros seems poorly tested right now
|
|
111
|
+
if errors == "ignore":
|
|
112
|
+
idx += 1
|
|
113
|
+
continue
|
|
114
|
+
if errors == "replace":
|
|
115
|
+
idx += 1
|
|
116
|
+
parts.append("\ufffd")
|
|
117
|
+
continue
|
|
118
|
+
|
|
119
|
+
# 2. Then fall back to native implementations if necessary:
|
|
120
|
+
exc = UnicodeDecodeError(
|
|
121
|
+
cls.encoding_name, realize(input), idx, idx + 1, err.reason()
|
|
122
|
+
)
|
|
123
|
+
replacement, idx = codecs.lookup_error(errors)(exc)
|
|
124
|
+
if isinstance(replacement, bytes):
|
|
125
|
+
replacement = codecs.decode(replacement, cls.encoding_name)
|
|
126
|
+
parts.append(replacement)
|
|
127
|
+
return "".join(parts), idx # type: ignore
|
|
128
|
+
|
|
129
|
+
@classmethod
|
|
130
|
+
def getregentry(cls) -> codecs.CodecInfo:
|
|
131
|
+
return _getregentry(cls)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _getregentry(stem_encoder: Type[StemEncoder]):
|
|
135
|
+
class StemIncrementalEncoder(codecs.BufferedIncrementalEncoder):
|
|
136
|
+
def _buffer_encode(
|
|
137
|
+
self, input: str, errors: str, final: bool
|
|
138
|
+
) -> Tuple[bytes, int]:
|
|
139
|
+
enc_name = stem_encoder.encoding_name
|
|
140
|
+
out, idx, err = stem_encoder._encode_chunk(input, 0)
|
|
141
|
+
assert isinstance(out, bytes)
|
|
142
|
+
if not err:
|
|
143
|
+
return (out, idx)
|
|
144
|
+
if isinstance(err, UnexpectedEndError) or not final:
|
|
145
|
+
return (out, idx)
|
|
146
|
+
exc = UnicodeEncodeError(enc_name, input, idx, idx + 1, err.reason())
|
|
147
|
+
replacement, idx = codecs.lookup_error(errors)(exc)
|
|
148
|
+
if isinstance(replacement, str):
|
|
149
|
+
replacement = codecs.encode(replacement, enc_name)
|
|
150
|
+
return (out + replacement, idx)
|
|
151
|
+
|
|
152
|
+
class StemIncrementalDecoder(codecs.BufferedIncrementalDecoder):
|
|
153
|
+
def _buffer_decode(
|
|
154
|
+
self, input: Buffer, errors: str, final: bool
|
|
155
|
+
) -> Tuple[str, int]:
|
|
156
|
+
if not isinstance(input, bytes):
|
|
157
|
+
input = memoryview(input).tobytes()
|
|
158
|
+
enc_name = stem_encoder.encoding_name
|
|
159
|
+
out, idx, err = stem_encoder._decode_chunk(input, 0)
|
|
160
|
+
assert isinstance(out, str)
|
|
161
|
+
if not err:
|
|
162
|
+
return out, idx
|
|
163
|
+
if isinstance(err, UnexpectedEndError) or not final:
|
|
164
|
+
return out, idx
|
|
165
|
+
exc = UnicodeDecodeError(enc_name, input, idx, idx + 1, err.reason())
|
|
166
|
+
replacement, idx = codecs.lookup_error(errors)(exc)
|
|
167
|
+
if isinstance(replacement, bytes):
|
|
168
|
+
replacement = codecs.decode(replacement, enc_name)
|
|
169
|
+
return (out + replacement, idx)
|
|
170
|
+
|
|
171
|
+
class StemStreamWriter(codecs.StreamWriter):
|
|
172
|
+
def encode(self, input: str, errors: str = "strict") -> Tuple[bytes, int]:
|
|
173
|
+
raise Exception # TODO implement
|
|
174
|
+
|
|
175
|
+
class StemStreamReader(codecs.StreamReader):
|
|
176
|
+
def decode(self, input: bytes, errors: str = "strict") -> Tuple[str, int]:
|
|
177
|
+
raise Exception
|
|
178
|
+
|
|
179
|
+
return codecs.CodecInfo(
|
|
180
|
+
name=stem_encoder.encoding_name,
|
|
181
|
+
encode=stem_encoder.encode, # type: ignore
|
|
182
|
+
decode=stem_encoder.decode, # type: ignore
|
|
183
|
+
incrementalencoder=StemIncrementalEncoder,
|
|
184
|
+
incrementaldecoder=StemIncrementalDecoder,
|
|
185
|
+
streamreader=StemStreamReader,
|
|
186
|
+
streamwriter=StemStreamWriter,
|
|
187
|
+
)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import codecs
|
|
2
|
+
from typing import List, Optional, Tuple, Union
|
|
3
|
+
|
|
4
|
+
from crosshair.libimpl.builtinslib import SymbolicBytes
|
|
5
|
+
from crosshair.libimpl.encodings._encutil import ChunkError, MidChunkError, StemEncoder
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class AsciiStemEncoder(StemEncoder):
|
|
9
|
+
encoding_name = "ascii"
|
|
10
|
+
|
|
11
|
+
@classmethod
|
|
12
|
+
def _encode_chunk(
|
|
13
|
+
cls, string: str, start: int
|
|
14
|
+
) -> Tuple[Union[bytes, SymbolicBytes], int, Optional[ChunkError]]:
|
|
15
|
+
byte_ints: List[int] = []
|
|
16
|
+
for idx in range(start, len(string)):
|
|
17
|
+
ch = string[idx]
|
|
18
|
+
cp = ord(ch)
|
|
19
|
+
if cp >= 0x80:
|
|
20
|
+
return (
|
|
21
|
+
SymbolicBytes(byte_ints),
|
|
22
|
+
idx,
|
|
23
|
+
MidChunkError("ordinal not in range"),
|
|
24
|
+
)
|
|
25
|
+
else:
|
|
26
|
+
byte_ints.append(cp)
|
|
27
|
+
return (SymbolicBytes(byte_ints), len(string), None)
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def _decode_chunk(
|
|
31
|
+
cls, byts: bytes, start: int
|
|
32
|
+
) -> Tuple[str, int, Optional[ChunkError]]:
|
|
33
|
+
chars: List[str] = []
|
|
34
|
+
for idx in range(start, len(byts)):
|
|
35
|
+
cp = byts[idx]
|
|
36
|
+
if cp >= 0x80:
|
|
37
|
+
return ("".join(chars), idx, MidChunkError("ordinal not in range"))
|
|
38
|
+
else:
|
|
39
|
+
chars.append(chr(cp))
|
|
40
|
+
return ("".join(chars), len(byts), None) # type: ignore
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def getregentry() -> codecs.CodecInfo:
|
|
44
|
+
return AsciiStemEncoder.getregentry()
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import codecs
|
|
2
|
+
from typing import List, Optional, Tuple, Union
|
|
3
|
+
|
|
4
|
+
from crosshair.libimpl.builtinslib import SymbolicBytes
|
|
5
|
+
from crosshair.libimpl.encodings._encutil import ChunkError, MidChunkError, StemEncoder
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Latin1StemEncoder(StemEncoder):
|
|
9
|
+
encoding_name = "iso8859-1"
|
|
10
|
+
|
|
11
|
+
@classmethod
|
|
12
|
+
def _encode_chunk(
|
|
13
|
+
cls, string: str, start: int
|
|
14
|
+
) -> Tuple[Union[bytes, SymbolicBytes], int, Optional[ChunkError]]:
|
|
15
|
+
byte_ints: List[int] = []
|
|
16
|
+
for idx in range(start, len(string)):
|
|
17
|
+
ch = string[idx]
|
|
18
|
+
cp = ord(ch)
|
|
19
|
+
if cp < 256:
|
|
20
|
+
byte_ints.append(cp)
|
|
21
|
+
else:
|
|
22
|
+
return (
|
|
23
|
+
SymbolicBytes(byte_ints),
|
|
24
|
+
idx,
|
|
25
|
+
MidChunkError("bytes must be in range(0, 256)"),
|
|
26
|
+
)
|
|
27
|
+
return (SymbolicBytes(byte_ints), len(string), None)
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def _decode_chunk(
|
|
31
|
+
cls, byts: bytes, start: int
|
|
32
|
+
) -> Tuple[str, int, Optional[ChunkError]]:
|
|
33
|
+
chars: List[str] = []
|
|
34
|
+
for cp in byts[start:]:
|
|
35
|
+
chars.append(chr(cp))
|
|
36
|
+
return ("".join(chars), len(byts), None)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def getregentry() -> codecs.CodecInfo:
|
|
40
|
+
return Latin1StemEncoder.getregentry()
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import codecs
|
|
2
|
+
from typing import List, Optional, Tuple, Union
|
|
3
|
+
|
|
4
|
+
from crosshair.libimpl.builtinslib import SymbolicBytes
|
|
5
|
+
from crosshair.libimpl.encodings._encutil import (
|
|
6
|
+
ChunkError,
|
|
7
|
+
MidChunkError,
|
|
8
|
+
StemEncoder,
|
|
9
|
+
UnexpectedEndError,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _encode_codepoint(codepoint: int) -> Tuple[int, ...]:
|
|
14
|
+
if codepoint <= 0b01111111:
|
|
15
|
+
return (codepoint,)
|
|
16
|
+
elif codepoint <= 0b111_11111111:
|
|
17
|
+
return (
|
|
18
|
+
(0b11000000 + ((codepoint >> 6) & 0b00011111)),
|
|
19
|
+
(0b10000000 + (codepoint & 0b00111111)),
|
|
20
|
+
)
|
|
21
|
+
elif codepoint <= 0b11111111_11111111:
|
|
22
|
+
return (
|
|
23
|
+
(0b11100000 + ((codepoint >> 12) & 0b00001111)),
|
|
24
|
+
(0b10000000 + ((codepoint >> 6) & 0b00111111)),
|
|
25
|
+
(0b10000000 + (codepoint & 0b00111111)),
|
|
26
|
+
)
|
|
27
|
+
else:
|
|
28
|
+
return (
|
|
29
|
+
(0b11110000 + ((codepoint >> 18) & 0b00000111)),
|
|
30
|
+
(0b10000000 + ((codepoint >> 12) & 0b00111111)),
|
|
31
|
+
(0b10000000 + ((codepoint >> 6) & 0b00111111)),
|
|
32
|
+
(0b10000000 + (codepoint & 0b00111111)),
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class Utf8StemEncoder(StemEncoder):
|
|
37
|
+
encoding_name = "utf-8"
|
|
38
|
+
|
|
39
|
+
@classmethod
|
|
40
|
+
def _encode_chunk(
|
|
41
|
+
cls, string: str, start: int
|
|
42
|
+
) -> Tuple[Union[bytes, SymbolicBytes], int, Optional[ChunkError]]:
|
|
43
|
+
byte_ints: List[int] = []
|
|
44
|
+
for ch in string[start:]:
|
|
45
|
+
byte_ints.extend(_encode_codepoint(ord(ch)))
|
|
46
|
+
return (SymbolicBytes(byte_ints), len(string), None)
|
|
47
|
+
|
|
48
|
+
@classmethod
|
|
49
|
+
def _decode_chunk(
|
|
50
|
+
cls, byts: bytes, start: int
|
|
51
|
+
) -> Tuple[str, int, Optional[ChunkError]]:
|
|
52
|
+
num_bytes = len(byts)
|
|
53
|
+
byt = byts[start]
|
|
54
|
+
end = start + 1
|
|
55
|
+
if byt >= 0b11000000:
|
|
56
|
+
end += 1
|
|
57
|
+
if byt >= 0b11100000:
|
|
58
|
+
end += 1
|
|
59
|
+
if byt >= 0b11110000:
|
|
60
|
+
if byt > 0b11110111:
|
|
61
|
+
return ("", start, MidChunkError(f"can't decode byte"))
|
|
62
|
+
end += 1
|
|
63
|
+
cp = byt & 0b00000111
|
|
64
|
+
mincp, maxcp = 0x10000, 0x10FFFF
|
|
65
|
+
else:
|
|
66
|
+
if byt > 0b11101111:
|
|
67
|
+
return ("", start, MidChunkError(f"can't decode byte"))
|
|
68
|
+
cp = byt & 0b00001111
|
|
69
|
+
mincp, maxcp = 0x0800, 0xFFFF
|
|
70
|
+
else:
|
|
71
|
+
if byt > 0b11011111:
|
|
72
|
+
return ("", start, MidChunkError(f"can't decode byte"))
|
|
73
|
+
cp = byt & 0b00011111
|
|
74
|
+
mincp, maxcp = 0x0080, 0x07FF
|
|
75
|
+
else:
|
|
76
|
+
cp = byt
|
|
77
|
+
mincp, maxcp = 0, 0x007F
|
|
78
|
+
if end > num_bytes:
|
|
79
|
+
return ("", start, UnexpectedEndError())
|
|
80
|
+
for idx in range(start + 1, end):
|
|
81
|
+
byt = byts[idx]
|
|
82
|
+
if 0b10_000000 <= byt <= 0b10_111111:
|
|
83
|
+
cp = (cp * 64) + (byts[idx] - 0b10_000000)
|
|
84
|
+
else:
|
|
85
|
+
return ("", start, MidChunkError(f"can't decode byte"))
|
|
86
|
+
if mincp <= cp <= maxcp:
|
|
87
|
+
return (chr(cp), end, None)
|
|
88
|
+
else:
|
|
89
|
+
return ("", start, MidChunkError(f"invalid start byte"))
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def getregentry() -> codecs.CodecInfo:
|
|
93
|
+
return Utf8StemEncoder.getregentry()
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import codecs
|
|
2
|
+
import sys
|
|
3
|
+
from io import BytesIO
|
|
4
|
+
|
|
5
|
+
import pytest # type: ignore
|
|
6
|
+
|
|
7
|
+
from crosshair.core_and_libs import MessageType, analyze_function, run_checkables
|
|
8
|
+
from crosshair.options import AnalysisOptionSet
|
|
9
|
+
from crosshair.test_util import ResultComparison, compare_results
|
|
10
|
+
|
|
11
|
+
_ERROR_HANDLERS = ["strict", "replace", "ignore"]
|
|
12
|
+
|
|
13
|
+
# crosshair: max_iterations=20
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def check_encode_ascii(string: str, errors: str) -> ResultComparison:
|
|
17
|
+
"""
|
|
18
|
+
pre: errors in _ERROR_HANDLERS
|
|
19
|
+
post: _
|
|
20
|
+
"""
|
|
21
|
+
return compare_results(lambda s, e: s.encode("ascii", e), string, errors)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def check_encode_latin1(string: str, errors: str) -> ResultComparison:
|
|
25
|
+
"""
|
|
26
|
+
pre: errors in _ERROR_HANDLERS
|
|
27
|
+
post: _
|
|
28
|
+
"""
|
|
29
|
+
return compare_results(lambda s, e: s.encode("latin1", e), string, errors)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def check_encode_utf8(string: str, errors: str) -> ResultComparison:
|
|
33
|
+
"""
|
|
34
|
+
pre: errors in _ERROR_HANDLERS
|
|
35
|
+
post: _
|
|
36
|
+
"""
|
|
37
|
+
return compare_results(lambda s, e: s.encode("utf8", e), string, errors)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def check_decode_ascii(bytestring: bytes, errors: str) -> ResultComparison:
|
|
41
|
+
"""
|
|
42
|
+
pre: errors in _ERROR_HANDLERS
|
|
43
|
+
post: _
|
|
44
|
+
"""
|
|
45
|
+
return compare_results(lambda b, e: b.decode("ascii", e), bytestring, errors)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def check_decode_latin1(bytestring: bytes, errors: str) -> ResultComparison:
|
|
49
|
+
"""
|
|
50
|
+
pre: errors in _ERROR_HANDLERS
|
|
51
|
+
post: _
|
|
52
|
+
"""
|
|
53
|
+
return compare_results(lambda b, e: b.decode("latin1", e), bytestring, errors)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def check_decode_utf8(bytestring: bytes, errors: str) -> ResultComparison:
|
|
57
|
+
"""
|
|
58
|
+
pre: errors in _ERROR_HANDLERS
|
|
59
|
+
post: _
|
|
60
|
+
"""
|
|
61
|
+
# crosshair: max_iterations=200
|
|
62
|
+
return compare_results(lambda b, e: b.decode("utf8", e), bytestring, errors)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
# TODO add handling for BytesIO(SymbolicBytes())
|
|
66
|
+
# def check_stream_decode_utf8(bytestring: bytes, errors: str) -> ResultComparison:
|
|
67
|
+
# """
|
|
68
|
+
# pre: errors in _ERROR_HANDLERS
|
|
69
|
+
# post: _
|
|
70
|
+
# """
|
|
71
|
+
# def read_stream(b, e):
|
|
72
|
+
# return codecs.getreader("utf8")(BytesIO(b), e).read()
|
|
73
|
+
# return compare_results(read_stream, bytestring, errors)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# This is the only real test definition.
|
|
77
|
+
# It runs crosshair on each of the "check" functions defined above.
|
|
78
|
+
@pytest.mark.parametrize("fn_name", [fn for fn in dir() if fn.startswith("check_")])
|
|
79
|
+
def test_builtin(fn_name: str) -> None:
|
|
80
|
+
this_module = sys.modules[__name__]
|
|
81
|
+
messages = run_checkables(analyze_function(getattr(this_module, fn_name)))
|
|
82
|
+
errors = [m for m in messages if m.state > MessageType.PRE_UNSAT]
|
|
83
|
+
assert errors == []
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from fractions import Fraction
|
|
2
|
+
|
|
3
|
+
from crosshair.core import SymbolicFactory, register_type
|
|
4
|
+
from crosshair.statespace import force_true
|
|
5
|
+
from crosshair.tracers import ResumedTracing
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _make_fraction(factory: SymbolicFactory):
|
|
9
|
+
n, d = factory(int, "_numerator"), factory(int, "_denominator")
|
|
10
|
+
with ResumedTracing():
|
|
11
|
+
force_true(d > 0)
|
|
12
|
+
return Fraction(n, d)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def make_registrations() -> None:
|
|
16
|
+
register_type(Fraction, _make_fraction)
|