[v2,1/4,gdb/python] Add gdbpy_handle_gdb_exception

Message ID 20240828082047.17174-1-tdevries@suse.de
State Committed
Headers
Series [v2,1/4,gdb/python] Add gdbpy_handle_gdb_exception |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_gdb_build--master-aarch64 success Build passed
linaro-tcwg-bot/tcwg_gdb_build--master-arm success Build passed
linaro-tcwg-bot/tcwg_gdb_check--master-aarch64 success Test passed

Commit Message

Tom de Vries Aug. 28, 2024, 8:20 a.m. UTC
  I've recently committed two patches:
- commit 2f8cd40c37a ("[gdb/python] Use GDB_PY_HANDLE_EXCEPTION more often")
- commit fbf8e4c35c2 ("[gdb/python] Use GDB_PY_SET_HANDLE_EXCEPTION more often")
which use the macros GDB_PY_HANDLE_EXCEPTION and GDB_PY_SET_HANDLE_EXCEPTION
more often, with the goal of making things more consistent.

Having done that, I wondered if a better approach could be possible.

Consider GDB_PY_HANDLE_EXCEPTION:
...
 /* Use this in a 'catch' block to convert the exception to a Python
    exception and return nullptr.  */
 #define GDB_PY_HANDLE_EXCEPTION(Exception)	\
   do {						\
     gdbpy_convert_exception (Exception);	\
     return nullptr;				\
   } while (0)
...

The macro nicely codifies how python handles exceptions:
- setting an error condition using some PyErr_Set* variant, and
- returning a value implying that something went wrong
presumably with the goal that using the macro will mean not accidentally:
- forgetting to return on error, or
- returning the wrong value on error.

The problems are that:
- the macro hides control flow, specifically the return statement, and
- the macro hides the return value.

For example, when reading somewhere:
...
  catch (const gdb_exception &except)
    {
      GDB_PY_HANDLE_EXCEPTION (except);
    }
...
in order to understand what this does, you have to know that the macro
returns, and that it returns nullptr.

Add a template gdbpy_handle_gdb_exception:
...
template<typename T>
[[nodiscard]] T
gdbpy_handle_gdb_exception (T val, const gdb_exception &e)
{
  gdbpy_convert_exception (e);
  return val;
}
...
which can be used instead:
...
  catch (const gdb_exception &except)
    {
      return gdbpy_handle_gdb_exception (nullptr, except);
    }
...

[ Initially I tried this:
...
template<auto val>
[[nodiscard]] auto
gdbpy_handle_gdb_exception (const gdb_exception &e)
{
  gdbpy_convert_exception (e);
  return val;
}
...
with which the usage is slightly better looking:
...
  catch (const gdb_exception &except)
    {
      return gdbpy_handle_gdb_exception<nullptr> (except);
    }
...
but I ran into trouble with older gcc compilers. ]

While still a single statement, we now have it clear:
- that the statement returns,
- what value the statement returns.

[ FWIW, this could also be handled by say:
...
-      GDB_PY_HANDLE_EXCEPTION (except);
+      GDB_PY_HANDLE_EXCEPTION_AND_RETURN_VAL (except, nullptr);
...
but I still didn't find the fact that it returns easy to spot.

Alternatively, this is the simplest form we could use:
...
      return gdbpy_convert_exception (e), nullptr;
...
but the pairing would not necessarily survive a copy/paste/edit cycle. ]

Also note how making the value explicit makes it easier to check for
consistency:
...
  catch (const gdb_exception &except)
    {
      return gdbpy_handle_gdb_exception (-1, except);
    }

  if (PyErr_Occurred ())
    return -1;
...
given that we do use the explicit constants almost everywhere else.

Compared to using GDB_PY_HANDLE_EXCEPTION, there is the burden now to specify
the return value, but I assume that this will be generally copy-pasted and
therefore present no problem.

Also, there's no longer a guarantee that there's an immediate return, but I
assume that nodiscard making sure that the return value is not silently
ignored is sufficient mitigation.

For now, re-implement GDB_PY_HANDLE_EXCEPTION and GDB_PY_SET_HANDLE_EXCEPTION
in terms of gdbpy_handle_gdb_exception.

Follow-up patches will eliminate the macros.

No functional changes.

Tested on x86_64-linux.
---
 gdb/python/python-internal.h | 28 +++++++++++++++++++---------
 1 file changed, 19 insertions(+), 9 deletions(-)


base-commit: 934fcc7c916ad2876515a099ee09fa40dc08b489
  

Comments

