# HG changeset patch
# User Stefan Behnel <stefan_ml@behnel.de>
# Date 1589015144 -7200
#      Sat May 09 11:05:44 2020 +0200
# Node ID 0c7c99e148c7fbb7ea6a0edb752e2a30d8e6a864
# Parent  8249756c48ec6e5e4b7ff723c6e88e2cf8b8945e
Reimplement __Pyx_PyDict_GetItemStrWithError() as a hacky version in Py2 to get the semantics right of returning a borrowed reference with non-KeyError exceptions left in place.
Closes https://github.com/cython/cython/issues/3578

diff --git a/CHANGES.rst b/CHANGES.rst
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -25,6 +25,10 @@
   could fail to acquire the GIL in some cases on function exit.
   (Github issue #3590 etc.)
 
+* A reference leak when processing keyword arguments in Py2 was resolved,
+  that appeared in 3.0a1.
+  (Github issue #3578)
+
 * The outdated getbuffer/releasebuffer implementations in the NumPy
   declarations were removed so that buffers declared as ``ndarray``
   now use the normal implementation in NumPy.
diff --git a/Cython/Utility/ModuleSetupCode.c b/Cython/Utility/ModuleSetupCode.c
--- a/Cython/Utility/ModuleSetupCode.c
+++ b/Cython/Utility/ModuleSetupCode.c
@@ -676,10 +676,28 @@
 #define __Pyx_PyDict_GetItemStr           PyDict_GetItem
 #else
 static CYTHON_INLINE PyObject * __Pyx_PyDict_GetItemStrWithError(PyObject *dict, PyObject *name) {
-    PyObject *res = PyObject_GetItem(dict, name);
-    if (res == NULL && PyErr_ExceptionMatches(PyExc_KeyError))
-        PyErr_Clear();
-    return res;
+    // This is tricky - we should return a borrowed reference but not swallow non-KeyError exceptions. 8-|
+    // But: this function is only used in Py2 and older PyPys,
+    // and currently only for argument parsing and other non-correctness-critical lookups
+    // and we know that 'name' is an interned 'str' with pre-calculated hash value (only comparisons can fail),
+    // thus, performance matters more than correctness here, especially in the "not found" case.
+#if CYTHON_COMPILING_IN_PYPY
+    // So we ignore any exceptions in old PyPys ...
+    return PyDict_GetItem(dict, name);
+#else
+    // and hack together a stripped-down and modified PyDict_GetItem() in CPython 2.
+    PyDictEntry *ep;
+    PyDictObject *mp = (PyDictObject*) dict;
+    long hash = ((PyStringObject *) name)->ob_shash;
+    assert(hash != -1); /* hash values of interned strings are always initialised */
+    ep = (mp->ma_lookup)(mp, name, hash);
+    if (ep == NULL) {
+        // error occurred
+        return NULL;
+    }
+    // found or not found
+    return ep->me_value;
+#endif
 }
 #define __Pyx_PyDict_GetItemStr           PyDict_GetItem
 #endif