Skip to content

[BUG] Null dereference with multiple assignments in single statement #7937

Closed

Description

@LordAro

Describe the bug

After upgrading to 3.3.0, I've started getting crashes with some compiled python code.

After compiling with debug symbols, I find that the crash is centred on the following C code:

  /* "lib/foo.py":242
 *             <snip>
 *
 *         part = offset = 0             # <<<<<<<<<<<<<<
 *         <snip>
 *         <snip>
*/
  __Pyx_INCREF(__pyx_mstate_global->__pyx_int_0);
  __Pyx_XDECREF_SET(__pyx_v_part, __pyx_mstate_global->__pyx_int_0);

  __pyx_v_offset = ((PyObject*)0);

which then gets used later on, and predictably, crashes horribly

Code to reproduce the behaviour:

Minimal reproducer:

def do_stuff():
    mapping: dict[int, int] = {}
    for line_number, _content in enumerate([]):
        part = line_number

    part = offset = 0

    for address in range(0, 1):
        if line_number := mapping.get(address):
            part = line_number
            offset = part
            print(offset)
uv run cython -o test.c test.py

And have a look at the generated c code.

(and obviously if you were to attempt to run this, it would segfault due to the null dereference)

Expected behaviour

Code should work as expected and not try to dereference a null object. Further reductions get the expected

  __Pyx_INCREF(__pyx_mstate_global->__pyx_int_0);
  __Pyx_XDECREF_SET(__pyx_v_part, __pyx_mstate_global->__pyx_int_0);
  __Pyx_INCREF(__pyx_mstate_global->__pyx_int_0);
  __pyx_v_offset = __pyx_mstate_global->__pyx_int_0;

or

  __Pyx_INCREF(__pyx_mstate_global->__pyx_int_0);
  __Pyx_XDECREF_SET(__pyx_v_part, __pyx_mstate_global->__pyx_int_0);
  __pyx_v_offset = 0;

apparently depending on how the type resolver manages things

So possibly there's some confusion going on about what to do when the type is determined when not?

OS

Linux & Windows

Python version

3.14.4

Cython version

3.3.0

Additional context

No response

Activity

  1. da-woods commented on Aug 26, 2026

    @da-woods
    Collaborator

    This bisects to f73bb64. My suspicion is that it's a change in type inference exposing an existing bug but could be wrong there.

  2. scoder commented on Aug 27, 2026

    @scoder
    Contributor

    it's a change in type inference exposing an existing bug

    Right, the return type of the .get() method was declared in that commit, which changes the set of assigned types for line_number and subsequently part.

    line_number gets correctly inferred as Python int due to the assignment from mapping.get(). This lets part and offset correctly get inferred as Python int, but the value on the right hand side of the cascaded assignment doesn't get adapted because we end up with two target types, object and int. Obviously not contradicting and simple to resolve, but we don't resolve it, currently. I'll see what I can come up with.

  3. added this to the 3.3.1 milestone on Aug 27, 2026
  4. scoder commented on Aug 27, 2026

    @scoder
    Contributor

    Turns out, the code was accidentally overwriting a variable in a loop. Simple bug. PR is #7940

  5. modified the milestones: 3.3.1, 3.2.10 on Aug 27, 2026
  6. scoder commented on Aug 27, 2026

    @scoder
    Contributor

    The bug equally applies to 3.2.x and just didn't show there yet.

  7. scoder commented on Aug 28, 2026

    @scoder
    Contributor

    3.2.x commit is fc3dfb1

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions