From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from simark.ca by simark.ca with LMTP id uZDDArr6mmrw5CgAWB0awg (envelope-from ) for ; Fri, 04 Sep 2026 13:07:06 -0400 Authentication-Results: simark.ca; dkim=pass (2048-bit key; unprotected) header.d=polymtl.ca header.i=@polymtl.ca header.a=rsa-sha256 header.s=oct2025 header.b=oPMHcfsq; dkim-atps=neutral Received: by simark.ca (Postfix, from userid 112) id 05B3C1E166; Fri, 04 Sep 2026 13:07:06 -0400 (EDT) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-25) on simark.ca X-Spam-Level: X-Spam-Status: No, score=-5.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 autolearn=ham autolearn_force=no version=4.0.1 Received: from vm01.sourceware.org (vm01.sourceware.org [IPv6:2620:52:6:3111::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 BE5101E033 for ; Fri, 04 Sep 2026 13:06:59 -0400 (EDT) Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 895F14BB1C3B for ; Fri, 4 Sep 2026 17:06:58 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 895F14BB1C3B Authentication-Results: sourceware.org; dkim=pass (2048-bit key, unprotected) header.d=polymtl.ca header.i=@polymtl.ca header.a=rsa-sha256 header.s=oct2025 header.b=oPMHcfsq Received: from smtp.polymtl.ca (smtp.polymtl.ca [132.207.4.11]) by sourceware.org (Postfix) with ESMTPS id E123F4BB3BF1 for ; Fri, 4 Sep 2026 17:05:55 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org E123F4BB3BF1 Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=polymtl.ca Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=polymtl.ca ARC-Filter: OpenARC Filter v1.0.0 sourceware.org E123F4BB3BF1 Authentication-Results: sourceware.org; arc=none smtp.remote-ip=132.207.4.11 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1788541556; cv=none; b=T1NY/hNrKxdCv+YNS3Fd8pl2fZaGT6/nqKy77SVH1KC94kyrugSSZLAu7+qmfhZa/5sMijnLMCDi5zFyLZVwl5qsKxpJ9VuXYJqTgtKY5Uc6R1bYN3LKuqP+UcNvSPPfyQCT3fnK3XV5jmJK33zIuMqBZ3wiVlMRr21SFTiev9U= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1788541556; c=relaxed/simple; bh=LCpIxRD47XzARcFa+6yLwubNAyg7ZuTl9uVsG4OGSe8=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=XI09fAe6x4MeHNNS7viHXa7sV5G4bmekRgjZ2Kvinmm7em+RsrlvEq3dn+CIbu1Hak80/i1U5eW0z0lw32lzRgrqRtIxdOvRkouqQKIpC8BwmyuDwA4LTiwxRxuh/I1sgOjsA5EDujEZO4ZcRisx13U9skbmrgd4oP8SgkkhGTo= ARC-Authentication-Results: i=1; sourceware.org; dkim=pass (2048-bit key, unprotected) header.d=polymtl.ca header.i=@polymtl.ca header.a=rsa-sha256 header.s=oct2025 header.b=oPMHcfsq DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org E123F4BB3BF1 Received: from simark.ca (simark.ca [158.69.221.121]) (authenticated bits=0) by smtp.polymtl.ca (8.14.7/8.14.7) with ESMTP id 684H5n97142337 (version=TLSv1/SSLv3 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Fri, 4 Sep 2026 13:05:54 -0400 DKIM-Filter: OpenDKIM Filter v2.11.0 smtp.polymtl.ca 684H5n97142337 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=polymtl.ca; s=oct2025; t=1788541554; bh=sRZGW6Li7rKIqzsE8imuSYLoHF86wIe9hGuxUBYU+pI=; h=From:To:Cc:Subject:Date:In-Reply-To:From; b=oPMHcfsqVPy05g87v6YYpJVIOzUwkMxxVNCYB7VdaEkRzTS5gePTT3ykB9G7jarpe b6MuLyBdldqWJ2K7YB2j5T/icBs28/B9ncaWDPN7CwxGE7ur8A/dEQTMxfaupTuI1A nxKffNWZNkW8IGuSWkqOF6ZIyNf7wWVpGHSVrvFlVnm/mFcmMt8W7BnAcv5/NEROey n6Z4SPY7NRA7GWH7yD2mVfssySprcWwYdZoxqLvs4fS1itX4WLpaOvV/EtVuTmwivb sYMAE0o0OXx2Y71eIBKg//4JJAqC7NMx4ZLqO5Uz5ot1gbIfw6Jpj2HO+GfD0xGOwP CKwoQPLWHHdlw== Received: by simark.ca (Postfix) id 328FC1E0A3; Fri, 04 Sep 2026 13:05:48 -0400 (EDT) From: simon.marchi@polymtl.ca To: gdb-patches@sourceware.org Cc: Simon Marchi Subject: [PATCH 09/17] gdb: move cp-name-parser.y's support code to cp-name-parser.c Date: Fri, 4 Sep 2026 12:56:41 -0400 Message-ID: <20260904170338.1643894-10-simon.marchi@polymtl.ca> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260904170338.1643894-1-simon.marchi@polymtl.ca> References: <20260904170338.1643894-1-simon.marchi@polymtl.ca> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Poly-FromMTA: (simark.ca [158.69.221.121]) at Fri, 4 Sep 2026 17:05:49 +0000 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 From: Simon Marchi The goal of this patch (and the following ones for the other parsers) is to move the C++ support code (everything but the parser rules themselves) from cp-name-parser.y to cp-name-parser.c. The reason for this is that I find it hard to read and maintain complex code in a .y file, where standard C++ tooling doesn't work. Move all the epilogue out to cp-name-parser.c, leaving cp-name-parser.y with just the prologue and the grammar rules. The code is mostly moved as-is. - struct cpname_state, along with the flag constants for d_qualify and d_int_type and the d_left/d_right macros, moves to the cp-name-parser.h header, so that both cp-name-parser-gen.c and cp-name-parser.c can see it. - The old yylex and yyerror functions are renamed explicitly to cpname_yylex and cpname_yyerror. They used to be effectively named that, thanks to the parser generator's -p flag, but now that they live in cp-name-parser.c, they just have that name. They are declared in cp-name-parser.h, so that cp-name-parser-gen.c can see them. cp-name-parser-gen.c calls yylex and yyerror, which -p turns into cpname_yylex and cpname_yyerror. - cp_demangled_name_to_comp is the function that calls into the generated parser's entry point, yyparse, or rather cpname_yyparse, thanks to the -p flag. Bison declares yyparse in the generated header, but byacc does not, so cp-name-parser.c has to declare it by hand unfortunately. - cp-support.c had its own copies of the d_left and d_right macros, drop them and use the ones from cp-name-parser.h. I wrapped most of cp-name-parser.{c,h} in namespace `cp_name_parser`. There is no real risk of symbol collisions here, but this is for consistency with the subsequent patches, so that all parsers use the same patterns. Add cp-name-parser.c to STALE_GENERATED_FILES, such that a stale cp-name-parser.c file in the build directory will make the build stop with a clear error. Change-Id: I2a47664d8e68f3e9ab41cf42378d2c61dcaa27f1 --- gdb/Makefile.in | 4 +- gdb/cp-name-parser.c | 1050 +++++++++++++++++++++++++++++++++++++++ gdb/cp-name-parser.h | 130 +++++ gdb/cp-name-parser.y | 1115 +----------------------------------------- gdb/cp-support.c | 4 +- 5 files changed, 1187 insertions(+), 1116 deletions(-) create mode 100644 gdb/cp-name-parser.c create mode 100644 gdb/cp-name-parser.h diff --git a/gdb/Makefile.in b/gdb/Makefile.in index 6cb5482bbc66..a7d79e08655e 100644 --- a/gdb/Makefile.in +++ b/gdb/Makefile.in @@ -1077,6 +1077,7 @@ COMMON_SFILES = \ corefile.c \ corelow.c \ cp-abi.c \ + cp-name-parser.c \ cp-namespace.c \ cp-support.c \ cp-valprint.c \ @@ -1374,6 +1375,7 @@ HFILES_NO_SRCDIR = \ config/nm-linux.h \ config/sparc/nm-sol2.h \ cp-abi.h \ + cp-name-parser.h \ cp-support.h \ cris-tdep.h \ csky-tdep.h \ @@ -2081,7 +2083,7 @@ YYHFILES = $(patsubst %-gen.c,%-gen.h,$(YYFILES)) # # Files in there can be removed after a while, once build directories with them # are unlikely to be around. -STALE_GENERATED_FILES = +STALE_GENERATED_FILES = cp-name-parser.c # Things which need to be built when making a distribution. diff --git a/gdb/cp-name-parser.c b/gdb/cp-name-parser.c new file mode 100644 index 000000000000..d04ed3594878 --- /dev/null +++ b/gdb/cp-name-parser.c @@ -0,0 +1,1050 @@ +/* Helper functions for the C++ name parser, for GDB. + + Copyright (C) 2003-2026 Free Software Foundation, Inc. + + This file is part of GDB. + + 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 . */ + +#include "cp-name-parser.h" +#include "cp-name-parser-gen.h" +#include "c-support.h" +#include "cp-support.h" +#include "gdbsupport/gdb_obstack.h" +#include "gdbsupport/selftest.h" + +/* The entry point of the bison/yacc-generated parser, defined in + cp-name-parser-gen.c. Bison produces a declaration for yyparse in + cp-name-parser-gen.h, but byacc does not, hence this declaration. */ + +int cpname_yyparse (cp_name_parser::cpname_state *state); + +namespace cp_name_parser { + +struct demangle_component * +cpname_state::d_grab () +{ + return obstack_new (&demangle_info->obstack); +} + +/* Helper functions. These wrap the demangler tree interface, handle + allocation from our global store, and return the allocated component. */ + +struct demangle_component * +cpname_state::fill_comp (enum demangle_component_type d_type, + struct demangle_component *lhs, + struct demangle_component *rhs) +{ + struct demangle_component *ret = d_grab (); + int i; + + i = cplus_demangle_fill_component (ret, d_type, lhs, rhs); + gdb_assert (i); + + return ret; +} + +struct demangle_component * +cpname_state::make_operator (const char *name, int args) +{ + struct demangle_component *ret = d_grab (); + int i; + + i = cplus_demangle_fill_operator (ret, name, args); + gdb_assert (i); + + return ret; +} + +struct demangle_component * +cpname_state::make_dtor (enum gnu_v3_dtor_kinds kind, + struct demangle_component *name) +{ + struct demangle_component *ret = d_grab (); + int i; + + i = cplus_demangle_fill_dtor (ret, kind, name); + gdb_assert (i); + + return ret; +} + +struct demangle_component * +cpname_state::make_builtin_type (const char *name) +{ + struct demangle_component *ret = d_grab (); + int i; + + i = cplus_demangle_fill_builtin_type (ret, name); + gdb_assert (i); + + return ret; +} + +struct demangle_component * +cpname_state::make_name (const char *name, int len) +{ + struct demangle_component *ret = d_grab (); + int i; + + i = cplus_demangle_fill_name (ret, name, len); + gdb_assert (i); + + return ret; +} + +/* Apply QUALIFIERS to LHS and return a qualified component. IS_METHOD + is set if LHS is a method, in which case the qualifiers are logically + applied to "this". We apply qualifiers in a consistent order; LHS + may already be qualified; duplicate qualifiers are not created. */ + +struct demangle_component * +cpname_state::d_qualify (struct demangle_component *lhs, int qualifiers, + int is_method) +{ + struct demangle_component **inner_p; + enum demangle_component_type type; + + /* For now the order is CONST (innermost), VOLATILE, RESTRICT. */ + +#define HANDLE_QUAL(TYPE, MTYPE, QUAL) \ + if ((qualifiers & QUAL) && (type != TYPE) && (type != MTYPE)) \ + { \ + *inner_p = fill_comp (is_method ? MTYPE : TYPE, \ + *inner_p, NULL); \ + inner_p = &d_left (*inner_p); \ + type = (*inner_p)->type; \ + } \ + else if (type == TYPE || type == MTYPE) \ + { \ + inner_p = &d_left (*inner_p); \ + type = (*inner_p)->type; \ + } + + inner_p = &lhs; + + type = (*inner_p)->type; + + HANDLE_QUAL (DEMANGLE_COMPONENT_RESTRICT, DEMANGLE_COMPONENT_RESTRICT_THIS, QUAL_RESTRICT); + HANDLE_QUAL (DEMANGLE_COMPONENT_VOLATILE, DEMANGLE_COMPONENT_VOLATILE_THIS, QUAL_VOLATILE); + HANDLE_QUAL (DEMANGLE_COMPONENT_CONST, DEMANGLE_COMPONENT_CONST_THIS, QUAL_CONST); +#undef HANDLE_QUAL + + return lhs; +} + +/* Return a builtin type corresponding to FLAGS. */ + +struct demangle_component * +cpname_state::d_int_type (int flags) +{ + const char *name; + + switch (flags) + { + case INT_SIGNED | INT_CHAR: + name = "signed char"; + break; + case INT_CHAR: + name = "char"; + break; + case INT_UNSIGNED | INT_CHAR: + name = "unsigned char"; + break; + case 0: + case INT_SIGNED: + name = "int"; + break; + case INT_UNSIGNED: + name = "unsigned int"; + break; + case INT_LONG: + case INT_SIGNED | INT_LONG: + name = "long"; + break; + case INT_UNSIGNED | INT_LONG: + name = "unsigned long"; + break; + case INT_SHORT: + case INT_SIGNED | INT_SHORT: + name = "short"; + break; + case INT_UNSIGNED | INT_SHORT: + name = "unsigned short"; + break; + case INT_LLONG | INT_LONG: + case INT_SIGNED | INT_LLONG | INT_LONG: + name = "long long"; + break; + case INT_UNSIGNED | INT_LLONG | INT_LONG: + name = "unsigned long long"; + break; + default: + return NULL; + } + + return make_builtin_type (name); +} + +/* Wrapper to create a unary operation. */ + +struct demangle_component * +cpname_state::d_unary (const char *name, struct demangle_component *lhs) +{ + return fill_comp (DEMANGLE_COMPONENT_UNARY, make_operator (name, 1), lhs); +} + +/* Wrapper to create a binary operation. */ + +struct demangle_component * +cpname_state::d_binary (const char *name, struct demangle_component *lhs, + struct demangle_component *rhs) +{ + return fill_comp (DEMANGLE_COMPONENT_BINARY, make_operator (name, 2), + fill_comp (DEMANGLE_COMPONENT_BINARY_ARGS, lhs, rhs)); +} + +/* Take care of parsing a number (anything that starts with a digit). + The number starts at P and contains LEN characters. Store the result in + YYLVAL. */ + +int +cpname_state::parse_number (const char *p, int len, int parsed_float, + cp_name_parser_YYSTYPE *lvalp) +{ + int unsigned_p = 0; + + /* Number of "L" suffixes encountered. */ + int long_p = 0; + + struct demangle_component *type, *name; + enum demangle_component_type literal_type; + + if (p[0] == '-') + { + literal_type = DEMANGLE_COMPONENT_LITERAL_NEG; + p++; + len--; + } + else + literal_type = DEMANGLE_COMPONENT_LITERAL; + + if (parsed_float) + { + /* It's a float since it contains a point or an exponent. */ + char c; + + /* The GDB lexer checks the result of scanf at this point. Not doing + this leaves our error checking slightly weaker but only for invalid + data. */ + + /* See if it has `f' or `l' suffix (float or long double). */ + + c = c_tolower (p[len - 1]); + + if (c == 'f') + { + len--; + type = make_builtin_type ("float"); + } + else if (c == 'l') + { + len--; + type = make_builtin_type ("long double"); + } + else if (c_isdigit (c) || c == '.') + type = make_builtin_type ("double"); + else + return ERROR; + + name = make_name (p, len); + lvalp->comp = fill_comp (literal_type, type, name); + + return FLOAT; + } + + /* Note that we do not automatically generate unsigned types. This + can't be done because we don't have access to the gdbarch + here. */ + + int base = 10; + if (len > 1 && p[0] == '0') + { + if (p[1] == 'x' || p[1] == 'X') + { + base = 16; + p += 2; + len -= 2; + } + else if (p[1] == 'b' || p[1] == 'B') + { + base = 2; + p += 2; + len -= 2; + } + else if (p[1] == 'd' || p[1] == 'D' || p[1] == 't' || p[1] == 'T') + { + /* Apparently gdb extensions. */ + base = 10; + p += 2; + len -= 2; + } + else + base = 8; + } + + long_p = 0; + unsigned_p = 0; + while (len > 0) + { + if (p[len - 1] == 'l' || p[len - 1] == 'L') + { + len--; + long_p++; + continue; + } + if (p[len - 1] == 'u' || p[len - 1] == 'U') + { + len--; + unsigned_p++; + continue; + } + break; + } + + /* Use gdb_mpz here in case a 128-bit value appears. */ + gdb_mpz value (0); + for (int off = 0; off < len; ++off) + { + int dig; + + if (c_isdigit (p[off])) + dig = p[off] - '0'; + else + dig = c_tolower (p[off]) - 'a' + 10; + + if (dig >= base) + return ERROR; + + value *= base; + value += dig; + } + + std::string printed = value.str (); + const char *copy = obstack_strdup (&demangle_info->obstack, printed); + + if (long_p == 0) + { + if (unsigned_p) + type = make_builtin_type ("unsigned int"); + else + type = make_builtin_type ("int"); + } + else if (long_p == 1) + { + if (unsigned_p) + type = make_builtin_type ("unsigned long"); + else + type = make_builtin_type ("long"); + } + else + { + if (unsigned_p) + type = make_builtin_type ("unsigned long long"); + else + type = make_builtin_type ("long long"); + } + + name = make_name (copy, strlen (copy)); + lvalp->comp = fill_comp (literal_type, type, name); + + return INT; +} + +} /* namespace cp_name_parser */ + +/* See cp-support.h. */ + +gdb::unique_xmalloc_ptr +cp_comp_to_string (struct demangle_component *result, int estimated_len) +{ + size_t err; + + char *res = gdb_cplus_demangle_print (DMGL_PARAMS | DMGL_ANSI, + result, estimated_len, &err); + return gdb::unique_xmalloc_ptr (res); +} + +/* Merge the two parse trees given by DEST and SRC. The parse tree + in SRC is attached to DEST at the node represented by TARGET. + + NOTE 1: Since there is no API to merge obstacks, this function does + even attempt to try it. Fortunately, we do not (yet?) need this ability. + The code will assert if SRC->obstack is not empty. + + NOTE 2: The string from which SRC was parsed must not be freed, since + this function will place pointers to that string into DEST. */ + +void +cp_merge_demangle_parse_infos (struct demangle_parse_info *dest, + struct demangle_component *target, + demangle_parse_info_up src) +{ + /* Copy the SRC's parse data into DEST. */ + *target = *src->tree; + + /* Make sure SRC is owned by DEST. */ + dest->infos.push_back (std::move (src)); +} + +/* Convert a demangled name to a demangle_component tree. On success, + a structure containing the root of the new tree is returned. On + error, NULL is returned, and an error message will be set in + *ERRMSG. */ + +demangle_parse_info_up +cp_demangled_name_to_comp (const char *demangled_name, + std::string *errmsg) +{ + auto result = std::make_unique (); + cp_name_parser::cpname_state state (demangled_name, result.get ()); + + /* Note that we can't set yydebug here, as is done in the other + parsers. Bison implements yydebug as a global, even with a pure + parser, and this parser is run from worker threads. So, changing + yydebug causes TSan reports. If you need to debug this parser, + debug gdb and set the global from the outer gdb. */ + if (cpname_yyparse (&state)) + { + if (state.global_errmsg && errmsg) + *errmsg = state.global_errmsg; + return NULL; + } + + result->tree = state.global_result; + + return result; +} + +/* Find the end of a symbol name starting at LEXPTR. */ + +static const char * +symbol_end (const char *lexptr) +{ + const char *p = lexptr; + + while (*p && (c_ident_is_alnum (*p) || *p == '_' || *p == '$' || *p == '.')) + p++; + + return p; +} + +static const char backslashable[] = "abefnrtv"; +static const char represented[] = "\a\b\e\f\n\r\t\v"; + +/* Translate the backslash the way we would in the host character set. */ +static int +c_parse_backslash (int host_char, int *target_char) +{ + const char *ix; + ix = strchr (backslashable, host_char); + if (! ix) + return 0; + else + *target_char = represented[ix - backslashable]; + return 1; +} + +/* Parse a C escape sequence. STRING_PTR points to a variable + containing a pointer to the string to parse. That pointer + should point to the character after the \. That pointer + is updated past the characters we use. The value of the + escape sequence is returned. + + A negative value means the sequence \ newline was seen, + which is supposed to be equivalent to nothing at all. + + If \ is followed by a null character, we return a negative + value and leave the string pointer pointing at the null character. + + If \ is followed by 000, we return 0 and leave the string pointer + after the zeros. A value of 0 does not mean end of string. */ + +static int +cp_parse_escape (const char **string_ptr) +{ + int target_char; + int c = *(*string_ptr)++; + if (c_parse_backslash (c, &target_char)) + return target_char; + else + switch (c) + { + case '\n': + return -2; + case 0: + (*string_ptr)--; + return 0; + case '^': + { + c = *(*string_ptr)++; + + if (c == '?') + return 0177; + else if (c == '\\') + target_char = cp_parse_escape (string_ptr); + else + target_char = c; + + /* Now target_char is something like `c', and we want to find + its control-character equivalent. */ + target_char = target_char & 037; + + return target_char; + } + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + { + int i = c - '0'; + int count = 0; + while (++count < 3) + { + c = (**string_ptr); + if (c >= '0' && c <= '7') + { + (*string_ptr)++; + i *= 8; + i += c - '0'; + } + else + { + break; + } + } + return i; + } + default: + return c; + } +} + +#define HANDLE_SPECIAL(string, comp) \ + if (startswith (tokstart, string)) \ + { \ + state->lexptr = tokstart + sizeof (string) - 1; \ + lvalp->lval = comp; \ + return DEMANGLER_SPECIAL; \ + } + +#define HANDLE_TOKEN2(string, token) \ + if (state->lexptr[1] == string[1]) \ + { \ + state->lexptr += 2; \ + lvalp->opname = string; \ + return token; \ + } + +#define HANDLE_TOKEN3(string, token) \ + if (state->lexptr[1] == string[1] && state->lexptr[2] == string[2]) \ + { \ + state->lexptr += 3; \ + lvalp->opname = string; \ + return token; \ + } + +void +cpname_yyerror (cp_name_parser::cpname_state *state, const char *msg) +{ + if (state->global_errmsg) + return; + + state->error_lexptr = state->prev_lexptr; + state->global_errmsg = msg ? msg : "parse error"; +} + +/* Read one token, getting characters through LEXPTR. */ + +int +cpname_yylex (cp_name_parser_YYSTYPE *lvalp, + cp_name_parser::cpname_state *state) +{ + int c; + int namelen; + const char *tokstart; + char *copy; + + retry: + state->prev_lexptr = state->lexptr; + tokstart = state->lexptr; + + switch (c = *tokstart) + { + case 0: + return 0; + + case ' ': + case '\t': + case '\n': + state->lexptr++; + goto retry; + + case '\'': + /* We either have a character constant ('0' or '\177' for example) + or we have a quoted symbol reference ('foo(int,int)' in C++ + for example). */ + state->lexptr++; + c = *state->lexptr++; + if (c == '\\') + c = cp_parse_escape (&state->lexptr); + else if (c == '\'') + { + cpname_yyerror (state, _("empty character constant")); + return ERROR; + } + + /* We over-allocate here, but it doesn't really matter . */ + copy = (char *) obstack_alloc (&state->demangle_info->obstack, 30); + xsnprintf (copy, 30, "%d", c); + + c = *state->lexptr++; + if (c != '\'') + { + cpname_yyerror (state, _("invalid character constant")); + return ERROR; + } + + lvalp->comp + = state->fill_comp (DEMANGLE_COMPONENT_LITERAL, + state->make_builtin_type ("char"), + state->make_name (copy, strlen (copy))); + + return INT; + + case '(': + if (startswith (tokstart, "(anonymous namespace)")) + { + state->lexptr += 21; + lvalp->comp = state->make_name ("(anonymous namespace)", + sizeof "(anonymous namespace)" - 1); + return NAME; + } + [[fallthrough]]; + + case ')': + case ',': + state->lexptr++; + return c; + + case '.': + if (state->lexptr[1] == '.' && state->lexptr[2] == '.') + { + state->lexptr += 3; + return ELLIPSIS; + } + + /* Might be a floating point number. */ + if (state->lexptr[1] < '0' || state->lexptr[1] > '9') + goto symbol; /* Nope, must be a symbol. */ + + goto try_number; + + case '-': + HANDLE_TOKEN2 ("-=", ASSIGN_MODIFY); + HANDLE_TOKEN2 ("--", DECREMENT); + HANDLE_TOKEN2 ("->", ARROW); + + /* For construction vtables. This is kind of hokey. */ + if (startswith (tokstart, "-in-")) + { + state->lexptr += 4; + return CONSTRUCTION_IN; + } + + if (state->lexptr[1] < '0' || state->lexptr[1] > '9') + { + state->lexptr++; + return '-'; + } + + try_number: + [[fallthrough]]; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + /* It's a number. */ + int got_dot = 0, got_e = 0, toktype; + const char *p = tokstart; + int hex = 0; + + if (c == '-') + p++; + + if (c == '0' && (p[1] == 'x' || p[1] == 'X')) + { + p += 2; + hex = 1; + } + else if (c == '0' && (p[1]=='t' || p[1]=='T' || p[1]=='d' || p[1]=='D')) + { + p += 2; + hex = 0; + } + + /* If the token includes the C++14 digits separator, we make a + copy so that we don't have to handle the separator in + parse_number. */ + std::optional no_tick; + for (;; ++p) + { + /* This test includes !hex because 'e' is a valid hex digit + and thus does not indicate a floating point number when + the radix is hex. */ + if (!hex && !got_e && (*p == 'e' || *p == 'E')) + got_dot = got_e = 1; + /* This test does not include !hex, because a '.' always indicates + a decimal floating point number regardless of the radix. + + NOTE drow/2005-03-09: This comment is not accurate in C99; + however, it's not clear that all the floating point support + in this file is doing any good here. */ + else if (!got_dot && *p == '.') + got_dot = 1; + else if (got_e && (p[-1] == 'e' || p[-1] == 'E') + && (*p == '-' || *p == '+')) + { + /* This is the sign of the exponent, not the end of + the number. */ + } + /* C++14 allows a separator. */ + else if (*p == '\'') + { + if (!no_tick.has_value ()) + no_tick.emplace (tokstart, p); + continue; + } + /* We will take any letters or digits. parse_number will + complain if past the radix, or if L or U are not final. */ + else if (! c_isalnum (*p)) + break; + if (no_tick.has_value ()) + no_tick->push_back (*p); + } + if (no_tick.has_value ()) + toktype = state->parse_number (no_tick->c_str (), no_tick->length (), + got_dot | got_e, lvalp); + else + toktype = state->parse_number (tokstart, p - tokstart, + got_dot | got_e, lvalp); + if (toktype == ERROR) + { + cpname_yyerror (state, _("invalid number")); + return ERROR; + } + state->lexptr = p; + return toktype; + } + + case '+': + HANDLE_TOKEN2 ("+=", ASSIGN_MODIFY); + HANDLE_TOKEN2 ("++", INCREMENT); + state->lexptr++; + return c; + case '*': + HANDLE_TOKEN2 ("*=", ASSIGN_MODIFY); + state->lexptr++; + return c; + case '/': + HANDLE_TOKEN2 ("/=", ASSIGN_MODIFY); + state->lexptr++; + return c; + case '%': + HANDLE_TOKEN2 ("%=", ASSIGN_MODIFY); + state->lexptr++; + return c; + case '|': + HANDLE_TOKEN2 ("|=", ASSIGN_MODIFY); + HANDLE_TOKEN2 ("||", OROR); + state->lexptr++; + return c; + case '&': + HANDLE_TOKEN2 ("&=", ASSIGN_MODIFY); + HANDLE_TOKEN2 ("&&", ANDAND); + state->lexptr++; + return c; + case '^': + HANDLE_TOKEN2 ("^=", ASSIGN_MODIFY); + state->lexptr++; + return c; + case '!': + HANDLE_TOKEN2 ("!=", NOTEQUAL); + state->lexptr++; + return c; + case '<': + HANDLE_TOKEN3 ("<<=", ASSIGN_MODIFY); + HANDLE_TOKEN3 ("<=>", SPACESHIP); + HANDLE_TOKEN2 ("<=", LEQ); + HANDLE_TOKEN2 ("<<", LSH); + state->lexptr++; + return c; + case '>': + HANDLE_TOKEN3 (">>=", ASSIGN_MODIFY); + HANDLE_TOKEN2 (">=", GEQ); + HANDLE_TOKEN2 (">>", RSH); + state->lexptr++; + return c; + case '=': + HANDLE_TOKEN2 ("==", EQUAL); + state->lexptr++; + return c; + case ':': + HANDLE_TOKEN2 ("::", COLONCOLON); + state->lexptr++; + return c; + + case '[': + case ']': + case '?': + case '@': + case '~': + case '{': + case '}': + symbol: + state->lexptr++; + return c; + + case '"': + /* These can't occur in C++ names. */ + cpname_yyerror (state, _("unexpected string literal")); + return ERROR; + } + + if (!(c == '_' || c == '$' || c_ident_is_alpha (c))) + { + /* We must have come across a bad character (e.g. ';'). */ + cpname_yyerror (state, _("invalid character")); + return ERROR; + } + + /* It's a name. See how long it is. */ + namelen = 0; + do + c = tokstart[++namelen]; + while (c_ident_is_alnum (c) || c == '_' || c == '$'); + + state->lexptr += namelen; + + /* Catch specific keywords. Notice that some of the keywords contain + spaces, and are sorted by the length of the first word. They must + all include a trailing space in the string comparison. */ + switch (namelen) + { + case 16: + if (startswith (tokstart, "reinterpret_cast")) + return REINTERPRET_CAST; + break; + case 12: + if (startswith (tokstart, "construction vtable for ")) + { + state->lexptr = tokstart + 24; + return CONSTRUCTION_VTABLE; + } + if (startswith (tokstart, "dynamic_cast")) + return DYNAMIC_CAST; + break; + case 11: + if (startswith (tokstart, "static_cast")) + return STATIC_CAST; + break; + case 9: + HANDLE_SPECIAL ("covariant return thunk to ", DEMANGLE_COMPONENT_COVARIANT_THUNK); + HANDLE_SPECIAL ("reference temporary for ", DEMANGLE_COMPONENT_REFTEMP); + break; + case 8: + HANDLE_SPECIAL ("typeinfo for ", DEMANGLE_COMPONENT_TYPEINFO); + HANDLE_SPECIAL ("typeinfo fn for ", DEMANGLE_COMPONENT_TYPEINFO_FN); + HANDLE_SPECIAL ("typeinfo name for ", DEMANGLE_COMPONENT_TYPEINFO_NAME); + if (startswith (tokstart, "operator")) + return OPERATOR; + if (startswith (tokstart, "restrict")) + return RESTRICT; + if (startswith (tokstart, "unsigned")) + return UNSIGNED; + if (startswith (tokstart, "template")) + return TEMPLATE; + if (startswith (tokstart, "volatile")) + return VOLATILE_KEYWORD; + break; + case 7: + HANDLE_SPECIAL ("virtual thunk to ", DEMANGLE_COMPONENT_VIRTUAL_THUNK); + if (startswith (tokstart, "wchar_t")) + return WCHAR_T; + break; + case 6: + if (startswith (tokstart, "global constructors keyed to ")) + { + const char *p; + state->lexptr = tokstart + 29; + lvalp->lval = DEMANGLE_COMPONENT_GLOBAL_CONSTRUCTORS; + /* Find the end of the symbol. */ + p = symbol_end (state->lexptr); + lvalp->comp = state->make_name (state->lexptr, p - state->lexptr); + state->lexptr = p; + return DEMANGLER_SPECIAL; + } + if (startswith (tokstart, "global destructors keyed to ")) + { + const char *p; + state->lexptr = tokstart + 28; + lvalp->lval = DEMANGLE_COMPONENT_GLOBAL_DESTRUCTORS; + /* Find the end of the symbol. */ + p = symbol_end (state->lexptr); + lvalp->comp = state->make_name (state->lexptr, p - state->lexptr); + state->lexptr = p; + return DEMANGLER_SPECIAL; + } + + HANDLE_SPECIAL ("vtable for ", DEMANGLE_COMPONENT_VTABLE); + if (startswith (tokstart, "delete")) + return DELETE; + if (startswith (tokstart, "struct")) + return STRUCT; + if (startswith (tokstart, "signed")) + return SIGNED_KEYWORD; + if (startswith (tokstart, "sizeof")) + return SIZEOF; + if (startswith (tokstart, "double")) + return DOUBLE_KEYWORD; + break; + case 5: + HANDLE_SPECIAL ("guard variable for ", DEMANGLE_COMPONENT_GUARD); + if (startswith (tokstart, "false")) + return FALSEKEYWORD; + if (startswith (tokstart, "class")) + return CLASS; + if (startswith (tokstart, "union")) + return UNION; + if (startswith (tokstart, "float")) + return FLOAT_KEYWORD; + if (startswith (tokstart, "short")) + return SHORT; + if (startswith (tokstart, "const")) + return CONST_KEYWORD; + break; + case 4: + if (startswith (tokstart, "void")) + return VOID; + if (startswith (tokstart, "bool")) + return BOOL; + if (startswith (tokstart, "char")) + return CHAR; + if (startswith (tokstart, "enum")) + return ENUM; + if (startswith (tokstart, "long")) + return LONG; + if (startswith (tokstart, "true")) + return TRUEKEYWORD; + break; + case 3: + HANDLE_SPECIAL ("VTT for ", DEMANGLE_COMPONENT_VTT); + HANDLE_SPECIAL ("non-virtual thunk to ", DEMANGLE_COMPONENT_THUNK); + if (startswith (tokstart, "new")) + return NEW; + if (startswith (tokstart, "int")) + return INT_KEYWORD; + break; + default: + break; + } + + lvalp->comp = state->make_name (tokstart, namelen); + return NAME; +} + +#if GDB_SELF_TEST + +static void +should_be_the_same (const char *one, const char *two) +{ + gdb::unique_xmalloc_ptr cpone = cp_canonicalize_string (one); + gdb::unique_xmalloc_ptr cptwo = cp_canonicalize_string (two); + + if (cpone != nullptr) + one = cpone.get (); + if (cptwo != nullptr) + two = cptwo.get (); + + SELF_CHECK (streq (one, two)); +} + +static void +should_parse (const char *name) +{ + auto parsed = cp_demangled_name_to_comp (name); + SELF_CHECK (parsed != nullptr); +} + +static void +canonicalize_tests () +{ + should_be_the_same ("short int", "short"); + should_be_the_same ("int short", "short"); + + should_be_the_same ("C<(char) 1>::m()", "C<(char) '\\001'>::m()"); + should_be_the_same ("x::y::z<1>", "x::y::z<0x01>"); + should_be_the_same ("x::y::z<1>", "x::y::z<01>"); + should_be_the_same ("x::y::z<(unsigned long long) 1>", "x::y::z<01ull>"); + should_be_the_same ("x::y::z<0b111>", "x::y::z<7>"); + should_be_the_same ("x::y::z<0b111>", "x::y::z<0t7>"); + should_be_the_same ("x::y::z<0b111>", "x::y::z<0D7>"); + + should_be_the_same ("x::y::z<0xff'ff>", "x::y::z<65535>"); + + should_be_the_same ("something", "something< void() >"); + should_be_the_same ("something", "something"); + + should_parse ("void whatever::operator<=>"); + + should_be_the_same ("Foozle::fogey > (Empty)", + "Foozle::fogey> (Empty)"); + + should_be_the_same ("something :: operator new [ ]", + "something::operator new[]"); + should_be_the_same ("something :: operator new", + "something::operator new"); + should_be_the_same ("operator()", "operator ()"); +} + +#endif + +INIT_GDB_FILE (cp_name_parser) +{ +#if GDB_SELF_TEST + selftests::register_test ("canonicalize", canonicalize_tests); +#endif +} diff --git a/gdb/cp-name-parser.h b/gdb/cp-name-parser.h new file mode 100644 index 000000000000..31b2227227fc --- /dev/null +++ b/gdb/cp-name-parser.h @@ -0,0 +1,130 @@ +/* State of the C++ name parser, for GDB. + + Copyright (C) 2003-2026 Free Software Foundation, Inc. + + This file is part of GDB. + + 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 . */ + +#ifndef GDB_CP_NAME_PARSER_H +#define GDB_CP_NAME_PARSER_H + +#include "demangle.h" + +union cp_name_parser_YYSTYPE; +struct demangle_parse_info; + +/* Flags passed to cpname_state::d_qualify. */ + +#define QUAL_CONST 1 +#define QUAL_RESTRICT 2 +#define QUAL_VOLATILE 4 + +/* Flags passed to cpname_state::d_int_type. */ + +#define INT_CHAR (1 << 0) +#define INT_SHORT (1 << 1) +#define INT_LONG (1 << 2) +#define INT_LLONG (1 << 3) + +#define INT_SIGNED (1 << 4) +#define INT_UNSIGNED (1 << 5) + +#define d_left(dc) (dc)->u.s_binary.left +#define d_right(dc) (dc)->u.s_binary.right + +namespace cp_name_parser { + +/* State of an ongoing parse. */ + +struct cpname_state +{ + cpname_state (const char *input, demangle_parse_info *info) + : lexptr (input), + prev_lexptr (input), + demangle_info (info) + { } + + /* Un-push a character into the lexer. This can only un-push the + previous character in the input string. */ + void unpush (char c) + { + gdb_assert (lexptr[-1] == c); + --lexptr; + } + + /* LEXPTR is the current pointer into our lex buffer. PREV_LEXPTR + is the start of the last token lexed, only used for diagnostics. + ERROR_LEXPTR is the first place an error occurred. GLOBAL_ERRMSG + is the first error message encountered. */ + + const char *lexptr, *prev_lexptr; + const char *error_lexptr = nullptr; + const char *global_errmsg = nullptr; + + demangle_parse_info *demangle_info; + + /* The parse tree created by the parser is stored here after a + successful parse. */ + + struct demangle_component *global_result = nullptr; + + struct demangle_component *d_grab (); + + /* Helper functions. These wrap the demangler tree interface, + handle allocation from our global store, and return the allocated + component. */ + + struct demangle_component *fill_comp (enum demangle_component_type d_type, + struct demangle_component *lhs, + struct demangle_component *rhs); + + struct demangle_component *make_operator (const char *name, int args); + + struct demangle_component *make_dtor (enum gnu_v3_dtor_kinds kind, + struct demangle_component *name); + + struct demangle_component *make_builtin_type (const char *name); + + struct demangle_component *make_name (const char *name, int len); + + struct demangle_component *d_qualify (struct demangle_component *lhs, + int qualifiers, int is_method); + + struct demangle_component *d_int_type (int flags); + + struct demangle_component *d_unary (const char *name, + struct demangle_component *lhs); + + struct demangle_component *d_binary (const char *name, + struct demangle_component *lhs, + struct demangle_component *rhs); + + int parse_number (const char *p, int len, int parsed_float, + cp_name_parser_YYSTYPE *lvalp); +}; + +} /* namespace cp_name_parser */ + +/* The lexer used by the generated parser. */ + +int cpname_yylex (cp_name_parser_YYSTYPE *lvalp, + cp_name_parser::cpname_state *state); + +/* The error handler invoked by the generated parser. Report MSG as a + parse error on the current parser state. */ + +void cpname_yyerror (cp_name_parser::cpname_state *state, const char *msg); + +#endif /* GDB_CP_NAME_PARSER_H */ diff --git a/gdb/cp-name-parser.y b/gdb/cp-name-parser.y index 44ef1a41eb39..687380c4ca68 100644 --- a/gdb/cp-name-parser.y +++ b/gdb/cp-name-parser.y @@ -32,18 +32,14 @@ this uses %lex-param and %parse-param rather than the simpler %param -- Byacc does not support the latter. */ %pure-parser -%lex-param {struct cpname_state *state} -%parse-param {struct cpname_state *state} +%lex-param {cp_name_parser::cpname_state *state} +%parse-param {cp_name_parser::cpname_state *state} %{ - -#include #include "demangle.h" +#include "cp-name-parser.h" #include "cp-support.h" -#include "c-support.h" -#include "parser-defs.h" -#include "gdbsupport/selftest.h" %} @@ -67,169 +63,6 @@ const char *opname; } -%{ - -struct cpname_state -{ - cpname_state (const char *input, demangle_parse_info *info) - : lexptr (input), - prev_lexptr (input), - demangle_info (info) - { } - - /* Un-push a character into the lexer. This can only un-push the - previous character in the input string. */ - void unpush (char c) - { - gdb_assert (lexptr[-1] == c); - --lexptr; - } - - /* LEXPTR is the current pointer into our lex buffer. PREV_LEXPTR - is the start of the last token lexed, only used for diagnostics. - ERROR_LEXPTR is the first place an error occurred. GLOBAL_ERRMSG - is the first error message encountered. */ - - const char *lexptr, *prev_lexptr; - const char *error_lexptr = nullptr; - const char *global_errmsg = nullptr; - - demangle_parse_info *demangle_info; - - /* The parse tree created by the parser is stored here after a - successful parse. */ - - struct demangle_component *global_result = nullptr; - - struct demangle_component *d_grab (); - - /* Helper functions. These wrap the demangler tree interface, - handle allocation from our global store, and return the allocated - component. */ - - struct demangle_component *fill_comp (enum demangle_component_type d_type, - struct demangle_component *lhs, - struct demangle_component *rhs); - - struct demangle_component *make_operator (const char *name, int args); - - struct demangle_component *make_dtor (enum gnu_v3_dtor_kinds kind, - struct demangle_component *name); - - struct demangle_component *make_builtin_type (const char *name); - - struct demangle_component *make_name (const char *name, int len); - - struct demangle_component *d_qualify (struct demangle_component *lhs, - int qualifiers, int is_method); - - struct demangle_component *d_int_type (int flags); - - struct demangle_component *d_unary (const char *name, - struct demangle_component *lhs); - - struct demangle_component *d_binary (const char *name, - struct demangle_component *lhs, - struct demangle_component *rhs); - - int parse_number (const char *p, int len, int parsed_float, YYSTYPE *lvalp); -}; - -struct demangle_component * -cpname_state::d_grab () -{ - return obstack_new (&demangle_info->obstack); -} - -/* Flags passed to d_qualify. */ - -#define QUAL_CONST 1 -#define QUAL_RESTRICT 2 -#define QUAL_VOLATILE 4 - -/* Flags passed to d_int_type. */ - -#define INT_CHAR (1 << 0) -#define INT_SHORT (1 << 1) -#define INT_LONG (1 << 2) -#define INT_LLONG (1 << 3) - -#define INT_SIGNED (1 << 4) -#define INT_UNSIGNED (1 << 5) - -/* Helper functions. These wrap the demangler tree interface, handle - allocation from our global store, and return the allocated component. */ - -struct demangle_component * -cpname_state::fill_comp (enum demangle_component_type d_type, - struct demangle_component *lhs, - struct demangle_component *rhs) -{ - struct demangle_component *ret = d_grab (); - int i; - - i = cplus_demangle_fill_component (ret, d_type, lhs, rhs); - gdb_assert (i); - - return ret; -} - -struct demangle_component * -cpname_state::make_operator (const char *name, int args) -{ - struct demangle_component *ret = d_grab (); - int i; - - i = cplus_demangle_fill_operator (ret, name, args); - gdb_assert (i); - - return ret; -} - -struct demangle_component * -cpname_state::make_dtor (enum gnu_v3_dtor_kinds kind, - struct demangle_component *name) -{ - struct demangle_component *ret = d_grab (); - int i; - - i = cplus_demangle_fill_dtor (ret, kind, name); - gdb_assert (i); - - return ret; -} - -struct demangle_component * -cpname_state::make_builtin_type (const char *name) -{ - struct demangle_component *ret = d_grab (); - int i; - - i = cplus_demangle_fill_builtin_type (ret, name); - gdb_assert (i); - - return ret; -} - -struct demangle_component * -cpname_state::make_name (const char *name, int len) -{ - struct demangle_component *ret = d_grab (); - int i; - - i = cplus_demangle_fill_name (ret, name, len); - gdb_assert (i); - - return ret; -} - -#define d_left(dc) (dc)->u.s_binary.left -#define d_right(dc) (dc)->u.s_binary.right - -static int yylex (YYSTYPE *, cpname_state *); -static void yyerror (cpname_state *, const char *); -%} - %type exp exp1 type start start_opt oper colon_name %type unqualified_name colon_ext_name %type templ template_arg @@ -1196,945 +1029,3 @@ exp : FALSEKEYWORD ; /* end of C++. */ - -%% - -/* Apply QUALIFIERS to LHS and return a qualified component. IS_METHOD - is set if LHS is a method, in which case the qualifiers are logically - applied to "this". We apply qualifiers in a consistent order; LHS - may already be qualified; duplicate qualifiers are not created. */ - -struct demangle_component * -cpname_state::d_qualify (struct demangle_component *lhs, int qualifiers, - int is_method) -{ - struct demangle_component **inner_p; - enum demangle_component_type type; - - /* For now the order is CONST (innermost), VOLATILE, RESTRICT. */ - -#define HANDLE_QUAL(TYPE, MTYPE, QUAL) \ - if ((qualifiers & QUAL) && (type != TYPE) && (type != MTYPE)) \ - { \ - *inner_p = fill_comp (is_method ? MTYPE : TYPE, \ - *inner_p, NULL); \ - inner_p = &d_left (*inner_p); \ - type = (*inner_p)->type; \ - } \ - else if (type == TYPE || type == MTYPE) \ - { \ - inner_p = &d_left (*inner_p); \ - type = (*inner_p)->type; \ - } - - inner_p = &lhs; - - type = (*inner_p)->type; - - HANDLE_QUAL (DEMANGLE_COMPONENT_RESTRICT, DEMANGLE_COMPONENT_RESTRICT_THIS, QUAL_RESTRICT); - HANDLE_QUAL (DEMANGLE_COMPONENT_VOLATILE, DEMANGLE_COMPONENT_VOLATILE_THIS, QUAL_VOLATILE); - HANDLE_QUAL (DEMANGLE_COMPONENT_CONST, DEMANGLE_COMPONENT_CONST_THIS, QUAL_CONST); - - return lhs; -} - -/* Return a builtin type corresponding to FLAGS. */ - -struct demangle_component * -cpname_state::d_int_type (int flags) -{ - const char *name; - - switch (flags) - { - case INT_SIGNED | INT_CHAR: - name = "signed char"; - break; - case INT_CHAR: - name = "char"; - break; - case INT_UNSIGNED | INT_CHAR: - name = "unsigned char"; - break; - case 0: - case INT_SIGNED: - name = "int"; - break; - case INT_UNSIGNED: - name = "unsigned int"; - break; - case INT_LONG: - case INT_SIGNED | INT_LONG: - name = "long"; - break; - case INT_UNSIGNED | INT_LONG: - name = "unsigned long"; - break; - case INT_SHORT: - case INT_SIGNED | INT_SHORT: - name = "short"; - break; - case INT_UNSIGNED | INT_SHORT: - name = "unsigned short"; - break; - case INT_LLONG | INT_LONG: - case INT_SIGNED | INT_LLONG | INT_LONG: - name = "long long"; - break; - case INT_UNSIGNED | INT_LLONG | INT_LONG: - name = "unsigned long long"; - break; - default: - return NULL; - } - - return make_builtin_type (name); -} - -/* Wrapper to create a unary operation. */ - -struct demangle_component * -cpname_state::d_unary (const char *name, struct demangle_component *lhs) -{ - return fill_comp (DEMANGLE_COMPONENT_UNARY, make_operator (name, 1), lhs); -} - -/* Wrapper to create a binary operation. */ - -struct demangle_component * -cpname_state::d_binary (const char *name, struct demangle_component *lhs, - struct demangle_component *rhs) -{ - return fill_comp (DEMANGLE_COMPONENT_BINARY, make_operator (name, 2), - fill_comp (DEMANGLE_COMPONENT_BINARY_ARGS, lhs, rhs)); -} - -/* Find the end of a symbol name starting at LEXPTR. */ - -static const char * -symbol_end (const char *lexptr) -{ - const char *p = lexptr; - - while (*p && (c_ident_is_alnum (*p) || *p == '_' || *p == '$' || *p == '.')) - p++; - - return p; -} - -/* Take care of parsing a number (anything that starts with a digit). - The number starts at P and contains LEN characters. Store the result in - YYLVAL. */ - -int -cpname_state::parse_number (const char *p, int len, int parsed_float, - YYSTYPE *lvalp) -{ - int unsigned_p = 0; - - /* Number of "L" suffixes encountered. */ - int long_p = 0; - - struct demangle_component *type, *name; - enum demangle_component_type literal_type; - - if (p[0] == '-') - { - literal_type = DEMANGLE_COMPONENT_LITERAL_NEG; - p++; - len--; - } - else - literal_type = DEMANGLE_COMPONENT_LITERAL; - - if (parsed_float) - { - /* It's a float since it contains a point or an exponent. */ - char c; - - /* The GDB lexer checks the result of scanf at this point. Not doing - this leaves our error checking slightly weaker but only for invalid - data. */ - - /* See if it has `f' or `l' suffix (float or long double). */ - - c = c_tolower (p[len - 1]); - - if (c == 'f') - { - len--; - type = make_builtin_type ("float"); - } - else if (c == 'l') - { - len--; - type = make_builtin_type ("long double"); - } - else if (c_isdigit (c) || c == '.') - type = make_builtin_type ("double"); - else - return ERROR; - - name = make_name (p, len); - lvalp->comp = fill_comp (literal_type, type, name); - - return FLOAT; - } - - /* Note that we do not automatically generate unsigned types. This - can't be done because we don't have access to the gdbarch - here. */ - - int base = 10; - if (len > 1 && p[0] == '0') - { - if (p[1] == 'x' || p[1] == 'X') - { - base = 16; - p += 2; - len -= 2; - } - else if (p[1] == 'b' || p[1] == 'B') - { - base = 2; - p += 2; - len -= 2; - } - else if (p[1] == 'd' || p[1] == 'D' || p[1] == 't' || p[1] == 'T') - { - /* Apparently gdb extensions. */ - base = 10; - p += 2; - len -= 2; - } - else - base = 8; - } - - long_p = 0; - unsigned_p = 0; - while (len > 0) - { - if (p[len - 1] == 'l' || p[len - 1] == 'L') - { - len--; - long_p++; - continue; - } - if (p[len - 1] == 'u' || p[len - 1] == 'U') - { - len--; - unsigned_p++; - continue; - } - break; - } - - /* Use gdb_mpz here in case a 128-bit value appears. */ - gdb_mpz value (0); - for (int off = 0; off < len; ++off) - { - int dig; - if (c_isdigit (p[off])) - dig = p[off] - '0'; - else - dig = c_tolower (p[off]) - 'a' + 10; - if (dig >= base) - return ERROR; - value *= base; - value += dig; - } - - std::string printed = value.str (); - const char *copy = obstack_strdup (&demangle_info->obstack, printed); - - if (long_p == 0) - { - if (unsigned_p) - type = make_builtin_type ("unsigned int"); - else - type = make_builtin_type ("int"); - } - else if (long_p == 1) - { - if (unsigned_p) - type = make_builtin_type ("unsigned long"); - else - type = make_builtin_type ("long"); - } - else - { - if (unsigned_p) - type = make_builtin_type ("unsigned long long"); - else - type = make_builtin_type ("long long"); - } - - name = make_name (copy, strlen (copy)); - lvalp->comp = fill_comp (literal_type, type, name); - - return INT; -} - -static const char backslashable[] = "abefnrtv"; -static const char represented[] = "\a\b\e\f\n\r\t\v"; - -/* Translate the backslash the way we would in the host character set. */ -static int -c_parse_backslash (int host_char, int *target_char) -{ - const char *ix; - ix = strchr (backslashable, host_char); - if (! ix) - return 0; - else - *target_char = represented[ix - backslashable]; - return 1; -} - -/* Parse a C escape sequence. STRING_PTR points to a variable - containing a pointer to the string to parse. That pointer - should point to the character after the \. That pointer - is updated past the characters we use. The value of the - escape sequence is returned. - - A negative value means the sequence \ newline was seen, - which is supposed to be equivalent to nothing at all. - - If \ is followed by a null character, we return a negative - value and leave the string pointer pointing at the null character. - - If \ is followed by 000, we return 0 and leave the string pointer - after the zeros. A value of 0 does not mean end of string. */ - -static int -cp_parse_escape (const char **string_ptr) -{ - int target_char; - int c = *(*string_ptr)++; - if (c_parse_backslash (c, &target_char)) - return target_char; - else - switch (c) - { - case '\n': - return -2; - case 0: - (*string_ptr)--; - return 0; - case '^': - { - c = *(*string_ptr)++; - - if (c == '?') - return 0177; - else if (c == '\\') - target_char = cp_parse_escape (string_ptr); - else - target_char = c; - - /* Now target_char is something like `c', and we want to find - its control-character equivalent. */ - target_char = target_char & 037; - - return target_char; - } - - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - { - int i = c - '0'; - int count = 0; - while (++count < 3) - { - c = (**string_ptr); - if (c >= '0' && c <= '7') - { - (*string_ptr)++; - i *= 8; - i += c - '0'; - } - else - { - break; - } - } - return i; - } - default: - return c; - } -} - -#define HANDLE_SPECIAL(string, comp) \ - if (startswith (tokstart, string)) \ - { \ - state->lexptr = tokstart + sizeof (string) - 1; \ - lvalp->lval = comp; \ - return DEMANGLER_SPECIAL; \ - } - -#define HANDLE_TOKEN2(string, token) \ - if (state->lexptr[1] == string[1]) \ - { \ - state->lexptr += 2; \ - lvalp->opname = string; \ - return token; \ - } - -#define HANDLE_TOKEN3(string, token) \ - if (state->lexptr[1] == string[1] && state->lexptr[2] == string[2]) \ - { \ - state->lexptr += 3; \ - lvalp->opname = string; \ - return token; \ - } - -/* Read one token, getting characters through LEXPTR. */ - -static int -yylex (YYSTYPE *lvalp, cpname_state *state) -{ - int c; - int namelen; - const char *tokstart; - char *copy; - - retry: - state->prev_lexptr = state->lexptr; - tokstart = state->lexptr; - - switch (c = *tokstart) - { - case 0: - return 0; - - case ' ': - case '\t': - case '\n': - state->lexptr++; - goto retry; - - case '\'': - /* We either have a character constant ('0' or '\177' for example) - or we have a quoted symbol reference ('foo(int,int)' in C++ - for example). */ - state->lexptr++; - c = *state->lexptr++; - if (c == '\\') - c = cp_parse_escape (&state->lexptr); - else if (c == '\'') - { - yyerror (state, _("empty character constant")); - return ERROR; - } - - /* We over-allocate here, but it doesn't really matter . */ - copy = (char *) obstack_alloc (&state->demangle_info->obstack, 30); - xsnprintf (copy, 30, "%d", c); - - c = *state->lexptr++; - if (c != '\'') - { - yyerror (state, _("invalid character constant")); - return ERROR; - } - - lvalp->comp - = state->fill_comp (DEMANGLE_COMPONENT_LITERAL, - state->make_builtin_type ("char"), - state->make_name (copy, strlen (copy))); - - return INT; - - case '(': - if (startswith (tokstart, "(anonymous namespace)")) - { - state->lexptr += 21; - lvalp->comp = state->make_name ("(anonymous namespace)", - sizeof "(anonymous namespace)" - 1); - return NAME; - } - [[fallthrough]]; - - case ')': - case ',': - state->lexptr++; - return c; - - case '.': - if (state->lexptr[1] == '.' && state->lexptr[2] == '.') - { - state->lexptr += 3; - return ELLIPSIS; - } - - /* Might be a floating point number. */ - if (state->lexptr[1] < '0' || state->lexptr[1] > '9') - goto symbol; /* Nope, must be a symbol. */ - - goto try_number; - - case '-': - HANDLE_TOKEN2 ("-=", ASSIGN_MODIFY); - HANDLE_TOKEN2 ("--", DECREMENT); - HANDLE_TOKEN2 ("->", ARROW); - - /* For construction vtables. This is kind of hokey. */ - if (startswith (tokstart, "-in-")) - { - state->lexptr += 4; - return CONSTRUCTION_IN; - } - - if (state->lexptr[1] < '0' || state->lexptr[1] > '9') - { - state->lexptr++; - return '-'; - } - - try_number: - [[fallthrough]]; - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - { - /* It's a number. */ - int got_dot = 0, got_e = 0, toktype; - const char *p = tokstart; - int hex = 0; - - if (c == '-') - p++; - - if (c == '0' && (p[1] == 'x' || p[1] == 'X')) - { - p += 2; - hex = 1; - } - else if (c == '0' && (p[1]=='t' || p[1]=='T' || p[1]=='d' || p[1]=='D')) - { - p += 2; - hex = 0; - } - - /* If the token includes the C++14 digits separator, we make a - copy so that we don't have to handle the separator in - parse_number. */ - std::optional no_tick; - for (;; ++p) - { - /* This test includes !hex because 'e' is a valid hex digit - and thus does not indicate a floating point number when - the radix is hex. */ - if (!hex && !got_e && (*p == 'e' || *p == 'E')) - got_dot = got_e = 1; - /* This test does not include !hex, because a '.' always indicates - a decimal floating point number regardless of the radix. - - NOTE drow/2005-03-09: This comment is not accurate in C99; - however, it's not clear that all the floating point support - in this file is doing any good here. */ - else if (!got_dot && *p == '.') - got_dot = 1; - else if (got_e && (p[-1] == 'e' || p[-1] == 'E') - && (*p == '-' || *p == '+')) - { - /* This is the sign of the exponent, not the end of - the number. */ - } - /* C++14 allows a separator. */ - else if (*p == '\'') - { - if (!no_tick.has_value ()) - no_tick.emplace (tokstart, p); - continue; - } - /* We will take any letters or digits. parse_number will - complain if past the radix, or if L or U are not final. */ - else if (! c_isalnum (*p)) - break; - if (no_tick.has_value ()) - no_tick->push_back (*p); - } - if (no_tick.has_value ()) - toktype = state->parse_number (no_tick->c_str (), - no_tick->length (), - got_dot|got_e, lvalp); - else - toktype = state->parse_number (tokstart, p - tokstart, - got_dot|got_e, lvalp); - if (toktype == ERROR) - { - yyerror (state, _("invalid number")); - return ERROR; - } - state->lexptr = p; - return toktype; - } - - case '+': - HANDLE_TOKEN2 ("+=", ASSIGN_MODIFY); - HANDLE_TOKEN2 ("++", INCREMENT); - state->lexptr++; - return c; - case '*': - HANDLE_TOKEN2 ("*=", ASSIGN_MODIFY); - state->lexptr++; - return c; - case '/': - HANDLE_TOKEN2 ("/=", ASSIGN_MODIFY); - state->lexptr++; - return c; - case '%': - HANDLE_TOKEN2 ("%=", ASSIGN_MODIFY); - state->lexptr++; - return c; - case '|': - HANDLE_TOKEN2 ("|=", ASSIGN_MODIFY); - HANDLE_TOKEN2 ("||", OROR); - state->lexptr++; - return c; - case '&': - HANDLE_TOKEN2 ("&=", ASSIGN_MODIFY); - HANDLE_TOKEN2 ("&&", ANDAND); - state->lexptr++; - return c; - case '^': - HANDLE_TOKEN2 ("^=", ASSIGN_MODIFY); - state->lexptr++; - return c; - case '!': - HANDLE_TOKEN2 ("!=", NOTEQUAL); - state->lexptr++; - return c; - case '<': - HANDLE_TOKEN3 ("<<=", ASSIGN_MODIFY); - HANDLE_TOKEN3 ("<=>", SPACESHIP); - HANDLE_TOKEN2 ("<=", LEQ); - HANDLE_TOKEN2 ("<<", LSH); - state->lexptr++; - return c; - case '>': - HANDLE_TOKEN3 (">>=", ASSIGN_MODIFY); - HANDLE_TOKEN2 (">=", GEQ); - HANDLE_TOKEN2 (">>", RSH); - state->lexptr++; - return c; - case '=': - HANDLE_TOKEN2 ("==", EQUAL); - state->lexptr++; - return c; - case ':': - HANDLE_TOKEN2 ("::", COLONCOLON); - state->lexptr++; - return c; - - case '[': - case ']': - case '?': - case '@': - case '~': - case '{': - case '}': - symbol: - state->lexptr++; - return c; - - case '"': - /* These can't occur in C++ names. */ - yyerror (state, _("unexpected string literal")); - return ERROR; - } - - if (!(c == '_' || c == '$' || c_ident_is_alpha (c))) - { - /* We must have come across a bad character (e.g. ';'). */ - yyerror (state, _("invalid character")); - return ERROR; - } - - /* It's a name. See how long it is. */ - namelen = 0; - do - c = tokstart[++namelen]; - while (c_ident_is_alnum (c) || c == '_' || c == '$'); - - state->lexptr += namelen; - - /* Catch specific keywords. Notice that some of the keywords contain - spaces, and are sorted by the length of the first word. They must - all include a trailing space in the string comparison. */ - switch (namelen) - { - case 16: - if (startswith (tokstart, "reinterpret_cast")) - return REINTERPRET_CAST; - break; - case 12: - if (startswith (tokstart, "construction vtable for ")) - { - state->lexptr = tokstart + 24; - return CONSTRUCTION_VTABLE; - } - if (startswith (tokstart, "dynamic_cast")) - return DYNAMIC_CAST; - break; - case 11: - if (startswith (tokstart, "static_cast")) - return STATIC_CAST; - break; - case 9: - HANDLE_SPECIAL ("covariant return thunk to ", DEMANGLE_COMPONENT_COVARIANT_THUNK); - HANDLE_SPECIAL ("reference temporary for ", DEMANGLE_COMPONENT_REFTEMP); - break; - case 8: - HANDLE_SPECIAL ("typeinfo for ", DEMANGLE_COMPONENT_TYPEINFO); - HANDLE_SPECIAL ("typeinfo fn for ", DEMANGLE_COMPONENT_TYPEINFO_FN); - HANDLE_SPECIAL ("typeinfo name for ", DEMANGLE_COMPONENT_TYPEINFO_NAME); - if (startswith (tokstart, "operator")) - return OPERATOR; - if (startswith (tokstart, "restrict")) - return RESTRICT; - if (startswith (tokstart, "unsigned")) - return UNSIGNED; - if (startswith (tokstart, "template")) - return TEMPLATE; - if (startswith (tokstart, "volatile")) - return VOLATILE_KEYWORD; - break; - case 7: - HANDLE_SPECIAL ("virtual thunk to ", DEMANGLE_COMPONENT_VIRTUAL_THUNK); - if (startswith (tokstart, "wchar_t")) - return WCHAR_T; - break; - case 6: - if (startswith (tokstart, "global constructors keyed to ")) - { - const char *p; - state->lexptr = tokstart + 29; - lvalp->lval = DEMANGLE_COMPONENT_GLOBAL_CONSTRUCTORS; - /* Find the end of the symbol. */ - p = symbol_end (state->lexptr); - lvalp->comp = state->make_name (state->lexptr, p - state->lexptr); - state->lexptr = p; - return DEMANGLER_SPECIAL; - } - if (startswith (tokstart, "global destructors keyed to ")) - { - const char *p; - state->lexptr = tokstart + 28; - lvalp->lval = DEMANGLE_COMPONENT_GLOBAL_DESTRUCTORS; - /* Find the end of the symbol. */ - p = symbol_end (state->lexptr); - lvalp->comp = state->make_name (state->lexptr, p - state->lexptr); - state->lexptr = p; - return DEMANGLER_SPECIAL; - } - - HANDLE_SPECIAL ("vtable for ", DEMANGLE_COMPONENT_VTABLE); - if (startswith (tokstart, "delete")) - return DELETE; - if (startswith (tokstart, "struct")) - return STRUCT; - if (startswith (tokstart, "signed")) - return SIGNED_KEYWORD; - if (startswith (tokstart, "sizeof")) - return SIZEOF; - if (startswith (tokstart, "double")) - return DOUBLE_KEYWORD; - break; - case 5: - HANDLE_SPECIAL ("guard variable for ", DEMANGLE_COMPONENT_GUARD); - if (startswith (tokstart, "false")) - return FALSEKEYWORD; - if (startswith (tokstart, "class")) - return CLASS; - if (startswith (tokstart, "union")) - return UNION; - if (startswith (tokstart, "float")) - return FLOAT_KEYWORD; - if (startswith (tokstart, "short")) - return SHORT; - if (startswith (tokstart, "const")) - return CONST_KEYWORD; - break; - case 4: - if (startswith (tokstart, "void")) - return VOID; - if (startswith (tokstart, "bool")) - return BOOL; - if (startswith (tokstart, "char")) - return CHAR; - if (startswith (tokstart, "enum")) - return ENUM; - if (startswith (tokstart, "long")) - return LONG; - if (startswith (tokstart, "true")) - return TRUEKEYWORD; - break; - case 3: - HANDLE_SPECIAL ("VTT for ", DEMANGLE_COMPONENT_VTT); - HANDLE_SPECIAL ("non-virtual thunk to ", DEMANGLE_COMPONENT_THUNK); - if (startswith (tokstart, "new")) - return NEW; - if (startswith (tokstart, "int")) - return INT_KEYWORD; - break; - default: - break; - } - - lvalp->comp = state->make_name (tokstart, namelen); - return NAME; -} - -static void -yyerror (cpname_state *state, const char *msg) -{ - if (state->global_errmsg) - return; - - state->error_lexptr = state->prev_lexptr; - state->global_errmsg = msg ? msg : "parse error"; -} - -/* See cp-support.h. */ - -gdb::unique_xmalloc_ptr -cp_comp_to_string (struct demangle_component *result, int estimated_len) -{ - size_t err; - - char *res = gdb_cplus_demangle_print (DMGL_PARAMS | DMGL_ANSI, - result, estimated_len, &err); - return gdb::unique_xmalloc_ptr (res); -} - -/* Merge the two parse trees given by DEST and SRC. The parse tree - in SRC is attached to DEST at the node represented by TARGET. - - NOTE 1: Since there is no API to merge obstacks, this function does - even attempt to try it. Fortunately, we do not (yet?) need this ability. - The code will assert if SRC->obstack is not empty. - - NOTE 2: The string from which SRC was parsed must not be freed, since - this function will place pointers to that string into DEST. */ - -void -cp_merge_demangle_parse_infos (struct demangle_parse_info *dest, - struct demangle_component *target, - demangle_parse_info_up src) - -{ - /* Copy the SRC's parse data into DEST. */ - *target = *src->tree; - - /* Make sure SRC is owned by DEST. */ - dest->infos.push_back (std::move (src)); -} - -/* Convert a demangled name to a demangle_component tree. On success, - a structure containing the root of the new tree is returned. On - error, NULL is returned, and an error message will be set in - *ERRMSG. */ - -demangle_parse_info_up -cp_demangled_name_to_comp (const char *demangled_name, - std::string *errmsg) -{ - auto result = std::make_unique (); - cpname_state state (demangled_name, result.get ()); - - /* Note that we can't set yydebug here, as is done in the other - parsers. Bison implements yydebug as a global, even with a pure - parser, and this parser is run from worker threads. So, changing - yydebug causes TSan reports. If you need to debug this parser, - debug gdb and set the global from the outer gdb. */ - if (yyparse (&state)) - { - if (state.global_errmsg && errmsg) - *errmsg = state.global_errmsg; - return NULL; - } - - result->tree = state.global_result; - - return result; -} - -#if GDB_SELF_TEST - -static void -should_be_the_same (const char *one, const char *two) -{ - gdb::unique_xmalloc_ptr cpone = cp_canonicalize_string (one); - gdb::unique_xmalloc_ptr cptwo = cp_canonicalize_string (two); - - if (cpone != nullptr) - one = cpone.get (); - if (cptwo != nullptr) - two = cptwo.get (); - - SELF_CHECK (streq (one, two)); -} - -static void -should_parse (const char *name) -{ - auto parsed = cp_demangled_name_to_comp (name); - SELF_CHECK (parsed != nullptr); -} - -static void -canonicalize_tests () -{ - should_be_the_same ("short int", "short"); - should_be_the_same ("int short", "short"); - - should_be_the_same ("C<(char) 1>::m()", "C<(char) '\\001'>::m()"); - should_be_the_same ("x::y::z<1>", "x::y::z<0x01>"); - should_be_the_same ("x::y::z<1>", "x::y::z<01>"); - should_be_the_same ("x::y::z<(unsigned long long) 1>", "x::y::z<01ull>"); - should_be_the_same ("x::y::z<0b111>", "x::y::z<7>"); - should_be_the_same ("x::y::z<0b111>", "x::y::z<0t7>"); - should_be_the_same ("x::y::z<0b111>", "x::y::z<0D7>"); - - should_be_the_same ("x::y::z<0xff'ff>", "x::y::z<65535>"); - - should_be_the_same ("something", "something< void() >"); - should_be_the_same ("something", "something"); - - should_parse ("void whatever::operator<=>"); - - should_be_the_same ("Foozle::fogey > (Empty)", - "Foozle::fogey> (Empty)"); - - should_be_the_same ("something :: operator new [ ]", - "something::operator new[]"); - should_be_the_same ("something :: operator new", - "something::operator new"); - should_be_the_same ("operator()", "operator ()"); -} - -#endif - -INIT_GDB_FILE (cp_name_parser) -{ -#if GDB_SELF_TEST - selftests::register_test ("canonicalize", canonicalize_tests); -#endif -} diff --git a/gdb/cp-support.c b/gdb/cp-support.c index b17f1e77d683..2eb4539d7b4a 100644 --- a/gdb/cp-support.c +++ b/gdb/cp-support.c @@ -19,6 +19,7 @@ along with this program. If not, see . */ #include "cp-support.h" +#include "cp-name-parser.h" #include "language.h" #include "demangle.h" #include "cli/cli-cmds.h" @@ -43,9 +44,6 @@ #include "typeprint.h" #include "inferior.h" -#define d_left(dc) (dc)->u.s_binary.left -#define d_right(dc) (dc)->u.s_binary.right - /* Functions related to demangled name parsing. */ static unsigned int cp_find_first_component_aux (const char *name, -- 2.55.0