]> granicus.if.org Git - python/commitdiff
Issue #27881: Fixed possible bugs when setting sqlite3.Connection.isolation_level.
authorSerhiy Storchaka <storchaka@gmail.com>
Thu, 1 Sep 2016 19:18:03 +0000 (22:18 +0300)
committerSerhiy Storchaka <storchaka@gmail.com>
Thu, 1 Sep 2016 19:18:03 +0000 (22:18 +0300)
Based on patch by Xiang Zhang.

Lib/sqlite3/test/regression.py
Misc/NEWS
Modules/_sqlite/connection.c
Modules/_sqlite/connection.h

index cb465bad7b322ee9000d5be2c770a87f22872d48..7dd0050528f8fa19541e6a7ba10c30bc40c771b9 100644 (file)
@@ -146,11 +146,34 @@ class RegressionTests(unittest.TestCase):
         self.assertRaises(TypeError, sqlite.register_adapter, {}, None)
 
     def CheckSetIsolationLevel(self):
-        """
-        See issue 3312.
-        """
+        # See issue 27881.
+        class CustomStr(str):
+            def upper(self):
+                return None
+            def __del__(self):
+                con.isolation_level = ""
+
         con = sqlite.connect(":memory:")
-        setattr(con, "isolation_level", "\xe9")
+        con.isolation_level = None
+        for level in "", "DEFERRED", "IMMEDIATE", "EXCLUSIVE":
+            with self.subTest(level=level):
+                con.isolation_level = level
+                con.isolation_level = level.lower()
+                con.isolation_level = level.capitalize()
+                con.isolation_level = CustomStr(level)
+
+        # setting isolation_level failure should not alter previous state
+        con.isolation_level = None
+        con.isolation_level = "DEFERRED"
+        pairs = [
+            (1, TypeError), (b'', TypeError), ("abc", ValueError),
+            ("IMMEDIATE\0EXCLUSIVE", ValueError), ("\xe9", ValueError),
+        ]
+        for value, exc in pairs:
+            with self.subTest(level=value):
+                with self.assertRaises(exc):
+                    con.isolation_level = value
+                self.assertEqual(con.isolation_level, "DEFERRED")
 
     def CheckCursorConstructorCallCheck(self):
         """
index 731685fd930dfa7521ab9ea7910ae8c366e8faa1..649a440777be4ff7771942b3a29dba04c3de45fc 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -50,6 +50,9 @@ Core and Builtins
 Library
 -------
 
+- Issue #27881: Fixed possible bugs when setting sqlite3.Connection.isolation_level.
+  Based on patch by Xiang Zhang.
+
 - Issue #27861: Fixed a crash in sqlite3.Connection.cursor() when a factory
   creates not a cursor.  Patch by Xiang Zhang.
 
index bea930d129ac3c1513f411dda48ef29614680deb..3c52108c10ea4f4beb728b9c132166bc788fc26f 100644 (file)
 
 _Py_IDENTIFIER(cursor);
 
+static const char * const begin_statements[] = {
+    "BEGIN ",
+    "BEGIN DEFERRED",
+    "BEGIN IMMEDIATE",
+    "BEGIN EXCLUSIVE",
+    NULL
+};
+
 static int pysqlite_connection_set_isolation_level(pysqlite_Connection* self, PyObject* isolation_level);
 static void _pysqlite_drop_unused_cursor_references(pysqlite_Connection* self);
 
@@ -258,9 +266,6 @@ void pysqlite_connection_dealloc(pysqlite_Connection* self)
         Py_END_ALLOW_THREADS
     }
 
-    if (self->begin_statement) {
-        PyMem_Free(self->begin_statement);
-    }
     Py_XDECREF(self->isolation_level);
     Py_XDECREF(self->function_pinboard);
     Py_XDECREF(self->row_factory);
@@ -1183,59 +1188,48 @@ static PyObject* pysqlite_connection_get_total_changes(pysqlite_Connection* self
 
 static int pysqlite_connection_set_isolation_level(pysqlite_Connection* self, PyObject* isolation_level)
 {
-    PyObject* res;
-    PyObject* begin_statement;
-    static PyObject* begin_word;
-
-    Py_XDECREF(self->isolation_level);
-
-    if (self->begin_statement) {
-        PyMem_Free(self->begin_statement);
-        self->begin_statement = NULL;
-    }
-
     if (isolation_level == Py_None) {
-        Py_INCREF(Py_None);
-        self->isolation_level = Py_None;
-
-        res = pysqlite_connection_commit(self, NULL);
+        PyObject *res = pysqlite_connection_commit(self, NULL);
         if (!res) {
             return -1;
         }
         Py_DECREF(res);
 
+        self->begin_statement = NULL;
         self->inTransaction = 0;
     } else {
-        const char *statement;
-        Py_ssize_t size;
-
-        Py_INCREF(isolation_level);
-        self->isolation_level = isolation_level;
-
-        if (!begin_word) {
-            begin_word = PyUnicode_FromString("BEGIN ");
-            if (!begin_word) return -1;
-        }
-        begin_statement = PyUnicode_Concat(begin_word, isolation_level);
-        if (!begin_statement) {
+        const char * const *candidate;
+        PyObject *uppercase_level;
+        _Py_IDENTIFIER(upper);
+
+        if (!PyUnicode_Check(isolation_level)) {
+            PyErr_Format(PyExc_TypeError,
+                         "isolation_level must be a string or None, not %.100s",
+                         Py_TYPE(isolation_level)->tp_name);
             return -1;
         }
 
-        statement = _PyUnicode_AsStringAndSize(begin_statement, &size);
-        if (!statement) {
-            Py_DECREF(begin_statement);
+        uppercase_level = _PyObject_CallMethodIdObjArgs(
+                        (PyObject *)&PyUnicode_Type, &PyId_upper,
+                        isolation_level, NULL);
+        if (!uppercase_level) {
             return -1;
         }
-        self->begin_statement = PyMem_Malloc(size + 2);
-        if (!self->begin_statement) {
-            Py_DECREF(begin_statement);
+        for (candidate = begin_statements; *candidate; candidate++) {
+            if (!PyUnicode_CompareWithASCIIString(uppercase_level, *candidate + 6))
+                break;
+        }
+        Py_DECREF(uppercase_level);
+        if (!*candidate) {
+            PyErr_SetString(PyExc_ValueError,
+                            "invalid value for isolation_level");
             return -1;
         }
-
-        strcpy(self->begin_statement, statement);
-        Py_DECREF(begin_statement);
+        self->begin_statement = *candidate;
     }
 
+    Py_INCREF(isolation_level);
+    Py_XSETREF(self->isolation_level, isolation_level);
     return 0;
 }
 
index fbd906377952c9f9588fb93ebf81c2686b54416f..adbfb54523235e555bbbf5ee7bb0ddc967319085 100644 (file)
@@ -55,9 +55,8 @@ typedef struct
     /* None for autocommit, otherwise a PyUnicode with the isolation level */
     PyObject* isolation_level;
 
-    /* NULL for autocommit, otherwise a string with the BEGIN statement; will be
-     * freed in connection destructor */
-    char* begin_statement;
+    /* NULL for autocommit, otherwise a string with the BEGIN statement */
+    const char* begin_statement;
 
     /* 1 if a check should be performed for each API call if the connection is
      * used from the same thread it was created in */