From 0392adf39949c1d35796efe4af92d9ab34ccc0c0 Mon Sep 17 00:00:00 2001 From: Jeongseop Lim Date: Sat, 31 Oct 2026 20:52:52 +0900 Subject: [PATCH] Convert the key before rejecting item assignment on sequences PyObjectSetItem and PyObjectDelItem raised TypeError as soon as the type had neither mp_ass_subscript nor sq_ass_item, without looking at the key. CPython first converts an index key with PyNumber_AsSsize_t(key, IndexError) when the type has a sequence protocol, so an exception from __index__, or IndexError for a key that does not fit in 64 bits, is raised instead. Add that branch to both nodes, checking the 64-bit range rather than GraalPy's 32-bit index size, since the converted value is not used. Signed-off-by: Jeongseop Lim --- .../src/tests/test_operator.py | 22 +++++++++++++++++++ .../graal/python/lib/PyObjectDelItem.java | 15 ++++++++++++- .../graal/python/lib/PyObjectSetItem.java | 15 ++++++++++++- 3 files changed, 50 insertions(+), 2 deletions(-) diff --git a/graalpython/com.oracle.graal.python.test/src/tests/test_operator.py b/graalpython/com.oracle.graal.python.test/src/tests/test_operator.py index ed526c81d7..138f2cebc3 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/test_operator.py +++ b/graalpython/com.oracle.graal.python.test/src/tests/test_operator.py @@ -67,3 +67,25 @@ def test_getitem(self): self.assertRaises(TypeError, operator.getitem) self.assertRaises(TypeError, operator.getitem, a, None) self.assertEqual(operator.getitem(a, 2), 2) + + def test_setitem_delitem_convert_index_before_type_error(self): + class RaisingIndex: + def __index__(self): + raise ZeroDivisionError + + class Plain: + pass + + self.assertRaises(ZeroDivisionError, operator.setitem, (), RaisingIndex(), 0) + self.assertRaises(ZeroDivisionError, operator.setitem, frozenset(), RaisingIndex(), 0) + self.assertRaises(ZeroDivisionError, operator.setitem, Plain(), RaisingIndex(), 0) + self.assertRaises(IndexError, operator.setitem, '', 2**100, 0) + self.assertRaises(ZeroDivisionError, operator.delitem, '', RaisingIndex()) + self.assertRaises(ZeroDivisionError, operator.delitem, Plain(), RaisingIndex()) + self.assertRaises(IndexError, operator.delitem, (), 2**100) + self.assertRaises(TypeError, operator.setitem, (), 2**40, 0) + self.assertRaises(TypeError, operator.delitem, '', 2**40) + self.assertRaises(TypeError, operator.setitem, (), 0, 0) + self.assertRaises(TypeError, operator.delitem, (), 'a') + self.assertRaises(TypeError, operator.setitem, 1, RaisingIndex(), 0) + self.assertRaises(TypeError, operator.delitem, list[int], RaisingIndex()) diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyObjectDelItem.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyObjectDelItem.java index 14e3d3cc33..2055eb00a8 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyObjectDelItem.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyObjectDelItem.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -40,9 +40,11 @@ */ package com.oracle.graal.python.lib; +import static com.oracle.graal.python.builtins.PythonBuiltinClassType.IndexError; import static com.oracle.graal.python.builtins.PythonBuiltinClassType.TypeError; import com.oracle.graal.python.builtins.objects.PNone; +import com.oracle.graal.python.builtins.objects.ints.PInt; import com.oracle.graal.python.builtins.objects.list.ListBuiltins; import com.oracle.graal.python.builtins.objects.list.PList; import com.oracle.graal.python.builtins.objects.type.TpSlots; @@ -124,6 +126,17 @@ static void doSequence(VirtualFrame frame, Node inliningTarget, Object object, T callSqItem.execute(frame, inliningTarget, slots.sq_ass_item(), object, index, PNone.NO_VALUE); } + @Specialization(guards = {"slots.mp_ass_subscript() == null", "slots.sq_ass_item() == null", "slots.has_as_sequence()"}) + @InliningCutoff + static void doSequenceNoAssItem(VirtualFrame frame, Node inliningTarget, Object object, @SuppressWarnings("unused") TpSlots slots, Object key, + @Cached PyIndexCheckNode indexCheckNode, + @Cached PyNumberIndexNode indexNode) { + if (indexCheckNode.execute(inliningTarget, key) && indexNode.execute(frame, inliningTarget, key) instanceof PInt pint && pint.bitLength() >= Long.SIZE) { + throw PRaiseNode.raiseStatic(inliningTarget, IndexError, ErrorMessages.CANNOT_FIT_P_INTO_INDEXSIZED_INT, key); + } + throw PRaiseNode.raiseStatic(inliningTarget, TypeError, ErrorMessages.OBJ_DOES_NOT_SUPPORT_ITEM_DELETION, object); + } + @Fallback @InliningCutoff static void error(Object object, @SuppressWarnings("unused") TpSlots slots, @SuppressWarnings("unused") Object key, diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyObjectSetItem.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyObjectSetItem.java index 1ee239c79b..671e864b95 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyObjectSetItem.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyObjectSetItem.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -40,8 +40,10 @@ */ package com.oracle.graal.python.lib; +import static com.oracle.graal.python.builtins.PythonBuiltinClassType.IndexError; import static com.oracle.graal.python.builtins.PythonBuiltinClassType.TypeError; +import com.oracle.graal.python.builtins.objects.ints.PInt; import com.oracle.graal.python.builtins.objects.list.ListBuiltins; import com.oracle.graal.python.builtins.objects.list.PList; import com.oracle.graal.python.builtins.objects.type.TpSlots; @@ -131,6 +133,17 @@ static void doSequence(VirtualFrame frame, Node inliningTarget, Object object, T callSqItem.execute(frame, inliningTarget, slots.sq_ass_item(), object, index, value); } + @Specialization(guards = {"slots.mp_ass_subscript() == null", "slots.sq_ass_item() == null", "slots.has_as_sequence()"}) + @InliningCutoff + static void doSequenceNoAssItem(VirtualFrame frame, Node inliningTarget, Object object, @SuppressWarnings("unused") TpSlots slots, Object key, @SuppressWarnings("unused") Object value, + @Cached PyIndexCheckNode indexCheckNode, + @Cached PyNumberIndexNode indexNode) { + if (indexCheckNode.execute(inliningTarget, key) && indexNode.execute(frame, inliningTarget, key) instanceof PInt pint && pint.bitLength() >= Long.SIZE) { + throw PRaiseNode.raiseStatic(inliningTarget, IndexError, ErrorMessages.CANNOT_FIT_P_INTO_INDEXSIZED_INT, key); + } + throw PRaiseNode.raiseStatic(inliningTarget, TypeError, ErrorMessages.OBJ_DOES_NOT_SUPPORT_ITEM_ASSIGMENT, object); + } + @Fallback @InliningCutoff static void error(Object object, @SuppressWarnings("unused") TpSlots slots, @SuppressWarnings("unused") Object key, @SuppressWarnings("unused") Object value,