From a5e7761ac3a37bb0952a406efae8a946c404dbd5 Mon Sep 17 00:00:00 2001 From: Jeongseop Lim Date: Tue, 20 Oct 2026 03:55:48 +0900 Subject: [PATCH] Skip the codec lookup when decoding empty bytes BytesCommonBuiltins.DecodeNode, which backs bytes.decode, bytearray.decode and str(obj, encoding), always went through the codec, so b''.decode('bogus') and str(b'', 'bogus') raised LookupError. CPython's bytes and bytearray decode call PyUnicode_FromEncodedObject, which returns '' for empty input without looking up the codec outside dev mode. GraalPy's own PyUnicodeFromEncodedObject already does this for the C API; DecodeNode now has the same shortcut. Signed-off-by: Jeongseop Lim --- .../src/tests/test_bytes.py | 10 ++++++++++ .../builtins/objects/bytes/BytesCommonBuiltins.java | 6 ++++++ 2 files changed, 16 insertions(+) diff --git a/graalpython/com.oracle.graal.python.test/src/tests/test_bytes.py b/graalpython/com.oracle.graal.python.test/src/tests/test_bytes.py index 7a74fd9518..e4ac58ade1 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/test_bytes.py +++ b/graalpython/com.oracle.graal.python.test/src/tests/test_bytes.py @@ -89,6 +89,16 @@ def __bytes__(self): assert_raises(TypeError, bytes, errors="strict") +def test_decode_empty_skips_codec_lookup(): + assert str(b'', 'x') == '' + assert str(bytearray(), 'x', 'y') == '' + assert str(memoryview(b''), 'x') == '' + assert b''.decode('x') == '' + assert bytearray().decode('x') == '' + assert_raises(LookupError, str, b'a', 'x') + assert_raises(TypeError, str, 1, 'x') + + def test_from_ssize(): assert bytes(0) == b'' assert bytes(1) == b'\x00' diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/bytes/BytesCommonBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/bytes/BytesCommonBuiltins.java index 5920ee72a9..a506ecbccb 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/bytes/BytesCommonBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/bytes/BytesCommonBuiltins.java @@ -203,9 +203,15 @@ protected ArgumentClinicProvider getArgumentClinic() { @Specialization static Object decode(VirtualFrame frame, Object self, TruffleString encoding, TruffleString errors, @Bind Node inliningTarget, + @Cached GetBytesStorage getBytesStorage, + @Cached InlinedConditionProfile emptyStringProfile, @Cached CodecsModuleBuiltins.DecodeNode decodeNode, @Cached PyUnicodeCheckNode unicodeCheckNode, @Cached PRaiseNode raiseNode) { + // like CPython's PyUnicode_FromEncodedObject: no codec lookup for empty input + if (emptyStringProfile.profile(inliningTarget, getBytesStorage.execute(inliningTarget, self).length() == 0)) { + return T_EMPTY_STRING; + } Object result = decodeNode.executeWithStrings(frame, self, encoding, errors); if (!unicodeCheckNode.execute(inliningTarget, result)) { throw raiseNode.raise(inliningTarget, TypeError, DECODER_RETURNED_P_INSTEAD_OF_BYTES, encoding, result);