From 4728efa3cc8e059dda4851f793fef52cfcc4d50e Mon Sep 17 00:00:00 2001 From: Bob Kline Date: Sat, 1 Jan 2022 21:47:25 -0500 Subject: [PATCH] Replace deprecated PyUnicode_FromUnicode(NULL, size) calls (#998) Current versions of Python write a deprecation warning message to stderr, which breaks CGI scripts running under web servers which fold stderr into stdout. Likely breaks other software. This change replaces the deprecated calls with PyUnicode_New(size, 0x10ffff). The second argument represents the highest code point for Unicode characters, which is used because the calls being replaced have no string value from which we can determine the smallest number of bytes needed to represent any value which can be stored in the newly-created object. --- src/pyodbccompat.h | 6 +++- src/pyodbcmodule.cpp | 2 +- tests3/issue998.py | 78 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 84 insertions(+), 2 deletions(-) create mode 100755 tests3/issue998.py diff --git a/src/pyodbccompat.h b/src/pyodbccompat.h index 74067095..0ae10062 100644 --- a/src/pyodbccompat.h +++ b/src/pyodbccompat.h @@ -66,13 +66,17 @@ inline void PyString_ConcatAndDel(PyObject** lhs, PyObject* rhs) #endif +#ifndef MAX_UNICODE + #define MAX_UNICODE 0x10ffff +#endif + inline PyObject* Text_New(Py_ssize_t length) { // Returns a new, uninitialized String (Python 2) or Unicode object (Python 3) object. #if PY_MAJOR_VERSION < 3 return PyString_FromStringAndSize(0, length); #else - return PyUnicode_FromUnicode(0, length); + return PyUnicode_New(length, MAX_UNICODE); #endif } diff --git a/src/pyodbcmodule.cpp b/src/pyodbcmodule.cpp index 771fe257..3f8fc548 100644 --- a/src/pyodbcmodule.cpp +++ b/src/pyodbcmodule.cpp @@ -1357,7 +1357,7 @@ static PyObject* MakeConnectionString(PyObject* existing, PyObject* parts) length += Text_Size(key) + 1 + Text_Size(value) + 1; // key=value; } - PyObject* result = PyUnicode_FromUnicode(0, length); + PyObject* result = Text_New(length); if (!result) return 0; diff --git a/tests3/issue998.py b/tests3/issue998.py new file mode 100755 index 00000000..24e58e2a --- /dev/null +++ b/tests3/issue998.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 +""" +Verify that no warning is emitted for `PyUnicode_FromUnicode(NULL, size)`. + +See https://github.com/mkleehammer/pyodbc/issues/998. +See also https://bugs.python.org/issue36346. +""" + +import io +import os +import sys +import unittest + +# pylint: disable-next=import-error +from tests3.testutils import add_to_path, load_setup_connection_string + +add_to_path() +import pyodbc # pylint: disable=wrong-import-position + +KB = 1024 +MB = KB * 1024 + +CONNECTION_STRING = None + +CONNECTION_STRING_ERROR_MESSAGE = ( + "Please create tmp/setup.cfg file or " + "set a valid value to CONNECTION_STRING." +) +NO_ERROR = None + + +class SQLPutDataUnicodeToBytesMemoryLeakTestCase(unittest.TestCase): + """Test case for issue998 bug fix.""" + + driver = pyodbc + + @classmethod + def setUpClass(cls): + """Set the connection string.""" + + filename = os.path.splitext(os.path.basename(__file__))[0] + cls.connection_string = ( + load_setup_connection_string(filename) or CONNECTION_STRING + ) + + if cls.connection_string: + return NO_ERROR + return ValueError(CONNECTION_STRING_ERROR_MESSAGE) + + def test_use_correct_unicode_factory_function(self): + """Verify that the obsolete function call has been replaced.""" + + # Create a results set. + with pyodbc.connect(self.connection_string, autocommit=True) as cnxn: + cursor = cnxn.cursor() + cursor.execute("SELECT 1 AS a, 2 AS b") + rows = cursor.fetchall() + + # Redirect stderr so we can detect the warning. + sys.stderr = redirected_stderr = io.StringIO() + + # Convert the results object to a string. + self.assertGreater(len(str(rows)), 0) + + # Restore stderr to the original stream. + sys.stderr = sys.__stderr__ + + # If the bug has been fixed, nothing will have been written to stderr. + self.assertEqual(len(redirected_stderr.getvalue()), 0) + + +def main(): + """Top-level driver for the test.""" + unittest.main() + + +if __name__ == "__main__": + main()