crosshair-tool 0.0.56__cp39-cp39-macosx_11_0_arm64.whl → 0.0.100__cp39-cp39-macosx_11_0_arm64.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-39-darwin.so +0 -0
- crosshair/__init__.py +1 -1
- crosshair/_mark_stacks.h +51 -24
- crosshair/_tracers.h +9 -5
- crosshair/_tracers_test.py +19 -9
- crosshair/auditwall.py +9 -8
- crosshair/auditwall_test.py +31 -19
- crosshair/codeconfig.py +3 -2
- crosshair/condition_parser.py +17 -133
- crosshair/condition_parser_test.py +54 -96
- crosshair/conftest.py +1 -1
- crosshair/copyext.py +91 -22
- crosshair/copyext_test.py +33 -0
- crosshair/core.py +259 -203
- crosshair/core_and_libs.py +20 -0
- crosshair/core_regestered_types_test.py +82 -0
- crosshair/core_test.py +693 -664
- crosshair/diff_behavior.py +76 -21
- crosshair/diff_behavior_test.py +132 -23
- crosshair/dynamic_typing.py +128 -18
- crosshair/dynamic_typing_test.py +91 -4
- crosshair/enforce.py +1 -6
- crosshair/enforce_test.py +15 -23
- crosshair/examples/check_examples_test.py +2 -1
- crosshair/fnutil.py +2 -3
- crosshair/fnutil_test.py +0 -7
- crosshair/fuzz_core_test.py +70 -83
- crosshair/libimpl/arraylib.py +10 -7
- 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 +5 -5
- crosshair/libimpl/builtinslib.py +1002 -682
- crosshair/libimpl/builtinslib_ch_test.py +108 -30
- crosshair/libimpl/builtinslib_test.py +431 -143
- crosshair/libimpl/codecslib.py +22 -2
- crosshair/libimpl/codecslib_test.py +41 -9
- crosshair/libimpl/collectionslib.py +44 -8
- crosshair/libimpl/collectionslib_test.py +108 -20
- crosshair/libimpl/copylib.py +1 -1
- crosshair/libimpl/copylib_test.py +18 -0
- crosshair/libimpl/datetimelib.py +84 -67
- crosshair/libimpl/datetimelib_ch_test.py +12 -7
- crosshair/libimpl/datetimelib_test.py +5 -6
- crosshair/libimpl/decimallib.py +5257 -0
- crosshair/libimpl/decimallib_ch_test.py +78 -0
- crosshair/libimpl/decimallib_test.py +76 -0
- crosshair/libimpl/encodings/_encutil.py +21 -11
- crosshair/libimpl/fractionlib.py +16 -0
- crosshair/libimpl/fractionlib_test.py +80 -0
- crosshair/libimpl/functoolslib.py +19 -7
- crosshair/libimpl/functoolslib_test.py +22 -6
- crosshair/libimpl/hashliblib.py +30 -0
- crosshair/libimpl/hashliblib_test.py +18 -0
- crosshair/libimpl/heapqlib.py +32 -5
- crosshair/libimpl/heapqlib_test.py +15 -12
- crosshair/libimpl/iolib.py +7 -4
- crosshair/libimpl/ipaddresslib.py +8 -0
- crosshair/libimpl/itertoolslib_test.py +1 -1
- crosshair/libimpl/mathlib.py +165 -2
- crosshair/libimpl/mathlib_ch_test.py +44 -0
- crosshair/libimpl/mathlib_test.py +59 -16
- crosshair/libimpl/oslib.py +7 -0
- crosshair/libimpl/pathliblib_test.py +10 -0
- crosshair/libimpl/randomlib.py +1 -0
- crosshair/libimpl/randomlib_test.py +6 -4
- crosshair/libimpl/relib.py +180 -59
- crosshair/libimpl/relib_ch_test.py +26 -2
- crosshair/libimpl/relib_test.py +77 -14
- crosshair/libimpl/timelib.py +35 -13
- crosshair/libimpl/timelib_test.py +13 -3
- crosshair/libimpl/typeslib.py +15 -0
- crosshair/libimpl/typeslib_test.py +36 -0
- crosshair/libimpl/unicodedatalib_test.py +3 -3
- 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 +21 -10
- crosshair/main.py +48 -28
- crosshair/main_test.py +59 -14
- crosshair/objectproxy.py +39 -14
- crosshair/objectproxy_test.py +27 -13
- crosshair/opcode_intercept.py +212 -24
- crosshair/opcode_intercept_test.py +172 -18
- crosshair/options.py +0 -1
- crosshair/patch_equivalence_test.py +5 -21
- crosshair/path_cover.py +7 -5
- crosshair/path_search.py +6 -4
- crosshair/path_search_test.py +1 -2
- crosshair/pathing_oracle.py +53 -10
- crosshair/pathing_oracle_test.py +21 -0
- crosshair/pure_importer_test.py +5 -21
- crosshair/register_contract.py +16 -6
- crosshair/register_contract_test.py +2 -14
- crosshair/simplestructs.py +154 -85
- crosshair/simplestructs_test.py +16 -2
- crosshair/smtlib.py +24 -0
- crosshair/smtlib_test.py +14 -0
- crosshair/statespace.py +319 -196
- crosshair/statespace_test.py +45 -0
- crosshair/stubs_parser.py +0 -2
- crosshair/test_util.py +87 -25
- crosshair/test_util_test.py +26 -0
- crosshair/tools/check_init_and_setup_coincide.py +0 -3
- crosshair/tools/generate_demo_table.py +2 -2
- crosshair/tracers.py +141 -49
- crosshair/type_repo.py +11 -4
- crosshair/unicode_categories.py +1 -0
- crosshair/util.py +158 -76
- crosshair/util_test.py +13 -20
- crosshair/watcher.py +4 -4
- crosshair/z3util.py +1 -1
- {crosshair_tool-0.0.56.dist-info → crosshair_tool-0.0.100.dist-info}/METADATA +45 -36
- crosshair_tool-0.0.100.dist-info/RECORD +176 -0
- {crosshair_tool-0.0.56.dist-info → crosshair_tool-0.0.100.dist-info}/WHEEL +2 -1
- crosshair/examples/hypothesis/__init__.py +0 -2
- crosshair/examples/hypothesis/bugs_detected/simple_strategies.py +0 -74
- crosshair_tool-0.0.56.dist-info/RECORD +0 -152
- /crosshair/{examples/hypothesis/bugs_detected/__init__.py → py.typed} +0 -0
- {crosshair_tool-0.0.56.dist-info → crosshair_tool-0.0.100.dist-info}/entry_points.txt +0 -0
- {crosshair_tool-0.0.56.dist-info → crosshair_tool-0.0.100.dist-info/licenses}/LICENSE +0 -0
- {crosshair_tool-0.0.56.dist-info → crosshair_tool-0.0.100.dist-info}/top_level.txt +0 -0
crosshair/statespace_test.py
CHANGED
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
import time
|
|
2
2
|
|
|
3
|
+
import pytest
|
|
3
4
|
import z3 # type: ignore
|
|
4
5
|
|
|
6
|
+
from crosshair.core import Patched, proxy_for_type
|
|
5
7
|
from crosshair.statespace import (
|
|
6
8
|
HeapRef,
|
|
7
9
|
RootNode,
|
|
8
10
|
SimpleStateSpace,
|
|
9
11
|
SnapshotRef,
|
|
10
12
|
StateSpace,
|
|
13
|
+
StateSpaceContext,
|
|
11
14
|
model_value_to_python,
|
|
12
15
|
)
|
|
16
|
+
from crosshair.tracers import COMPOSITE_TRACER
|
|
17
|
+
from crosshair.util import UnknownSatisfiability
|
|
13
18
|
|
|
14
19
|
_HEAD_SNAPSHOT = SnapshotRef(-1)
|
|
15
20
|
|
|
@@ -27,6 +32,21 @@ def test_find_key_in_heap():
|
|
|
27
32
|
assert isinstance(dictval, dict)
|
|
28
33
|
|
|
29
34
|
|
|
35
|
+
def test_timeout() -> None:
|
|
36
|
+
num_ints = 100
|
|
37
|
+
space = StateSpace(time.monotonic() + 60_000, 0.1, RootNode())
|
|
38
|
+
with pytest.raises(UnknownSatisfiability):
|
|
39
|
+
with Patched(), StateSpaceContext(space), COMPOSITE_TRACER:
|
|
40
|
+
ints = [proxy_for_type(int, f"i{i}") for i in range(num_ints)]
|
|
41
|
+
for i in range(num_ints - 2):
|
|
42
|
+
t0 = time.monotonic()
|
|
43
|
+
if ints[i] * ints[i + 1] == ints[i + 2]:
|
|
44
|
+
pass
|
|
45
|
+
ints[i + 1] += ints[i]
|
|
46
|
+
solve_time = time.monotonic() - t0
|
|
47
|
+
assert 0.05 < solve_time < 0.5
|
|
48
|
+
|
|
49
|
+
|
|
30
50
|
def test_infinite_timeout() -> None:
|
|
31
51
|
space = StateSpace(time.monotonic() + 1000, float("+inf"), RootNode())
|
|
32
52
|
assert space.solver.check(True) == z3.sat
|
|
@@ -61,3 +81,28 @@ def test_model_value_to_python_AlgebraicNumRef():
|
|
|
61
81
|
rt2 = z3.simplify(z3.Sqrt(2))
|
|
62
82
|
assert type(rt2) == z3.AlgebraicNumRef
|
|
63
83
|
model_value_to_python(rt2)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def test_model_value_to_python_ArithRef():
|
|
87
|
+
# Tests that a plain z3.ArithRef can be exported as Python
|
|
88
|
+
# See https://github.com/pschanely/CrossHair/issues/381
|
|
89
|
+
rt2 = z3.ToInt(2 ** z3.Int("x"))
|
|
90
|
+
print("type(rt2)", type(rt2))
|
|
91
|
+
assert type(rt2) == z3.ArithRef
|
|
92
|
+
model_value_to_python(rt2)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def test_smt_fanout(space: SimpleStateSpace):
|
|
96
|
+
option1 = z3.Bool("option1")
|
|
97
|
+
option2 = z3.Bool("option2")
|
|
98
|
+
space.add(z3.Xor(option1, option2)) # Ensure exactly one option can be set
|
|
99
|
+
exprs_and_results = [(option1, "result1"), (option2, "result2")]
|
|
100
|
+
|
|
101
|
+
result = space.smt_fanout(exprs_and_results, desc="choose_one")
|
|
102
|
+
assert result in ("result1", "result2")
|
|
103
|
+
if result == "result1":
|
|
104
|
+
assert space.is_possible(option1)
|
|
105
|
+
assert not space.is_possible(option2)
|
|
106
|
+
else:
|
|
107
|
+
assert not space.is_possible(option1)
|
|
108
|
+
assert space.is_possible(option2)
|
crosshair/stubs_parser.py
CHANGED
|
@@ -22,8 +22,6 @@ def signature_from_stubs(fn: Callable) -> Tuple[List[Signature], bool]:
|
|
|
22
22
|
"""
|
|
23
23
|
Try to find signature(s) for the given function in the stubs.
|
|
24
24
|
|
|
25
|
-
Note: this feature is only available for Python >= 3.8.
|
|
26
|
-
|
|
27
25
|
For overloaded functions, all signatures found will be returned.
|
|
28
26
|
|
|
29
27
|
:param fn: The function to lookup a signature for.
|
crosshair/test_util.py
CHANGED
|
@@ -2,7 +2,20 @@ import pathlib
|
|
|
2
2
|
import sys
|
|
3
3
|
from copy import deepcopy
|
|
4
4
|
from dataclasses import dataclass, replace
|
|
5
|
-
from
|
|
5
|
+
from decimal import Decimal
|
|
6
|
+
from math import isnan
|
|
7
|
+
from numbers import Real
|
|
8
|
+
from typing import (
|
|
9
|
+
Callable,
|
|
10
|
+
Collection,
|
|
11
|
+
Iterable,
|
|
12
|
+
List,
|
|
13
|
+
Mapping,
|
|
14
|
+
Optional,
|
|
15
|
+
Sequence,
|
|
16
|
+
Set,
|
|
17
|
+
Tuple,
|
|
18
|
+
)
|
|
6
19
|
|
|
7
20
|
from crosshair.core import (
|
|
8
21
|
AnalysisMessage,
|
|
@@ -14,21 +27,27 @@ from crosshair.core import (
|
|
|
14
27
|
)
|
|
15
28
|
from crosshair.options import AnalysisOptionSet
|
|
16
29
|
from crosshair.statespace import context_statespace
|
|
17
|
-
from crosshair.tracers import NoTracing,
|
|
30
|
+
from crosshair.tracers import NoTracing, ResumedTracing
|
|
18
31
|
from crosshair.util import (
|
|
19
|
-
|
|
20
|
-
|
|
32
|
+
assert_tracing,
|
|
33
|
+
ch_stack,
|
|
21
34
|
debug,
|
|
22
35
|
in_debug,
|
|
36
|
+
is_iterable,
|
|
23
37
|
is_pure_python,
|
|
24
38
|
name_of_type,
|
|
25
|
-
test_stack,
|
|
26
|
-
true_type,
|
|
27
39
|
)
|
|
28
40
|
|
|
29
41
|
ComparableLists = Tuple[List, List]
|
|
30
42
|
|
|
31
43
|
|
|
44
|
+
class _Missing:
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
_MISSING = _Missing()
|
|
49
|
+
|
|
50
|
+
|
|
32
51
|
def simplefs(path: pathlib.Path, files: dict) -> None:
|
|
33
52
|
for name, contents in files.items():
|
|
34
53
|
subpath = path / name
|
|
@@ -54,8 +73,8 @@ def check_states(
|
|
|
54
73
|
)
|
|
55
74
|
elif expected == MessageType.CONFIRMED:
|
|
56
75
|
local_opts = AnalysisOptionSet(
|
|
57
|
-
per_condition_timeout=
|
|
58
|
-
per_path_timeout=
|
|
76
|
+
per_condition_timeout=60,
|
|
77
|
+
per_path_timeout=20,
|
|
59
78
|
max_uninteresting_iterations=sys.maxsize,
|
|
60
79
|
)
|
|
61
80
|
elif expected == MessageType.POST_ERR:
|
|
@@ -123,9 +142,40 @@ def check_messages(checkables: Iterable[Checkable], **kw) -> ComparableLists:
|
|
|
123
142
|
return (msgs, [AnalysisMessage(**kw)])
|
|
124
143
|
|
|
125
144
|
|
|
126
|
-
|
|
127
|
-
|
|
145
|
+
_NAN_ABLE = (Decimal, Real)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def flexible_equal(a: object, b: object) -> bool:
|
|
149
|
+
if a is b:
|
|
128
150
|
return True
|
|
151
|
+
if type(a) is type(b) and type(a).__eq__ is object.__eq__:
|
|
152
|
+
# If types match and it uses identity-equals, we can't do much. Assume equal.
|
|
153
|
+
return True
|
|
154
|
+
if isinstance(a, _NAN_ABLE) and isinstance(b, _NAN_ABLE) and isnan(a) and isnan(b):
|
|
155
|
+
return True
|
|
156
|
+
if (
|
|
157
|
+
is_iterable(a)
|
|
158
|
+
and not isinstance(a, Collection)
|
|
159
|
+
and is_iterable(b)
|
|
160
|
+
and not isinstance(b, Collection)
|
|
161
|
+
): # unsized iterables compare by contents
|
|
162
|
+
a, b = list(a), list(b) # type: ignore
|
|
163
|
+
if (
|
|
164
|
+
type(a) == type(b)
|
|
165
|
+
and isinstance(a, Collection)
|
|
166
|
+
and not isinstance(a, (str, bytes, Set))
|
|
167
|
+
):
|
|
168
|
+
# Recursively apply flexible_equal for most containers:
|
|
169
|
+
if len(a) != len(b): # type: ignore
|
|
170
|
+
return False
|
|
171
|
+
if isinstance(a, Mapping):
|
|
172
|
+
for k, v in a.items():
|
|
173
|
+
if not flexible_equal(v, b.get(k, _MISSING)): # type: ignore
|
|
174
|
+
return False
|
|
175
|
+
return True
|
|
176
|
+
else:
|
|
177
|
+
return all(flexible_equal(ai, bi) for ai, bi in zip(a, b)) # type: ignore
|
|
178
|
+
|
|
129
179
|
return a == b
|
|
130
180
|
|
|
131
181
|
|
|
@@ -133,6 +183,7 @@ def nan_equal(a, b):
|
|
|
133
183
|
class ExecutionResult:
|
|
134
184
|
ret: object # return value
|
|
135
185
|
exc: Optional[BaseException] # exception raised, if any
|
|
186
|
+
tb: Optional[str]
|
|
136
187
|
# args after the function terminates:
|
|
137
188
|
post_args: Sequence
|
|
138
189
|
post_kwargs: Mapping[str, object]
|
|
@@ -141,7 +192,7 @@ class ExecutionResult:
|
|
|
141
192
|
if not isinstance(other, ExecutionResult):
|
|
142
193
|
return False
|
|
143
194
|
return (
|
|
144
|
-
|
|
195
|
+
flexible_equal(self.ret, other.ret)
|
|
145
196
|
and type(self.exc) == type(other.exc)
|
|
146
197
|
and self.post_args == other.post_args
|
|
147
198
|
and self.post_kwargs == other.post_kwargs
|
|
@@ -152,7 +203,7 @@ class ExecutionResult:
|
|
|
152
203
|
if self.exc:
|
|
153
204
|
exc = self.exc
|
|
154
205
|
exc_type = name_of_type(type(exc))
|
|
155
|
-
tb =
|
|
206
|
+
tb = self.tb or "(missing traceback)"
|
|
156
207
|
ret = f"exc={exc_type}: {str(exc)} {tb}"
|
|
157
208
|
else:
|
|
158
209
|
ret = f"ret={self.ret!r}"
|
|
@@ -178,7 +229,8 @@ def summarize_execution(
|
|
|
178
229
|
if not kwargs:
|
|
179
230
|
kwargs = {}
|
|
180
231
|
ret: object = None
|
|
181
|
-
exc = None
|
|
232
|
+
exc: Optional[Exception] = None
|
|
233
|
+
tbstr: Optional[str] = None
|
|
182
234
|
try:
|
|
183
235
|
possibly_symbolic_ret = fn(*args, **kwargs)
|
|
184
236
|
if detach_path:
|
|
@@ -199,13 +251,14 @@ def summarize_execution(
|
|
|
199
251
|
except Exception as e:
|
|
200
252
|
exc = e
|
|
201
253
|
if detach_path:
|
|
202
|
-
context_statespace().detach_path()
|
|
254
|
+
context_statespace().detach_path(e)
|
|
203
255
|
exc = deep_realize(exc)
|
|
204
256
|
# NOTE: deep_realize somehow empties the __traceback__ member; re-assign it:
|
|
205
257
|
exc.__traceback__ = e.__traceback__
|
|
258
|
+
tbstr = ch_stack(currently_handling=exc)
|
|
206
259
|
if in_debug():
|
|
207
|
-
debug("hit exception:", type(exc), exc,
|
|
208
|
-
return ExecutionResult(ret, exc, args, kwargs)
|
|
260
|
+
debug("hit exception:", type(exc), exc, tbstr)
|
|
261
|
+
return ExecutionResult(ret, exc, tbstr, args, kwargs)
|
|
209
262
|
|
|
210
263
|
|
|
211
264
|
@dataclass
|
|
@@ -235,8 +288,8 @@ def compare_returns(fn: Callable, *a: object, **kw: object) -> ResultComparison:
|
|
|
235
288
|
return comparison
|
|
236
289
|
|
|
237
290
|
|
|
291
|
+
@assert_tracing(True)
|
|
238
292
|
def compare_results(fn: Callable, *a: object, **kw: object) -> ResultComparison:
|
|
239
|
-
assert is_tracing()
|
|
240
293
|
original_a = deepcopy(a)
|
|
241
294
|
original_kw = deepcopy(kw)
|
|
242
295
|
symbolic_result = summarize_execution(fn, a, kw)
|
|
@@ -245,16 +298,25 @@ def compare_results(fn: Callable, *a: object, **kw: object) -> ResultComparison:
|
|
|
245
298
|
concrete_kw = deep_realize(original_kw)
|
|
246
299
|
|
|
247
300
|
# Check that realization worked, too:
|
|
248
|
-
|
|
249
|
-
|
|
301
|
+
with NoTracing():
|
|
302
|
+
labels_and_args = [
|
|
303
|
+
*(
|
|
304
|
+
(f"Argument {idx + 1}", a[idx], arg)
|
|
305
|
+
for idx, arg in enumerate(concrete_a)
|
|
306
|
+
),
|
|
307
|
+
*((f"Keyword argument '{k}'", kw[k], v) for k, v in concrete_kw.items()),
|
|
308
|
+
]
|
|
309
|
+
for label, symbolic_arg, concrete_arg in labels_and_args:
|
|
310
|
+
with ResumedTracing():
|
|
311
|
+
symbolic_type = type(symbolic_arg)
|
|
312
|
+
concrete_type = type(concrete_arg)
|
|
313
|
+
true_concrete_type = type(concrete_arg)
|
|
250
314
|
assert (
|
|
251
|
-
|
|
252
|
-
), f"
|
|
253
|
-
for k, v in concrete_kw.items():
|
|
254
|
-
if true_type(v) != type(v):
|
|
315
|
+
true_concrete_type == concrete_type
|
|
316
|
+
), f"{label} did not realize. It is {true_concrete_type} instead of {concrete_type}."
|
|
255
317
|
assert (
|
|
256
|
-
|
|
257
|
-
), f"
|
|
318
|
+
true_concrete_type == symbolic_type
|
|
319
|
+
), f"{label} should realize to {symbolic_type}; it is {true_concrete_type} instead."
|
|
258
320
|
|
|
259
321
|
with NoTracing():
|
|
260
322
|
concrete_result = summarize_execution(
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from crosshair.test_util import flexible_equal
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def test_flexible_equal():
|
|
5
|
+
assert float("nan") != float("nan")
|
|
6
|
+
assert flexible_equal(float("nan"), float("nan"))
|
|
7
|
+
assert flexible_equal((42, float("nan")), (42, float("nan")))
|
|
8
|
+
assert not flexible_equal([float("nan"), 11], [float("nan"), 22])
|
|
9
|
+
|
|
10
|
+
def gen():
|
|
11
|
+
yield 11
|
|
12
|
+
yield 22
|
|
13
|
+
|
|
14
|
+
assert flexible_equal(gen(), iter([11, 22]))
|
|
15
|
+
assert not flexible_equal(gen(), iter([11, 22, 33]))
|
|
16
|
+
assert not flexible_equal(gen(), iter([11]))
|
|
17
|
+
|
|
18
|
+
ordered_set_1 = {10_000, 20_000} | {30_000}
|
|
19
|
+
ordered_set_2 = {30_000, 20_000} | {10_000}
|
|
20
|
+
assert list(ordered_set_1) != list(ordered_set_2) # (different orderings)
|
|
21
|
+
assert flexible_equal(ordered_set_1, ordered_set_2)
|
|
22
|
+
|
|
23
|
+
ordered_dict_1 = {1: 2, 3: 4}
|
|
24
|
+
ordered_dict_2 = {3: 4, 1: 2}
|
|
25
|
+
assert list(ordered_dict_1.items()) != list(ordered_dict_2.items())
|
|
26
|
+
assert flexible_equal(ordered_dict_1, ordered_dict_2)
|
|
@@ -107,7 +107,7 @@ def divide_stdlib_module(
|
|
|
107
107
|
modulename: str, items: list[tuple[str, str, str]]
|
|
108
108
|
) -> dict[str, list[tuple[str, str, str]]]:
|
|
109
109
|
ret = defaultdict(list)
|
|
110
|
-
for
|
|
110
|
+
for name, color, src in items:
|
|
111
111
|
if name.endswith("_method"):
|
|
112
112
|
name = name.removesuffix("_method")
|
|
113
113
|
(classname, methodname) = name.split("_", 1)
|
|
@@ -120,7 +120,7 @@ def divide_stdlib_module(
|
|
|
120
120
|
|
|
121
121
|
|
|
122
122
|
stdlib = {}
|
|
123
|
-
for
|
|
123
|
+
for modulename, items in stdlib_demos().items():
|
|
124
124
|
stdlib[modulename] = divide_stdlib_module(modulename, items)
|
|
125
125
|
|
|
126
126
|
|