Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 47 additions & 5 deletions graalpython/com.oracle.graal.python.cext/src/longobject.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* Copyright (c) 2018, 2025, Oracle and/or its affiliates.
/* Copyright (c) 2018, 2026, Oracle and/or its affiliates.
* Copyright (C) 1996-2017 Python Software Foundation
*
* Licensed under the PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
Expand Down Expand Up @@ -2453,6 +2453,7 @@ PyLong_FromString(const char *str, char **pend, int base)
{
int sign = 1, error_if_nonzero = 0;
const char *start, *orig_str = str;
int orig_base = base;
PyObject *z = NULL;
PyObject *strobj;
Py_ssize_t slen;
Expand Down Expand Up @@ -2517,7 +2518,6 @@ PyLong_FromString(const char *str, char **pend, int base)
long long result = strtoll(str, &endptr, base);
if (error_if_nonzero && result != 0) {
// let upcall handle the error reporting
base = 0;
break;
}
// POSIX.1-2008: strtoll must not set errno on success, and set
Expand All @@ -2538,11 +2538,53 @@ PyLong_FromString(const char *str, char **pend, int base)
}
}
if (!z) {
z = GraalPyPrivate_Long_FromString((char *)orig_str, base);
if (z) {
// TODO: we should probably set the **pend out argument
z = GraalPyPrivate_Long_FromString(orig_str, orig_base);
if (!z && pend) {
/*
* We have an exception already, but we need to redo the validation
* to compute pend. Adapted from long_from_string_base
*/
*pend = (char *)str;
const char *end, *p;
char prev = 0;
start = p = str;
/* Leading underscore not allowed. */
if (*start == '_') {
return NULL;
}
/* Verify all characters are digits and underscores. */
while (_PyLong_DigitValue[Py_CHARMASK(*p)] < base || *p == '_') {
if (*p == '_') {
/* Double underscore not allowed. */
if (prev == '_') {
*pend = (char *)(p - 1);
return NULL;
}
}
prev = *p;
++p;
}
/* Trailing underscore not allowed. */
if (prev == '_') {
*pend = (char *)(p - 1);
return NULL;
}
end = p;
*pend = (char *)end;
/* Reject empty strings */
if (start == end) {
return NULL;
}
/* Allow only trailing whitespace after `end` */
while (*p && Py_ISSPACE(*p)) {
p++;
}
*pend = (char *)p;
}
}
if (z && pend) {
*pend = (char *)(str + strlen(str));
}
return z;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@

from . import CPyExtTestCase, CPyExtFunction, CPyExtFunctionOutVars, unhandled_error_compare


int_bits = struct.calcsize('i') * 8
max_int = 2 ** (int_bits - 1) - 1
min_int = -2 ** (int_bits - 1)
Expand Down Expand Up @@ -133,6 +132,22 @@ def _reference_is_compact(args):
return 1 if -2147483648 <= n <= 2147483647 else 0


def _reference_long_from_string(args):
num, base = args
if not num.strip():
return ValueError, len(num)
try:
return int(num, base), len(num)
except Exception as e:
for i in range(len(num)):
try:
int(num[:i + 1], base)
except Exception:
if num[0] == '0' and base == 0:
i = len(num)
return type(e), i


class DummyNonInt():
pass

Expand Down Expand Up @@ -360,21 +375,39 @@ class TestPyLong(CPyExtTestCase):
)

test_PyLong_FromString = CPyExtFunction(
lambda args: int(args[0], args[1]),
_reference_long_from_string,
lambda: (
("00", 0),
("03", 0),
("0003", 0),
(" 12 ", 10),
(" 12abg13 ", 22),
("12", 0),
("12321321", 0),
("0x132f1", 0),
("0x132132ff213213213231", 0),
("13123441234123423412341234123412341234124312341234213213213213213231", 0),
("1312344123412342341234123412341234123x4124312341234213213213213213231", 0),
("", 0),
(" ", 0),
("123 ", 0),
("-123 ", 0),
("123 x", 0),
("x", 0),
("_1", 0),
("1_", 0),
("1_1", 0),
("1__1", 0),
),
code='''PyObject* wrap_PyLong_FromString(const char* str, int base) {
char* pend;
return PyLong_FromString(str, &pend, base);
PyObject* val = PyLong_FromString(str, &pend, base);
if (!val) {
PyObject* exc = PyErr_GetRaisedException();
val = Py_NewRef(Py_TYPE(exc));
Py_DECREF(exc);
}
return Py_BuildValue("OL", val, pend - str);
}''',
callfunction="wrap_PyLong_FromString",
resultspec="O",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
import static com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiCallPath.Direct;
import static com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiCallPath.Ignored;
import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.CONST_UNSIGNED_CHAR_PTR;
import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.CharPtrAsTruffleString;
import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.ConstCharPtrAsTruffleString;
import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.ConstPyLongObject;
import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.Int;
import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.LONG_LONG;
Expand Down Expand Up @@ -74,8 +74,8 @@
import com.oracle.graal.python.builtins.objects.cext.capi.CExtNodes.CastToNativeLongNode;
import com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor;
import com.oracle.graal.python.builtins.objects.cext.common.CExtCommonNodes.ConvertPIntToPrimitiveNode;
import com.oracle.graal.python.builtins.objects.cext.common.CExtCommonNodes.TransformPExceptionToNativeCachedNode;
import com.oracle.graal.python.builtins.objects.cext.common.CExtCommonNodesFactory.ConvertPIntToPrimitiveNodeGen;
import com.oracle.graal.python.builtins.objects.cext.common.CExtCommonNodes.TransformPExceptionToNativeCachedNode;
import com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess;
import com.oracle.graal.python.builtins.objects.ints.IntBuiltins;
import com.oracle.graal.python.builtins.objects.ints.IntNodes;
Expand Down Expand Up @@ -221,7 +221,7 @@ static Object fromDouble(double d,
}
}

@CApiBuiltin(ret = PyObjectTransfer, args = {CharPtrAsTruffleString, Int}, call = Ignored)
@CApiBuiltin(ret = PyObjectTransfer, args = {ConstCharPtrAsTruffleString, Int}, call = Ignored)
abstract static class GraalPyPrivate_Long_FromString extends CApiBinaryBuiltinNode {

@Specialization
Expand Down
Loading