From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from simark.ca by simark.ca with LMTP id AalXE4sxsWr/pCwAWB0awg (envelope-from ) for ; Mon, 21 Sep 2026 09:30:51 -0400 Authentication-Results: simark.ca; dkim=pass (2048-bit key; unprotected) header.d=vrany.io header.i=@vrany.io header.a=rsa-sha256 header.s=protonmail header.b=oWst3+NW; dkim-atps=neutral Received: by simark.ca (Postfix, from userid 112) id 427CC1E051; Mon, 21 Sep 2026 09:30:51 -0400 (EDT) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-25) on simark.ca X-Spam-Level: X-Spam-Status: No, score=-2.4 required=5.0 tests=ARC_SIGNED,ARC_VALID,BAYES_00, DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,MAILING_LIST_MULTI, RCVD_IN_DNSWL_MED,RCVD_IN_VALIDITY_CERTIFIED_BLOCKED, RCVD_IN_VALIDITY_RPBL_BLOCKED,RCVD_IN_VALIDITY_SAFE_BLOCKED autolearn=ham autolearn_force=no version=4.0.1 Received: from vm01.sourceware.org (vm01.sourceware.org [38.145.34.32]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange x25519 server-signature ECDSA (prime256v1) server-digest SHA256) (No client certificate requested) by simark.ca (Postfix) with ESMTPS id C66181E01F for ; Mon, 21 Sep 2026 09:30:47 -0400 (EDT) Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 4F5D34BA79A2 for ; Mon, 21 Sep 2026 13:30:47 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 4F5D34BA79A2 Authentication-Results: sourceware.org; dkim=pass (2048-bit key, unprotected) header.d=vrany.io header.i=@vrany.io header.a=rsa-sha256 header.s=protonmail header.b=oWst3+NW Received: from mail-4323.protonmail.ch (mail-4323.protonmail.ch [185.70.43.23]) by sourceware.org (Postfix) with ESMTPS id D4A494BA5436 for ; Mon, 21 Sep 2026 13:30:08 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org D4A494BA5436 Authentication-Results: sourceware.org; dmarc=pass (p=quarantine dis=none) header.from=vrany.io Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=vrany.io ARC-Filter: OpenARC Filter v1.0.0 sourceware.org D4A494BA5436 Authentication-Results: sourceware.org; arc=none smtp.remote-ip=185.70.43.23 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1789997409; cv=none; b=WWY/pbOXstUxWvkO8LYuhSHSotY5gCpjz2Yp3cAVqIga32BQnuR6/gAkk1dKRUepNZtQ03fLRm1arcDAqjNIltVJ1BGKb8brJWdLjAL76MVSlFQyk9gRtRGIw6rIurqGu96xHOdzokaFpsWNILF5vrG8FNB6xsa8LQGHwKtsjzM= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1789997409; c=relaxed/simple; bh=JGWdb9Ow93Iyz3NEME8P37dVTStDEgpROuyq9wxcrK4=; h=DKIM-Signature:Date:To:From:Subject:Message-ID:MIME-Version; b=W0VvJicPgsQo/jZSgfhUag1I2cY6rQu3nTLVy8E1IJmtAYaDrMjqX5Dn5K4o+zGN5Osg1WUbMJhuizmStIWBOZNDDhYdxd2SkX/rJ/whfQS4e+5tlJ6noTZroudsQd/zaqj4hBHzcuEsh6ctCbp/rauWX0ESkOhyLIvaij/TfjU= ARC-Authentication-Results: i=1; sourceware.org; dkim=pass (2048-bit key, unprotected) header.d=vrany.io header.i=@vrany.io header.a=rsa-sha256 header.s=protonmail header.b=oWst3+NW DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org D4A494BA5436 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=vrany.io; s=protonmail; t=1789997399; x=1790256599; bh=6VDK5FiXj9kc/CrROLVdKW388RqAezh4BHaCXggjaIA=; h=Date:To:From:Subject:Message-ID:In-Reply-To:References: Feedback-ID:From:To:Cc:Date:Subject:Reply-To:Feedback-ID: Message-ID:BIMI-Selector; b=oWst3+NWAd5DCQt5D8zt1nbo14w2n3B4GocgdcfDhFkBfpHRFIXcxRryehbVeEoO2 eRDXKGdyEi6tZxjf5HcJy5wnTR3GMxQaqhv4C20minKjWjhhedVs/M0fu029QryLS3 ra5KWuXKj9mvzsZK/+6hZ/x2j9Mjr7u7IYaCAEk1D2FyU1mXM09LSTnuZMn2T+HxqQ 1zHv6HOtFDhvJyKh+nmZXdZ1EcsLKWU4yvXJqLzyk4gA8fI8SPqk9M4To5HfgUpPxT UeG03pNetyMhx6/u6Yg7Y/nSwVBH1p5Of7ukJ37rE3xtqAci1GlEYSjGOOMUq6j9NG wvHuED3dhjd2w== Date: Mon, 21 Sep 2026 13:29:53 +0000 To: Tom Tromey , gdb-patches@sourceware.org From: Jan Vrany Subject: Re: [PATCH] Convert py-connection.c to the Python safety API Message-ID: In-Reply-To: <20260904180714.2872376-1-tom@tromey.com> References: <20260904180714.2872376-1-tom@tromey.com> Feedback-ID: 40767693:user:proton X-Pm-Message-ID: ac60bebb48cd3e4bd839d1aa104bb4d46b45bb40 MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable X-BeenThere: gdb-patches@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Gdb-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: gdb-patches-bounces~public-inbox=simark.ca@sourceware.org On Fri, 2026-09-04 at 12:07 -0600, Tom Tromey wrote: > This changes much of py-connection.c to the Python safety API. >=20 > A few things aren't yet changed: >=20 > * Event emission > * target_to_connection_object >=20 > These will be dealt with later. >=20 > Note that this patch fixes a reference leak in the "send_packet" > command.=C2=A0 Previously it did: >=20 > -=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 packet_obj =3D PyUnicode_AsASCIIString (p= acket_obj); >=20 > However PyUnicode_AsASCIIString returns a new reference; and this > reference was never released. >=20 > This patch includes the noargs_function template function from another > patch I sent.=C2=A0 The two are identical. >=20 > I made a couple of safety-related changes to py-wrappers.h.=C2=A0 Note th= at > there is one special case where the buffer returned by > PyBytes_AsStringAndSize may be written to.=C2=A0 If gdb ever needs to > exploit this case, I think a new API should be introduced. > --- > =C2=A0gdb/python/py-connection.c=C2=A0=C2=A0 | 316 +++++++++++++++-------= ------------- > =C2=A0gdb/python/py-safety.h=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 |=C2= =A0 21 +++ > =C2=A0gdb/python/py-wrappers.h=C2=A0=C2=A0=C2=A0=C2=A0 |=C2=A0 29 +++- > =C2=A0gdb/python/python-internal.h |=C2=A0=C2=A0 2 +- > =C2=A0gdb/python/python.c=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2= =A0=C2=A0 |=C2=A0=C2=A0 4 +- > =C2=A05 files changed, 180 insertions(+), 192 deletions(-) >=20 > diff --git a/gdb/python/py-connection.c b/gdb/python/py-connection.c > index bc738669b79..7df2618bcd3 100644 > --- a/gdb/python/py-connection.c > +++ b/gdb/python/py-connection.c > @@ -40,6 +40,53 @@ struct connection_object : public PyObject > =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 indicates that this Python object is now i= n the invalid state (see > =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 the is_valid() method below).=C2=A0 */ > =C2=A0=C2=A0 struct process_stratum_target *target; > + > +=C2=A0 /* Require that this object be valid.=C2=A0 */ > +=C2=A0 void require () const; > + I think it'd be nice to have consistent naming (and maybe even implementati= on). In py-symbol.c and py-tui.c it is "require_valid". Perhaps even the implementation can be unified to use is_valid() instead of nullptr test? void require_valid () const { if (!is_valid ()) gdbpy_err_format (PyExc_RuntimeError,=C2=A0 _("Connection no longer exists.")); } Thanks! Jan > +=C2=A0 /* Implement is_valid method.=C2=A0 */ > +=C2=A0 bool is_valid () const > +=C2=A0 { > +=C2=A0=C2=A0=C2=A0 return target !=3D nullptr; > +=C2=A0 } > + > +=C2=A0 /* Return the id number of this connection.=C2=A0 */ > +=C2=A0 int get_connection_num () const > +=C2=A0 { > +=C2=A0=C2=A0=C2=A0 require (); > +=C2=A0=C2=A0=C2=A0 return target->connection_number; > +=C2=A0 } > + > +=C2=A0 /* Return a string that gives the short name for this connection = type.=C2=A0 */ > +=C2=A0 const char *get_connection_type () const > +=C2=A0 { > +=C2=A0=C2=A0=C2=A0 require (); > +=C2=A0=C2=A0=C2=A0 return target->shortname (); > +=C2=A0 } > + > +=C2=A0 /* Return a string that gives a longer description of this > +=C2=A0=C2=A0=C2=A0=C2=A0 connection type.=C2=A0 */ > +=C2=A0 const char *get_description () const > +=C2=A0 { > +=C2=A0=C2=A0=C2=A0 require (); > +=C2=A0=C2=A0=C2=A0 return target->longname (); > +=C2=A0 } > + > +=C2=A0 /* Return a string that gives additional details about this > +=C2=A0=C2=A0=C2=A0=C2=A0 connection, or None, if there are no additional= details for this > +=C2=A0=C2=A0=C2=A0=C2=A0 connection type.=C2=A0 */ > +=C2=A0 const char *get_connection_details () const > +=C2=A0 { > +=C2=A0=C2=A0=C2=A0 require (); > +=C2=A0=C2=A0=C2=A0 return target->connection_string (); > +=C2=A0 } > + > +=C2=A0 /* Implement repr() for gdb.TargetConnection.=C2=A0 */ > +=C2=A0 gdbpy_ref<> repr (); > + > +=C2=A0 /* The send_packet method.=C2=A0 */ > +=C2=A0 gdbpy_ref<> send_packet (gdbpy_borrowed_ref<> args, > +=09=09=09=C2=A0=C2=A0 gdbpy_opt_borrowed_ref<> kw); > =C2=A0}; >=20 > =C2=A0static_assert (gdb::is_python_allocatable_v); > @@ -48,23 +95,20 @@ extern PyTypeObject connection_object_type; >=20 > =C2=A0extern PyTypeObject remote_connection_object_type; >=20 > -/* Require that CONNECTION be valid.=C2=A0 */ > -#define CONNPY_REQUIRE_VALID(connection)=09=09=09\ > -=C2=A0 do {=09=09=09=09=09=09=09=09\ > -=C2=A0=C2=A0=C2=A0 if (connection->target =3D=3D nullptr)=09=09=09=09\ > -=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 {=09=09=09=09=09=09=09=09\ > -=09PyErr_SetString (PyExc_RuntimeError,=09=09=09\ > -=09=09=09 _("Connection no longer exists."));=09\ > -=09return nullptr;=09=09=09=09=09=09\ > -=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 }=09=09=09=09=09=09=09=09\ > -=C2=A0 } while (0) > - > =C2=A0/* A map between process_stratum targets and the Python object repr= esenting > =C2=A0=C2=A0=C2=A0 them.=C2=A0 We actually hold a gdbpy_ref around the Py= thon object so that > =C2=A0=C2=A0=C2=A0 reference counts are handled correctly when entries ar= e deleted.=C2=A0 */ > =C2=A0static gdb::unordered_map =C2=A0=09=09=09=C2=A0 gdbpy_ref> all_connection_object= s; >=20 > +void > +connection_object::require () const > +{ > +=C2=A0 if (target =3D=3D nullptr) > +=C2=A0=C2=A0=C2=A0 gdbpy_err_set_string (PyExc_RuntimeError, > +=09=09=09=C2=A0 _("Connection no longer exists.")); > +} > + > =C2=A0/* Return a reference to a gdb.TargetConnection object for TARGET.= =C2=A0 If > =C2=A0=C2=A0=C2=A0 TARGET is nullptr then a reference to None is returned= . >=20 > @@ -107,27 +151,26 @@ target_to_connection_object (process_stratum_target= *target) > =C2=A0/* Return a list of gdb.TargetConnection objects, one for each curr= ently > =C2=A0=C2=A0=C2=A0 active connection.=C2=A0 The returned list is in no pa= rticular order.=C2=A0 */ >=20 > -PyObject * > -gdbpy_connections (PyObject *self, PyObject *args) > +gdbpy_ref<> > +gdbpy_connections () > =C2=A0{ > -=C2=A0 gdbpy_ref<> list (PyList_New (0)); > -=C2=A0 if (list =3D=3D nullptr) > -=C2=A0=C2=A0=C2=A0 return nullptr; > +=C2=A0 gdbpy_ref<> list =3D gdbpy_new_list (0); >=20 > =C2=A0=C2=A0 for (process_stratum_target *target : all_non_exited_process= _targets ()) > =C2=A0=C2=A0=C2=A0=C2=A0 { > =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 gdb_assert (target !=3D nullptr); >=20 > =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 gdbpy_ref<> conn =3D target_to_conne= ction_object (target); > +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 /* FIXME: Python safety.=C2=A0 target_to_= connection_object should > +=09 throw on error.=C2=A0 */ > =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 if (conn =3D=3D nullptr) > =C2=A0=09return nullptr; > =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 gdb_assert (conn.get () !=3D Py_None= ); >=20 > -=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 if (PyList_Append (list.get (), conn.get = ()) < 0) > -=09return nullptr; > +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 gdbpy_list_append (list, conn); > =C2=A0=C2=A0=C2=A0=C2=A0 } >=20 > -=C2=A0 return list.release (); > +=C2=A0 return list; > =C2=A0} >=20 > =C2=A0/* Emit a connection event for TARGET to REGISTRY.=C2=A0 Return 0 o= n success, or > @@ -193,90 +236,20 @@ connpy_connection_dealloc (PyObject *obj) >=20 > =C2=A0/* Implement repr() for gdb.TargetConnection.=C2=A0 */ >=20 > -static PyObject * > -connpy_repr (PyObject *obj) > +gdbpy_ref<> > +connection_object::repr () > =C2=A0{ > -=C2=A0 connection_object *self =3D (connection_object *) obj; > -=C2=A0 process_stratum_target *target =3D self->target; > - > =C2=A0=C2=A0 if (target =3D=3D nullptr) > -=C2=A0=C2=A0=C2=A0 return gdb_py_invalid_object_repr (obj); > - > -=C2=A0 return PyUnicode_FromFormat ("<%s num=3D%d, what=3D\"%s\">", > -=09=09=09=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 gdbpy_py_obj_tp_name (obj)= .c_str (), > -=09=09=09=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 target->connection_number, > -=09=09=09=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 make_target_connection_str= ing (target).c_str ()); > -} > - > -/* Implementation of gdb.TargetConnection.is_valid() -> Boolean.=C2= =A0 Returns > -=C2=A0=C2=A0 True if this connection object is still associated with a > -=C2=A0=C2=A0 process_stratum_target, otherwise, returns False.=C2=A0 */ > - > -static PyObject * > -connpy_is_valid (PyObject *self, PyObject *args) > -{ > -=C2=A0 connection_object *conn =3D (connection_object *) self; > - > -=C2=A0 if (conn->target =3D=3D nullptr) > -=C2=A0=C2=A0=C2=A0 return py_false ().release (); > - > -=C2=A0 return py_true ().release (); > -} > - > -/* Return the id number of this connection.=C2=A0 */ > - > -static PyObject * > -connpy_get_connection_num (PyObject *self, void *closure) > -{ > -=C2=A0 connection_object *conn =3D (connection_object *) self; > - > -=C2=A0 CONNPY_REQUIRE_VALID (conn); > - > -=C2=A0 auto num =3D conn->target->connection_number; > -=C2=A0 return gdb_py_object_from_longest (num).release (); > -} > - > -/* Return a string that gives the short name for this connection type.= =C2=A0 */ > - > -static PyObject * > -connpy_get_connection_type (PyObject *self, void *closure) > -{ > -=C2=A0 connection_object *conn =3D (connection_object *) self; > - > -=C2=A0 CONNPY_REQUIRE_VALID (conn); > - > -=C2=A0 const char *shortname =3D conn->target->shortname (); > -=C2=A0 return host_string_to_python_string (shortname).release (); > -} > - > -/* Return a string that gives a longer description of this connection ty= pe.=C2=A0 */ > - > -static PyObject * > -connpy_get_description (PyObject *self, void *closure) > -{ > -=C2=A0 connection_object *conn =3D (connection_object *) self; > - > -=C2=A0 CONNPY_REQUIRE_VALID (conn); > - > -=C2=A0 const char *longname =3D conn->target->longname (); > -=C2=A0 return host_string_to_python_string (longname).release (); > -} > - > -/* Return a string that gives additional details about this connection, = or > -=C2=A0=C2=A0 None, if there are no additional details for this connectio= n type.=C2=A0 */ > - > -static PyObject * > -connpy_get_connection_details (PyObject *self, void *closure) > -{ > -=C2=A0 connection_object *conn =3D (connection_object *) self; > - > -=C2=A0 CONNPY_REQUIRE_VALID (conn); > - > -=C2=A0 const char *details =3D conn->target->connection_string (); > -=C2=A0 if (details !=3D nullptr) > -=C2=A0=C2=A0=C2=A0 return host_string_to_python_string (details).release= (); > -=C2=A0 else > -=C2=A0=C2=A0=C2=A0 return py_none ().release (); > +=C2=A0=C2=A0=C2=A0 /* FIXME: Python safety.=C2=A0 gdb_py_invalid_object_= repr ought to > +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 throw on error, and return gdbpy_re= f<>, but currently does > +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 not.=C2=A0 */ > +=C2=A0=C2=A0=C2=A0 return gdbpy_ref<> (gdb_py_invalid_object_repr (this)= ); > + > +=C2=A0 return (gdbpy_unicode_from_format > +=09=C2=A0 ("<%s num=3D%d, what=3D\"%s\">", > +=09=C2=A0=C2=A0 gdbpy_py_obj_tp_name (this).c_str (), > +=09=C2=A0=C2=A0 target->connection_number, > +=09=C2=A0=C2=A0 make_target_connection_string (target).c_str ())); > =C2=A0} >=20 > =C2=A0/* Python specific initialization for this file.=C2=A0 */ > @@ -310,16 +283,16 @@ struct py_send_packet_callbacks : public send_remot= e_packet_callbacks > =C2=A0=C2=A0 void sending (gdb::array_view &buf) override > =C2=A0=C2=A0 { /* Nothing.=C2=A0 */ } >=20 > -=C2=A0 /* When the result is returned create a Python object and assign = this > -=C2=A0=C2=A0=C2=A0=C2=A0 into M_RESULT.=C2=A0 If for any reason we can't= create a Python object to > -=C2=A0=C2=A0=C2=A0=C2=A0 represent the result then M_RESULT is set to nu= llptr, and Python's > -=C2=A0=C2=A0=C2=A0=C2=A0 internal error flags will be set.=C2=A0 If the = result we got back from the > -=C2=A0=C2=A0=C2=A0=C2=A0 remote is empty then set the result to None.= =C2=A0 */ > +=C2=A0 /* When the result is returned create a Python object and assign > +=C2=A0=C2=A0=C2=A0=C2=A0 this into M_RESULT.=C2=A0 If for any reason we = can't create a Python > +=C2=A0=C2=A0=C2=A0=C2=A0 object to represent the result then an exceptio= n is thrown.=C2=A0 If > +=C2=A0=C2=A0=C2=A0=C2=A0 the result we got back from the remote is empty= then set the > +=C2=A0=C2=A0=C2=A0=C2=A0 result to None.=C2=A0 */ >=20 > =C2=A0=C2=A0 void received (gdb::array_view &buf) override > =C2=A0=C2=A0 { > =C2=A0=C2=A0=C2=A0=C2=A0 if (buf.size () > 0 && buf.data ()[0] !=3D '\0') > -=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 m_result.reset (PyBytes_FromStringAndSize= (buf.data (), buf.size ())); > +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 m_result =3D gdbpy_bytes_from_string_and_= size (buf); > =C2=A0=C2=A0=C2=A0=C2=A0 else > =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 { > =C2=A0=09/* We didn't get back any result data; set the result to None.= =C2=A0 */ > @@ -327,22 +300,14 @@ struct py_send_packet_callbacks : public send_remot= e_packet_callbacks > =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 } > =C2=A0=C2=A0 } >=20 > -=C2=A0 /* Get a reference to the result as a Python object.=C2=A0 It is = invalid to > -=C2=A0=C2=A0=C2=A0=C2=A0 call this before sending a packet to the remote= and processing the > -=C2=A0=C2=A0=C2=A0=C2=A0 reply. > +=C2=A0 /* Return the resulting Python object.=C2=A0 It is invalid to cal= l this > +=C2=A0=C2=A0=C2=A0=C2=A0 before sending a packet to the remote and proce= ssing the reply. >=20 > -=C2=A0=C2=A0=C2=A0=C2=A0 The result value is setup in the RECEIVED call = above.=C2=A0 If the RECEIVED > -=C2=A0=C2=A0=C2=A0=C2=A0 call causes an error then the result value will= be set to nullptr, > -=C2=A0=C2=A0=C2=A0=C2=A0 and the error reason is left stored in Python's= global error state. > +=C2=A0=C2=A0=C2=A0=C2=A0 The result value is setup in the RECEIVED call = above.=C2=A0 */ >=20 > -=C2=A0=C2=A0=C2=A0=C2=A0 It is important that the result is inspected im= mediately after sending > -=C2=A0=C2=A0=C2=A0=C2=A0 a packet to the remote, and any error fetched,= =C2=A0 calling any other > -=C2=A0=C2=A0=C2=A0=C2=A0 Python functions that might clear the error sta= te, or rely on an error > -=C2=A0=C2=A0=C2=A0=C2=A0 not being set will cause undefined behavior.= =C2=A0 */ > - > -=C2=A0 gdbpy_ref<> result () const > +=C2=A0 gdbpy_ref<> &&result () > =C2=A0=C2=A0 { > -=C2=A0=C2=A0=C2=A0 return m_result; > +=C2=A0=C2=A0=C2=A0 return std::move (m_result); > =C2=A0=C2=A0 } >=20 > =C2=A0private: > @@ -357,70 +322,45 @@ struct py_send_packet_callbacks : public send_remot= e_packet_callbacks > =C2=A0=C2=A0=C2=A0 the packet to be sent must be non-empty, otherwise an = exception will be > =C2=A0=C2=A0=C2=A0 thrown.=C2=A0 */ >=20 > -static PyObject * > -connpy_send_packet (PyObject *self, PyObject *args, PyObject *kw) > +gdbpy_ref<> > +connection_object::send_packet (gdbpy_borrowed_ref<> args, > +=09=09=09=09gdbpy_opt_borrowed_ref<> kw) > =C2=A0{ > -=C2=A0 connection_object *conn =3D (connection_object *) self; > - > -=C2=A0 CONNPY_REQUIRE_VALID (conn); > +=C2=A0 require (); >=20 > =C2=A0=C2=A0 static const char *keywords[] =3D {"packet", nullptr}; > =C2=A0=C2=A0 PyObject *packet_obj; >=20 > -=C2=A0 if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "O", keywords, > -=09=09=09=09=09&packet_obj)) > -=C2=A0=C2=A0=C2=A0 return nullptr; > +=C2=A0 gdbpy_arg_parse_tuple_and_keywords (args, kw, "O", keywords, &pac= ket_obj); >=20 > =C2=A0=C2=A0 /* If the packet is a unicode string then convert it to a by= tes object.=C2=A0 */ > +=C2=A0 gdbpy_ref<> ascii_object; > =C2=A0=C2=A0 if (PyUnicode_Check (packet_obj)) > =C2=A0=C2=A0=C2=A0=C2=A0 { > =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 /* We encode the string to bytes usi= ng the ascii codec, if this fails > =C2=A0=09 then a suitable error will have been set.=C2=A0 */ > -=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 packet_obj =3D PyUnicode_AsASCIIString (p= acket_obj); > -=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 if (packet_obj =3D=3D nullptr) > -=09return nullptr; > +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 ascii_object =3D gdbpy_unicode_as_ascii_s= tring (packet_obj); > +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 packet_obj =3D ascii_object.get (); > =C2=A0=C2=A0=C2=A0=C2=A0 } >=20 > =C2=A0=C2=A0 /* Check the packet is now a bytes object.=C2=A0 */ > =C2=A0=C2=A0 if (!PyBytes_Check (packet_obj)) > -=C2=A0=C2=A0=C2=A0 { > -=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 PyErr_SetString (PyExc_TypeError, _("Pack= et is not a bytes object")); > -=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 return nullptr; > -=C2=A0=C2=A0=C2=A0 } > +=C2=A0=C2=A0=C2=A0 gdbpy_err_set_string (PyExc_TypeError, _("Packet is n= ot a bytes object")); >=20 > =C2=A0=C2=A0 Py_ssize_t packet_len =3D 0; > -=C2=A0 char *packet_str_nonconst =3D nullptr; > -=C2=A0 if (PyBytes_AsStringAndSize (packet_obj, &packet_str_nonconst, > -=09=09=09=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 &packet_len) < 0) > -=C2=A0=C2=A0=C2=A0 return nullptr; > -=C2=A0 const char *packet_str =3D packet_str_nonconst; > -=C2=A0 gdb_assert (packet_str !=3D nullptr); > +=C2=A0 const char *packet_str =3D nullptr; > +=C2=A0 gdbpy_bytes_as_string_and_size (packet_obj, &packet_str, &packet_= len); >=20 > =C2=A0=C2=A0 if (packet_len =3D=3D 0) > -=C2=A0=C2=A0=C2=A0 { > -=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 PyErr_SetString (PyExc_ValueError, _("Pac= ket must not be empty")); > -=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 return nullptr; > -=C2=A0=C2=A0=C2=A0 } > +=C2=A0=C2=A0=C2=A0 gdbpy_err_set_string (PyExc_ValueError, _("Packet mus= t not be empty")); >=20 > -=C2=A0 try > -=C2=A0=C2=A0=C2=A0 { > -=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 scoped_restore_current_thread restore_thr= ead; > -=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 switch_to_target_no_thread (conn->target)= ; > - > -=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 gdb::array_view view (packet_= str, packet_len); > -=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 py_send_packet_callbacks callbacks; > -=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 send_remote_packet (view, &callbacks); > -=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 PyObject *result =3D callbacks.result ().= release (); > -=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 /* If we encountered an error converting = the reply to a Python > -=09 object, then the result here can be nullptr.=C2=A0 In that case, Pyt= hon > -=09 should be aware that an error occurred.=C2=A0 */ > -=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 gdb_assert ((result =3D=3D nullptr) =3D= =3D (PyErr_Occurred () !=3D nullptr)); > -=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 return result; > -=C2=A0=C2=A0=C2=A0 } > -=C2=A0 catch (const gdb_exception &except) > -=C2=A0=C2=A0=C2=A0 { > -=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 return gdbpy_handle_gdb_exception (nullpt= r, except); > -=C2=A0=C2=A0=C2=A0 } > +=C2=A0 scoped_restore_current_thread restore_thread; > +=C2=A0 switch_to_target_no_thread (target); > + > +=C2=A0 gdb::array_view view (packet_str, packet_len); > +=C2=A0 py_send_packet_callbacks callbacks; > +=C2=A0 send_remote_packet (view, &callbacks); > +=C2=A0 return callbacks.result (); > =C2=A0} >=20 > =C2=A0/* Global initialization for this file.=C2=A0 */ > @@ -437,36 +377,48 @@ GDBPY_INITIALIZE_FILE (gdbpy_initialize_connection)= ; >=20 > =C2=A0static PyMethodDef connection_object_methods[] =3D > =C2=A0{ > -=C2=A0 { "is_valid", connpy_is_valid, METH_NOARGS, > +=C2=A0 noargs_method ("= is_valid", > =C2=A0=C2=A0=C2=A0=C2=A0 "is_valid () -> Boolean.\n\ > -Return true if this TargetConnection is valid, false if not." }, > -=C2=A0 { NULL } > +Return true if this TargetConnection is valid, false if not."), > +=C2=A0 { nullptr } > =C2=A0}; >=20 > =C2=A0/* Methods for the gdb.RemoteTargetConnection object type.=C2=A0 */ >=20 > =C2=A0static PyMethodDef remote_connection_object_methods[] =3D > =C2=A0{ > -=C2=A0 { "send_packet", (PyCFunction) connpy_send_packet, > -=C2=A0=C2=A0=C2=A0 METH_VARARGS | METH_KEYWORDS, > +=C2=A0 varargs_method > +=C2=A0=C2=A0 ("send_packet", > =C2=A0=C2=A0=C2=A0=C2=A0 "send_packet (PACKET) -> Bytes\n\ > -Send PACKET to a remote target, return the reply as a bytes array." }, > -=C2=A0 { NULL } > +Send PACKET to a remote target, return the reply as a bytes array."), > +=C2=A0 { nullptr } > =C2=A0}; >=20 > =C2=A0/* Attributes for the gdb.TargetConnection object type.=C2=A0 */ >=20 > =C2=A0static gdb_PyGetSetDef connection_object_getset[] =3D > =C2=A0{ > -=C2=A0 { "num", connpy_get_connection_num, NULL, > -=C2=A0=C2=A0=C2=A0 "ID number of this connection, as assigned by GDB.", = NULL }, > -=C2=A0 { "type", connpy_get_connection_type, NULL, > -=C2=A0=C2=A0=C2=A0 "A short string that is the name for this connection = type.", NULL }, > -=C2=A0 { "description", connpy_get_description, NULL, > -=C2=A0=C2=A0=C2=A0 "A longer string describing this connection type.", N= ULL }, > -=C2=A0 { "details", connpy_get_connection_details, NULL, > -=C2=A0=C2=A0=C2=A0 "A string containing additional connection details.",= NULL }, > -=C2=A0 { NULL } > +=C2=A0 { "num", > +=C2=A0=C2=A0=C2=A0 wrap_getter, > +=C2=A0=C2=A0=C2=A0 nullptr, > +=C2=A0=C2=A0=C2=A0 "ID number of this connection, as assigned by GDB.", > +=C2=A0=C2=A0=C2=A0 nullptr }, > +=C2=A0 { "type", > +=C2=A0=C2=A0=C2=A0 wrap_getter, > +=C2=A0=C2=A0=C2=A0 nullptr, > +=C2=A0=C2=A0=C2=A0 "A short string that is the name for this connection = type.", > +=C2=A0=C2=A0=C2=A0 nullptr }, > +=C2=A0 { "description", > +=C2=A0=C2=A0=C2=A0 wrap_getter, > +=C2=A0=C2=A0=C2=A0 nullptr, > +=C2=A0=C2=A0=C2=A0 "A longer string describing this connection type.", > +=C2=A0=C2=A0=C2=A0 nullptr }, > +=C2=A0 { "details", > +=C2=A0=C2=A0=C2=A0 wrap_getter, > +=C2=A0=C2=A0=C2=A0 nullptr, > +=C2=A0=C2=A0=C2=A0 "A string containing additional connection details.", > +=C2=A0=C2=A0=C2=A0 nullptr }, > +=C2=A0 { nullptr } > =C2=A0}; >=20 > =C2=A0/* Define the gdb.TargetConnection object type.=C2=A0 */ > @@ -482,7 +434,7 @@ PyTypeObject connection_object_type =3D > =C2=A0=C2=A0 0,=09=09=09=09=C2=A0 /* tp_getattr */ > =C2=A0=C2=A0 0,=09=09=09=09=C2=A0 /* tp_setattr */ > =C2=A0=C2=A0 0,=09=09=09=09=C2=A0 /* tp_compare */ > -=C2=A0 connpy_repr,=09=09=09=C2=A0 /* tp_repr */ > +=C2=A0 wrap_tp_callback, /*= tp_repr */ > =C2=A0=C2=A0 0,=09=09=09=09=C2=A0 /* tp_as_number */ > =C2=A0=C2=A0 0,=09=09=09=09=C2=A0 /* tp_as_sequence */ > =C2=A0=C2=A0 0,=09=09=09=09=C2=A0 /* tp_as_mapping */ > @@ -525,7 +477,7 @@ PyTypeObject remote_connection_object_type =3D > =C2=A0=C2=A0 0,=09=09=09=09=C2=A0 /* tp_getattr */ > =C2=A0=C2=A0 0,=09=09=09=09=C2=A0 /* tp_setattr */ > =C2=A0=C2=A0 0,=09=09=09=09=C2=A0 /* tp_compare */ > -=C2=A0 connpy_repr,=09=09=09=C2=A0 /* tp_repr */ > +=C2=A0 wrap_tp_callback, /*= tp_repr */ > =C2=A0=C2=A0 0,=09=09=09=09=C2=A0 /* tp_as_number */ > =C2=A0=C2=A0 0,=09=09=09=09=C2=A0 /* tp_as_sequence */ > =C2=A0=C2=A0 0,=09=09=09=09=C2=A0 /* tp_as_mapping */ > diff --git a/gdb/python/py-safety.h b/gdb/python/py-safety.h > index 3294f38c8b6..06324868817 100644 > --- a/gdb/python/py-safety.h > +++ b/gdb/python/py-safety.h > @@ -233,6 +233,27 @@ varargs_wrapper (PyObject *self, PyObject *args, PyO= bject *kw) >=20 > =C2=A0} /* namespace safety_details */ >=20 > +/* Create a PyMethodDef for a no-argument function.=C2=A0 It takes the > +=C2=A0=C2=A0 underlying function F as template parameters, and the name = and > +=C2=A0=C2=A0 documentation as arguments.=C2=A0 The function F is wrapped= to call > +=C2=A0=C2=A0 to_python and to catch exceptions per the safety protocol.= =C2=A0 F > +=C2=A0=C2=A0 should not accept any arguments.=C2=A0 */ > +template > +constexpr PyMethodDef > +noargs_function (const char *name, const char *doc) > +{ > +=C2=A0 using namespace safety_details; > +=C2=A0 return { > +=C2=A0=C2=A0=C2=A0 name, > +=C2=A0=C2=A0=C2=A0 [] (PyObject *self, PyObject *args) -> PyObject * > +=C2=A0=C2=A0=C2=A0 { > +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 return wrapped_function (); > +=C2=A0=C2=A0=C2=A0 }, > +=C2=A0=C2=A0=C2=A0 METH_NOARGS, > +=C2=A0=C2=A0=C2=A0 doc, > +=C2=A0 }; > +} > + > =C2=A0/* Create a PyMethodDef for a no-argument method.=C2=A0 It takes th= e > =C2=A0=C2=A0=C2=A0 underlying class C and a pointer-to-method M as templa= te > =C2=A0=C2=A0=C2=A0 parameters, and the name and documentation as argument= s.=C2=A0 The > diff --git a/gdb/python/py-wrappers.h b/gdb/python/py-wrappers.h > index 6c2b5e4d41e..c93722646c8 100644 > --- a/gdb/python/py-wrappers.h > +++ b/gdb/python/py-wrappers.h > @@ -81,14 +81,18 @@ gdbpy_bytes_as_string (gdbpy_borrowed_ref<> ref) > =C2=A0=C2=A0 return result; > =C2=A0} >=20 > -/* Wrapper for PyBytes_AsStringAndSize.=C2=A0 */ > +/* Wrapper for PyBytes_AsStringAndSize.=C2=A0 Note that, unlike the > +=C2=A0=C2=A0 underlying Python function, this attempts to be const-corre= ct -- > +=C2=A0=C2=A0 the caller should not write to the returned buffer.=C2= =A0 */ > =C2=A0static inline void > =C2=A0gdbpy_bytes_as_string_and_size (gdbpy_borrowed_ref<> ref, > -=09=09=09=09char **buffer, > +=09=09=09=09const char **buffer, > =C2=A0=09=09=09=09Py_ssize_t *length) > =C2=A0{ > -=C2=A0 if (PyBytes_AsStringAndSize (ref, buffer, length) =3D=3D -1) > +=C2=A0 char *temp; > +=C2=A0 if (PyBytes_AsStringAndSize (ref, &temp, length) =3D=3D -1) > =C2=A0=C2=A0=C2=A0=C2=A0 throw gdb_python_exception (); > +=C2=A0 *buffer =3D temp; > =C2=A0} >=20 > =C2=A0/* Wrapper for PyBytes_FromString.=C2=A0 */ > @@ -103,13 +107,14 @@ gdbpy_bytes_from_string (const char *str) > =C2=A0} >=20 > =C2=A0/* Wrapper for PyBytes_FromStringAndSize.=C2=A0 */ > -static inline gdbpy_ref<> > -gdbpy_bytes_from_string_and_size (const char *str, Py_ssize_t len) > +template > +gdbpy_ref<> > +gdbpy_bytes_from_string_and_size (gdb::array_view data) > =C2=A0{ > =C2=A0=C2=A0 /* Python allows STR=3D=3Dnullptr but it leaves the object > =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 uninitialized, and I think we should avoid= this in gdb.=C2=A0 */ > -=C2=A0 gdb_assert (str !=3D nullptr); > -=C2=A0 gdbpy_ref<> result (PyBytes_FromStringAndSize (str, len)); > +=C2=A0 gdb_assert (data.data () !=3D nullptr); > +=C2=A0 gdbpy_ref<> result (PyBytes_FromStringAndSize (data.data (), data= .size ())); > =C2=A0=C2=A0 if (result =3D=3D nullptr) > =C2=A0=C2=A0=C2=A0=C2=A0 throw gdb_python_exception (); > =C2=A0=C2=A0 return result; > @@ -217,6 +222,16 @@ gdbpy_unicode_from_format (const char *fmt, Arg... a= rgs) > =C2=A0=C2=A0 return result; > =C2=A0} >=20 > +/* Wrapper for PyUnicode_AsASCIIString.=C2=A0 */ > +static inline gdbpy_ref<> > +gdbpy_unicode_as_ascii_string (gdbpy_borrowed_ref<> arg) > +{ > +=C2=A0 gdbpy_ref<> result (PyUnicode_AsASCIIString (arg)); > +=C2=A0 if (result =3D=3D nullptr) > +=C2=A0=C2=A0=C2=A0 throw gdb_python_exception (); > +=C2=A0 return result; > +} > + > =C2=A0/* Wrapper for PyErr_SetString.=C2=A0 This always throws.=C2=A0 */ > =C2=A0[[noreturn]] static inline void > =C2=A0gdbpy_err_set_string (gdbpy_borrowed_ref<> type, const char *str) > diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h > index 2e8f35729cd..9d6c8eb4a01 100644 > --- a/gdb/python/python-internal.h > +++ b/gdb/python/python-internal.h > @@ -541,7 +541,7 @@ PyObject *gdbpy_buffer_to_membuf (gdb::unique_xmalloc= _ptr buffer, >=20 > =C2=A0struct process_stratum_target; > =C2=A0gdbpy_ref<> target_to_connection_object (process_stratum_target *ta= rget); > -PyObject *gdbpy_connections (PyObject *self, PyObject *args); > +gdbpy_ref<> gdbpy_connections (); >=20 > =C2=A0const struct block *block_object_to_block (PyObject *obj); > =C2=A0struct symbol *symbol_object_to_symbol (PyObject *obj); > diff --git a/gdb/python/python.c b/gdb/python/python.c > index 14c243b135e..c7ca29f9b01 100644 > --- a/gdb/python/python.c > +++ b/gdb/python/python.c > @@ -3279,9 +3279,9 @@ Register a TUI window constructor."), > =C2=A0=C2=A0=C2=A0=C2=A0 "architecture_names () -> List.\n\ > =C2=A0Return a list of all the architecture names GDB understands." }, >=20 > -=C2=A0 { "connections", gdbpy_connections, METH_NOARGS, > +=C2=A0 noargs_function ("connections", > =C2=A0=C2=A0=C2=A0=C2=A0 "connections () -> List.\n\ > -Return a list of gdb.TargetConnection objects." }, > +Return a list of gdb.TargetConnection objects."), >=20 > =C2=A0=C2=A0 { "format_address", (PyCFunction) gdbpy_format_address, > =C2=A0=C2=A0=C2=A0=C2=A0 METH_VARARGS | METH_KEYWORDS, > -- > 2.49.0