Tom de Vries Sept. 23, 2024, 8:12 a.m. UTC | #1
On 8/28/24 10:20, Tom de Vries wrote:
> I've recently committed two patches:
> - commit 2f8cd40c37a ("[gdb/python] Use GDB_PY_HANDLE_EXCEPTION more often")
> - commit fbf8e4c35c2 ("[gdb/python] Use GDB_PY_SET_HANDLE_EXCEPTION more often")
> which use the macros GDB_PY_HANDLE_EXCEPTION and GDB_PY_SET_HANDLE_EXCEPTION
> more often, with the goal of making things more consistent.
> 
> Having done that, I wondered if a better approach could be possible.
> 
> Consider GDB_PY_HANDLE_EXCEPTION:
> ...
>   /* Use this in a 'catch' block to convert the exception to a Python
>      exception and return nullptr.  */
>   #define GDB_PY_HANDLE_EXCEPTION(Exception)	\
>     do {						\
>       gdbpy_convert_exception (Exception);	\
>       return nullptr;				\
>     } while (0)
> ...
> 
> The macro nicely codifies how python handles exceptions:
> - setting an error condition using some PyErr_Set* variant, and
> - returning a value implying that something went wrong
> presumably with the goal that using the macro will mean not accidentally:
> - forgetting to return on error, or
> - returning the wrong value on error.
> 
> The problems are that:
> - the macro hides control flow, specifically the return statement, and
> - the macro hides the return value.
> 
> For example, when reading somewhere:
> ...
>    catch (const gdb_exception &except)
>      {
>        GDB_PY_HANDLE_EXCEPTION (except);
>      }
> ...
> in order to understand what this does, you have to know that the macro
> returns, and that it returns nullptr.
> 
> Add a template gdbpy_handle_gdb_exception:
> ...
> template<typename T>
> [[nodiscard]] T
> gdbpy_handle_gdb_exception (T val, const gdb_exception &e)
> {
>    gdbpy_convert_exception (e);
>    return val;
> }
> ...
> which can be used instead:
> ...
>    catch (const gdb_exception &except)
>      {
>        return gdbpy_handle_gdb_exception (nullptr, except);
>      }
> ...
> 
> [ Initially I tried this:
> ...
> template<auto val>
> [[nodiscard]] auto
> gdbpy_handle_gdb_exception (const gdb_exception &e)
> {
>    gdbpy_convert_exception (e);
>    return val;
> }
> ...
> with which the usage is slightly better looking:
> ...
>    catch (const gdb_exception &except)
>      {
>        return gdbpy_handle_gdb_exception<nullptr> (except);
>      }
> ...
> but I ran into trouble with older gcc compilers. ]
> 
> While still a single statement, we now have it clear:
> - that the statement returns,
> - what value the statement returns.
> 
> [ FWIW, this could also be handled by say:
> ...
> -      GDB_PY_HANDLE_EXCEPTION (except);
> +      GDB_PY_HANDLE_EXCEPTION_AND_RETURN_VAL (except, nullptr);
> ...
> but I still didn't find the fact that it returns easy to spot.
> 
> Alternatively, this is the simplest form we could use:
> ...
>        return gdbpy_convert_exception (e), nullptr;
> ...
> but the pairing would not necessarily survive a copy/paste/edit cycle. ]
> 
> Also note how making the value explicit makes it easier to check for
> consistency:
> ...
>    catch (const gdb_exception &except)
>      {
>        return gdbpy_handle_gdb_exception (-1, except);
>      }
> 
>    if (PyErr_Occurred ())
>      return -1;
> ...
> given that we do use the explicit constants almost everywhere else.
> 
> Compared to using GDB_PY_HANDLE_EXCEPTION, there is the burden now to specify
> the return value, but I assume that this will be generally copy-pasted and
> therefore present no problem.
> 
> Also, there's no longer a guarantee that there's an immediate return, but I
> assume that nodiscard making sure that the return value is not silently
> ignored is sufficient mitigation.
> 
> For now, re-implement GDB_PY_HANDLE_EXCEPTION and GDB_PY_SET_HANDLE_EXCEPTION
> in terms of gdbpy_handle_gdb_exception.
> 
> Follow-up patches will eliminate the macros.
> 
> No functional changes.
> 

Ping.

Thanks,
- Tom

