Skip to content

Commit 309c56d

Browse files
authored
Add PyMapping_HasKeyWithError() function (#73)
Add PyMapping_HasKeyWithError() and PyMapping_HasKeyStringWithError() functions. Fix also undefined behavior in PyMapping_GetOptionalItemString() when PyUnicode_FromString() fails: set '*result' to NULL.
1 parent 8109811 commit 309c56d

File tree

3 files changed

+79
-24
lines changed

3 files changed

+79
-24
lines changed

docs/api.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,14 @@ Python 3.13
5858
5959
See `PyMapping_GetOptionalItemString() documentation <https://docs.python.org/dev/c-api/mapping.html#c.PyMapping_GetOptionalItemString>`__.
6060
61+
.. c:function:: int PyMapping_HasKeyWithError(PyObject *obj, PyObject *key)
62+
63+
See `PyMapping_HasKeyWithError() documentation <https://docs.python.org/dev/c-api/mapping.html#c.PyMapping_HasKeyWithError>`__.
64+
65+
.. c:function:: int PyMapping_HasKeyStringWithError(PyObject *obj, const char *key)
66+
67+
See `PyMapping_HasKeyStringWithError() documentation <https://docs.python.org/dev/c-api/mapping.html#c.PyMapping_HasKeyStringWithError>`__.
68+
6169
.. c:function:: int PyModule_Add(PyObject *module, const char *name, PyObject *value)
6270
6371
See `PyModule_Add() documentation <https://docs.python.org/dev/c-api/module.html#c.PyModule_Add>`__.

pythoncapi_compat.h

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -711,7 +711,8 @@ PyObject_GetOptionalAttrString(PyObject *obj, const char *name, PyObject **resul
711711
#endif
712712

713713

714-
// gh-106307 added PyObject_GetOptionalAttr() to Python 3.13.0a1
714+
// gh-106307 added PyObject_GetOptionalAttr() and
715+
// PyMapping_GetOptionalItemString() to Python 3.13.0a1
715716
#if PY_VERSION_HEX < 0x030D00A1
716717
static inline int
717718
PyMapping_GetOptionalItem(PyObject *obj, PyObject *key, PyObject **result)
@@ -738,6 +739,7 @@ PyMapping_GetOptionalItemString(PyObject *obj, const char *key, PyObject **resul
738739
key_obj = PyString_FromString(key);
739740
#endif
740741
if (key_obj == NULL) {
742+
*result = NULL;
741743
return -1;
742744
}
743745
rc = PyMapping_GetOptionalItem(obj, key_obj, result);
@@ -746,6 +748,28 @@ PyMapping_GetOptionalItemString(PyObject *obj, const char *key, PyObject **resul
746748
}
747749
#endif
748750

751+
// gh-108511 added PyMapping_HasKeyWithError() and
752+
// PyMapping_HasKeyStringWithError() to Python 3.13.0a1
753+
#if PY_VERSION_HEX < 0x030D00A1
754+
static inline int
755+
PyMapping_HasKeyWithError(PyObject *obj, PyObject *key)
756+
{
757+
PyObject *res;
758+
int rc = PyMapping_GetOptionalItem(obj, key, &res);
759+
Py_XDECREF(res);
760+
return rc;
761+
}
762+
763+
static inline int
764+
PyMapping_HasKeyStringWithError(PyObject *obj, const char *key)
765+
{
766+
PyObject *res;
767+
int rc = PyMapping_GetOptionalItemString(obj, key, &res);
768+
Py_XDECREF(res);
769+
return rc;
770+
}
771+
#endif
772+
749773

750774
// gh-106004 added PyDict_GetItemRef() and PyDict_GetItemStringRef()
751775
// to Python 3.13.0a1

tests/test_pythoncapi_compat_cext.c

Lines changed: 46 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@
5050
# define ASSERT_REFCNT(expr)
5151
#endif
5252

53+
// Marker to check that pointer value was set
54+
static const char uninitialized[] = "uninitialized";
55+
#define UNINITIALIZED_OBJ ((PyObject *)uninitialized)
56+
5357

5458
static PyObject*
5559
create_string(const char *str)
@@ -806,7 +810,7 @@ test_weakref(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
806810
}
807811

808812
// test PyWeakref_GetRef(), reference is alive
809-
PyObject *ref = Py_True; // marker to check that value was set
813+
PyObject *ref = UNINITIALIZED_OBJ;
810814
assert(PyWeakref_GetRef(weakref, &ref) == 1);
811815
assert(ref == obj);
812816
assert(Py_REFCNT(obj) == (refcnt + 1));
@@ -1016,28 +1020,28 @@ test_getattr(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
10161020

10171021
// test PyObject_GetOptionalAttr(): attribute exists
10181022
attr_name = create_string("version");
1019-
value = Py_True; // marker value
1023+
value = UNINITIALIZED_OBJ;
10201024
assert(PyObject_GetOptionalAttr(obj, attr_name, &value) == 1);
10211025
assert(value != _Py_NULL);
10221026
Py_DECREF(value);
10231027
Py_DECREF(attr_name);
10241028

10251029
// test PyObject_GetOptionalAttrString(): attribute exists
1026-
value = Py_True; // marker value
1030+
value = UNINITIALIZED_OBJ;
10271031
assert(PyObject_GetOptionalAttrString(obj, "version", &value) == 1);
10281032
assert(value != _Py_NULL);
10291033
Py_DECREF(value);
10301034

10311035
// test PyObject_GetOptionalAttr(): attribute doesn't exist
10321036
attr_name = create_string("nonexistant_attr_name");
1033-
value = Py_True; // marker value
1037+
value = UNINITIALIZED_OBJ;
10341038
assert(PyObject_GetOptionalAttr(obj, attr_name, &value) == 0);
10351039
assert(value == _Py_NULL);
10361040
Py_DECREF(attr_name);
10371041
assert(!PyErr_Occurred());
10381042

10391043
// test PyObject_GetOptionalAttrString(): attribute doesn't exist
1040-
value = Py_True; // marker value
1044+
value = UNINITIALIZED_OBJ;
10411045
assert(PyObject_GetOptionalAttrString(obj, "nonexistant_attr_name", &value) == 0);
10421046
assert(value == _Py_NULL);
10431047
assert(!PyErr_Occurred());
@@ -1056,37 +1060,56 @@ test_getitem(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
10561060
assert(value != _Py_NULL);
10571061
PyObject *obj = Py_BuildValue("{sO}", "key", value);
10581062
assert(obj != _Py_NULL);
1059-
PyObject *key;
1063+
PyObject *present_key, *missing_key;
10601064
PyObject *item;
10611065

1066+
present_key = create_string("key");
1067+
missing_key = create_string("dontexist");
1068+
10621069
// test PyMapping_GetOptionalItem(): key is present
1063-
key = create_string("key");
1064-
item = Py_True; // marker value
1065-
assert(PyMapping_GetOptionalItem(obj, key, &item) == 1);
1070+
item = UNINITIALIZED_OBJ;
1071+
assert(PyMapping_GetOptionalItem(obj, present_key, &item) == 1);
10661072
assert(item == value);
10671073
Py_DECREF(item);
1068-
Py_DECREF(key);
1074+
assert(!PyErr_Occurred());
1075+
1076+
// test PyMapping_HasKeyWithError(): key is present
1077+
assert(PyMapping_HasKeyWithError(obj, present_key) == 1);
1078+
assert(!PyErr_Occurred());
10691079

10701080
// test PyMapping_GetOptionalItemString(): key is present
1071-
item = Py_True; // marker value
1081+
item = UNINITIALIZED_OBJ;
10721082
assert(PyMapping_GetOptionalItemString(obj, "key", &item) == 1);
10731083
assert(item == value);
10741084
Py_DECREF(item);
10751085

1086+
// test PyMapping_HasKeyStringWithError(): key is present
1087+
assert(PyMapping_HasKeyStringWithError(obj, "key") == 1);
1088+
assert(!PyErr_Occurred());
1089+
10761090
// test PyMapping_GetOptionalItem(): missing key
1077-
key = create_string("dontexist");
1078-
item = Py_True; // marker value
1079-
assert(PyMapping_GetOptionalItem(obj, key, &item) == 0);
1091+
item = UNINITIALIZED_OBJ;
1092+
assert(PyMapping_GetOptionalItem(obj, missing_key, &item) == 0);
10801093
assert(item == _Py_NULL);
1081-
Py_DECREF(key);
1094+
assert(!PyErr_Occurred());
1095+
1096+
// test PyMapping_HasKeyWithError(): missing key
1097+
assert(PyMapping_HasKeyWithError(obj, missing_key) == 0);
1098+
assert(!PyErr_Occurred());
10821099

10831100
// test PyMapping_GetOptionalItemString(): missing key
1084-
item = Py_True; // marker value
1101+
item = UNINITIALIZED_OBJ;
10851102
assert(PyMapping_GetOptionalItemString(obj, "dontexist", &item) == 0);
10861103
assert(item == _Py_NULL);
10871104

1105+
// test PyMapping_HasKeyStringWithError(): missing key
1106+
assert(PyMapping_HasKeyStringWithError(obj, "dontexist") == 0);
1107+
assert(!PyErr_Occurred());
1108+
10881109
Py_DECREF(obj);
10891110
Py_DECREF(value);
1111+
Py_DECREF(present_key);
1112+
Py_DECREF(missing_key);
10901113
Py_RETURN_NONE;
10911114
}
10921115

@@ -1147,45 +1170,45 @@ test_dict_api(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
11471170
PyErr_Clear();
11481171

11491172
// test PyDict_GetItemRef(), key is present
1150-
get_value = Py_Ellipsis; // marker value
1173+
get_value = UNINITIALIZED_OBJ;
11511174
assert(PyDict_GetItemRef(dict, key, &get_value) == 1);
11521175
assert(get_value == value);
11531176
Py_DECREF(get_value);
11541177

11551178
// test PyDict_GetItemStringRef(), key is present
1156-
get_value = Py_Ellipsis; // marker value
1179+
get_value = UNINITIALIZED_OBJ;
11571180
assert(PyDict_GetItemStringRef(dict, "key", &get_value) == 1);
11581181
assert(get_value == value);
11591182
Py_DECREF(get_value);
11601183

11611184
// test PyDict_GetItemRef(), missing key
1162-
get_value = Py_Ellipsis; // marker value
1185+
get_value = UNINITIALIZED_OBJ;
11631186
assert(PyDict_GetItemRef(dict, missing_key, &get_value) == 0);
11641187
assert(!PyErr_Occurred());
11651188
assert(get_value == NULL);
11661189

11671190
// test PyDict_GetItemStringRef(), missing key
1168-
get_value = Py_Ellipsis; // marker value
1191+
get_value = UNINITIALIZED_OBJ;
11691192
assert(PyDict_GetItemStringRef(dict, "missing_key", &get_value) == 0);
11701193
assert(!PyErr_Occurred());
11711194
assert(get_value == NULL);
11721195

11731196
// test PyDict_GetItemRef(), invalid dict
1174-
get_value = Py_Ellipsis; // marker value
1197+
get_value = UNINITIALIZED_OBJ;
11751198
assert(PyDict_GetItemRef(invalid_dict, key, &get_value) == -1);
11761199
assert(PyErr_ExceptionMatches(PyExc_SystemError));
11771200
PyErr_Clear();
11781201
assert(get_value == NULL);
11791202

11801203
// test PyDict_GetItemStringRef(), invalid dict
1181-
get_value = Py_Ellipsis; // marker value
1204+
get_value = UNINITIALIZED_OBJ;
11821205
assert(PyDict_GetItemStringRef(invalid_dict, "key", &get_value) == -1);
11831206
assert(PyErr_ExceptionMatches(PyExc_SystemError));
11841207
PyErr_Clear();
11851208
assert(get_value == NULL);
11861209

11871210
// test PyDict_GetItemRef(), invalid key
1188-
get_value = Py_Ellipsis; // marker value
1211+
get_value = UNINITIALIZED_OBJ;
11891212
assert(PyDict_GetItemRef(dict, invalid_key, &get_value) == -1);
11901213
assert(PyErr_ExceptionMatches(PyExc_TypeError));
11911214
PyErr_Clear();

0 commit comments

Comments
 (0)