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

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