[1/2,gdb/python] Handle normalized exception returned by PyErr_Fetch

Message ID 20240304160347.31182-1-tdevries@suse.de
State Superseded
Headers
Series [1/2,gdb/python] Handle normalized exception returned by PyErr_Fetch |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_gdb_build--master-aarch64 fail Testing failed
linaro-tcwg-bot/tcwg_gdb_build--master-arm fail Testing failed

Commit Message

Tom de Vries March 4, 2024, 4:03 p.m. UTC
  With python 3.12 and test-case gdb.python/py-block.exp, before commit
a207f6b3a38 ("Rewrite "python" command exception handling"), we had:
...
(gdb) python print (block['nonexistent'])^M
Traceback (most recent call last):^M
  File "<string>", line 1, in <module>^M
KeyError: 'nonexistent'^M
Error while executing Python code.^M
(gdb) PASS: gdb.python/py-block.exp: check nonexistent variable
...
but after the commit we have:
...
(gdb) python print (block['nonexistent'])^M
Python Exception <class 'KeyError'>: 'nonexistent'^M
Error occurred in Python: 'nonexistent'^M
(gdb) FAIL: gdb.python/py-block.exp: check nonexistent variable
...

In contrast, with python 3.6 we have:
...
(gdb) python print (block['nonexistent'])^M
Python Exception <class 'KeyError'>: nonexistent^M
Error occurred in Python: nonexistent^M
(gdb) PASS: gdb.python/py-block.exp: check nonexistent variable
...

The change in the test-case is:
...
-gdb_test "python print (block\['nonexistent'\])" ".*KeyError: 'nonexistent'.*" \
+gdb_test "python print (block\['nonexistent'\])" ".*KeyError.*: nonexistent.*" \
          "check nonexistent variable"
...
which drops the single quotes around the nonexistent string, which matches the
output with python 3.6, but not with python 3.12.

The difference is caused by a difference in the result of PyErr_Fetch.

[ PyErr_Fetch is deprecated in python 3.12, this will be addressed in a
follow-up commit. ]

With python 3.6, we have PyErr_Fetch returning:
...
(gdb) p PyObject_Print(error_value, stderr, 0)
'nonexistent'$3 = 0
(gdb) p PyObject_Print(error_value, stderr, 1)
nonexistent$4 = 0
...
but with python 3.12, we have instead:
...
(gdb) p (int)PyObject_Print(error_value,stderr, 0)
KeyError('nonexistent')$3 = 0
(gdb) p (int)PyObject_Print(error_value,stderr, 1)
'nonexistent'$4 = 0
...

In more detail, with python 3.12 we get a normalized exception, so the
error_value is an object of class KeyError.  With python 3.6, error_value is
an unnormalized exception, meaning not of class KeyError.

Apparantly we rely here on PyErr_Fetch to return an unnormalized exception.

Fix this by pretending to have an unnormalized exception in
gdbpy_err_fetch::to_string.

Tested on aarch64-linux.

PR python/31425
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=31425
---
 gdb/python/py-utils.c | 14 +++++++++++++-
 1 file changed, 13 insertions(+), 1 deletion(-)


base-commit: 1485a3fb63619cced99dd7a4a043cf01a0f423d9
  

Comments

Tom Tromey March 4, 2024, 6:40 p.m. UTC | #1
>>>>> "Tom" == Tom de Vries <tdevries@suse.de> writes:

Tom> +	  /* Detected a normalized exception.  */
Tom> +	  PyObject *args = PyException_GetArgs (m_error_value.get ());

According to:

https://docs.python.org/3/c-api/exceptions.html

... PyException_GetArgs returns a new reference; so this code leaks
memory.

I think args should be a gdbpy_ref<> here.

Tom
  

Patch

diff --git a/gdb/python/py-utils.c b/gdb/python/py-utils.c
index 9382eb62a5f..8eee33f89bb 100644
--- a/gdb/python/py-utils.c
+++ b/gdb/python/py-utils.c
@@ -196,7 +196,19 @@  gdbpy_err_fetch::to_string () const
      gdb.GdbError ("message").  */
 
   if (m_error_value.get () != nullptr && m_error_value.get () != Py_None)
-    return gdbpy_obj_to_string (m_error_value.get ());
+    {
+      if ((PyObject *)Py_TYPE (m_error_value.get ()) == m_error_type.get ())
+	{
+	  /* Detected a normalized exception.  */
+	  PyObject *args = PyException_GetArgs (m_error_value.get ());
+	  if (PyTuple_Size (args) == 1)
+	    {
+	      /* Pretend to be looking at an unnormalized exception.  */
+	      return gdbpy_obj_to_string (PyTuple_GetItem (args, 0));
+	    }
+	}
+      return gdbpy_obj_to_string (m_error_value.get ());
+    }
   else
     return gdbpy_obj_to_string (m_error_type.get ());
 }