From 13dc498f4728674cd5fcd8838a9b095586551909 Mon Sep 17 00:00:00 2001 From: Jeongseop Lim Date: Fri, 30 Oct 2026 03:15:53 +0900 Subject: [PATCH 1/2] Reject weak references with a callback to non-weakrefable objects refTypeWithCallback skipped the weaklistoffset check that refType does, so weakref.ref(obj, cb) and weakref.proxy(obj, cb) accepted dicts, None and other builtin objects CPython cannot reference weakly. Add the check, and give the builtin types that CPython does reference weakly but that were missing from getBuiltinTypeWeaklistoffset their CPython 3.13 offsets: deque, array, mmap, Context, SimpleQueue, Struct and the TypeVar family. Without the entries the check would reject them, and today they already fail without a callback. Signed-off-by: Jeongseop Lim --- .../src/tests/test_weakref.py | 29 +++++++++++++++++++ .../modules/WeakRefModuleBuiltins.java | 19 ++++++++++-- .../referencetype/ReferenceTypeBuiltins.java | 6 +++- 3 files changed, 50 insertions(+), 4 deletions(-) diff --git a/graalpython/com.oracle.graal.python.test/src/tests/test_weakref.py b/graalpython/com.oracle.graal.python.test/src/tests/test_weakref.py index 2c23ff2714..084c197f16 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/test_weakref.py +++ b/graalpython/com.oracle.graal.python.test/src/tests/test_weakref.py @@ -322,3 +322,32 @@ def __call__(self): proxy = weakref.proxy(a) assert callable(proxy) is True assert proxy() == 42 + + +def test_weakref_with_callback_object_type_support(): + def callback(ref): + pass + + for obj in ({}, [1, 2], 42, None): + for create in (weakref.ref, weakref.proxy): + try: + create(obj, callback) + except TypeError: + pass + else: + assert False, "should throw TypeError for unsupported objects" + + +def test_weakref_builtin_types_with_weaklist(): + import _queue, _struct, array, collections, contextvars, mmap, typing + + def callback(ref): + pass + + P = typing.ParamSpec('P') + objs = [collections.deque(), array.array('b'), mmap.mmap(-1, 1), contextvars.copy_context(), + _queue.SimpleQueue(), _struct.Struct('b'), typing.TypeVar('T'), typing.TypeVarTuple('Ts'), + P, P.args, P.kwargs] + for obj in objs: + assert weakref.ref(obj)() is obj + assert weakref.ref(obj, callback)() is obj diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/WeakRefModuleBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/WeakRefModuleBuiltins.java index 1b432d0974..0a46022805 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/WeakRefModuleBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/WeakRefModuleBuiltins.java @@ -112,7 +112,8 @@ public static int getBuiltinTypeWeaklistoffset(PythonBuiltinClassType cls) { -> 40; case PMethod, // method PFileIO, // _io.FileIO - PTee // itertools._tee + PTee, // itertools._tee + ContextVarsContext // _contextvars.Context -> 32; case PFunction -> 80; // function case PIOBase, // _io._IOBase @@ -122,9 +123,14 @@ public static int getBuiltinTypeWeaklistoffset(PythonBuiltinClassType cls) { PLock // _thread.LockType -> 24; case PBytesIO, // _io.BytesIO - PPartial // functools.partial + PPartial, // functools.partial + PArray, // array.array + PStruct // _struct.Struct -> 48; - case PPattern -> 64; // _sre.Pattern + case PPattern, // _sre.Pattern + PSimpleQueue // _queue.SimpleQueue + -> 64; + case PMMap -> 72; // mmap.mmap case PStringIO -> 112; // _io.StringIO case PBufferedReader, // _io.BufferedReader PBufferedWriter, // _io.BufferedWriter @@ -134,6 +140,13 @@ public static int getBuiltinTypeWeaklistoffset(PythonBuiltinClassType cls) { case PickleBuffer -> 96; // _pickle.PickleBuffer case PTextIOWrapper -> 176; // _io.TextIOWrapper case POrderedDict -> 104; + case PDeque -> 208; // collections.deque + case PTypeVar, // typing.TypeVar + PTypeVarTuple, // typing.TypeVarTuple + PParamSpec, // typing.ParamSpec + PParamSpecArgs, // typing.ParamSpecArgs + PParamSpecKwargs // typing.ParamSpecKwargs + -> -32; default -> 0; // @formatter:on }; diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/referencetype/ReferenceTypeBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/referencetype/ReferenceTypeBuiltins.java index 03494c1aed..a4381b8213 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/referencetype/ReferenceTypeBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/referencetype/ReferenceTypeBuiltins.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -160,9 +160,13 @@ static PReferenceType refType(Object cls, Object object, @SuppressWarnings("unus @Specialization(guards = {"!isNativeObject(object)", "!isPNone(callback)"}) static PReferenceType refTypeWithCallback(Object cls, Object object, Object callback, @Bind Node inliningTarget, + @Cached.Exclusive @Cached GetClassNode getClassNode, @Cached.Exclusive @Cached HiddenAttr.ReadNode readQueueNode, @Bind PythonLanguage language, @Shared @Cached TypeNodes.GetInstanceShape getInstanceShape) { + if (getClassNode.execute(inliningTarget, object) instanceof PythonBuiltinClassType type && type.getWeaklistoffset() == 0) { + throw PRaiseNode.raiseStatic(inliningTarget, TypeError, ErrorMessages.CANNOT_CREATE_WEAK_REFERENCE_TO, object); + } return PFactory.createReferenceType(language, cls, getInstanceShape.execute(cls), object, callback, getWeakReferenceQueue(inliningTarget, readQueueNode)); } From 56676dc125e68525c4806a0fe94374bbd7b125c1 Mon Sep 17 00:00:00 2001 From: Jeongseop Lim Date: Sat, 31 Oct 2026 12:31:45 +0900 Subject: [PATCH 2/2] Spell out the weakref tests without loops Each object and constructor gets its own assertion, so a failure names the case, and the mmap is closed after use. Signed-off-by: Jeongseop Lim --- .../src/tests/test_weakref.py | 66 ++++++++++++++----- 1 file changed, 51 insertions(+), 15 deletions(-) diff --git a/graalpython/com.oracle.graal.python.test/src/tests/test_weakref.py b/graalpython/com.oracle.graal.python.test/src/tests/test_weakref.py index 084c197f16..6dc783e438 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/test_weakref.py +++ b/graalpython/com.oracle.graal.python.test/src/tests/test_weakref.py @@ -38,6 +38,16 @@ # SOFTWARE. import weakref + +def assert_raises(err, fn, *args, **kwargs): + raised = False + try: + fn(*args, **kwargs) + except err: + raised = True + assert raised + + MAX_WAIT_COUNT = 500 import sys @@ -328,14 +338,14 @@ def test_weakref_with_callback_object_type_support(): def callback(ref): pass - for obj in ({}, [1, 2], 42, None): - for create in (weakref.ref, weakref.proxy): - try: - create(obj, callback) - except TypeError: - pass - else: - assert False, "should throw TypeError for unsupported objects" + assert_raises(TypeError, weakref.ref, {}, callback) + assert_raises(TypeError, weakref.proxy, {}, callback) + assert_raises(TypeError, weakref.ref, [1, 2], callback) + assert_raises(TypeError, weakref.proxy, [1, 2], callback) + assert_raises(TypeError, weakref.ref, 42, callback) + assert_raises(TypeError, weakref.proxy, 42, callback) + assert_raises(TypeError, weakref.ref, None, callback) + assert_raises(TypeError, weakref.proxy, None, callback) def test_weakref_builtin_types_with_weaklist(): @@ -344,10 +354,36 @@ def test_weakref_builtin_types_with_weaklist(): def callback(ref): pass - P = typing.ParamSpec('P') - objs = [collections.deque(), array.array('b'), mmap.mmap(-1, 1), contextvars.copy_context(), - _queue.SimpleQueue(), _struct.Struct('b'), typing.TypeVar('T'), typing.TypeVarTuple('Ts'), - P, P.args, P.kwargs] - for obj in objs: - assert weakref.ref(obj)() is obj - assert weakref.ref(obj, callback)() is obj + d = collections.deque() + assert weakref.ref(d)() is d + assert weakref.ref(d, callback)() is d + a = array.array('b') + assert weakref.ref(a)() is a + assert weakref.ref(a, callback)() is a + with mmap.mmap(-1, 1) as m: + assert weakref.ref(m)() is m + assert weakref.ref(m, callback)() is m + c = contextvars.copy_context() + assert weakref.ref(c)() is c + assert weakref.ref(c, callback)() is c + q = _queue.SimpleQueue() + assert weakref.ref(q)() is q + assert weakref.ref(q, callback)() is q + s = _struct.Struct('b') + assert weakref.ref(s)() is s + assert weakref.ref(s, callback)() is s + t = typing.TypeVar('T') + assert weakref.ref(t)() is t + assert weakref.ref(t, callback)() is t + ts = typing.TypeVarTuple('Ts') + assert weakref.ref(ts)() is ts + assert weakref.ref(ts, callback)() is ts + p = typing.ParamSpec('P') + assert weakref.ref(p)() is p + assert weakref.ref(p, callback)() is p + pa = p.args + assert weakref.ref(pa)() is pa + assert weakref.ref(pa, callback)() is pa + pk = p.kwargs + assert weakref.ref(pk)() is pk + assert weakref.ref(pk, callback)() is pk