]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-110235: Raise TypeError for duplicate/unknown fields in PyStructSequence construct...
authorXuehai Pan <XuehaiPan@pku.edu.cn>
Wed, 4 Oct 2023 16:44:17 +0000 (00:44 +0800)
committerGitHub <noreply@github.com>
Wed, 4 Oct 2023 16:44:17 +0000 (19:44 +0300)
Lib/test/test_structseq.py
Misc/NEWS.d/next/C API/2023-10-03-06-19-10.gh-issue-110235.uec5AG.rst [new file with mode: 0644]
Objects/structseq.c

index c6c0afaf077acceaf67291babb774842fe26a8d1..2ef1316e08fb8b8ca44f4b7bccaa49d86f3023ac 100644 (file)
@@ -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 (file)
index 0000000..ff26f25
--- /dev/null
@@ -0,0 +1,2 @@
+Raise :exc:`TypeError` for duplicate/unknown fields in ``PyStructSequence`` constructor.\r
+Patched by Xuehai Pan.
index 0ca622edc2ba37c8bb90245289a8692038b2b04b..2c98288039c58c634207761a3578512f7a569f94 100644 (file)
@@ -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);