> Tested on x86_64-linux.
> ---
>   gdb/python/python-internal.h | 28 +++++++++++++++++++---------
>   1 file changed, 19 insertions(+), 9 deletions(-)
> 
> diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h
> index bf3ab67ea74..da357e71e8d 100644
> --- a/gdb/python/python-internal.h
> +++ b/gdb/python/python-internal.h
> @@ -934,19 +934,17 @@ class gdbpy_gil
>   
>   /* Use this in a 'catch' block to convert the exception to a Python
>      exception and return nullptr.  */
> -#define GDB_PY_HANDLE_EXCEPTION(Exception)	\
> -  do {						\
> -    gdbpy_convert_exception (Exception);	\
> -    return nullptr;				\
> +#define GDB_PY_HANDLE_EXCEPTION(Exception)				\
> +  do {									\
> +    return gdbpy_handle_gdb_exception (nullptr, Exception);		\
>     } while (0)
>   
>   /* Use this in a 'catch' block to convert the exception to a Python
>      exception and return -1.  */
> -#define GDB_PY_SET_HANDLE_EXCEPTION(Exception)				\
> -    do {								\
> -      gdbpy_convert_exception (Exception);				\
> -      return -1;							\
> -    } while (0)
> +#define GDB_PY_SET_HANDLE_EXCEPTION(Exception)			\
> +  do {								\
> +    return gdbpy_handle_gdb_exception (-1, Exception);	\
> +  } while (0)
>   
>   int gdbpy_print_python_errors_p (void);
>   void gdbpy_print_stack (void);
> @@ -1013,6 +1011,18 @@ extern PyObject *gdbpy_gdberror_exc;
>   extern void gdbpy_convert_exception (const struct gdb_exception &)
>       CPYCHECKER_SETS_EXCEPTION;
>   
> + /* Use this in a 'catch' block to convert the exception E to a Python
> +    exception and return value VAL to signal that an exception occurred.
> +    Typically at the use site, that value will be returned immediately.  */
> +
> +template<typename T>
> +[[nodiscard]] T
> +gdbpy_handle_gdb_exception (T val, const gdb_exception &e)
> +{
> +  gdbpy_convert_exception (e);
> +  return val;
> +}
> +
>   int get_addr_from_python (PyObject *obj, CORE_ADDR *addr)
>       CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION;
>   
> 
> base-commit: 934fcc7c916ad2876515a099ee09fa40dc08b489
  
Tom Tromey Sept. 23, 2024, 6:06 p.m. UTC | #2
>>>>> "Tom" == Tom de Vries <tdevries@suse.de> writes:

>> For now, re-implement GDB_PY_HANDLE_EXCEPTION and
>> GDB_PY_SET_HANDLE_EXCEPTION
>> in terms of gdbpy_handle_gdb_exception.
>> Follow-up patches will eliminate the macros.
>> No functional changes.

Tom> Ping.

Thanks for doing this.
These are all ok.

Approved-By: Tom Tromey <tom@tromey.com>

Tom
  
Tom de Vries Sept. 24, 2024, 11:07 a.m. UTC | #3
On 9/23/24 20:06, Tom Tromey wrote:
>>>>>> "Tom" == Tom de Vries <tdevries@suse.de> writes:
> 
>>> For now, re-implement GDB_PY_HANDLE_EXCEPTION and
>>> GDB_PY_SET_HANDLE_EXCEPTION
>>> in terms of gdbpy_handle_gdb_exception.
>>> Follow-up patches will eliminate the macros.
>>> No functional changes.
> 
> Tom> Ping.
> 
> Thanks for doing this.
> These are all ok.
> 
> Approved-By: Tom Tromey <tom@tromey.com>

Thanks for the review, pushed.

- Tom
  

Patch

diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h
index bf3ab67ea74..da357e71e8d 100644
--- a/gdb/python/python-internal.h
+++ b/gdb/python/python-internal.h
@@ -934,19 +934,17 @@  class gdbpy_gil
 
 /* Use this in a 'catch' block to convert the exception to a Python
    exception and return nullptr.  */
-#define GDB_PY_HANDLE_EXCEPTION(Exception)	\
-  do {						\
-    gdbpy_convert_exception (Exception);	\
-    return nullptr;				\
+#define GDB_PY_HANDLE_EXCEPTION(Exception)				\
+  do {									\
+    return gdbpy_handle_gdb_exception (nullptr, Exception);		\
   } while (0)
 
 /* Use this in a 'catch' block to convert the exception to a Python
    exception and return -1.  */
-#define GDB_PY_SET_HANDLE_EXCEPTION(Exception)				\
-    do {								\
-      gdbpy_convert_exception (Exception);				\
-      return -1;							\
-    } while (0)
+#define GDB_PY_SET_HANDLE_EXCEPTION(Exception)			\
+  do {								\
+    return gdbpy_handle_gdb_exception (-1, Exception);	\
+  } while (0)
 
 int gdbpy_print_python_errors_p (void);
 void gdbpy_print_stack (void);
@@ -1013,6 +1011,18 @@  extern PyObject *gdbpy_gdberror_exc;
 extern void gdbpy_convert_exception (const struct gdb_exception &)
     CPYCHECKER_SETS_EXCEPTION;
 
+ /* Use this in a 'catch' block to convert the exception E to a Python
+    exception and return value VAL to signal that an exception occurred.
+    Typically at the use site, that value will be returned immediately.  */
+
+template<typename T>
+[[nodiscard]] T
+gdbpy_handle_gdb_exception (T val, const gdb_exception &e)
+{
+  gdbpy_convert_exception (e);
+  return val;
+}
+
 int get_addr_from_python (PyObject *obj, CORE_ADDR *addr)
     CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION;