summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorXuehai Pan <XuehaiPan@pku.edu.cn>2023-10-04 16:44:17 (GMT)
committerGitHub <noreply@github.com>2023-10-04 16:44:17 (GMT)
commit9561648f4a5d8486b67ee4bbe24a239b2a93212c (patch)
treeb884d30b0b02bf869fe9de9fc5d887e2298f0dd2
parentbf4bc36069ef1ed4be4be2ae70404f78bff056d9 (diff)
downloadcpython-9561648f4a5d8486b67ee4bbe24a239b2a93212c.zip
cpython-9561648f4a5d8486b67ee4bbe24a239b2a93212c.tar.gz
cpython-9561648f4a5d8486b67ee4bbe24a239b2a93212c.tar.bz2
gh-110235: Raise TypeError for duplicate/unknown fields in PyStructSequence constructor (GH-110258)
-rw-r--r--Lib/test/test_structseq.py60
-rw-r--r--Misc/NEWS.d/next/C API/2023-10-03-06-19-10.gh-issue-110235.uec5AG.rst2
-rw-r--r--Objects/structseq.c29
3 files changed, 84 insertions, 7 deletions
diff --git a/Lib/test/test_structseq.py b/Lib/test/test_structseq.py
index c6c0afa..2ef1316 100644
--- a/Lib/test/test_structseq.py
+++ b/Lib/test/test_structseq.py
@@ -1,6 +1,7 @@
import copy
import os
import pickle
+import re
import time
import unittest
@@ -91,10 +92,69 @@ class StructSeqTest(unittest.TestCase):
self.assertRaises(TypeError, t, "123")
self.assertRaises(TypeError, t, "123", dict={})
self.assertRaises(TypeError, t, "123456789", dict=None)
+ self.assertRaises(TypeError, t, seq="123456789", dict={})
+
+ self.assertEqual(t("123456789"), tuple("123456789"))
+ self.assertEqual(t("123456789", {}), tuple("123456789"))
+ self.assertEqual(t("123456789", dict={}), tuple("123456789"))
+ self.assertEqual(t(sequence="123456789", dict={}), tuple("123456789"))
+
+ self.assertEqual(t("1234567890"), tuple("123456789"))
+ self.assertEqual(t("1234567890").tm_zone, "0")
+ self.assertEqual(t("123456789", {"tm_zone": "some zone"}), tuple("123456789"))
+ self.assertEqual(t("123456789", {"tm_zone": "some zone"}).tm_zone, "some zone")
s = "123456789"
self.assertEqual("".join(t(s)), s)
+ def test_constructor_with_duplicate_fields(self):
+ t = time.struct_time
+
+ error_message = re.escape("got duplicate or unexpected field name(s)")
+ with self.assertRaisesRegex(TypeError, error_message):
+ t("1234567890", dict={"tm_zone": "some zone"})
+ with self.assertRaisesRegex(TypeError, error_message):
+ t("1234567890", dict={"tm_zone": "some zone", "tm_mon": 1})
+ with self.assertRaisesRegex(TypeError, error_message):
+ t("1234567890", dict={"error": 0, "tm_zone": "some zone"})
+ with self.assertRaisesRegex(TypeError, error_message):
+ t("1234567890", dict={"error": 0, "tm_zone": "some zone", "tm_mon": 1})
+
+ def test_constructor_with_duplicate_unnamed_fields(self):
+ assert os.stat_result.n_unnamed_fields > 0
+ n_visible_fields = os.stat_result.n_sequence_fields
+
+ r = os.stat_result(range(n_visible_fields), {'st_atime': -1.0})
+ self.assertEqual(r.st_atime, -1.0)
+ self.assertEqual(r, tuple(range(n_visible_fields)))
+
+ r = os.stat_result((*range(n_visible_fields), -1.0))
+ self.assertEqual(r.st_atime, -1.0)
+ self.assertEqual(r, tuple(range(n_visible_fields)))
+
+ with self.assertRaisesRegex(TypeError,
+ re.escape("got duplicate or unexpected field name(s)")):
+ os.stat_result((*range(n_visible_fields), -1.0), {'st_atime': -1.0})
+
+ def test_constructor_with_unknown_fields(self):
+ t = time.struct_time
+
+ error_message = re.escape("got duplicate or unexpected field name(s)")
+ with self.assertRaisesRegex(TypeError, error_message):
+ t("123456789", dict={"tm_year": 0})
+ with self.assertRaisesRegex(TypeError, error_message):
+ t("123456789", dict={"tm_year": 0, "tm_mon": 1})
+ with self.assertRaisesRegex(TypeError, error_message):
+ t("123456789", dict={"tm_zone": "some zone", "tm_mon": 1})
+ with self.assertRaisesRegex(TypeError, error_message):
+ t("123456789", dict={"tm_zone": "some zone", "error": 0})
+ with self.assertRaisesRegex(TypeError, error_message):
+ t("123456789", dict={"error": 0, "tm_zone": "some zone", "tm_mon": 1})
+ with self.assertRaisesRegex(TypeError, error_message):
+ t("123456789", dict={"error": 0})
+ with self.assertRaisesRegex(TypeError, error_message):
+ t("123456789", dict={"tm_zone": "some zone", "error": 0})
+
def test_eviltuple(self):
class Exc(Exception):
pass
diff --git a/Misc/NEWS.d/next/C API/2023-10-03-06-19-10.gh-issue-110235.uec5AG.rst b/Misc/NEWS.d/next/C API/2023-10-03-06-19-10.gh-issue-110235.uec5AG.rst
new file mode 100644
index 0000000..ff26f25
--- /dev/null
+++ b/Misc/NEWS.d/next/C API/2023-10-03-06-19-10.gh-issue-110235.uec5AG.rst
@@ -0,0 +1,2 @@
+Raise :exc:`TypeError` for duplicate/unknown fields in ``PyStructSequence`` constructor.
+Patched by Xuehai Pan.
diff --git a/Objects/structseq.c b/Objects/structseq.c
index 0ca622e..2c98288 100644
--- a/Objects/structseq.c
+++ b/Objects/structseq.c
@@ -216,19 +216,34 @@ structseq_new_impl(PyTypeObject *type, PyObject *arg, PyObject *dict)
res->ob_item[i] = Py_NewRef(v);
}
Py_DECREF(arg);
- for (; i < max_len; ++i) {
- PyObject *ob = NULL;
- if (dict != NULL) {
- const char *name = type->tp_members[i-n_unnamed_fields].name;
+ if (dict != NULL && PyDict_GET_SIZE(dict) > 0) {
+ Py_ssize_t n_found_keys = 0;
+ for (i = len; i < max_len; ++i) {
+ PyObject *ob = NULL;
+ const char *name = type->tp_members[i - n_unnamed_fields].name;
if (PyDict_GetItemStringRef(dict, name, &ob) < 0) {
Py_DECREF(res);
return NULL;
}
+ if (ob == NULL) {
+ ob = Py_NewRef(Py_None);
+ }
+ else {
+ ++n_found_keys;
+ }
+ res->ob_item[i] = ob;
+ }
+ if (PyDict_GET_SIZE(dict) > n_found_keys) {
+ PyErr_Format(PyExc_TypeError,
+ "%.500s() got duplicate or unexpected field name(s)",
+ type->tp_name);
+ Py_DECREF(res);
+ return NULL;
}
- if (ob == NULL) {
- ob = Py_NewRef(Py_None);
+ } else {
+ for (i = len; i < max_len; ++i) {
+ res->ob_item[i] = Py_NewRef(Py_None);
}
- res->ob_item[i] = ob;
}
_PyObject_GC_TRACK(res);