0

This piece of code is from the Python C-API reference:

item = PyLong_FromLong(0L);
if (item == NULL)
    goto error;

Assuming the interpreter is CPython, the memory for the Python 0 object is already allocated, so what could go wrong there? Reading the source code for PyLong_FromLong, for small-integer values it would immediately return get_small_int((sdigit)0L). The get_small_int function is really very simple:

static PyObject *
get_small_int(sdigit ival)
{
    assert(IS_SMALL_INT(ival));
    PyThreadState *tstate = _PyThreadState_GET();
    PyObject *v = (PyObject*)tstate->interp->small_ints[ival + NSMALLNEGINTS];
    Py_INCREF(v);
    return v;
}

The assertion on the first line won't fail because PyLong_FromLong already verified it. _PyThreadState_GET() is a macro that, according to a comment next to its definition, is unsafe: it does not check for error and it can return NULL. This might look like a source of failure but note that tstate->interp is accessed normally, which would segfault the interpreter if the macro had returned NULL. After that, the wanted reference to the Python 0 object is Py_INCREF'd and returned to the original caller of PyLong_FromLong(0L).

Did I understant correctly that CPython's PyLong_FromLong can't fail for small-integer arguments, or did I miss something? Also, just for completeness, can extension modules written in C can be used from other interpreters, or when writing them I can assume that they will be dealing with CPython?

fonini
  • 2,989
  • 3
  • 21
  • 39
  • 1
    Oh it may be trivial to [make this assumption fail with `ctypes`](https://stackoverflow.com/questions/24060991/evil-ctypes-hack-in-python). The question mentions 32-bit platforms, but on a Python 3 interpreter running on a 64-bit platform you may try something like `ctypes.c_long.from_address(id(1) + 24).value = 2` to manipulate the underlying value of `1`, and then do `1 == 2` and the interpreter may segfault. – metatoaster Feb 18 '20 at 23:58
  • Nice, though this is something I would definitely assume that didn't happen hehehe. – fonini Feb 19 '20 at 00:14
  • 2
    PyPy attempts to define a C API compatibility layer (as does Jython I think) so the assumption that you're always dealing with CPython may not be true. But may still be a reasonable assumption to take anyway. I think I'd just a bit wary of writing code based on exactly what is considered a "small integer" – DavidW Feb 19 '20 at 09:23

1 Answers1

1

Yes, you are absolutely right, there would never be any erro here, the small ints are preallocated, so PyLong_FromLong(0L); never fails.

But why still test if item was a NULL? As I see, this just follows a partten:

  • new an PyLongObject;
  • then test if successed.

The PyLong_FromLong does some optomization by caching small ints, but it still returns a New Object mostly and logicaly, though 0L does not. And as we a function caller we should not rely on the inner cache strtegy. So leave some code to check would make sense and safer.