From patchwork Fri Oct 6 15:11:05 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jan Vrany X-Patchwork-Id: 77218 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 6B1D23856DDA for ; Fri, 6 Oct 2023 15:11:52 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 6B1D23856DDA DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=sourceware.org; s=default; t=1696605112; bh=jzQK59A7L8A5AmU4CsIf6RQ8EX7GflC6W6hykt32P4E=; h=To:CC:Subject:Date:In-Reply-To:References:List-Id: List-Unsubscribe:List-Archive:List-Post:List-Help:List-Subscribe: From:Reply-To:From; b=rGNpg1WU8m0znqfeCe6w5c+5QAAhcz2w9gMEEigmt+ZQf9r1XILnzJfok7Aid2CHE VhOAV60uQ4SHK4k7f7VrGnIgWietL59C8F2dKpVOIgd6Fjyp/k7k0jNKvF9oeGhA0J MPy0rLELJvFOGxmbPJG+rEDzvKgYcNwqejUgmE9M= X-Original-To: gdb-patches@sourceware.org Delivered-To: gdb-patches@sourceware.org Received: from us-smtp-delivery-114.mimecast.com (us-smtp-delivery-114.mimecast.com [170.10.129.114]) by sourceware.org (Postfix) with ESMTPS id 5509B3856DC2 for ; Fri, 6 Oct 2023 15:11:24 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 5509B3856DC2 Received: from NAM12-DM6-obe.outbound.protection.outlook.com (mail-dm6nam12lp2172.outbound.protection.outlook.com [104.47.59.172]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id us-mta-509-EgAJX56tM4GjH87-oICowg-1; Fri, 06 Oct 2023 11:11:22 -0400 X-MC-Unique: EgAJX56tM4GjH87-oICowg-1 Received: from SA0PR17MB4314.namprd17.prod.outlook.com (2603:10b6:806:e7::16) by CO3PR17MB5775.namprd17.prod.outlook.com (2603:10b6:303:17b::7) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.6813.19; Fri, 6 Oct 2023 15:11:19 +0000 Received: from SA0PR17MB4314.namprd17.prod.outlook.com ([fe80::30eb:4c4a:cb5a:dd91]) by SA0PR17MB4314.namprd17.prod.outlook.com ([fe80::30eb:4c4a:cb5a:dd91%4]) with mapi id 15.20.6838.029; Fri, 6 Oct 2023 15:11:19 +0000 To: gdb-patches@sourceware.org CC: Jan Vrany , Andrew Burgess Subject: [PATCH v3 1/2] gdb/python: generalize serialize_mi_result() Date: Fri, 6 Oct 2023 16:11:05 +0100 Message-ID: <20231006151106.315633-2-jan.vrany@labware.com> X-Mailer: git-send-email 2.40.1 In-Reply-To: <20231006151106.315633-1-jan.vrany@labware.com> References: <87bkddtamc.fsf@redhat.com> <20231006151106.315633-1-jan.vrany@labware.com> X-ClientProxiedBy: LO4P123CA0347.GBRP123.PROD.OUTLOOK.COM (2603:10a6:600:18d::10) To SA0PR17MB4314.namprd17.prod.outlook.com (2603:10b6:806:e7::16) MIME-Version: 1.0 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: SA0PR17MB4314:EE_|CO3PR17MB5775:EE_ X-MS-Office365-Filtering-Correlation-Id: e748f2b7-bd09-4e2c-8cda-08dbc67e7ea2 X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0 X-Microsoft-Antispam-Message-Info: EqNQxKuuZoaHUULVMwOXHOVfjqJ6TzT42/HuMwrOkOHd1UCGhrOGGW/DjRvOiE62j9hN8UUWLfygl9To/7c9qVhLrkjtltGMLhDPlZJXuxtCzf3HeZImPfhE7QAKaAkYa2E/MIe9UBTPkfOyG+wfrWTjj8UHLjGmOkKV86CpokVssWnD7VJ0r8qFcZ7t742IdmP2XI+RrMZmrtwOEfP4zSAAi9FRayux996lL9tG2lDNUf+mY4H9FnWvffilSBfTnObWD4Y95K4W2pmbbizAkLIayMkdwtnQBopvJFeb6vOaSh67ivbDMxQMxmb3BHZgcklih3p7/3Df+kRZAjRFyQzgaAIxSZGrqFpuvvmBdqtdyAbEVZST7EJGbF7FnsW3ce4IbdmPJL70QZyjKBJty7GL22wBYNIbug/cBADqliSSSDqeXgRWjFzwt324/1G59Lff9znmPYX0iweB2xez+qTD3g2yk9ZaRNCbyGcEI0Eg+pUtDfkZi2Y/KTyObPpxzBouAtQ+Oz61GMnelpT+TgZ0yGokrTvdMoYteWxz69eaEhzIDeZzcsAKkXIWmCJQ X-Forefront-Antispam-Report: CIP:255.255.255.255; CTRY:; LANG:en; SCL:1; SRV:; IPV:NLI; SFV:NSPM; H:SA0PR17MB4314.namprd17.prod.outlook.com; PTR:; CAT:NONE; SFS:(13230031)(366004)(136003)(346002)(39850400004)(396003)(376002)(230922051799003)(64100799003)(451199024)(1800799009)(186009)(478600001)(6506007)(6512007)(6666004)(6486002)(2616005)(26005)(316002)(54906003)(30864003)(1076003)(8676002)(36756003)(4326008)(2906002)(6916009)(5660300002)(8936002)(44832011)(66556008)(66946007)(41300700001)(86362001)(66476007)(38100700002)(83380400001); DIR:OUT; SFP:1101 X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 1 X-MS-Exchange-AntiSpam-MessageData-0: OyjzuZnnmG2p4nJAUcpQKFPkMGt7pjaB4DrAtMiN40Tz+YFWWzKZ/FURl/Qd0PFOObch7xCeBgLdGA18OXz5jak0EOwU7KBPa9fA+YmKEhx1UlkjJcgeGn37ZbCx1fMn7eNo1xRNuAXSS9WNWUSm02aXOmxqFEzIGx6t69TG5j/v3svuyQWJu5HrQyR3iDSzT0pFEwLxh5WHU0SzxQ1CMzDeWX/hAKti0o07ODCUmJMb6lKIg0WbO0ycFc8VHmNb4stexZVYgwjL2QzkkpWvST1vfwv22LeH29OspwNj/EFfABK1jszyT7PS3mQ9h4m339VbGnPjpkU3Y4QywjmXzLKlKVgxL9JEiRzTHHJsp3W421Qw3TwraYk7gmVCKahXjU14E2V212KY47qso6uVL8/e85DVl4aQz+RI/Du4AXtPv6lg0eMSVMEB/SAElM2p17U6Ta92Q878Bog/gy9ffi7fkl4yFFl1pfvFclBit+6vWgKJUFSOFvFQG7S7KX7AwShaXsTi3GRXTMdwlmqb6CP9D0+2izamlWD2VdWiKmroEz85WmU4QV+C+OpcTyuO1wqke7xigTRtcIrfxdUypkpKo5J061AujyCH2nehTGpxnVufQWqJjmyoCdHBVoJ+r94ottEoVuaej3Xf3J+pN0LcbXirEoOIpXVwnzzIKhuB32QnnTad6tLaFDw8dQ02ZLfarHztmuzn2a4o4thb8dTE7skiF87BfTsh5yhVuxx3IUqI8TSMw5AvdkI+ggP+/1OmFU/KeRf+upBtmgsCL5eqIhQxtDk/n/D7ikUHa23lprAg30kYRSCPp5y69yRFHr711TOUfu4Ijm/aLLaB+R/bMbSIbkGLlVW/eGYqaaMME2nSh1vivlBmvfHsDX0t/zzfTB8KgtsdIrqe8Mi3JdKnGMIruoQwcEip4csG0fDTOiW9a3jFN/++ht0O1kBPLch9S4+PXfmjJqLsix7SzKL6QzwtqMHezx7QMZuI4C+3CbRbRmBUhtNCTG0j2+leQpALJeQSJcRMIjklON0pdKKp6GcZ+WZoVJifoyChA0QJ18t5RNm5mIIYfz4zT9+QmA7wOwQZ75xbhpd/ZuxABcu44lINRfdlOA3TWrQDm3p2sR5ApbokDXo/mVBVc66/dLsQloyrww3wwtQr45M+8xakO+P0noWrT5IsKBAMlBqknzgZbqnxDw6a8f6bxp5jdRSgBybJWVUACsGlt7l+BshDxe6cpKOuMZuv19LIa1gYvgYGVLP65+5MUSMv6M7Y6wNmS0IwRPurX5LGqRFVgkLh0NS1Ef5Un1yisU4fDOjyE4DzF/U9eQHtkrFavvapjeTVlPtG+mFW6j3mpZDUq6LMXIIC3GVZYEG4YG+D0if5xnCAOoHTpMqKk2OTUeLpC6ZmJkLRad8sTyj9BogjmRWn3Nh6bX/hNSjr0kgNFELRFHiSMYCVGb9RHrraEmihe6d+toCwvTgXVUcwCrqF23fPvrjL3nvST63ZoTgABc5y9LQ/OW5LohJTLlTVKneHFowSUGcGGGA3aMx+5BiipJVR7QAYqqTlyWhB836Cd+aBvkPnxb3cY/OjZFlITWHb X-OriginatorOrg: labware.com X-MS-Exchange-CrossTenant-Network-Message-Id: e748f2b7-bd09-4e2c-8cda-08dbc67e7ea2 X-MS-Exchange-CrossTenant-AuthSource: SA0PR17MB4314.namprd17.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 06 Oct 2023 15:11:19.6750 (UTC) X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted X-MS-Exchange-CrossTenant-Id: b5db0322-1aa0-4c0a-859c-ad0f96966f4c X-MS-Exchange-CrossTenant-MailboxType: HOSTED X-MS-Exchange-CrossTenant-UserPrincipalName: 0+BQ6h2GIyCRHadf0UmgDRb3dXiwZPmbZqLJd/oPK9vQZRz5WAqnJ2dqHE9ICpvyYjBzPkmy3vPHuBN9e4v/vA== X-MS-Exchange-Transport-CrossTenantHeadersStamped: CO3PR17MB5775 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: labware.com X-Spam-Status: No, score=-12.1 required=5.0 tests=BAYES_00, GIT_PATCH_0, KAM_DMARC_STATUS, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H5, RCVD_IN_MSPIKE_WL, SPF_HELO_NONE, SPF_PASS, TXREP autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org 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: , X-Patchwork-Original-From: Jan Vrany via Gdb-patches From: Jan Vrany Reply-To: Jan Vrany Errors-To: gdb-patches-bounces+patchwork=sourceware.org@sourceware.org Sender: "Gdb-patches" This commit generalizes serialize_mi_result() to make usable in different contexts than printing result of custom MI command. To do so, the check whether passed Python object is a dictionary has been moved to the caller - at the very least, different uses require different error messages. Also it has been renamed to serialize_mi_results() to better match GDB/MI output syntax (see corresponding section in documentation, in particular rules 'result-record' and 'async-output'. Since it is now more generic function, it has been moved to py-mi.c. This is a preparation for implementing Python support for sending custom MI async events. Approved-By: Andrew Burgess --- gdb/python/py-mi.c | 159 ++++++++++++++++++++++++++++++ gdb/python/py-micmd.c | 185 ++--------------------------------- gdb/python/python-internal.h | 13 +++ 3 files changed, 181 insertions(+), 176 deletions(-) diff --git a/gdb/python/py-mi.c b/gdb/python/py-mi.c index 66dc6fb8a32..36bcb6ceece 100644 --- a/gdb/python/py-mi.c +++ b/gdb/python/py-mi.c @@ -296,3 +296,162 @@ gdbpy_execute_mi_command (PyObject *self, PyObject *args, PyObject *kw) return uiout.result (); } + +/* Convert KEY_OBJ into a string that can be used as a field name in MI + output. KEY_OBJ must be a Python string object, and must only contain + characters suitable for use as an MI field name. + + If KEY_OBJ is not a string, or if KEY_OBJ contains invalid characters, + then an error is thrown. Otherwise, KEY_OBJ is converted to a string + and returned. */ + +static gdb::unique_xmalloc_ptr +py_object_to_mi_key (PyObject *key_obj) +{ + /* The key must be a string. */ + if (!PyUnicode_Check (key_obj)) + { + gdbpy_ref<> key_repr (PyObject_Repr (key_obj)); + gdb::unique_xmalloc_ptr key_repr_string; + if (key_repr != nullptr) + key_repr_string = python_string_to_target_string (key_repr.get ()); + if (key_repr_string == nullptr) + gdbpy_handle_exception (); + + gdbpy_error (_("non-string object used as key: %s"), + key_repr_string.get ()); + } + + gdb::unique_xmalloc_ptr key_string + = python_string_to_target_string (key_obj); + if (key_string == nullptr) + gdbpy_handle_exception (); + + /* Predicate function, returns true if NAME is a valid field name for use + in MI result output, otherwise, returns false. */ + auto is_valid_key_name = [] (const char *name) -> bool + { + gdb_assert (name != nullptr); + + if (*name == '\0' || !isalpha (*name)) + return false; + + for (; *name != '\0'; ++name) + if (!isalnum (*name) && *name != '_' && *name != '-') + return false; + + return true; + }; + + if (!is_valid_key_name (key_string.get ())) + { + if (*key_string.get () == '\0') + gdbpy_error (_("Invalid empty key in MI result")); + else + gdbpy_error (_("Invalid key in MI result: %s"), key_string.get ()); + } + + return key_string; +} + +/* Serialize RESULT and print it in MI format to the current_uiout. + FIELD_NAME is used as the name of this result field. + + RESULT can be a dictionary, a sequence, an iterator, or an object that + can be converted to a string, these are converted to the matching MI + output format (dictionaries as tuples, sequences and iterators as lists, + and strings as named fields). + + If anything goes wrong while formatting the output then an error is + thrown. + + This function is the recursive inner core of serialize_mi_result, and + should only be called from that function. */ + +static void +serialize_mi_result_1 (PyObject *result, const char *field_name) +{ + struct ui_out *uiout = current_uiout; + + if (PyDict_Check (result)) + { + PyObject *key, *value; + Py_ssize_t pos = 0; + ui_out_emit_tuple tuple_emitter (uiout, field_name); + while (PyDict_Next (result, &pos, &key, &value)) + { + gdb::unique_xmalloc_ptr key_string + (py_object_to_mi_key (key)); + serialize_mi_result_1 (value, key_string.get ()); + } + } + else if (PySequence_Check (result) && !PyUnicode_Check (result)) + { + ui_out_emit_list list_emitter (uiout, field_name); + Py_ssize_t len = PySequence_Size (result); + if (len == -1) + gdbpy_handle_exception (); + for (Py_ssize_t i = 0; i < len; ++i) + { + gdbpy_ref<> item (PySequence_ITEM (result, i)); + if (item == nullptr) + gdbpy_handle_exception (); + serialize_mi_result_1 (item.get (), nullptr); + } + } + else if (PyIter_Check (result)) + { + gdbpy_ref<> item; + ui_out_emit_list list_emitter (uiout, field_name); + while (true) + { + item.reset (PyIter_Next (result)); + if (item == nullptr) + { + if (PyErr_Occurred () != nullptr) + gdbpy_handle_exception (); + break; + } + serialize_mi_result_1 (item.get (), nullptr); + } + } + else + { + if (PyLong_Check (result)) + { + int overflow = 0; + gdb_py_longest val = gdb_py_long_as_long_and_overflow (result, + &overflow); + if (PyErr_Occurred () != nullptr) + gdbpy_handle_exception (); + if (overflow == 0) + { + uiout->field_signed (field_name, val); + return; + } + /* Fall through to the string case on overflow. */ + } + + gdb::unique_xmalloc_ptr string (gdbpy_obj_to_string (result)); + if (string == nullptr) + gdbpy_handle_exception (); + uiout->field_string (field_name, string.get ()); + } +} + +/* See python-internal.h. */ + +void +serialize_mi_results (PyObject *results) +{ + gdb_assert (PyDict_Check (results)); + + PyObject *key, *value; + Py_ssize_t pos = 0; + while (PyDict_Next (results, &pos, &key, &value)) + { + gdb::unique_xmalloc_ptr key_string + (py_object_to_mi_key (key)); + serialize_mi_result_1 (value, key_string.get ()); + } +} diff --git a/gdb/python/py-micmd.c b/gdb/python/py-micmd.c index 01fc6060ece..0153f84e7a0 100644 --- a/gdb/python/py-micmd.c +++ b/gdb/python/py-micmd.c @@ -173,178 +173,6 @@ extern PyTypeObject micmdpy_object_type static PyObject *invoke_cst; -/* Convert KEY_OBJ into a string that can be used as a field name in MI - output. KEY_OBJ must be a Python string object, and must only contain - characters suitable for use as an MI field name. - - If KEY_OBJ is not a string, or if KEY_OBJ contains invalid characters, - then an error is thrown. Otherwise, KEY_OBJ is converted to a string - and returned. */ - -static gdb::unique_xmalloc_ptr -py_object_to_mi_key (PyObject *key_obj) -{ - /* The key must be a string. */ - if (!PyUnicode_Check (key_obj)) - { - gdbpy_ref<> key_repr (PyObject_Repr (key_obj)); - gdb::unique_xmalloc_ptr key_repr_string; - if (key_repr != nullptr) - key_repr_string = python_string_to_target_string (key_repr.get ()); - if (key_repr_string == nullptr) - gdbpy_handle_exception (); - - gdbpy_error (_("non-string object used as key: %s"), - key_repr_string.get ()); - } - - gdb::unique_xmalloc_ptr key_string - = python_string_to_target_string (key_obj); - if (key_string == nullptr) - gdbpy_handle_exception (); - - /* Predicate function, returns true if NAME is a valid field name for use - in MI result output, otherwise, returns false. */ - auto is_valid_key_name = [] (const char *name) -> bool - { - gdb_assert (name != nullptr); - - if (*name == '\0' || !isalpha (*name)) - return false; - - for (; *name != '\0'; ++name) - if (!isalnum (*name) && *name != '_' && *name != '-') - return false; - - return true; - }; - - if (!is_valid_key_name (key_string.get ())) - { - if (*key_string.get () == '\0') - gdbpy_error (_("Invalid empty key in MI result")); - else - gdbpy_error (_("Invalid key in MI result: %s"), key_string.get ()); - } - - return key_string; -} - -/* Serialize RESULT and print it in MI format to the current_uiout. - FIELD_NAME is used as the name of this result field. - - RESULT can be a dictionary, a sequence, an iterator, or an object that - can be converted to a string, these are converted to the matching MI - output format (dictionaries as tuples, sequences and iterators as lists, - and strings as named fields). - - If anything goes wrong while formatting the output then an error is - thrown. - - This function is the recursive inner core of serialize_mi_result, and - should only be called from that function. */ - -static void -serialize_mi_result_1 (PyObject *result, const char *field_name) -{ - struct ui_out *uiout = current_uiout; - - if (PyDict_Check (result)) - { - PyObject *key, *value; - Py_ssize_t pos = 0; - ui_out_emit_tuple tuple_emitter (uiout, field_name); - while (PyDict_Next (result, &pos, &key, &value)) - { - gdb::unique_xmalloc_ptr key_string - (py_object_to_mi_key (key)); - serialize_mi_result_1 (value, key_string.get ()); - } - } - else if (PySequence_Check (result) && !PyUnicode_Check (result)) - { - ui_out_emit_list list_emitter (uiout, field_name); - Py_ssize_t len = PySequence_Size (result); - if (len == -1) - gdbpy_handle_exception (); - for (Py_ssize_t i = 0; i < len; ++i) - { - gdbpy_ref<> item (PySequence_ITEM (result, i)); - if (item == nullptr) - gdbpy_handle_exception (); - serialize_mi_result_1 (item.get (), nullptr); - } - } - else if (PyIter_Check (result)) - { - gdbpy_ref<> item; - ui_out_emit_list list_emitter (uiout, field_name); - while (true) - { - item.reset (PyIter_Next (result)); - if (item == nullptr) - { - if (PyErr_Occurred () != nullptr) - gdbpy_handle_exception (); - break; - } - serialize_mi_result_1 (item.get (), nullptr); - } - } - else - { - if (PyLong_Check (result)) - { - int overflow = 0; - gdb_py_longest val = gdb_py_long_as_long_and_overflow (result, - &overflow); - if (PyErr_Occurred () != nullptr) - gdbpy_handle_exception (); - if (overflow == 0) - { - uiout->field_signed (field_name, val); - return; - } - /* Fall through to the string case on overflow. */ - } - - gdb::unique_xmalloc_ptr string (gdbpy_obj_to_string (result)); - if (string == nullptr) - gdbpy_handle_exception (); - uiout->field_string (field_name, string.get ()); - } -} - -/* Serialize RESULT and print it in MI format to the current_uiout. - - This function handles the top-level result initially returned from the - invoke method of the Python command implementation. At the top-level - the result must be a dictionary. The values within this dictionary can - be a wider range of types. Handling the values of the top-level - dictionary is done by serialize_mi_result_1, see that function for more - details. - - If anything goes wrong while parsing and printing the MI output then an - error is thrown. */ - -static void -serialize_mi_result (PyObject *result) -{ - /* At the top-level, the result must be a dictionary. */ - - if (!PyDict_Check (result)) - gdbpy_error (_("Result from invoke must be a dictionary")); - - PyObject *key, *value; - Py_ssize_t pos = 0; - while (PyDict_Next (result, &pos, &key, &value)) - { - gdb::unique_xmalloc_ptr key_string - (py_object_to_mi_key (key)); - serialize_mi_result_1 (value, key_string.get ()); - } -} - /* Called when the MI command is invoked. PARSE contains the parsed command line arguments from the user. */ @@ -381,14 +209,19 @@ mi_command_py::invoke (struct mi_parse *parse) const gdb_assert (this->m_pyobj != nullptr); gdb_assert (PyErr_Occurred () == nullptr); - gdbpy_ref<> result + gdbpy_ref<> results (PyObject_CallMethodObjArgs ((PyObject *) this->m_pyobj.get (), invoke_cst, argobj.get (), nullptr)); - if (result == nullptr) + if (results == nullptr) gdbpy_handle_exception (); - if (result != Py_None) - serialize_mi_result (result.get ()); + if (results != Py_None) + { + /* At the top-level, the results must be a dictionary. */ + if (!PyDict_Check (results.get ())) + gdbpy_error (_("Result from invoke must be a dictionary")); + serialize_mi_results (results.get ()); + } } /* See declaration above. */ diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h index 93217375cc5..60b795ff98c 100644 --- a/gdb/python/python-internal.h +++ b/gdb/python/python-internal.h @@ -486,6 +486,19 @@ struct gdbarch *arch_object_to_gdbarch (PyObject *obj); extern PyObject *gdbpy_execute_mi_command (PyObject *self, PyObject *args, PyObject *kw); +/* Serialize RESULTS and print it in MI format to the current_uiout. + + This function handles the top-level results passed as a dictionary. + The caller is responsible for ensuring that. The values within this + dictionary can be a wider range of types. Handling the values of the top-level + dictionary is done by serialize_mi_result_1, see that function for more + details. + + If anything goes wrong while parsing and printing the MI output then an + error is thrown. */ + +extern void serialize_mi_results (PyObject *results); + /* Convert Python object OBJ to a program_space pointer. OBJ must be a gdb.Progspace reference. Return nullptr if the gdb.Progspace is not valid (see gdb.Progspace.is_valid), otherwise return the program_space From patchwork Fri Oct 6 15:11:06 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jan Vrany X-Patchwork-Id: 77219 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 81DA8385697F for ; Fri, 6 Oct 2023 15:11:57 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 81DA8385697F DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=sourceware.org; s=default; t=1696605117; bh=hMWy7JNkeiS757ieFiDAEc7iRm+sdqZStC7I17PLUIA=; h=To:CC:Subject:Date:In-Reply-To:References:List-Id: List-Unsubscribe:List-Archive:List-Post:List-Help:List-Subscribe: From:Reply-To:From; b=J50+nO2fvqgXBGTvCL90R2/BQ3B7NqJk9BG1c/UOc5fK/9G83hKapvfDznhWAS2j/ tL/ULynm9mr4+6yVw2Qs9h289COI62u/WJQPZA+E5yy3nMgbYyvHC8Yq6UVorxUpqo Nnj+BG41RYN1buEoHmBau6bC5fm4Q23Py6ibcMQA= X-Original-To: gdb-patches@sourceware.org Delivered-To: gdb-patches@sourceware.org Received: from us-smtp-delivery-114.mimecast.com (us-smtp-delivery-114.mimecast.com [170.10.129.114]) by sourceware.org (Postfix) with ESMTPS id 2FD103856943 for ; Fri, 6 Oct 2023 15:11:30 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 2FD103856943 Received: from NAM12-DM6-obe.outbound.protection.outlook.com (mail-dm6nam12lp2169.outbound.protection.outlook.com [104.47.59.169]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id us-mta-641-BmcP-16FNEujdFcOF8KVXw-1; Fri, 06 Oct 2023 11:11:28 -0400 X-MC-Unique: BmcP-16FNEujdFcOF8KVXw-1 Received: from SA0PR17MB4314.namprd17.prod.outlook.com (2603:10b6:806:e7::16) by CO3PR17MB5775.namprd17.prod.outlook.com (2603:10b6:303:17b::7) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.6813.19; Fri, 6 Oct 2023 15:11:26 +0000 Received: from SA0PR17MB4314.namprd17.prod.outlook.com ([fe80::30eb:4c4a:cb5a:dd91]) by SA0PR17MB4314.namprd17.prod.outlook.com ([fe80::30eb:4c4a:cb5a:dd91%4]) with mapi id 15.20.6838.029; Fri, 6 Oct 2023 15:11:25 +0000 To: gdb-patches@sourceware.org CC: Jan Vrany , Eli Zaretskii Subject: [PATCH v3 2/2] gdb/python: implement support for sending custom MI async notifications Date: Fri, 6 Oct 2023 16:11:06 +0100 Message-ID: <20231006151106.315633-3-jan.vrany@labware.com> X-Mailer: git-send-email 2.40.1 In-Reply-To: <20231006151106.315633-1-jan.vrany@labware.com> References: <87bkddtamc.fsf@redhat.com> <20231006151106.315633-1-jan.vrany@labware.com> X-ClientProxiedBy: LO4P123CA0463.GBRP123.PROD.OUTLOOK.COM (2603:10a6:600:1aa::18) To SA0PR17MB4314.namprd17.prod.outlook.com (2603:10b6:806:e7::16) MIME-Version: 1.0 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: SA0PR17MB4314:EE_|CO3PR17MB5775:EE_ X-MS-Office365-Filtering-Correlation-Id: efdf3112-091c-4c5a-23a4-08dbc67e8250 X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0 X-Microsoft-Antispam-Message-Info: WTYAxigPPZkLRiLZmqAqTCJXS3qlmaEbnKgP9pYBfGBy3tX6rfCeHo6/ziB/QoklJqQOr2UaN4fibm5Xj1yaPAJL+zTZ3MY+8ly8G8Q0VWBvTWYSCxY4bvbmI2CO+WjWSxWY9thr28GSKxORwy7UEellYE6WjNcuIv4N92h+q3hcBlkWwjVIVPL4fhpQLzSmQRsznSgtq9dkehpdVUdKUk6lEZR4otvFBpMIPwhZMUIwGNxsjfOt5F6K6vN9m82VPXu5zYnAbvd0h4NPaFq/Nooz85ukeotCLLHEs4zIzGVzgMk34gHEaG2AV3ARbabDBy6nmvvmVB4JGDFGkFMKnr4sly1KeZ426BLcKhnQgli298KLIjexh6i3BCpltwyK8DIDcUv/t/RGfP01P5g4wbErOibj3WMpWY3/GMvSnJEEm/Meq/B5U5BwHUEF/FjJ6T6QgfKjiYY7YOg3mPI6KeP/NM4MlFxr1YpkWBU+5YGl28DG3FJ7WFg/YCejE7clKQ/qfHHPWdGOmW4S0M5f0M1AV1ps3nfs1lmZ4nMUosPofk+esvCt9bn/XbLv/6X46GhXrVPekMfZx83IZg5f87lazMs8W9STU/ir6FqmtRk= X-Forefront-Antispam-Report: CIP:255.255.255.255; CTRY:; LANG:en; SCL:1; SRV:; IPV:NLI; SFV:NSPM; H:SA0PR17MB4314.namprd17.prod.outlook.com; PTR:; CAT:NONE; SFS:(13230031)(366004)(136003)(346002)(39850400004)(396003)(376002)(230922051799003)(64100799003)(451199024)(1800799009)(186009)(478600001)(6506007)(6512007)(6666004)(6486002)(2616005)(15650500001)(26005)(316002)(54906003)(1076003)(8676002)(36756003)(4326008)(2906002)(6916009)(5660300002)(8936002)(44832011)(66556008)(66946007)(41300700001)(86362001)(66476007)(38100700002)(83380400001)(2004002); DIR:OUT; SFP:1101 X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 1 X-MS-Exchange-AntiSpam-MessageData-0: LBWcnodkitkG67v8bzaxVPZOJj/10TRUEWtTbqXmaFxbqnGpK4ujKQbl/n6LhiwtQQA8Off47SxcBYutK1vLpXNxDJ+k5cWSSUcZ0/95GAC7TISi3yng+uIykmMXZBzJspIrCPXFcOZoTFYHFh2Ysbp7FsFnEonVKWlpdeZvbjd9m4nlGcUFoybb4o89F2mIOQF3T5ygSlkxU8XVg+d51NCE/6i+q/ujv0s5vrBoCgzDBgSAuzfnHYCDJOUfKgxcnjTcV3ACLtBk4l5CbSuuCNDTVcLO34uhuFQZHkfq22GrJ75dTSjpUaTCgvaV5chXMnGPGBR5NxfEvI2rNIEl/eamc1bhwcMGighCWFYe2TeftbqWPij0mWbrBFxDBf11VRxxp4oyHOm1eI50yKamYyexhuC/mDZ5+uyv98+U2zonUlOILTY0/JJc9f80Tr85WfZEUYbBa/OZ0E3pusEXgek7BEccriAgTvBefyijJL8qMqUMvAHbxdbb6grY5YNFaXNz4F2vrbu9iJgPpbXHpB+qCRtu+5VB4CZlTYaRBQr3OXBxnVOQSA/gta3MuAPlylHoltJxWrJCmAdnrQbaQeAlPlHuUh+LkwaghWxVgMXTFHSKoij2jd4ZC+fnPgnRmv5zF9t8dckNd/R4HL+bwr7UXAuf1ClD3Tc4l9sLWxP2HdQjUQ8KziYn4Bc6NEsPUYYeIcfuqHfmi93jiEYVsIDpZ5rbYHzfDdZH757RsBvbrf/faW90xZLL70gj2Adl50ftgd0H17HqBfvDjY5e40zhmaa/DLiS2wrlRfCiDzL9p+xyqP3U8Nw5iK231YiW80QcfFNEatoCawvC3di9Y83BkVFipXyWS6VrLnwJwmnq6OT/cc0FPDQa//qTH1Rdd52Uo5aEcDeC7NPzTGUG4wQsEnTvB5SlQYtplK6tlXRFz325BNB1OS3ruCbSxR/yBLccAKyit6Hy/LeGurMQk19XW0kLfWrBTq6DVEcYi2cOLWDdBDA1EPqpvOCnJ2o+NLx20EeXWuN68Aj8hDzS2OybUaUoBWIwzlVs/L5lWn563ZPRhK6c/1KqMkqCcBKRqu0wp5gsgrcXUobQ+f2EZWchvWyoKAmkoydJ6kNYvej3KjUHxuz5I4gOQKJTsd9lMmSmwqwMl9xrySY2xStppq9lx350ggBff9TlZwjqf0RWVK3KFvRAbK8b8jR9kN/ha25C3wE1p3mUkA19kJ+pwUF+FNP01n1FXMdVaVlWqAr36Z2K0Su09z+XtZDCFMLx+se2yFoOUzNspsac27bse7N/kXsoLemkVo1GQmvIivHAsXPsVEi0UXQ3H6O45zJgMbctVPksnUpauXo5yenOAQXgL8h1mKnAYKQPt/NlFrlUO2H6XM1h6kAdYdkZLfUIB1b8idwqcCapa8/3bf0c5oeI5JPb7h25IL0Mr7ckrEOLoNelGgQTMAFsxgJ4RHlvftzc6dc0APIDV7pMNAmPxehVbIP1cy2IbddPhzB+mhGWMywi7t410Q5x8/Wgi2quNtZtPZfSkx5VQ8BDIEt5hiME4g04wl47uRLDFf+wR1IdnOZkYG6st4V97eNs8/ab X-OriginatorOrg: labware.com X-MS-Exchange-CrossTenant-Network-Message-Id: efdf3112-091c-4c5a-23a4-08dbc67e8250 X-MS-Exchange-CrossTenant-AuthSource: SA0PR17MB4314.namprd17.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 06 Oct 2023 15:11:25.9337 (UTC) X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted X-MS-Exchange-CrossTenant-Id: b5db0322-1aa0-4c0a-859c-ad0f96966f4c X-MS-Exchange-CrossTenant-MailboxType: HOSTED X-MS-Exchange-CrossTenant-UserPrincipalName: 6jIbaMbLrdErvLkfNEy+XZOfNJ2+ELyF1uiUKKiJiAL0qFdK66S9T3fEjntTNN7WqVtc31WberBJ6bBUGiZTnQ== X-MS-Exchange-Transport-CrossTenantHeadersStamped: CO3PR17MB5775 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: labware.com X-Spam-Status: No, score=-12.1 required=5.0 tests=BAYES_00, GIT_PATCH_0, KAM_DMARC_STATUS, KAM_SHORT, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H5, RCVD_IN_MSPIKE_WL, SPF_HELO_NONE, SPF_PASS, TXREP autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org 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: , X-Patchwork-Original-From: Jan Vrany via Gdb-patches From: Jan Vrany Reply-To: Jan Vrany Errors-To: gdb-patches-bounces+patchwork=sourceware.org@sourceware.org Sender: "Gdb-patches" This commit adds a new Python function, gdb.notify_mi, that can be used to emit custom async notification to MI channel. This can be used, among other things, to implement notifications about events MI does not support, such as remote connection closed or register change. Reviewed-By: Eli Zaretskii --- gdb/NEWS | 3 + gdb/doc/python.texi | 45 ++++++++++++++ gdb/python/py-mi.c | 74 +++++++++++++++++++++++ gdb/python/python-internal.h | 5 ++ gdb/python/python.c | 4 ++ gdb/testsuite/gdb.python/py-mi-notify.exp | 71 ++++++++++++++++++++++ 6 files changed, 202 insertions(+) create mode 100644 gdb/testsuite/gdb.python/py-mi-notify.exp diff --git a/gdb/NEWS b/gdb/NEWS index 2f6378f9c7a..c99734ba502 100644 --- a/gdb/NEWS +++ b/gdb/NEWS @@ -348,6 +348,9 @@ show tui mouse-events gdb.Progspace that is either being added to GDB, or removed from GDB. + ** New function gdb.notify_mi(NAME, DATA), that emits custom + GDB/MI async notification. + *** Changes in GDB 13 * MI version 1 is deprecated, and will be removed in GDB 14. diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi index a97e4451897..546b4d4b962 100644 --- a/gdb/doc/python.texi +++ b/gdb/doc/python.texi @@ -211,6 +211,7 @@ optional arguments while skipping others. Example: * Recordings In Python:: Accessing recordings from Python. * CLI Commands In Python:: Implementing new CLI commands in Python. * GDB/MI Commands In Python:: Implementing new @sc{gdb/mi} commands in Python. +* GDB/MI Notifications In Python:: Implementing new @sc{gdb/mi} notifications in Python. * Parameters In Python:: Adding new @value{GDBN} parameters. * Functions In Python:: Writing new convenience functions. * Progspaces In Python:: Program spaces. @@ -4804,6 +4805,50 @@ Here is how this works using the commands from the example above: @{'string': 'abc, def, ghi'@} @end smallexample +@node GDB/MI Notifications In Python +@subsubsection @sc{gdb/mi} Notifications In Python + +@cindex MI notifications in python +@cindex notifications in python, GDB/MI +@cindex python notifications, GDB/MI + +It is possible to emit @sc{gdb/mi} notifications from +Python. Use the @code{gdb.notify_mi} function to do that. + +@defun gdb.notify_mi (name @r{[}, data@r{]}) +Emit a @sc{gdb/mi} asynchronous notification. @var{name} is the name of the +notification, consisting of alphanumeric characters and a hyphen (@code{-}). +@var{data} is any additional data to be emitted with the notification, passed +as a Python dictionary. This argument is optional. The dictionary is converted +to a @sc{gdb/mi} @var{result} records (@pxref{GDB/MI Output Syntax}) the same way +as result of Python MI command (@pxref{GDB/MI Commands In Python}). + +If @var{data} is @code{None} then no additional values are emitted. +@end defun + +While using existing notification names (@pxref{GDB/MI Async Records}) with +@code{gdb.notify_mi} is allowed, users are encouraged to prefix user-defined +notification with a hyphen (@code{-}) to avoid possible conflict. +@value{GDBN} will never introduce notification starting with hyphen. + +Here is how to emit @code{=-connection-removed} whenever a connection to remote +GDB server is closed (@pxref{Connections In Python}): + +@smallexample +def notify_connection_removed(event): + data = @{"id": event.connection.num, "type": event.connection.type@} + gdb.notify_mi("-connection-removed", data) + + +gdb.events.connection_removed.connect(notify_connection_removed) +@end smallexample + +Then, each time a connection is closed, there will be a notification on MI channel: + +@smallexample +=-connection-removed,id="1",type="remote" +@end smallexample + @node Parameters In Python @subsubsection Parameters In Python diff --git a/gdb/python/py-mi.c b/gdb/python/py-mi.c index 36bcb6ceece..a7b4f4fa3cf 100644 --- a/gdb/python/py-mi.c +++ b/gdb/python/py-mi.c @@ -19,8 +19,14 @@ #include "defs.h" #include "python-internal.h" +#include "utils.h" +#include "ui.h" #include "ui-out.h" +#include "interps.h" +#include "target.h" #include "mi/mi-parse.h" +#include "mi/mi-console.h" +#include "mi/mi-interp.h" /* A ui_out subclass that creates a Python object based on the data that is passed in. */ @@ -455,3 +461,71 @@ serialize_mi_results (PyObject *results) serialize_mi_result_1 (value, key_string.get ()); } } + +/* See python-internal.h. */ + +PyObject * +gdbpy_notify_mi (PyObject *self, PyObject *args, PyObject *kwargs) +{ + static const char *keywords[] = { "name", "data", nullptr }; + char *name = nullptr; + PyObject *data = Py_None; + + if (!gdb_PyArg_ParseTupleAndKeywords (args, kwargs, "s|O", keywords, + &name, &data)) + return nullptr; + + /* Validate notification name. */ + const int name_len = strlen (name); + if (name_len == 0) + { + PyErr_SetString (PyExc_ValueError, _("MI notification name is empty.")); + return nullptr; + } + for (int i = 0; i < name_len; i++) + { + if (!isalnum (name[i]) && name[i] != '-') + { + PyErr_Format + (PyExc_ValueError, + _("MI notification name contains invalid character: %c."), + name[i]); + return nullptr; + } + } + + /* Validate additional data. */ + if (!(data == Py_None || PyDict_Check (data))) + { + PyErr_Format + (PyExc_ValueError, + _("MI notification data must be either None or a dictionary, not %s"), + Py_TYPE (data)->tp_name); + return nullptr; + } + + SWITCH_THRU_ALL_UIS () + { + struct mi_interp *mi = as_mi_interp (top_level_interpreter ()); + + if (mi == nullptr) + continue; + + target_terminal::scoped_restore_terminal_state term_state; + target_terminal::ours_for_output (); + + gdb_printf (mi->event_channel, "%s", name); + if (data != Py_None) + { + ui_out *mi_uiout = mi->interp_ui_out (); + ui_out_redirect_pop redir (mi_uiout, mi->event_channel); + scoped_restore restore_uiout + = make_scoped_restore (¤t_uiout, mi_uiout); + + serialize_mi_results (data); + } + gdb_flush (mi->event_channel); + } + + Py_RETURN_NONE; +} diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h index 60b795ff98c..847bed84dfe 100644 --- a/gdb/python/python-internal.h +++ b/gdb/python/python-internal.h @@ -499,6 +499,11 @@ extern PyObject *gdbpy_execute_mi_command (PyObject *self, PyObject *args, extern void serialize_mi_results (PyObject *results); +/* Implementation of the gdb.notify_mi function. */ + +extern PyObject *gdbpy_notify_mi (PyObject *self, PyObject *args, + PyObject *kw); + /* Convert Python object OBJ to a program_space pointer. OBJ must be a gdb.Progspace reference. Return nullptr if the gdb.Progspace is not valid (see gdb.Progspace.is_valid), otherwise return the program_space diff --git a/gdb/python/python.c b/gdb/python/python.c index 6a978d632e9..faa7e0c217d 100644 --- a/gdb/python/python.c +++ b/gdb/python/python.c @@ -2669,6 +2669,10 @@ Return the name of the currently selected language." }, "print_options () -> dict\n\ Return the current print options." }, + { "notify_mi", (PyCFunction) gdbpy_notify_mi, + METH_VARARGS | METH_KEYWORDS, + "notify_mi (name, data) -> None\n\ +Output async record to MI channels if any." }, {NULL, NULL, 0, NULL} }; diff --git a/gdb/testsuite/gdb.python/py-mi-notify.exp b/gdb/testsuite/gdb.python/py-mi-notify.exp new file mode 100644 index 00000000000..8ba770391c2 --- /dev/null +++ b/gdb/testsuite/gdb.python/py-mi-notify.exp @@ -0,0 +1,71 @@ +# Copyright (C) 2023 Free Software Foundation, Inc. +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# Test custom MI notifications implemented in Python. + +load_lib gdb-python.exp +load_lib mi-support.exp +set MIFLAGS "-i=mi" + +gdb_exit +if {[mi_gdb_start]} { + return +} + +if {[lsearch -exact [mi_get_features] python] < 0} { + unsupported "python support is disabled" + return -1 +} + +standard_testfile + +mi_gdb_test "set python print-stack full" \ + ".*\\^done" \ + "set python print-stack full" + +mi_gdb_test "python gdb.notify_mi('-test-notification')" \ + ".*=-test-notification\r\n\\^done" \ + "python notification, no additional data parameter" + +mi_gdb_test "python gdb.notify_mi('-test-notification', None)" \ + ".*=-test-notification\r\n\\^done" \ + "python notification, no additional data" + +mi_gdb_test "python gdb.notify_mi('-test-notification', \{ 'data1' : 1 , 'data2' : 2 })" \ + ".*=-test-notification,data1=\"1\",data2=\"2\"\r\n\\^done" \ + "python notification, with additional data" + +mi_gdb_test "python gdb.notify_mi('-test-notification', 1)" \ + ".*\\^error,msg=\".*\"" \ + "python notification, invalid additional data" + +mi_gdb_test "python gdb.notify_mi('', None)" \ + ".*\\^error,msg=\".*\"" \ + "python notification, empty notification name" + +mi_gdb_test "python gdb.notify_mi('**invalid**', None)" \ + ".*\\^error,msg=\".*\"" \ + "python notification, invalid notification name" + +mi_gdb_test "python gdb.notify_mi(\[1,2,3\], None)" \ + ".*\\^error,msg=\".*\"" \ + "python notification, non-string notification name" + +mi_gdb_test "python gdb.notify_mi()" \ + ".*\\^error,msg=\".*\"" \ + "python notification, no arguments passed" + +mi_gdb_test "python gdb.notify_mi('thread-group-added', \{'id' : 'i2'\})" \ + ".*=thread-group-added,id=\"i2\"\r\n\\^done" \ + "python notification, using existing internal notification name"