Skip to content

Assertion failure in insertdict #143189

Closed
Closed

Description

@devdanzin

Crash report

What happened?

It's possible to cause the interpreter to abort by running the code below:

from threading import Thread
from asyncio import Runner, gather, to_thread

class Str(str): ...
class Class: ...

def instance_1__setattr__():
    Class().__setattr__(Str(), "")

async def run_async_tasks():
    await gather(to_thread(instance_1__setattr__))

Thread(target=Class().__setattr__, args=("", "")).start()
Runner().run(run_async_tasks())

Backtrace:

python: Objects/dictobject.c:1916: int insertdict(PyDictObject *, PyObject *, Py_hash_t, PyObject *): Assertion `old_value != NULL' failed.

Thread 3 "asyncio_0" received signal SIGABRT, Aborted.

#0  __pthread_kill_implementation (threadid=<optimized out>, signo=6, no_tid=0) at ./nptl/pthread_kill.c:44
#1  __pthread_kill_internal (threadid=<optimized out>, signo=6) at ./nptl/pthread_kill.c:89
#2  __GI___pthread_kill (threadid=<optimized out>, signo=signo@entry=6) at ./nptl/pthread_kill.c:100
#3  0x00007ffff7c45e2e in __GI_raise (sig=sig@entry=6) at ../sysdeps/posix/raise.c:26
#4  0x00007ffff7c28888 in __GI_abort () at ./stdlib/abort.c:77
#5  0x00007ffff7c287f0 in __assert_fail_base (fmt=<optimized out>, assertion=<optimized out>, file=<optimized out>, line=<optimized out>, function=<optimized out>) at ./assert/assert.c:118
#6  0x00007ffff7c3c19f in __assert_fail (assertion=<optimized out>, file=<optimized out>, line=<optimized out>, function=<optimized out>) at ./assert/assert.c:127
#7  0x0000555555ba106f in insertdict (mp=0x7bffb60d0190, key=<Str() at remote 0x7bffb61c01e0>, hash=0, value='') at Objects/dictobject.c:1916
#8  0x0000555555bbb4be in store_instance_attr_lock_held (obj=<Class() at remote 0x7bffb61301a0>, values=values@entry=0x7bffb61301c0, name=<Str() at remote 0x7bffb61c01e0>, value='') at Objects/dictobject.c:6882
#9  0x0000555555bba260 in _PyObject_StoreInstanceAttribute (obj=obj@entry=<Class() at remote 0x7bffb61301a0>, name=name@entry=<Str() at remote 0x7bffb61c01e0>, value=value@entry='') at Objects/dictobject.c:6988
#10 0x0000555555c0c80e in _PyObject_GenericSetAttrWithDict (obj=<Class() at remote 0x7bffb61301a0>, name=<Str() at remote 0x7bffb61c01e0>, value='', dict=0x0) at Objects/object.c:1972
#11 0x0000555555cd32d5 in wrap_setattr (self=<optimized out>, args=(<Str() at remote 0x7bffb61c01e0>, ''), wrapped=<optimized out>) at Objects/typeobject.c:9933
#12 0x0000555555ad9531 in wrapperdescr_raw_call (descr=0x7bffb40a02b0, self=<Class() at remote 0x7bffb61301a0>, args=(<Str() at remote 0x7bffb61c01e0>, ''), kwds=0x0) at Objects/descrobject.c:532
#13 wrapperdescr_call (_descr=<wrapper_descriptor at remote 0x7bffb40a02b0>, args=(<Str() at remote 0x7bffb61c01e0>, ''), kwds=0x0) at Objects/descrobject.c:570
#14 0x0000555555aa9b3d in _PyObject_MakeTpCall (tstate=0x7e8ff6ff4210, callable=<wrapper_descriptor at remote 0x7bffb40a02b0>, args=0x7bffb37fb068, nargs=3, keywords=0x0) at Objects/call.c:242
#15 0x0000555555e831cb in _Py_VectorCallInstrumentation_StackRefSteal (callable=<wrapper_descriptor at remote 0x7bffb40a02b0>, arguments=arguments@entry=0x7e8ff70035d8, total_args=total_args@entry=3, kwnames=kwnames@entry=0x0, 
    call_instrumentation=false, frame=0x7e8ff7003580, this_instr=0x7bffb60a012c, tstate=0x7e8ff6ff4210) at Python/ceval.c:1097
#16 0x0000555555ebe861 in _PyEval_EvalFrameDefault (tstate=<optimized out>, frame=<optimized out>, throwflag=<optimized out>) at Python/generated_cases.c.h:1680
#17 0x0000555555e82094 in _PyEval_EvalFrame (tstate=0x7e8ff6ff4210, frame=0x7e8ff7003580, throwflag=0) at ./Include/internal/pycore_ceval.h:119
#18 _PyEval_Vector (tstate=0x7e8ff6ff4210, func=0x7bffb53b9a10, locals=0x0, args=0x7bffb5440828, argcount=0, kwnames=0x0) at Python/ceval.c:2542
#19 0x0000555555f3ebc3 in _PyObject_VectorcallTstate (tstate=0x7e8ff6ff4210, callable=<function at remote 0x7bffb53b9a10>, args=0x7bffb37ff6c0, nargsf=22, kwnames=0x0) at ./Include/internal/pycore_call.h:136
#20 context_run (self=<_contextvars.Context at remote 0x7bffb5440fd0>, args=args@entry=0x7bffb5440820, nargs=nargs@entry=1, kwnames=kwnames@entry=0x0) at Python/context.c:722
#21 0x0000555555bf1e3c in cfunction_vectorcall_FASTCALL_KEYWORDS (func=<built-in method run of _contextvars.Context object at remote 0x7bffb5440fd0>, args=0x7bffb5440820, nargsf=1, kwnames=0x0) at Objects/methodobject.c:465
#22 0x00005555562a65f0 in _PyObject_VectorcallTstate (tstate=tstate@entry=0x7e8ff6ff4210, callable=<built-in method run of _contextvars.Context object at remote 0x7bffb5440fd0>, args=args@entry=0x7bffb5440820, nargsf=nargsf@entry=1, 
    kwnames=kwnames@entry=0x0) at ./Include/internal/pycore_call.h:136
#23 0x00005555562ac4c5 in partial_vectorcall (self=self@entry=<functools.partial at remote 0x7bffb5325d10>, args=args@entry=0x555556907810 <_PyRuntime+172496>, nargsf=0, kwnames=kwnames@entry=0x0) at ./Modules/_functoolsmodule.c:390
#24 0x0000555555aab83f in _PyVectorcall_Call (tstate=<optimized out>, func=<optimized out>, callable=<optimized out>, tuple=(), kwargs=<optimized out>) at Objects/call.c:273
#25 0x0000555555eaa594 in _PyEval_EvalFrameDefault (tstate=<optimized out>, frame=<optimized out>, throwflag=<optimized out>) at Python/generated_cases.c.h:2534
#26 0x0000555555e82094 in _PyEval_EvalFrame (tstate=0x7e8ff6ff4210, frame=0x7e8ff7003328, throwflag=0) at ./Include/internal/pycore_ceval.h:119
#27 _PyEval_Vector (tstate=0x7e8ff6ff4210, func=0x7bffb49cb1b0, locals=0x0, args=0x7bffb26ffa20, argcount=1, kwnames=0x0) at Python/ceval.c:2542
#28 0x0000555555ab53f0 in _PyObject_VectorcallTstate (tstate=tstate@entry=0x7e8ff6ff4210, callable=callable@entry=<function at remote 0x7bffb49cb1b0>, args=0x7bffb26ffa20, nargsf=nargsf@entry=1, kwnames=kwnames@entry=0x0)
    at ./Include/internal/pycore_call.h:136
#29 0x0000555555ab2aea in method_vectorcall (method=<method at remote 0x7bffb60d0070>, args=<optimized out>, nargsf=<optimized out>, kwnames=<optimized out>) at Objects/classobject.c:73
#30 0x0000555555f3ebc3 in _PyObject_VectorcallTstate (tstate=0x7e8ff6ff4210, callable=<method at remote 0x7bffb60d0070>, args=0x7bffb37ff6c0, nargsf=22, kwnames=0x0) at ./Include/internal/pycore_call.h:136
#31 context_run (self=<_contextvars.Context at remote 0x7bffb5443250>, args=0x7bffb37fd950, nargs=1, kwnames=0x0) at Python/context.c:722
#32 0x0000555555e8501a in _PyCallMethodDescriptorFastWithKeywords_StackRefSteal (callable=<method_descriptor at remote 0x7bffb40a8580>, meth=meth@entry=0x555556712e60 <PyContext_methods+160>, 
    self=<_contextvars.Context at remote 0x7bffb5443250>, arguments=0x7e8ff70032f8, total_args=total_args@entry=2) at Python/ceval.c:1248
#33 0x0000555555eb41c6 in _PyEval_EvalFrameDefault (tstate=<optimized out>, frame=<optimized out>, throwflag=<optimized out>) at Python/generated_cases.c.h:3470
#34 0x0000555555e82094 in _PyEval_EvalFrame (tstate=0x7e8ff6ff4210, frame=0x7e8ff7003220, throwflag=0) at ./Include/internal/pycore_ceval.h:119
#35 _PyEval_Vector (tstate=0x7e8ff6ff4210, func=0x7bffb49cb290, locals=0x0, args=0x7bffb26ff120, argcount=1, kwnames=0x0) at Python/ceval.c:2542

May be related to #142731.

Found using fusil by @vstinner.

CPython versions tested on:

CPython main branch

Operating systems tested on:

Linux

Output from running 'python -VV' on the command line:

Python 3.15.0a3+ free-threading build (heads/main:cf6758ff9eb, Dec 24 2025, 21:09:14) [Clang 21.1.2 (2ubuntu6)]

Linked PRs

Activity

  1. changed the title [-]Assertion failure in `insertdict` on a free threaded build[/-] [+]Assertion failure in `insertdict` on a free-threading build[/+] on Dec 26, 2025
  2. changed the title [-]Assertion failure in `insertdict` on a free-threading build[/-] [+]Assertion failure in `insertdict`[/+] on Dec 27, 2025
  3. colesbury commented on Dec 27, 2025

    @colesbury
    Contributor

    Not free threading specific. The assertion is just wrong after:

  4. devdanzin commented on Dec 29, 2025

    @devdanzin
    MemberAuthor

    Should we just remove it, or is there an interesting check to assert there?

  5. colesbury commented on Dec 29, 2025

    @colesbury
    Contributor

    I think it's more than just the assertion that is wrong. @methane I think the logic in the PR we reviewed doesn't handle the case where you insert a unicode subclass that matches something in the shared keys (but not in the dict) properly. Here is a repro without any threads:

    class MyClass: pass
    class MyStr(str): pass
    
    obj = MyClass()
    obj.attr1 = "attr1"
    
    obj2 = MyClass()
    d = obj2.__dict__
    d[MyStr("attr1")] = "attr1"  # Crashes in debug builds
    assert isinstance(list(d.keys())[0], MyStr)  # Fails in release builds of 3.13+ with "IndexError: list index out of range"
  6. methane commented on Dec 30, 2025

    @methane
    Member

    @colesbury you are right.

    I did not consider the case where key exists in the shared keys table but old_value==NULL.
    In that case, since the dict cannot use shared keys table, conversion to a combined table is necessary.

    diff --git a/Objects/dictobject.c b/Objects/dictobject.c
    index ac4a46dab10..e363253480b 100644
    --- a/Objects/dictobject.c
    +++ b/Objects/dictobject.c
    @@ -1898,7 +1898,7 @@ insertdict(PyInterpreterState *interp, PyDictObject *mp,
                 goto Fail;
         }
    
    -    if (ix == DKIX_EMPTY) {
    +    if (old_value == NULL) {
             // insert_combined_dict() will convert from non DICT_KEYS_GENERAL table
             // into DICT_KEYS_GENERAL table if key is not Unicode.
             // We don't convert it before _Py_dict_lookup because non-Unicode key
    
  7. added a commit that references this issue on Dec 30, 2025
  8. methane commented on Dec 30, 2025

    @methane
    Member

    For the record, this issue is very similar to #142218.

    #142218 is crash when inserting non-Unicode key when matching Unicode key is in split table and there is a value.
    This issue is crash when inserting non-Unicode key when matching Unicode key is in split table, but there is no value in the dict.

  9. added a commit that references this issue on Jan 12, 2026
  10. added a commit that references this issue on Jan 12, 2026
  11. added a commit that references this issue on Jan 13, 2026
  12. added 2 commits that reference this issue on Jan 13, 2026
  13. added a commit that references this issue on Feb 15, 2026
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

    interpreter-core(Objects, Python, Grammar, and Parser dirs)type-crashA hard crash of the interpreter, possibly with a core dump

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions