Skip to content

_operator: methodcaller_clear has the wrong signature for the tp_clear slot (returns void, not int) #156762

Description

@ashm-dev

Bug report

Bug description:

methodcaller_clear in Modules/_operator.c is declared as returning void, but it is
installed in the Py_tp_clear slot, which requires inquiry
(typedef int (*inquiry)(PyObject *);, Include/object.h:285).

/* Modules/_operator.c:1743 */
static void
methodcaller_clear(PyObject *op)
{
    methodcallerobject *mc = methodcallerobject_CAST(op);
    Py_CLEAR(mc->name);
    Py_CLEAR(mc->args);
    Py_CLEAR(mc->kwds);
    Py_CLEAR(mc->vectorcall_args);
    Py_CLEAR(mc->vectorcall_kwnames);
}

/* Modules/_operator.c:1928 */
static PyType_Slot methodcaller_type_slots[] = {
    ...
    {Py_tp_clear, methodcaller_clear},
    ...
};

The mismatch is not diagnosed at compile time because PyType_Slot.pfunc is void *
(Include/object.h:327), so the function pointer converts implicitly and is later cast
back to inquiry.

UBSan with -fsanitize=function reports it whenever a methodcaller instance is cleared
by the cyclic GC:

Python/gc_free_threading.c:1761:24: runtime error: call to function methodcaller_clear through pointer to incorrect function type 'int (*)(struct _object *)'
Modules/_operator.c:1745: note: methodcaller_clear defined here

The trace above is from a free-threaded build. The defect is in the slot registration, not in
either GC, so it is present in both builds and both reach the bad call the same way:

  • default build: Python/gc.c:1105
  • free-threaded build: Python/gc_free_threading.c:1761

Impact

Benign on the mainstream targets I checked. Both GC implementations discard the result
((void) clear(op); at Python/gc.c:1105 and Python/gc_free_threading.c:1761), and
operator.methodcaller does not set Py_TPFLAGS_BASETYPE, so the return baseclear(self);
path in subtype_clear (Objects/typeobject.c:2751) cannot reach it.

It is still an indirect call through an incompatible function type, i.e. undefined behaviour,
and it matters for:

  • builds using Clang CFI (-fsanitize=cfi-icall), where an indirect-call type mismatch aborts;
  • WebAssembly targets, where indirect call signatures are checked by the runtime;
  • UBSan builds in general, where it is persistent noise that hides other findings.

methodcaller_traverse immediately below (Modules/_operator.c:1764) uses the correct
static int form, so this looks like an oversight from the slot-signature cleanup.

I grepped every tp_clear slot registration under Modules/, Objects/ and Python/:
methodcaller_clear is the only one with a void return type.

Reproducer

The wrong slot signature is present in every build; a sanitizer build is only what makes it
visible. Configured with:

CC=clang CXX=clang++ LDFLAGS='-fuse-ld=lld' ./configure --with-address-sanitizer --with-undefined-behavior-sanitizer --with-pydebug --disable-gil

--with-undefined-behavior-sanitizer adds -fsanitize=undefined (configure.ac:3528), and
Clang includes function in that group, which is the check that fires here.

Then:

./python -c "import gc, operator
class C: pass
c = C()
c.m = operator.methodcaller('foo', c)
del c
gc.collect()"

The methodcaller keeps a reference to c through its args tuple, so the cycle
c -> m -> args -> c reaches tp_clear and the diagnostic fires.

Suggested fix

-static void
+static int
 methodcaller_clear(PyObject *op)
 {
     methodcallerobject *mc = methodcallerobject_CAST(op);
     Py_CLEAR(mc->name);
     Py_CLEAR(mc->args);
     Py_CLEAR(mc->kwds);
     Py_CLEAR(mc->vectorcall_args);
     Py_CLEAR(mc->vectorcall_kwnames);
+    return 0;
 }

methodcaller_dealloc (Modules/_operator.c:1759) already calls it as
(void)methodcaller_clear(op); and needs no change.

Environment

  • CPython main, commit b93576a2b66
  • Linux x86-64
  • Clang 22.1.8, lld
  • Free-threaded debug build with ASan and UBSan:
CC=clang CXX=clang++ LDFLAGS='-fuse-ld=lld' ./configure --with-address-sanitizer --with-undefined-behavior-sanitizer --with-pydebug --disable-gil

CPython versions tested on:

3.16, CPython main branch, 3.15

Operating systems tested on:

Linux

Linked PRs

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    3.14bugs and security fixes3.15pre-release feature fixes, bugs and security fixes3.16new features, bugs and security fixesextension-modulesC modules in the Modules dirtype-bugAn unexpected behavior, bug, or error

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions