Replace sanitize hash two pass

This commit is contained in:
xmarre 2026-03-15 07:30:18 +01:00
parent a6624a9afd
commit 24fdbb9aca
2 changed files with 29 additions and 7 deletions

View File

@ -170,17 +170,17 @@ def _signature_to_hashable_impl(obj, depth=0, max_depth=_MAX_SIGNATURE_DEPTH, ac
return _FAILED_SIGNATURE
key_value, key_sort = key_result
value_value, value_sort = value_result
ordered_items.append((((key_sort, value_sort)), (key_value, value_value)))
ordered_items.append((key_sort, value_sort, key_value, value_value))
ordered_items.sort(key=lambda item: item[0])
ordered_items.sort(key=lambda item: (item[0], item[1]))
for index in range(1, len(ordered_items)):
previous_sort_key, previous_item = ordered_items[index - 1]
current_sort_key, current_item = ordered_items[index]
if previous_sort_key == current_sort_key and previous_item != current_item:
previous_key_sort = ordered_items[index - 1][0]
current_key_sort = ordered_items[index][0]
if previous_key_sort == current_key_sort:
return _FAILED_SIGNATURE
value = ("dict", tuple(item for _, item in ordered_items))
sort_key = ("dict", tuple(sort_key for sort_key, _ in ordered_items))
value = ("dict", tuple((key_value, value_value) for _, _, key_value, value_value in ordered_items))
sort_key = ("dict", tuple((key_sort, value_sort) for key_sort, value_sort, _, _ in ordered_items))
elif obj_type is list or obj_type is tuple:
try:
items = list(obj)

View File

@ -240,6 +240,28 @@ def test_signature_to_hashable_fails_closed_for_ambiguous_dict_ordering(caching_
assert isinstance(sanitized, caching.Unhashable)
def test_signature_to_hashable_fails_closed_on_dict_key_sort_collisions_even_with_distinct_values(caching_module, monkeypatch):
"""Different values must not mask dict key-sort collisions during canonicalization."""
caching, _ = caching_module
original = caching._signature_to_hashable_impl
key_a = object()
key_b = object()
def colliding_key_canonicalize(obj, *args, **kwargs):
"""Force two distinct raw keys to share the same canonical sort key."""
if obj is key_a:
return ("key-a", ("COLLIDE",))
if obj is key_b:
return ("key-b", ("COLLIDE",))
return original(obj, *args, **kwargs)
monkeypatch.setattr(caching, "_signature_to_hashable_impl", colliding_key_canonicalize)
sanitized = caching._signature_to_hashable({key_a: 1, key_b: 2})
assert isinstance(sanitized, caching.Unhashable)
@pytest.mark.parametrize(
"container_factory",
[