Pieter Eendebak 08d7bd28fe
gh-140232: Do not track frozenset objects with immutables (#140234)
Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com>
Co-authored-by: Mikhail Efimov <efimov.mikhail@gmail.com>
Co-authored-by: Victor Stinner <vstinner@python.org>
2026-01-28 11:27:37 +01:00

305 lines
7.3 KiB
C

#include "parts.h"
#include "util.h"
static PyObject *
set_check(PyObject *self, PyObject *obj)
{
NULLABLE(obj);
RETURN_INT(PySet_Check(obj));
}
static PyObject *
set_checkexact(PyObject *self, PyObject *obj)
{
NULLABLE(obj);
RETURN_INT(PySet_CheckExact(obj));
}
static PyObject *
frozenset_check(PyObject *self, PyObject *obj)
{
NULLABLE(obj);
RETURN_INT(PyFrozenSet_Check(obj));
}
static PyObject *
frozenset_checkexact(PyObject *self, PyObject *obj)
{
NULLABLE(obj);
RETURN_INT(PyFrozenSet_CheckExact(obj));
}
static PyObject *
anyset_check(PyObject *self, PyObject *obj)
{
NULLABLE(obj);
RETURN_INT(PyAnySet_Check(obj));
}
static PyObject *
anyset_checkexact(PyObject *self, PyObject *obj)
{
NULLABLE(obj);
RETURN_INT(PyAnySet_CheckExact(obj));
}
static PyObject *
set_new(PyObject *self, PyObject *args)
{
PyObject *iterable = NULL;
if (!PyArg_ParseTuple(args, "|O", &iterable)) {
return NULL;
}
return PySet_New(iterable);
}
static PyObject *
frozenset_new(PyObject *self, PyObject *args)
{
PyObject *iterable = NULL;
if (!PyArg_ParseTuple(args, "|O", &iterable)) {
return NULL;
}
return PyFrozenSet_New(iterable);
}
static PyObject *
set_size(PyObject *self, PyObject *obj)
{
NULLABLE(obj);
RETURN_SIZE(PySet_Size(obj));
}
static PyObject *
set_contains(PyObject *self, PyObject *args)
{
PyObject *obj, *item;
if (!PyArg_ParseTuple(args, "OO", &obj, &item)) {
return NULL;
}
NULLABLE(obj);
NULLABLE(item);
RETURN_INT(PySet_Contains(obj, item));
}
static PyObject *
set_add(PyObject *self, PyObject *args)
{
PyObject *obj, *item;
if (!PyArg_ParseTuple(args, "OO", &obj, &item)) {
return NULL;
}
NULLABLE(obj);
NULLABLE(item);
RETURN_INT(PySet_Add(obj, item));
}
static PyObject *
set_discard(PyObject *self, PyObject *args)
{
PyObject *obj, *item;
if (!PyArg_ParseTuple(args, "OO", &obj, &item)) {
return NULL;
}
NULLABLE(obj);
NULLABLE(item);
RETURN_INT(PySet_Discard(obj, item));
}
static PyObject *
set_pop(PyObject *self, PyObject *obj)
{
NULLABLE(obj);
return PySet_Pop(obj);
}
static PyObject *
set_clear(PyObject *self, PyObject *obj)
{
NULLABLE(obj);
RETURN_INT(PySet_Clear(obj));
}
static PyObject *
test_frozenset_add_in_capi(PyObject *self, PyObject *Py_UNUSED(obj))
{
// Test that `frozenset` can be used with `PySet_Add`,
// when frozenset is just created in CAPI.
PyObject *fs = PyFrozenSet_New(NULL);
if (fs == NULL) {
return NULL;
}
PyObject *num = PyLong_FromLong(1);
if (num == NULL) {
goto error;
}
if (PySet_Add(fs, num) < 0) {
goto error;
}
int contains = PySet_Contains(fs, num);
if (contains < 0) {
goto error;
}
else if (contains == 0) {
goto unexpected;
}
Py_DECREF(fs);
Py_DECREF(num);
Py_RETURN_NONE;
unexpected:
PyErr_SetString(PyExc_ValueError, "set does not contain expected value");
error:
Py_DECREF(fs);
Py_XDECREF(num);
return NULL;
}
static PyObject *
raiseTestError(const char* test_name, const char* msg)
{
PyErr_Format(PyExc_AssertionError, "%s: %s", test_name, msg);
return NULL;
}
static PyObject *
test_frozenset_add_in_capi_tracking_immutable(PyObject *self, PyObject *Py_UNUSED(ignored))
{
// Test: GC tracking - frozenset with only immutable items should not be tracked
PyObject *frozenset = PyFrozenSet_New(NULL);
if (frozenset == NULL) {
return NULL;
}
if (PySet_Add(frozenset, Py_True) < 0) {
Py_DECREF(frozenset);
return NULL;
}
if (PyObject_GC_IsTracked(frozenset)) {
Py_DECREF(frozenset);
return raiseTestError("test_frozenset_add_in_capi_tracking_immutable",
"frozenset with only bool should not be GC tracked");
}
Py_DECREF(frozenset);
Py_RETURN_NONE;
}
static PyObject *
test_frozenset_add_in_capi_tracking(PyObject *self, PyObject *Py_UNUSED(ignored))
{
// Test: GC tracking - frozenset with tracked object should be tracked
PyObject *frozenset = PyFrozenSet_New(NULL);
if (frozenset == NULL) {
return NULL;
}
PyObject *tracked_obj = PyErr_NewException("_testlimitedcapi.py_set_add", NULL, NULL);
if (tracked_obj == NULL) {
goto error;
}
if (!PyObject_GC_IsTracked(tracked_obj)) {
Py_DECREF(frozenset);
Py_DECREF(tracked_obj);
return raiseTestError("test_frozenset_add_in_capi_tracking",
"test object should be tracked");
}
if (PySet_Add(frozenset, tracked_obj) < 0) {
goto error;
}
Py_DECREF(tracked_obj);
if (!PyObject_GC_IsTracked(frozenset)) {
Py_DECREF(frozenset);
return raiseTestError("test_frozenset_add_in_capi_tracking",
"frozenset with with GC tracked object should be tracked");
}
Py_DECREF(frozenset);
Py_RETURN_NONE;
error:
Py_DECREF(frozenset);
Py_XDECREF(tracked_obj);
return NULL;
}
static PyObject *
test_set_contains_does_not_convert_unhashable_key(PyObject *self, PyObject *Py_UNUSED(obj))
{
// See https://docs.python.org/3/c-api/set.html#c.PySet_Contains
PyObject *outer_set = PySet_New(NULL);
PyObject *needle = PySet_New(NULL);
if (needle == NULL) {
Py_DECREF(outer_set);
return NULL;
}
PyObject *num = PyLong_FromLong(42);
if (num == NULL) {
Py_DECREF(outer_set);
Py_DECREF(needle);
return NULL;
}
if (PySet_Add(needle, num) < 0) {
Py_DECREF(outer_set);
Py_DECREF(needle);
Py_DECREF(num);
return NULL;
}
int result = PySet_Contains(outer_set, needle);
Py_DECREF(num);
Py_DECREF(needle);
Py_DECREF(outer_set);
if (result < 0) {
if (PyErr_ExceptionMatches(PyExc_TypeError)) {
PyErr_Clear();
Py_RETURN_NONE;
}
return NULL;
}
PyErr_SetString(PyExc_AssertionError,
"PySet_Contains should have raised TypeError for unhashable key");
return NULL;
}
static PyMethodDef test_methods[] = {
{"set_check", set_check, METH_O},
{"set_checkexact", set_checkexact, METH_O},
{"frozenset_check", frozenset_check, METH_O},
{"frozenset_checkexact", frozenset_checkexact, METH_O},
{"anyset_check", anyset_check, METH_O},
{"anyset_checkexact", anyset_checkexact, METH_O},
{"set_new", set_new, METH_VARARGS},
{"frozenset_new", frozenset_new, METH_VARARGS},
{"set_size", set_size, METH_O},
{"set_contains", set_contains, METH_VARARGS},
{"set_add", set_add, METH_VARARGS},
{"set_discard", set_discard, METH_VARARGS},
{"set_pop", set_pop, METH_O},
{"set_clear", set_clear, METH_O},
{"test_frozenset_add_in_capi", test_frozenset_add_in_capi, METH_NOARGS},
{"test_frozenset_add_in_capi_tracking", test_frozenset_add_in_capi_tracking, METH_NOARGS},
{"test_frozenset_add_in_capi_tracking_immutable", test_frozenset_add_in_capi_tracking_immutable, METH_NOARGS},
{"test_set_contains_does_not_convert_unhashable_key",
test_set_contains_does_not_convert_unhashable_key, METH_NOARGS},
{NULL},
};
int
_PyTestLimitedCAPI_Init_Set(PyObject *m)
{
if (PyModule_AddFunctions(m, test_methods) < 0) {
return -1;
}
return 0;
}