From 6a627b30835556193c3323b7bf2434f91ed4c755 Mon Sep 17 00:00:00 2001 From: stepan Date: Tue, 20 Oct 2026 21:29:00 +0200 Subject: [PATCH 01/13] Reject null buffer views for arrays --- .../src/tests/unittest_tags/test_array.txt | 14 ++++++++++++++ .../modules/cext/PythonCextArrayBuiltins.java | 5 +++++ .../oracle/graal/python/nodes/ErrorMessages.java | 1 + 3 files changed, 20 insertions(+) diff --git a/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_array.txt b/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_array.txt index 51b7666bd6..49cc739ed7 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_array.txt +++ b/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_array.txt @@ -38,6 +38,7 @@ test.test_array.ByteTest.test_iterationcontains @ darwin-arm64,linux-aarch64,lin test.test_array.ByteTest.test_iterator_pickle @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_array.ByteTest.test_len @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_array.ByteTest.test_mul @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github +test.test_array.ByteTest.test_obsolete_write_lock @ linux-x86_64 test.test_array.ByteTest.test_overflow @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_array.ByteTest.test_pickle @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_array.ByteTest.test_pickle_for_empty_array @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github @@ -95,6 +96,7 @@ test.test_array.DoubleTest.test_iterator_pickle @ darwin-arm64,linux-aarch64,lin test.test_array.DoubleTest.test_len @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_array.DoubleTest.test_mul @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_array.DoubleTest.test_nan @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github +test.test_array.DoubleTest.test_obsolete_write_lock @ linux-x86_64 test.test_array.DoubleTest.test_pickle @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_array.DoubleTest.test_pickle_for_empty_array @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_array.DoubleTest.test_pop @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github @@ -149,6 +151,7 @@ test.test_array.FloatTest.test_iterator_pickle @ darwin-arm64,linux-aarch64,linu test.test_array.FloatTest.test_len @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_array.FloatTest.test_mul @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_array.FloatTest.test_nan @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github +test.test_array.FloatTest.test_obsolete_write_lock @ linux-x86_64 test.test_array.FloatTest.test_pickle @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_array.FloatTest.test_pickle_for_empty_array @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_array.FloatTest.test_pop @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github @@ -202,6 +205,7 @@ test.test_array.IntTest.test_iterationcontains @ darwin-arm64,linux-aarch64,linu test.test_array.IntTest.test_iterator_pickle @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_array.IntTest.test_len @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_array.IntTest.test_mul @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github +test.test_array.IntTest.test_obsolete_write_lock @ linux-x86_64 test.test_array.IntTest.test_overflow @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_array.IntTest.test_pickle @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_array.IntTest.test_pickle_for_empty_array @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github @@ -272,6 +276,7 @@ test.test_array.LongLongTest.test_iterationcontains @ darwin-arm64,linux-aarch64 test.test_array.LongLongTest.test_iterator_pickle @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_array.LongLongTest.test_len @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_array.LongLongTest.test_mul @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github +test.test_array.LongLongTest.test_obsolete_write_lock @ linux-x86_64 test.test_array.LongLongTest.test_overflow @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_array.LongLongTest.test_pickle @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_array.LongLongTest.test_pickle_for_empty_array @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github @@ -327,6 +332,7 @@ test.test_array.LongTest.test_iterationcontains @ darwin-arm64,linux-aarch64,lin test.test_array.LongTest.test_iterator_pickle @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_array.LongTest.test_len @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_array.LongTest.test_mul @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github +test.test_array.LongTest.test_obsolete_write_lock @ linux-x86_64 test.test_array.LongTest.test_overflow @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_array.LongTest.test_pickle @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_array.LongTest.test_pickle_for_empty_array @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github @@ -385,6 +391,7 @@ test.test_array.ShortTest.test_iterationcontains @ darwin-arm64,linux-aarch64,li test.test_array.ShortTest.test_iterator_pickle @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_array.ShortTest.test_len @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_array.ShortTest.test_mul @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github +test.test_array.ShortTest.test_obsolete_write_lock @ linux-x86_64 test.test_array.ShortTest.test_overflow @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_array.ShortTest.test_pickle @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_array.ShortTest.test_pickle_for_empty_array @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github @@ -437,6 +444,7 @@ test.test_array.UCS4Test.test_issue17223 @ darwin-arm64,linux-aarch64,linux-aarc test.test_array.UCS4Test.test_iterator_pickle @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_array.UCS4Test.test_len @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_array.UCS4Test.test_mul @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github +test.test_array.UCS4Test.test_obsolete_write_lock @ linux-x86_64 test.test_array.UCS4Test.test_pickle @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_array.UCS4Test.test_pickle_for_empty_array @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_array.UCS4Test.test_pop @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github @@ -488,6 +496,7 @@ test.test_array.UnicodeTest.test_issue17223 @ darwin-arm64,linux-aarch64,linux-a test.test_array.UnicodeTest.test_iterator_pickle @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_array.UnicodeTest.test_len @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_array.UnicodeTest.test_mul @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github +test.test_array.UnicodeTest.test_obsolete_write_lock @ linux-x86_64 test.test_array.UnicodeTest.test_pickle @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_array.UnicodeTest.test_pickle_for_empty_array @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_array.UnicodeTest.test_pop @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github @@ -543,6 +552,7 @@ test.test_array.UnsignedByteTest.test_iterationcontains @ darwin-arm64,linux-aar test.test_array.UnsignedByteTest.test_iterator_pickle @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_array.UnsignedByteTest.test_len @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_array.UnsignedByteTest.test_mul @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github +test.test_array.UnsignedByteTest.test_obsolete_write_lock @ linux-x86_64 test.test_array.UnsignedByteTest.test_overflow @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_array.UnsignedByteTest.test_pickle @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_array.UnsignedByteTest.test_pickle_for_empty_array @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github @@ -599,6 +609,7 @@ test.test_array.UnsignedIntTest.test_iterationcontains @ darwin-arm64,linux-aarc test.test_array.UnsignedIntTest.test_iterator_pickle @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_array.UnsignedIntTest.test_len @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_array.UnsignedIntTest.test_mul @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github +test.test_array.UnsignedIntTest.test_obsolete_write_lock @ linux-x86_64 test.test_array.UnsignedIntTest.test_overflow @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_array.UnsignedIntTest.test_pickle @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_array.UnsignedIntTest.test_pickle_for_empty_array @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github @@ -655,6 +666,7 @@ test.test_array.UnsignedLongLongTest.test_iterationcontains @ darwin-arm64,linux test.test_array.UnsignedLongLongTest.test_iterator_pickle @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_array.UnsignedLongLongTest.test_len @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_array.UnsignedLongLongTest.test_mul @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github +test.test_array.UnsignedLongLongTest.test_obsolete_write_lock @ linux-x86_64 test.test_array.UnsignedLongLongTest.test_overflow @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_array.UnsignedLongLongTest.test_pickle @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_array.UnsignedLongLongTest.test_pickle_for_empty_array @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github @@ -711,6 +723,7 @@ test.test_array.UnsignedLongTest.test_iterationcontains @ darwin-arm64,linux-aar test.test_array.UnsignedLongTest.test_iterator_pickle @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_array.UnsignedLongTest.test_len @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_array.UnsignedLongTest.test_mul @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github +test.test_array.UnsignedLongTest.test_obsolete_write_lock @ linux-x86_64 test.test_array.UnsignedLongTest.test_overflow @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_array.UnsignedLongTest.test_pickle @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_array.UnsignedLongTest.test_pickle_for_empty_array @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github @@ -767,6 +780,7 @@ test.test_array.UnsignedShortTest.test_iterationcontains @ darwin-arm64,linux-aa test.test_array.UnsignedShortTest.test_iterator_pickle @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_array.UnsignedShortTest.test_len @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_array.UnsignedShortTest.test_mul @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github +test.test_array.UnsignedShortTest.test_obsolete_write_lock @ linux-x86_64 test.test_array.UnsignedShortTest.test_overflow @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_array.UnsignedShortTest.test_pickle @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_array.UnsignedShortTest.test_pickle_for_empty_array @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextArrayBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextArrayBuiltins.java index 7963b5127c..0b19831216 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextArrayBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextArrayBuiltins.java @@ -46,6 +46,7 @@ import static com.oracle.graal.python.annotations.ArgDescriptor.PyObject; import static com.oracle.graal.python.annotations.ArgDescriptor.Py_ssize_t; import static com.oracle.graal.python.annotations.ArgDescriptor.Void; +import static com.oracle.graal.python.builtins.PythonBuiltinClassType.BufferError; import static com.oracle.graal.python.builtins.PythonBuiltinClassType.MemoryError; import static com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiCallPath.Direct; import static com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiCallPath.Ignored; @@ -70,6 +71,7 @@ import com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess; import com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess.WriteTruffleStringNode; import com.oracle.graal.python.builtins.objects.type.TypeNodes; +import com.oracle.graal.python.nodes.ErrorMessages; import com.oracle.graal.python.nodes.PRaiseNode; import com.oracle.graal.python.runtime.nativeaccess.NativeMemory; import com.oracle.graal.python.runtime.object.PFactory; @@ -122,6 +124,9 @@ static long GraalPyArray_Data(long arrayPtr) { @CApiBuiltin(ret = Int, args = {PyObject, PY_BUFFER_PTR, Int}, call = Ignored, retConversion = int.class, argConversions = {long.class, long.class, int.class}) static int GraalPyPrivate_Array_getbuffer(long arrayPtr, long pyBufferPtr, int flags) { + if (pyBufferPtr == NULLPTR) { + throw PRaiseNode.raiseStatic(null, BufferError, ErrorMessages.ARRAY_BUFFER_NULL_VIEW); + } PArray array = expectArray(arrayPtr, "GraalPyPrivate_Array_getbuffer"); long bufPtr = ArrayNodes.EnsureNativeStorageNode.executeUncached(array).getPtr(); writePtrField(pyBufferPtr, CFields.Py_buffer__buf, bufPtr); diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java index 410f9a9f28..4cf4e56ba7 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java @@ -94,6 +94,7 @@ public abstract class ErrorMessages { public static final TruffleString ARG_SHOULD_BE_BYTES_BUFFER_OR_ASCII_NOT_P = tsLiteral("argument should be bytes, buffer or ASCII string, not '%p'"); public static final TruffleString ARRAY_ASSIGN_OUT_OF_BOUNDS = tsLiteral("array assignment index out of range"); public static final TruffleString ARRAY_OUT_OF_BOUNDS = tsLiteral("array index out of range"); + public static final TruffleString ARRAY_BUFFER_NULL_VIEW = tsLiteral("array_buffer_getbuf: view==NULL argument is obsolete"); public static final TruffleString ARRAY_INDICES_MUST_BE_INTS = tsLiteral("array indices must be integers"); public static final TruffleString AST_IDENTIFIER_MUST_BE_OF_TYPE_STR = tsLiteral("AST identifier must be of type str"); public static final TruffleString AST_STRING_MUST_BE_OF_TYPE_STR = tsLiteral("AST string must be of type str"); From 397bd666d3f377b8d0c67ea6ebd1e900848c6762 Mon Sep 17 00:00:00 2001 From: stepan Date: Tue, 20 Oct 2026 21:47:30 +0200 Subject: [PATCH 02/13] Handle empty Unicode allocations before maxchar validation PyUnicode_New(0, invalid_maxchar) incorrectly raised SystemError. Return the empty string before choosing the native character width, matching the size-zero fast path in CPython. --- graalpython/com.oracle.graal.python.cext/src/unicodeobject.c | 5 +++++ .../src/tests/unittest_tags/test_capi.txt | 1 + 2 files changed, 6 insertions(+) diff --git a/graalpython/com.oracle.graal.python.cext/src/unicodeobject.c b/graalpython/com.oracle.graal.python.cext/src/unicodeobject.c index cc2a9d05ec..0f52d57012 100644 --- a/graalpython/com.oracle.graal.python.cext/src/unicodeobject.c +++ b/graalpython/com.oracle.graal.python.cext/src/unicodeobject.c @@ -1440,6 +1440,11 @@ _PyUnicode_Dump(PyObject *op) PyObject * PyUnicode_New(Py_ssize_t size, Py_UCS4 maxchar) { + /* Optimization for empty strings */ + if (size == 0) { + return unicode_get_empty(); + } + // GraalPy change: different implementation int kind; int is_ascii; diff --git a/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_capi.txt b/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_capi.txt index 25793ac6eb..ef909cbfc8 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_capi.txt +++ b/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_capi.txt @@ -520,6 +520,7 @@ test.test_capi.test_unicode.CAPITest.test_fromstring @ darwin-arm64,linux-aarch6 test.test_capi.test_unicode.CAPITest.test_getdefaultencoding @ darwin-arm64,linux-aarch64,linux-x86_64,win32-AMD64 test.test_capi.test_unicode.CAPITest.test_getlength @ darwin-arm64,linux-aarch64,linux-x86_64,win32-AMD64 test.test_capi.test_unicode.CAPITest.test_interninplace @ darwin-arm64,linux-aarch64,linux-x86_64,win32-AMD64 +test.test_capi.test_unicode.CAPITest.test_new @ linux-x86_64 test.test_capi.test_unicode.CAPITest.test_readchar @ darwin-arm64,linux-aarch64,linux-x86_64,win32-AMD64 test.test_capi.test_unicode.CAPITest.test_substring @ darwin-arm64,linux-aarch64,linux-x86_64,win32-AMD64 test.test_capi.test_weakref.CAPIWeakrefTest.test_pyweakref_check @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github From e97a215218f0bb515079ecfd2a374f6440c47303 Mon Sep 17 00:00:00 2001 From: stepan Date: Tue, 20 Oct 2026 21:47:30 +0200 Subject: [PATCH 03/13] Validate null strings in PyUnicode_FromStringAndSize --- .../com.oracle.graal.python.cext/src/unicodeobject.c | 12 ++++++++++-- .../src/tests/unittest_tags/test_capi.txt | 1 + 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/graalpython/com.oracle.graal.python.cext/src/unicodeobject.c b/graalpython/com.oracle.graal.python.cext/src/unicodeobject.c index 0f52d57012..48887ad7ca 100644 --- a/graalpython/com.oracle.graal.python.cext/src/unicodeobject.c +++ b/graalpython/com.oracle.graal.python.cext/src/unicodeobject.c @@ -2153,8 +2153,16 @@ PyUnicode_FromStringAndSize(const char *u, Py_ssize_t size) "Negative size passed to PyUnicode_FromStringAndSize"); return NULL; } - // GraalPy change - return GraalPyPrivate_Unicode_FromUTF((void*) u, size, 1); + if (u != NULL) { + // GraalPy change: decode through the managed Unicode implementation. + return GraalPyPrivate_Unicode_FromUTF((void*) u, size, 1); + } + if (size > 0) { + PyErr_SetString(PyExc_SystemError, + "NULL string with positive size with NULL passed to PyUnicode_FromStringAndSize"); + return NULL; + } + return unicode_get_empty(); } #if 0 // GraalPy change diff --git a/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_capi.txt b/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_capi.txt index ef909cbfc8..afa5f3c7b4 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_capi.txt +++ b/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_capi.txt @@ -517,6 +517,7 @@ test.test_capi.test_unicode.CAPITest.test_fromkindanddata @ darwin-arm64,linux-a test.test_capi.test_unicode.CAPITest.test_fromobject @ darwin-arm64,linux-aarch64,linux-x86_64,win32-AMD64 test.test_capi.test_unicode.CAPITest.test_fromordinal @ darwin-arm64,linux-aarch64,linux-x86_64,win32-AMD64 test.test_capi.test_unicode.CAPITest.test_fromstring @ darwin-arm64,linux-aarch64,linux-x86_64,win32-AMD64 +test.test_capi.test_unicode.CAPITest.test_fromstringandsize @ linux-x86_64 test.test_capi.test_unicode.CAPITest.test_getdefaultencoding @ darwin-arm64,linux-aarch64,linux-x86_64,win32-AMD64 test.test_capi.test_unicode.CAPITest.test_getlength @ darwin-arm64,linux-aarch64,linux-x86_64,win32-AMD64 test.test_capi.test_unicode.CAPITest.test_interninplace @ darwin-arm64,linux-aarch64,linux-x86_64,win32-AMD64 From 66eca677b928879a5e9d308a78354d6a15f775d6 Mon Sep 17 00:00:00 2001 From: stepan Date: Tue, 20 Oct 2026 21:47:30 +0200 Subject: [PATCH 04/13] Validate native wide-character constructor arguments --- .../src/tests/unittest_tags/test_capi.txt | 1 + .../modules/cext/PythonCextUnicodeBuiltins.java | 11 +++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_capi.txt b/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_capi.txt index afa5f3c7b4..44eb00e2e2 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_capi.txt +++ b/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_capi.txt @@ -518,6 +518,7 @@ test.test_capi.test_unicode.CAPITest.test_fromobject @ darwin-arm64,linux-aarch6 test.test_capi.test_unicode.CAPITest.test_fromordinal @ darwin-arm64,linux-aarch64,linux-x86_64,win32-AMD64 test.test_capi.test_unicode.CAPITest.test_fromstring @ darwin-arm64,linux-aarch64,linux-x86_64,win32-AMD64 test.test_capi.test_unicode.CAPITest.test_fromstringandsize @ linux-x86_64 +test.test_capi.test_unicode.CAPITest.test_fromwidechar @ linux-x86_64 test.test_capi.test_unicode.CAPITest.test_getdefaultencoding @ darwin-arm64,linux-aarch64,linux-x86_64,win32-AMD64 test.test_capi.test_unicode.CAPITest.test_getlength @ darwin-arm64,linux-aarch64,linux-x86_64,win32-AMD64 test.test_capi.test_unicode.CAPITest.test_interninplace @ darwin-arm64,linux-aarch64,linux-x86_64,win32-AMD64 diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextUnicodeBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextUnicodeBuiltins.java index e2f4cdd7ef..a3b685d3bf 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextUnicodeBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextUnicodeBuiltins.java @@ -1175,9 +1175,16 @@ abstract static class PyUnicode_FromWideChar extends CApiBinaryBuiltinNode { Object doInt(long arr, long size, @Bind Node inliningTarget, @Cached ReadUnicodeArrayNode readArray, - @Cached TruffleString.FromIntArrayUTF32Node fromArray) { + @Cached TruffleString.FromIntArrayUTF32Node fromArray, + @Cached PRaiseNode raiseNode) { + if ((arr == NULLPTR && size != 0) || size < -1) { + throw raiseNode.raiseBadInternalCall(inliningTarget); + } + if (size > Integer.MAX_VALUE / Integer.BYTES) { + throw raiseNode.raise(inliningTarget, MemoryError); + } assert TS_ENCODING == Encoding.UTF_32 : "needs switch_encoding otherwise"; - return PFactory.createString(PythonLanguage.get(inliningTarget), fromArray.execute(readArray.execute(inliningTarget, arr, castToInt(size), CStructs.wchar_t.size()))); + return PFactory.createString(PythonLanguage.get(inliningTarget), fromArray.execute(readArray.execute(inliningTarget, arr, (int) size, CStructs.wchar_t.size()))); } } From 0456e4f1518902c59a7400f8a489389d8853eaac Mon Sep 17 00:00:00 2001 From: stepan Date: Tue, 20 Oct 2026 21:47:30 +0200 Subject: [PATCH 05/13] Return not-found for out-of-range Unicode search characters PyUnicode_FindChar passed invalid UCS4 values to chr(), leaving an exception pending and aborting the native fixture. Return -1 without an exception when the character exceeds the Unicode range. --- .../src/tests/unittest_tags/test_capi.txt | 1 + .../modules/cext/PythonCextUnicodeBuiltins.java | 10 ++++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_capi.txt b/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_capi.txt index 44eb00e2e2..abf4de59e0 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_capi.txt +++ b/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_capi.txt @@ -511,6 +511,7 @@ test.test_capi.test_unicode.CAPITest.test_count @ darwin-arm64,linux-aarch64,lin test.test_capi.test_unicode.CAPITest.test_equaltoutf8 @ darwin-arm64,linux-aarch64,linux-x86_64,win32-AMD64 test.test_capi.test_unicode.CAPITest.test_equaltoutf8andsize @ darwin-arm64,linux-aarch64,linux-x86_64,win32-AMD64 test.test_capi.test_unicode.CAPITest.test_find @ darwin-arm64,linux-aarch64,linux-x86_64,win32-AMD64 +test.test_capi.test_unicode.CAPITest.test_findchar @ linux-x86_64 test.test_capi.test_unicode.CAPITest.test_format @ darwin-arm64,linux-aarch64,linux-x86_64,win32-AMD64 test.test_capi.test_unicode.CAPITest.test_from_format @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github test.test_capi.test_unicode.CAPITest.test_fromkindanddata @ darwin-arm64,linux-aarch64,linux-x86_64,win32-AMD64 diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextUnicodeBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextUnicodeBuiltins.java index a3b685d3bf..f18928aa82 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextUnicodeBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextUnicodeBuiltins.java @@ -527,22 +527,28 @@ static Object formatLong(Object val, int alt, int prec, int type, @ImportStatic(PythonCextUnicodeBuiltins.class) abstract static class PyUnicode_FindChar extends CApi5BuiltinNode { @Specialization(guards = {"isString(string) || isStringSubtype(inliningTarget, string, getClassNode, isSubtypeNode)", "direction > 0"}) - static long find(Object string, Object c, long start, long end, @SuppressWarnings("unused") int direction, + static long find(Object string, int c, long start, long end, @SuppressWarnings("unused") int direction, @SuppressWarnings("unused") @Bind Node inliningTarget, @Shared @Cached ChrNode chrNode, @Cached FindNode findNode, @SuppressWarnings("unused") @Shared @Cached GetClassNode getClassNode, @SuppressWarnings("unused") @Shared @Cached IsSubtypeNode isSubtypeNode) { + if (c < 0 || c > Character.MAX_CODE_POINT) { + return -1; + } return (Integer) findNode.execute(null, string, chrNode.execute(null, c), start, end); } @Specialization(guards = {"isString(string) || isStringSubtype(inliningTarget, string, getClassNode, isSubtypeNode)", "direction <= 0"}) - static long find(Object string, Object c, long start, long end, @SuppressWarnings("unused") int direction, + static long find(Object string, int c, long start, long end, @SuppressWarnings("unused") int direction, @SuppressWarnings("unused") @Bind Node inliningTarget, @Shared @Cached ChrNode chrNode, @Cached RFindNode rFindNode, @SuppressWarnings("unused") @Shared @Cached GetClassNode getClassNode, @SuppressWarnings("unused") @Shared @Cached IsSubtypeNode isSubtypeNode) { + if (c < 0 || c > Character.MAX_CODE_POINT) { + return -1; + } return (Integer) rFindNode.execute(null, string, chrNode.execute(null, c), start, end); } From fb6acb297c9203271e8b10f08826066e9bf22b44 Mon Sep 17 00:00:00 2001 From: stepan Date: Tue, 20 Oct 2026 21:47:31 +0200 Subject: [PATCH 06/13] Apply Unicode tailmatch bounds once --- .../src/tests/unittest_tags/test_capi.txt | 1 + .../cext/PythonCextUnicodeBuiltins.java | 33 +++++++------------ 2 files changed, 12 insertions(+), 22 deletions(-) diff --git a/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_capi.txt b/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_capi.txt index abf4de59e0..24045ac0b9 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_capi.txt +++ b/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_capi.txt @@ -526,6 +526,7 @@ test.test_capi.test_unicode.CAPITest.test_interninplace @ darwin-arm64,linux-aar test.test_capi.test_unicode.CAPITest.test_new @ linux-x86_64 test.test_capi.test_unicode.CAPITest.test_readchar @ darwin-arm64,linux-aarch64,linux-x86_64,win32-AMD64 test.test_capi.test_unicode.CAPITest.test_substring @ darwin-arm64,linux-aarch64,linux-x86_64,win32-AMD64 +test.test_capi.test_unicode.CAPITest.test_tailmatch @ linux-x86_64 test.test_capi.test_weakref.CAPIWeakrefTest.test_pyweakref_check @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_capi.test_weakref.CAPIWeakrefTest.test_pyweakref_checkproxy @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_capi.test_weakref.CAPIWeakrefTest.test_pyweakref_checkref @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextUnicodeBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextUnicodeBuiltins.java index f18928aa82..db2c7cd401 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextUnicodeBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextUnicodeBuiltins.java @@ -141,11 +141,10 @@ import com.oracle.graal.python.builtins.objects.str.PString; import com.oracle.graal.python.builtins.objects.str.StringBuiltins; import com.oracle.graal.python.builtins.objects.str.StringBuiltins.EncodeNode; -import com.oracle.graal.python.builtins.objects.str.StringBuiltins.EndsWithNode; import com.oracle.graal.python.builtins.objects.str.StringBuiltins.FindNode; +import com.oracle.graal.python.builtins.objects.str.StringBuiltins.PrefixSuffixNode; import com.oracle.graal.python.builtins.objects.str.StringBuiltins.RFindNode; import com.oracle.graal.python.builtins.objects.str.StringBuiltins.ReplaceNode; -import com.oracle.graal.python.builtins.objects.str.StringBuiltins.StartsWithNode; import com.oracle.graal.python.builtins.objects.str.StringNodes.StringMaterializeNode; import com.oracle.graal.python.lib.PyNumberIndexNode; import com.oracle.graal.python.lib.PyObjectIsTrueNode; @@ -739,32 +738,22 @@ static Object compare(Object left, Object right, NativeToPythonNode.class, long.class, long.class, int.class}) @ImportStatic(PythonCextUnicodeBuiltins.class) abstract static class PyUnicode_Tailmatch extends CApi5BuiltinNode { - @Specialization(guards = {"isAnyString(inliningTarget, string, getClassNode, isSubtypeNode)", "isAnyString(inliningTarget, substring, getClassNode, isSubtypeNode)", "direction > 0"}) - static long tailmatch(Object string, Object substring, long start, long end, @SuppressWarnings("unused") int direction, - @Bind Node inliningTarget, - @Shared @Cached PyObjectLookupAttr lookupAttrNode, - @Shared @Cached PySliceNew sliceNode, - @Shared @Cached CallNode callNode, - @Cached EndsWithNode endsWith, + @Specialization(guards = {"direction > 0", "isAnyString(inliningTarget, string, getClassNode, isSubtypeNode)", "isAnyString(inliningTarget, substring, getClassNode, isSubtypeNode)"}) + static long tailmatchEnd(Object string, Object substring, long start, long end, @SuppressWarnings("unused") int direction, + @SuppressWarnings("unused") @Bind Node inliningTarget, + @Exclusive @Cached PrefixSuffixNode endsWith, @SuppressWarnings("unused") @Shared @Cached GetClassNode getClassNode, @SuppressWarnings("unused") @Shared @Cached IsSubtypeNode isSubtypeNode) { - Object getItemCallable = lookupAttrNode.execute(null, inliningTarget, string, T___GETITEM__); - Object slice = callNode.executeWithoutFrame(getItemCallable, sliceNode.execute(inliningTarget, start, end, PNone.NONE)); - return (boolean) endsWith.execute(null, slice, substring, start, end) ? 1 : 0; + return endsWith.endsWith(string, substring, castLong(start), castLong(end)) ? 1 : 0; } - @Specialization(guards = {"isAnyString(inliningTarget, string, getClassNode, isSubtypeNode)", "isAnyString(inliningTarget, substring, getClassNode, isSubtypeNode)", "direction <= 0"}) - static long tailmatch(Object string, Object substring, long start, long end, @SuppressWarnings("unused") int direction, - @Bind Node inliningTarget, - @Shared @Cached PyObjectLookupAttr lookupAttrNode, - @Shared @Cached PySliceNew sliceNode, - @Shared @Cached CallNode callNode, - @Cached StartsWithNode startsWith, + @Specialization(guards = {"direction <= 0", "isAnyString(inliningTarget, string, getClassNode, isSubtypeNode)", "isAnyString(inliningTarget, substring, getClassNode, isSubtypeNode)"}) + static long tailmatchStart(Object string, Object substring, long start, long end, @SuppressWarnings("unused") int direction, + @SuppressWarnings("unused") @Bind Node inliningTarget, + @Exclusive @Cached PrefixSuffixNode startsWith, @SuppressWarnings("unused") @Shared @Cached GetClassNode getClassNode, @SuppressWarnings("unused") @Shared @Cached IsSubtypeNode isSubtypeNode) { - Object getItemCallable = lookupAttrNode.execute(null, inliningTarget, string, T___GETITEM__); - Object slice = callNode.executeWithoutFrame(getItemCallable, sliceNode.execute(inliningTarget, start, end, PNone.NONE)); - return (boolean) startsWith.execute(null, slice, substring, start, end) ? 1 : 0; + return startsWith.startsWith(string, substring, castLong(start), castLong(end)) ? 1 : 0; } @SuppressWarnings("unused") From 5421418ac91ed1e484b3263abd423a4eff60b229 Mon Sep 17 00:00:00 2001 From: stepan Date: Tue, 20 Oct 2026 21:47:31 +0200 Subject: [PATCH 07/13] Saturate C API Unicode split and replacement counts Valid Py_ssize_t counts overflowed the int-based string nodes. Saturate them when entering PyUnicode_Split and PyUnicode_Replace, preserving unlimited negative counts. --- .../src/tests/unittest_tags/test_capi.txt | 2 ++ .../cext/PythonCextUnicodeBuiltins.java | 36 +++---------------- 2 files changed, 7 insertions(+), 31 deletions(-) diff --git a/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_capi.txt b/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_capi.txt index 24045ac0b9..f835cad4a4 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_capi.txt +++ b/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_capi.txt @@ -525,6 +525,8 @@ test.test_capi.test_unicode.CAPITest.test_getlength @ darwin-arm64,linux-aarch64 test.test_capi.test_unicode.CAPITest.test_interninplace @ darwin-arm64,linux-aarch64,linux-x86_64,win32-AMD64 test.test_capi.test_unicode.CAPITest.test_new @ linux-x86_64 test.test_capi.test_unicode.CAPITest.test_readchar @ darwin-arm64,linux-aarch64,linux-x86_64,win32-AMD64 +test.test_capi.test_unicode.CAPITest.test_replace @ linux-x86_64 +test.test_capi.test_unicode.CAPITest.test_split @ linux-x86_64 test.test_capi.test_unicode.CAPITest.test_substring @ darwin-arm64,linux-aarch64,linux-x86_64,win32-AMD64 test.test_capi.test_unicode.CAPITest.test_tailmatch @ linux-x86_64 test.test_capi.test_weakref.CAPIWeakrefTest.test_pyweakref_check @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextUnicodeBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextUnicodeBuiltins.java index db2c7cd401..1fba7eff25 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextUnicodeBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextUnicodeBuiltins.java @@ -83,7 +83,6 @@ import static com.oracle.graal.python.nodes.StringLiterals.T_STRICT; import static com.oracle.graal.python.nodes.StringLiterals.T_UTF8; import static com.oracle.graal.python.nodes.util.CastToJavaIntLossyNode.castLong; -import static com.oracle.graal.python.runtime.PythonContext.NATIVE_NULL; import static com.oracle.graal.python.runtime.nativeaccess.NativeMemory.NULLPTR; import static com.oracle.graal.python.runtime.nativeaccess.NativeMemory.readByteArrayElement; import static com.oracle.graal.python.util.PythonUtils.TS_ENCODING; @@ -790,36 +789,11 @@ static Object encode(@SuppressWarnings("unused") Object obj, @SuppressWarnings(" @CApiBuiltin(ret = PyObject, args = {PyObject, PyObject, PyObject, Py_ssize_t}, call = Direct, retConversion = PythonToNativeNewRefNode.class, argConversions = {NativeToPythonNode.class, NativeToPythonNode.class, NativeToPythonNode.class, long.class}) - @ImportStatic(PythonCextUnicodeBuiltins.class) abstract static class PyUnicode_Replace extends CApiQuaternaryBuiltinNode { - @Specialization(guards = {"isString(s)", "isString(substr)", "isString(replstr)"}) - static Object replace(Object s, Object substr, Object replstr, long count, - @Shared @Cached ReplaceNode replaceNode) { - return replaceNode.execute(null, s, substr, replstr, count); - } - - @Specialization(guards = {"!isString(s)", "!isString(substr)", "!isString(replstr)", - "isStringSubtype(inliningTarget, s, getClassNode, isSubtypeNode)", - "isStringSubtype(inliningTarget, substr, getClassNode, isSubtypeNode)", - "isStringSubtype(inliningTarget, replstr, getClassNode, isSubtypeNode)"}) - static Object replace(Object s, Object substr, Object replstr, long count, - @SuppressWarnings("unused") @Bind Node inliningTarget, - @Shared @Cached ReplaceNode replaceNode, - @SuppressWarnings("unused") @Shared @Cached GetClassNode getClassNode, - @SuppressWarnings("unused") @Shared @Cached IsSubtypeNode isSubtypeNode) { - return replace(s, substr, replstr, count, replaceNode); - } - - @SuppressWarnings("unused") - @Specialization(guards = {"!isString(s)", "!isString(substr)", "!isString(replstr)", - "!isStringSubtype(inliningTarget, s, getClassNode, isSubtypeNode)", - "!isStringSubtype(inliningTarget, substr, getClassNode, isSubtypeNode)", - "!isStringSubtype(inliningTarget, replstr, getClassNode, isSubtypeNode)"}) + @Specialization static Object replace(Object s, Object substr, Object replstr, long count, - @SuppressWarnings("unused") @Bind Node inliningTarget, - @SuppressWarnings("unused") @Shared @Cached GetClassNode getClassNode, - @SuppressWarnings("unused") @Shared @Cached IsSubtypeNode isSubtypeNode) { - return NATIVE_NULL; + @Cached ReplaceNode replaceNode) { + return replaceNode.execute(null, s, substr, replstr, castLong(count)); } } @@ -1050,9 +1024,9 @@ static int contains(Object haystack, Object needle, NativeToPythonNode.class, long.class}) abstract static class PyUnicode_Split extends CApiTernaryBuiltinNode { @Specialization - static Object split(Object string, Object sep, Object maxsplit, + static Object split(Object string, Object sep, long maxsplit, @Cached StringBuiltins.SplitNode splitNode) { - return splitNode.execute(null, string, sep, maxsplit); + return splitNode.execute(null, string, sep, castLong(maxsplit)); } } From a68c7c3902589009dc5796f696e961df6cb0ace7 Mon Sep 17 00:00:00 2001 From: stepan Date: Tue, 20 Oct 2026 21:47:31 +0200 Subject: [PATCH 08/13] Implement the Unicode rsplit C API using the existing string node --- .../src/tests/unittest_tags/test_capi.txt | 1 + .../modules/cext/PythonCextUnicodeBuiltins.java | 10 ++++++++++ .../builtins/objects/cext/capi/CApiFunction.java | 1 - 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_capi.txt b/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_capi.txt index f835cad4a4..9254db2c73 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_capi.txt +++ b/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_capi.txt @@ -526,6 +526,7 @@ test.test_capi.test_unicode.CAPITest.test_interninplace @ darwin-arm64,linux-aar test.test_capi.test_unicode.CAPITest.test_new @ linux-x86_64 test.test_capi.test_unicode.CAPITest.test_readchar @ darwin-arm64,linux-aarch64,linux-x86_64,win32-AMD64 test.test_capi.test_unicode.CAPITest.test_replace @ linux-x86_64 +test.test_capi.test_unicode.CAPITest.test_rsplit @ linux-x86_64 test.test_capi.test_unicode.CAPITest.test_split @ linux-x86_64 test.test_capi.test_unicode.CAPITest.test_substring @ darwin-arm64,linux-aarch64,linux-x86_64,win32-AMD64 test.test_capi.test_unicode.CAPITest.test_tailmatch @ linux-x86_64 diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextUnicodeBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextUnicodeBuiltins.java index 1fba7eff25..6993912164 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextUnicodeBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextUnicodeBuiltins.java @@ -1030,6 +1030,16 @@ static Object split(Object string, Object sep, long maxsplit, } } + @CApiBuiltin(ret = PyObject, args = {PyObject, PyObject, Py_ssize_t}, call = Direct, retConversion = PythonToNativeNewRefNode.class, argConversions = {NativeToPythonNode.class, + NativeToPythonNode.class, long.class}) + abstract static class PyUnicode_RSplit extends CApiTernaryBuiltinNode { + @Specialization + static Object split(Object string, Object sep, long maxsplit, + @Cached StringBuiltins.RSplitNode splitNode) { + return splitNode.execute(null, string, sep, castLong(maxsplit)); + } + } + @CApiBuiltin(ret = PyObject, args = {Pointer, Py_ssize_t, ConstCharPtr, Int}, call = Ignored, retConversion = PythonToNativeNewRefNode.class, argConversions = {long.class, long.class, CharPtrToPythonNode.class, int.class}) abstract static class GraalPyPrivate_Unicode_DecodeUTF8Stateful extends CApiQuaternaryBuiltinNode { diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiFunction.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiFunction.java index 309f75d45b..8fd495970f 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiFunction.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiFunction.java @@ -905,7 +905,6 @@ public final class CApiFunction { @CApiBuiltin(name = "PyUnicode_IsIdentifier", ret = Int, args = {PyObject}, call = NotImplemented) @CApiBuiltin(name = "PyUnicode_Partition", ret = PyObject, args = {PyObject, PyObject}, call = NotImplemented) @CApiBuiltin(name = "PyUnicode_RPartition", ret = PyObject, args = {PyObject, PyObject}, call = NotImplemented) - @CApiBuiltin(name = "PyUnicode_RSplit", ret = PyObject, args = {PyObject, PyObject, Py_ssize_t}, call = NotImplemented) @CApiBuiltin(name = "PyUnicode_Resize", ret = Int, args = {PyObjectPtr, Py_ssize_t}, call = NotImplemented) @CApiBuiltin(name = "PyUnicode_RichCompare", ret = PyObject, args = {PyObject, PyObject, Int}, call = NotImplemented) @CApiBuiltin(name = "PyUnicode_Splitlines", ret = PyObject, args = {PyObject, Int}, call = NotImplemented) From f792808ff18183a39cc9a7e14c4194ba20c5e5f5 Mon Sep 17 00:00:00 2001 From: stepan Date: Tue, 20 Oct 2026 22:04:20 +0200 Subject: [PATCH 09/13] Handle overflowing FileIO descriptors as filesystem paths --- .../src/tests/unittest_tags/test_fileio.txt | 1 + .../python/builtins/modules/io/IONodes.java | 37 +++++++------------ 2 files changed, 15 insertions(+), 23 deletions(-) diff --git a/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_fileio.txt b/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_fileio.txt index 4109a9ead2..b63ce82d84 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_fileio.txt +++ b/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_fileio.txt @@ -33,6 +33,7 @@ test.test_fileio.COtherFileTests.testBooleanFd @ darwin-arm64,linux-aarch64,linu test.test_fileio.COtherFileTests.testBytesOpen @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_fileio.COtherFileTests.testConstructorHandlesNULChars @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_fileio.COtherFileTests.testInvalidFd @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github +test.test_fileio.COtherFileTests.testInvalidFd_overflow @ linux-x86_64 test.test_fileio.COtherFileTests.testInvalidInit @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_fileio.COtherFileTests.testInvalidModeStrings @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_fileio.COtherFileTests.testModeStrings @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/io/IONodes.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/io/IONodes.java index 05c709668a..9d64a589b7 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/io/IONodes.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/io/IONodes.java @@ -44,7 +44,7 @@ import static com.oracle.graal.python.nodes.ErrorMessages.EMBEDDED_NULL_CHARACTER; import static com.oracle.graal.python.nodes.ErrorMessages.EXPECTED_OBJ_TYPE_S_GOT_P; import static com.oracle.graal.python.nodes.ErrorMessages.INVALID_MODE_S; -import static com.oracle.graal.python.nodes.ErrorMessages.OPENER_RETURNED_D; +import static com.oracle.graal.python.nodes.ErrorMessages.NEG_FILE_DESC; import static com.oracle.graal.python.runtime.exception.PythonErrorType.RuntimeWarning; import static com.oracle.graal.python.runtime.exception.PythonErrorType.TypeError; import static com.oracle.graal.python.runtime.exception.PythonErrorType.ValueError; @@ -57,15 +57,15 @@ import com.oracle.graal.python.builtins.objects.PNone; import com.oracle.graal.python.builtins.objects.bytes.BytesNodes; import com.oracle.graal.python.lib.PyIndexCheckNode; -import com.oracle.graal.python.lib.PyNumberAsSizeNode; +import com.oracle.graal.python.lib.PyLongAsIntNode; import com.oracle.graal.python.nodes.BuiltinNames; import com.oracle.graal.python.nodes.ErrorMessages; -import com.oracle.graal.python.nodes.PGuards; import com.oracle.graal.python.nodes.PRaiseNode; import com.oracle.graal.python.nodes.StringLiterals; import com.oracle.graal.python.nodes.function.builtins.clinic.ArgumentCastNode; import com.oracle.graal.python.nodes.util.CannotCastException; import com.oracle.graal.python.nodes.util.CastToTruffleStringNode; +import com.oracle.graal.python.runtime.exception.PException; import com.oracle.graal.python.runtime.object.PFactory; import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.dsl.Bind; @@ -73,7 +73,6 @@ import com.oracle.truffle.api.dsl.Fallback; import com.oracle.truffle.api.dsl.GenerateCached; import com.oracle.truffle.api.dsl.GenerateInline; -import com.oracle.truffle.api.dsl.ImportStatic; import com.oracle.truffle.api.dsl.NeverDefault; import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.frame.VirtualFrame; @@ -407,7 +406,6 @@ public static CreateIOModeNode create(boolean warnUniversal) { } } - @ImportStatic(PGuards.class) public abstract static class CastOpenNameNode extends ArgumentCastNode { public static final int MAX = Integer.MAX_VALUE; @@ -432,34 +430,27 @@ static int fast(long fd) { return (int) fd; } - @Specialization(guards = {"!isBoolean(nameobj)", "!isInteger(nameobj)"}) + @Fallback static Object generic(VirtualFrame frame, Object nameobj, @Bind Node inliningTarget, @Cached BytesNodes.DecodeUTF8FSPathNode fspath, @Cached PyIndexCheckNode indexCheckNode, - @Cached PyNumberAsSizeNode asSizeNode, + @Cached PyLongAsIntNode asIntNode, @Cached PRaiseNode raiseNode) { if (indexCheckNode.execute(inliningTarget, nameobj)) { - int fd = asSizeNode.executeExact(frame, inliningTarget, nameobj); + int fd; + try { + fd = asIntNode.execute(frame, inliningTarget, nameobj); + } catch (PException e) { + // FileIO treats failed descriptor conversions, including overflow, as paths. + return fspath.execute(frame, nameobj); + } if (fd < 0) { - err(fd, raiseNode); + throw raiseNode.raise(inliningTarget, ValueError, NEG_FILE_DESC); } return fd; - } else { - return fspath.execute(frame, nameobj); } - } - - @Specialization(guards = "fd < 0") - static int err(int fd, - @Bind Node inliningTarget) { - throw PRaiseNode.raiseStatic(inliningTarget, ValueError, OPENER_RETURNED_D, fd); - } - - @Specialization(guards = "fd < 0") - static int err(long fd, - @Bind Node inliningTarget) { - throw PRaiseNode.raiseStatic(inliningTarget, ValueError, OPENER_RETURNED_D, fd); + return fspath.execute(frame, nameobj); } @ClinicConverterFactory From ee40093fd8ed74003efaaf60fa84acf724d7d196 Mon Sep 17 00:00:00 2001 From: stepan Date: Tue, 20 Oct 2026 22:36:44 +0200 Subject: [PATCH 10/13] Reject oversized 16-bit socket byte-order conversions htons() and ntohs() retained the old deprecation warning and silently truncated integers above 65535, while CPython now raises OverflowError. --- .../src/tests/unittest_tags/test_socket.txt | 1 + .../graal/python/builtins/modules/SocketModuleBuiltins.java | 4 +--- .../src/com/oracle/graal/python/nodes/ErrorMessages.java | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_socket.txt b/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_socket.txt index 9749c341ae..d6473c4362 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_socket.txt +++ b/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_socket.txt @@ -67,6 +67,7 @@ test.test_socket.GeneralModuleTests.testIPv6toString @ darwin-arm64,linux-aarch6 test.test_socket.GeneralModuleTests.testInterpreterCrash @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_socket.GeneralModuleTests.testNewAttributes @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_socket.GeneralModuleTests.testNtoH @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github +test.test_socket.GeneralModuleTests.testNtoHErrors @ linux-x86_64 test.test_socket.GeneralModuleTests.testRefCountGetNameInfo @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_socket.GeneralModuleTests.testSendAfterClose @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_socket.GeneralModuleTests.testSendtoErrors @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/SocketModuleBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/SocketModuleBuiltins.java index 1a46b7f817..cb7697280e 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/SocketModuleBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/SocketModuleBuiltins.java @@ -40,7 +40,6 @@ */ package com.oracle.graal.python.builtins.modules; -import static com.oracle.graal.python.builtins.PythonBuiltinClassType.DeprecationWarning; import static com.oracle.graal.python.builtins.PythonBuiltinClassType.OSError; import static com.oracle.graal.python.builtins.PythonBuiltinClassType.OverflowError; import static com.oracle.graal.python.builtins.PythonBuiltinClassType.SocketGAIError; @@ -944,14 +943,13 @@ abstract static class NToHSNode extends PythonUnaryBuiltinNode { static int convert(VirtualFrame frame, Object xObj, @Bind Node inliningTarget, @Cached PyLongAsIntNode asIntNode, - @Cached WarningsModuleBuiltins.WarnNode warnNode, @Cached PRaiseNode raiseNode) { int x = asIntNode.execute(frame, inliningTarget, xObj); if (x < 0) { throw raiseNode.raise(inliningTarget, OverflowError, ErrorMessages.NTOHS_CANT_CONVERT_NEG_PYTHON_INT); } if (x > 0xFFFF) { - warnNode.warnEx(frame, DeprecationWarning, ErrorMessages.NTOH_PYTHON_STRING_TOO_LARGE_TO_CONVERT, 1); + throw raiseNode.raise(inliningTarget, OverflowError, ErrorMessages.NTOHS_PYTHON_INT_TOO_LARGE_TO_CONVERT); } short i = (short) x; if (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN) { diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java index 4cf4e56ba7..643c7bf5f7 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java @@ -868,7 +868,7 @@ public abstract class ErrorMessages { public static final TruffleString RES_O_O_RANGE = tsLiteral("%s result out of range"); public static final TruffleString FLOAT_TO_LARGE_TO_PACK_WITH_S_FMT = tsLiteral("float too large to pack with %s format"); public static final TruffleString CANNOT_UNPACK_NON_ITERABLE = tsLiteral("cannot unpack non-iterable %p object"); - public static final TruffleString NTOH_PYTHON_STRING_TOO_LARGE_TO_CONVERT = tsLiteral("ntohs: Python int too large to convert to 16-bit unsigned integer (The silent truncation is deprecated)"); + public static final TruffleString NTOHS_PYTHON_INT_TOO_LARGE_TO_CONVERT = tsLiteral("ntohs: Python int too large to convert to C 16-bit unsigned integer"); public static final TruffleString ARG_1_MUST_BE_UNICODE_NOT_P = tsLiteral("array() argument 1 must be a unicode character, not %p"); public static final TruffleString LINE_BUFFERING_ISNT_SUPPORTED = tsLiteral("line buffering (buffering=1) isn't supported in binary mode, the default buffer size will be used"); public static final TruffleString UNCLOSED_FILE = tsLiteral("unclosed file %r"); From 0a652ce5cfce0c4dc9cf44a30ae010da1b629a0d Mon Sep 17 00:00:00 2001 From: stepan Date: Tue, 20 Oct 2026 22:49:35 +0200 Subject: [PATCH 11/13] Preserve exception details in unraisable-hook reports C-API unraisable exceptions without a traceback lost their call site, and the default hook truncated qualified class names and printed __main__. Custom-hook failures discarded the exception and wrote to host stderr. Synthesize a current-frame traceback when needed, use qualified type names, and route missing or failing hooks through the builtin default reporter on sys.stderr. Keep this fallback independent of the mutable sys.__unraisablehook__ attribute and ignore failures while reporting. --- .../src/tests/unittest_tags/test_sys.txt | 3 + .../builtins/modules/SysModuleBuiltins.java | 12 ++-- .../builtins/objects/type/TypeNodes.java | 14 ++++- .../python/nodes/WriteUnraisableNode.java | 63 ++++++++++--------- 4 files changed, 57 insertions(+), 35 deletions(-) diff --git a/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_sys.txt b/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_sys.txt index 9dfe0eb2a0..3d90d83a51 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_sys.txt +++ b/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_sys.txt @@ -45,4 +45,7 @@ test.test_sys.SysModuleTest.test_sys_tracebacklimit @ darwin-arm64,linux-aarch64 test.test_sys.SysModuleTest.test_sys_version_info_no_instantiation @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_sys.SysModuleTest.test_thread_info @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_sys.UnraisableHookTest.test_custom_unraisablehook @ darwin-arm64,linux-aarch64,linux-x86_64,win32-AMD64 +test.test_sys.UnraisableHookTest.test_custom_unraisablehook_fail @ linux-x86_64 +test.test_sys.UnraisableHookTest.test_original_unraisablehook @ linux-x86_64 +test.test_sys.UnraisableHookTest.test_original_unraisablehook_exception_qualname @ linux-x86_64 test.test_sys.UnraisableHookTest.test_original_unraisablehook_wrong_type @ darwin-arm64,linux-aarch64,linux-x86_64,win32-AMD64 diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/SysModuleBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/SysModuleBuiltins.java index f1c0a64bff..d7b1ff6f57 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/SysModuleBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/SysModuleBuiltins.java @@ -99,6 +99,7 @@ import static com.oracle.graal.python.nodes.BuiltinNames.T___DISPLAYHOOK__; import static com.oracle.graal.python.nodes.BuiltinNames.T___EXCEPTHOOK__; import static com.oracle.graal.python.nodes.BuiltinNames.T___GRAALPYTHON__; +import static com.oracle.graal.python.nodes.BuiltinNames.T___MAIN__; import static com.oracle.graal.python.nodes.BuiltinNames.T___NOTES__; import static com.oracle.graal.python.nodes.BuiltinNames.T___STDERR__; import static com.oracle.graal.python.nodes.BuiltinNames.T___STDIN__; @@ -202,6 +203,7 @@ import com.oracle.graal.python.builtins.objects.str.StringUtils; import com.oracle.graal.python.builtins.objects.traceback.PTraceback; import com.oracle.graal.python.builtins.objects.traceback.TracebackBuiltins; +import com.oracle.graal.python.builtins.objects.type.TypeNodes; import com.oracle.graal.python.builtins.objects.tuple.PTuple; import com.oracle.graal.python.builtins.objects.tuple.StructSequence; import com.oracle.graal.python.builtins.objects.tuple.TupleBuiltins; @@ -1468,7 +1470,7 @@ static Object setCoroDepth(Object newValue) { "* err_msg: Error message, can be None.\n" + "* object: Object causing the exception, can be None.") @GenerateNodeFactory - abstract static class UnraisableHookNode extends PythonBuiltinNode { + public abstract static class UnraisableHookNode extends PythonBuiltinNode { private void writeUnraisableExc(Node inliningTarget, TracebackBuiltins.GetTracebackFrameNode getTbFrameNode, TracebackBuiltins.MaterializeTruffleStacktraceNode materializeStNode, PythonModule sys, Object out, Object excType, Object excValue, Object excTb, Object errMsg, Object obj) { @@ -1499,8 +1501,7 @@ private void writeUnraisableExc(Node inliningTarget, TracebackBuiltins.GetTraceb TruffleString className; try { - className = getTypeName(excType); - className = classNameNoDot(className); + className = TypeNodes.GetQualNameNode.executeUncached(excType); } catch (PException pe) { className = null; } @@ -1510,7 +1511,7 @@ private void writeUnraisableExc(Node inliningTarget, TracebackBuiltins.GetTraceb fileWriteString(out, T_VALUE_UNKNOWN); } else { moduleName = castToString(v); - if (!moduleName.equalsUncached(T_BUILTINS, TS_ENCODING)) { + if (!moduleName.equalsUncached(T_BUILTINS, TS_ENCODING) && !moduleName.equalsUncached(T___MAIN__, TS_ENCODING)) { fileWriteString(out, moduleName); fileWriteString(out, T_DOT); } @@ -1564,6 +1565,9 @@ Object doit(VirtualFrame frame, PythonModule sys, Object args, private void writeUnraisableExc(Node inliningTarget, TracebackBuiltins.GetTracebackFrameNode getTbFrameNode, TracebackBuiltins.MaterializeTruffleStacktraceNode materializeStNode, PythonModule sys, Object excType, Object excValue, Object excTb, Object errMsg, Object obj) { Object stdErr = objectLookupAttr(sys, T_STDERR); + if (stdErr == PNone.NO_VALUE || stdErr == PNone.NONE) { + return; + } writeUnraisableExc(inliningTarget, getTbFrameNode, materializeStNode, sys, stdErr, excType, excValue, excTb, errMsg, obj); fileFlush(stdErr); } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/type/TypeNodes.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/type/TypeNodes.java index 52aee987f2..010868bed4 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/type/TypeNodes.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/type/TypeNodes.java @@ -164,6 +164,7 @@ import com.oracle.graal.python.builtins.objects.type.TypeNodesFactory.GetIndexedSlotsCountNodeGen; import com.oracle.graal.python.builtins.objects.type.TypeNodesFactory.GetMroStorageNodeGen; import com.oracle.graal.python.builtins.objects.type.TypeNodesFactory.GetNameNodeGen; +import com.oracle.graal.python.builtins.objects.type.TypeNodesFactory.GetQualNameNodeGen; import com.oracle.graal.python.builtins.objects.type.TypeNodesFactory.GetSolidBaseNodeGen; import com.oracle.graal.python.builtins.objects.type.TypeNodesFactory.GetTpNameNodeGen; import com.oracle.graal.python.builtins.objects.type.TypeNodesFactory.GetTypeFlagsNodeGen; @@ -608,6 +609,15 @@ TruffleString doNativeClass(PythonNativeClass obj, public abstract static class GetQualNameNode extends Node { public abstract TruffleString execute(Node inliningTarget, Object type); + public static TruffleString executeUncached(Object type) { + return GetQualNameNodeGen.getUncached().execute(null, type); + } + + @Specialization + static TruffleString getQualName(PythonBuiltinClassType clazz) { + return clazz.getName(); + } + @Specialization static TruffleString getQualName(PythonManagedClass clazz) { return clazz.getQualName(); @@ -617,7 +627,7 @@ static TruffleString getQualName(PythonManagedClass clazz) { static TruffleString getQualName(Node inliningTarget, PythonAbstractNativeObject clazz, @Cached(inline = false) GetTypeFlagsNode getTypeFlagsNode, @Cached(inline = false) CStructAccess.ReadObjectNode readObjectNode, - @Cached(inline = false) CStructAccess.ReadCharPtrNode readCharPtrNode, + @Cached GetNameNode getNameNode, @Cached CastToTruffleStringNode cast) { assert IsTypeNode.executeUncached(clazz); long flags = getTypeFlagsNode.execute(clazz); @@ -629,7 +639,7 @@ static TruffleString getQualName(Node inliningTarget, PythonAbstractNativeObject throw CompilerDirectives.shouldNotReachHere("Cannot cast ht_qualname to string"); } } else { - return readCharPtrNode.readFromObj(clazz, PyTypeObject__tp_name); + return getNameNode.execute(inliningTarget, clazz); } } } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/WriteUnraisableNode.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/WriteUnraisableNode.java index d201ac7d4d..07db4f8bec 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/WriteUnraisableNode.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/WriteUnraisableNode.java @@ -40,23 +40,24 @@ */ package com.oracle.graal.python.nodes; -import static com.oracle.graal.python.util.PythonUtils.TS_ENCODING; - -import java.io.IOException; +import static com.oracle.graal.python.util.PythonUtils.tsLiteral; import com.oracle.graal.python.PythonLanguage; import com.oracle.graal.python.builtins.modules.SysModuleBuiltins; +import com.oracle.graal.python.builtins.modules.SysModuleBuiltinsFactory.UnraisableHookNodeFactory; import com.oracle.graal.python.builtins.objects.PNone; import com.oracle.graal.python.builtins.objects.exception.ExceptionNodes; +import com.oracle.graal.python.builtins.objects.frame.PFrame; import com.oracle.graal.python.builtins.objects.module.PythonModule; import com.oracle.graal.python.lib.PyObjectLookupAttr; import com.oracle.graal.python.nodes.call.CallNode; +import com.oracle.graal.python.nodes.frame.ReadFrameNode; import com.oracle.graal.python.nodes.object.GetClassNode; +import com.oracle.graal.python.runtime.CallerFlags; import com.oracle.graal.python.runtime.PythonContext; import com.oracle.graal.python.runtime.exception.PException; import com.oracle.graal.python.runtime.object.PFactory; -import com.oracle.truffle.api.CompilerDirectives; -import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import com.oracle.graal.python.util.PythonUtils; import com.oracle.truffle.api.dsl.Bind; import com.oracle.truffle.api.dsl.Cached; import com.oracle.truffle.api.dsl.GenerateInline; @@ -70,6 +71,7 @@ @GenerateUncached @GenerateInline(false) // footprint reduction 84 -> 68 public abstract class WriteUnraisableNode extends Node { + private static final TruffleString T_HOOK_FAILED = tsLiteral("Exception ignored in sys.unraisablehook"); public final void execute(VirtualFrame frame, Object exception, TruffleString message, Object object) { executeInternal(frame, exception, message, object); @@ -89,41 +91,44 @@ static void writeUnraisable(VirtualFrame frame, Object exception, TruffleString @Cached GetClassNode getClassNode, @Bind PythonLanguage language, @Cached ExceptionNodes.GetTracebackNode getTracebackNode, - @Cached TruffleString.CopyToByteArrayNode copyToByteArrayNode) { + @Cached ExceptionNodes.SetTracebackNode setTracebackNode, + @Cached ReadFrameNode readFrameNode) { PythonContext context = PythonContext.get(inliningTarget); + PythonModule sysModule = context.getSysModule(); + Object unraisablehook = PNone.NONE; try { - PythonModule sysModule = context.getSysModule(); - Object unraisablehook = lookup.execute(frame, inliningTarget, sysModule, BuiltinNames.T_UNRAISABLEHOOK); + unraisablehook = lookup.execute(frame, inliningTarget, sysModule, BuiltinNames.T_UNRAISABLEHOOK); Object exceptionType = getClassNode.execute(inliningTarget, exception); Object traceback = getTracebackNode.execute(inliningTarget, exception); + if (traceback == PNone.NONE) { + PFrame currentFrame = readFrameNode.getCurrentPythonFrame(frame, CallerFlags.NEEDS_LASTI); + if (currentFrame != null) { + currentFrame.getRef().markAsEscaped(); + traceback = PFactory.createTracebackWithLasti(language, currentFrame, currentFrame.getLine(), currentFrame.getLasti(), null); + setTracebackNode.execute(inliningTarget, exception, traceback); + } + } Object hookArguments = PFactory.createStructSeq(language, SysModuleBuiltins.UNRAISABLEHOOK_ARGS_DESC, exceptionType, exception, traceback, message != null ? message : PNone.NONE, object != null ? object : PNone.NONE); - callNode.execute(frame, unraisablehook, hookArguments); - } catch (PException e) { - ignoreException(context, message, copyToByteArrayNode); - } - } - - @TruffleBoundary - private static void ignoreException(PythonContext context, TruffleString message, TruffleString.CopyToByteArrayNode copyToByteArrayNode) { - try { - if (message != null) { - byte[] data = new byte[message.byteLength(TS_ENCODING)]; - copyToByteArrayNode.execute(message, 0, data, 0, data.length, TS_ENCODING); - context.getEnv().err().write(data); + if (unraisablehook == PNone.NONE || unraisablehook == PNone.NO_VALUE) { + callNode.execute(frame, PythonUtils.createMethod(language, null, UnraisableHookNodeFactory.getInstance(), null, 0), sysModule, hookArguments); } else { - context.getEnv().err().write(ignoredMsg()); + callNode.execute(frame, unraisablehook, hookArguments); + } + } catch (PException e) { + Object hookException = e.getEscapedException(); + Object hookExceptionType = getClassNode.execute(inliningTarget, hookException); + Object hookTraceback = getTracebackNode.execute(inliningTarget, hookException); + try { + Object hookArguments = PFactory.createStructSeq(language, SysModuleBuiltins.UNRAISABLEHOOK_ARGS_DESC, hookExceptionType, hookException, hookTraceback, + T_HOOK_FAILED, unraisablehook); + callNode.execute(frame, PythonUtils.createMethod(language, null, UnraisableHookNodeFactory.getInstance(), null, 0), sysModule, hookArguments); + } catch (PException ignored) { + // Reporting an unraisable exception must not raise another exception. } - } catch (IOException ioException) { - throw CompilerDirectives.shouldNotReachHere(); } } - @TruffleBoundary - private static byte[] ignoredMsg() { - return "Exception ignored in sys.unraisablehook".getBytes(); - } - public static WriteUnraisableNode getUncached() { return WriteUnraisableNodeGen.getUncached(); } From 4504e2e0a6b7379c007907bd5cdd8c2a33a2185e Mon Sep 17 00:00:00 2001 From: stepan Date: Tue, 20 Oct 2026 23:02:39 +0200 Subject: [PATCH 12/13] Implement the traceback-print C API using the shared formatter --- .../tests/unittest_tags/test_traceback.txt | 2 ++ .../cext/PythonCextTracebackBuiltins.java | 21 +++++++++++++++++++ .../objects/cext/capi/CApiFunction.java | 1 - .../graal/python/lib/PyTraceBackPrint.java | 5 +++++ 4 files changed, 28 insertions(+), 1 deletion(-) diff --git a/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_traceback.txt b/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_traceback.txt index fd31fedaf1..bee4db89d3 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_traceback.txt +++ b/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_traceback.txt @@ -154,6 +154,8 @@ test.test_traceback.TestTracebackFormat.test_format_stack @ darwin-arm64,linux-a test.test_traceback.TestTracebackFormat.test_print_exception_bad_type_python @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_traceback.TestTracebackFormat.test_print_stack @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_traceback.TestTracebackFormat.test_stack_format @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github +test.test_traceback.TestTracebackFormat.test_traceback_format @ linux-x86_64 +test.test_traceback.TestTracebackFormat.test_traceback_format_with_cleared_frames @ linux-x86_64 test.test_traceback.TracebackCases.test_base_exception @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_traceback.TracebackCases.test_caret @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_traceback.TracebackCases.test_encoded_file @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextTracebackBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextTracebackBuiltins.java index 745e41fb2e..ef1e70a94b 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextTracebackBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextTracebackBuiltins.java @@ -43,12 +43,14 @@ import static com.oracle.graal.python.annotations.ArgDescriptor.ConstCharPtr; import static com.oracle.graal.python.annotations.ArgDescriptor.Int; import static com.oracle.graal.python.annotations.ArgDescriptor.PyFrameObject; +import static com.oracle.graal.python.annotations.ArgDescriptor.PyObject; import static com.oracle.graal.python.annotations.ArgDescriptor.Void; import static com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiCallPath.Direct; import static com.oracle.graal.python.runtime.nativeaccess.NativeMemory.NULLPTR; import com.oracle.graal.python.PythonLanguage; import com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiBuiltin; +import com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiBinaryBuiltinNode; import com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiTernaryBuiltinNode; import com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiUnaryBuiltinNode; import com.oracle.graal.python.builtins.objects.PNone; @@ -59,10 +61,13 @@ import com.oracle.graal.python.builtins.objects.exception.PBaseException; import com.oracle.graal.python.builtins.objects.frame.PFrame; import com.oracle.graal.python.builtins.objects.traceback.PTraceback; +import com.oracle.graal.python.builtins.objects.traceback.TracebackBuiltins; +import com.oracle.graal.python.lib.PyTraceBackPrint; import com.oracle.graal.python.nodes.frame.ReadFrameNode; import com.oracle.graal.python.runtime.CallerFlags; import com.oracle.graal.python.runtime.PythonContext; import com.oracle.graal.python.runtime.object.PFactory; +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.dsl.Bind; import com.oracle.truffle.api.dsl.Cached; import com.oracle.truffle.api.dsl.Specialization; @@ -71,6 +76,22 @@ public final class PythonCextTracebackBuiltins { + @CApiBuiltin(ret = Int, args = {PyObject, PyObject}, call = Direct, retConversion = int.class, argConversions = {NativeToPythonNode.class, NativeToPythonNode.class}) + abstract static class PyTraceBack_Print extends CApiBinaryBuiltinNode { + @TruffleBoundary + @Specialization + static int print(Object traceback, Object file, + @Bind Node inliningTarget, + @Bind PythonContext context, + @Cached TracebackBuiltins.GetTracebackFrameNode getTbFrameNode, + @Cached TracebackBuiltins.MaterializeTruffleStacktraceNode materializeStNode) { + if (traceback != PNone.NO_VALUE) { + PyTraceBackPrint.print(inliningTarget, getTbFrameNode, materializeStNode, context.getSysModule(), file, traceback, false, false, 0, null); + } + return 0; + } + } + @CApiBuiltin(ret = Void, args = {ConstCharPtr, ConstCharPtr, Int}, call = Direct, retConversion = void.class, argConversions = {CharPtrToPythonNode.class, CharPtrToPythonNode.class, int.class}) abstract static class _PyTraceback_Add extends CApiTernaryBuiltinNode { diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiFunction.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiFunction.java index 8fd495970f..64237269a0 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiFunction.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiFunction.java @@ -857,7 +857,6 @@ public final class CApiFunction { @CApiBuiltin(name = "PyThread_acquire_lock", ret = Int, args = {PY_THREAD_TYPE_LOCK, Int}, call = CImpl) @CApiBuiltin(name = "PyThread_allocate_lock", ret = PY_THREAD_TYPE_LOCK, args = {}, call = CImpl) @CApiBuiltin(name = "PyThread_release_lock", ret = Void, args = {PY_THREAD_TYPE_LOCK}, call = CImpl) - @CApiBuiltin(name = "PyTraceBack_Print", ret = Int, args = {PyObject, PyObject}, call = NotImplemented) @CApiBuiltin(name = "PyType_ClearCache", ret = UNSIGNED_INT, args = {}, call = NotImplemented) @CApiBuiltin(name = "PyType_GetFullyQualifiedName", ret = PyObject, args = {PyTypeObject}, call = CImpl) @CApiBuiltin(name = "PyType_SUPPORTS_WEAKREFS", ret = Int, args = {PyTypeObject}, call = CImpl) diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyTraceBackPrint.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyTraceBackPrint.java index d5c53cab02..c01742533d 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyTraceBackPrint.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyTraceBackPrint.java @@ -79,6 +79,7 @@ import com.oracle.graal.python.runtime.exception.PException; import com.oracle.graal.python.util.OverflowException; import com.oracle.truffle.api.CompilerAsserts; +import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.TruffleFile; import com.oracle.truffle.api.frame.VirtualFrame; @@ -125,6 +126,10 @@ public static void fileWriteString(Object file, String data) { } public static void fileWriteString(VirtualFrame frame, Node inliningTarget, Object file, TruffleString data, PyObjectGetAttr getAttr, CallNode callNode) { + if (file == PNone.NO_VALUE) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + throw PRaiseNode.getUncached().raiseBadInternalCall(inliningTarget); + } final Object writeMethod = getAttr.execute(frame, inliningTarget, file, T_WRITE); callNode.execute(frame, writeMethod, data); } From bb0beb531849d61a4c910da8bbc78ead9d9ed997 Mon Sep 17 00:00:00 2001 From: stepan Date: Thu, 22 Oct 2026 18:08:01 +0200 Subject: [PATCH 13/13] Document verbatim CPython source and change marker convention --- .../com.oracle.graal.python.cext/AGENTS.md | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/graalpython/com.oracle.graal.python.cext/AGENTS.md b/graalpython/com.oracle.graal.python.cext/AGENTS.md index 2fec21bb92..f0e2b6874a 100644 --- a/graalpython/com.oracle.graal.python.cext/AGENTS.md +++ b/graalpython/com.oracle.graal.python.cext/AGENTS.md @@ -15,6 +15,26 @@ C headers and native code implementing CPython C-API compatibility plus adapted - Follow CPython invariants in headers (examples: do not nest `Py_BEGIN_ALLOW_THREADS`; “never mix allocators” warnings). - Large unicode databases/headers are often generated (e.g., `unicodename_db.h`, `unicodedata_db.h`): avoid hand edits. +## CPYTHON SOURCE CONVENTION + +In files derived from CPython under `src/`, `include/`, and `modules/`, all content +is verbatim CPython source unless explicitly marked with a `// GraalPy change:` +comment. This includes comments, whitespace, declarations, and generated Clinic +wrappers, not just executable statements. + +- When restoring upstream code, copy the complete snippet exactly, including its + comments and blank lines. Preserve upstream function order where possible. +- Mark every intentional deviation with `// GraalPy change:` immediately before + the changed code, and briefly explain why GraalPy needs it. Make the scope clear; + use an end marker for a multi-function block. For a wholly custom implementation + in a CPython-derived file, explicitly state that the marker covers the whole file. +- Mark disabled upstream code and replacement stubs on their preprocessor guards, + for example `#if 0 // GraalPy change: ...` and `#endif // GraalPy change: ...`. +- When restoring upstream behavior, remove obsolete change markers only where the + resulting content is verbatim upstream. Keep markers for remaining adaptations. +- Apply this convention to CPython-derived files; GraalPy-only build configuration + and Java implementations do not need CPython change markers. + ## ANTI-PATTERNS - **NEVER** nest `Py_BEGIN_ALLOW_THREADS` blocks (see `include/ceval.h`). - Never mix `PyMem_*` / `PyObject_*` allocators with platform `malloc` family.