From 6e72ca205c018b9906fa2047ffb1be7f546e0643 Mon Sep 17 00:00:00 2001 From: Tom Tromey Date: Mon, 15 Apr 2013 17:36:14 +0000 Subject: [PATCH] PR c++/9065: * NEWS: Update. * breakpoint.c (watchpoint_exp_is_const): Add OP_TYPEID. * c-exp.y (TYPEID): New token. (exp): Add new TYPEID productions. (ident_tokens): Add "typeid". * cp-abi.c (cplus_typeid, cplus_typeid_type): New functions. * cp-abi.h (cplus_typeid, cplus_typeid_type): Declare. (struct cp_abi_ops) : New fields. * eval.c (evaluate_subexp_standard) : New case. * expprint.c (dump_subexp_body_standard) : New case. * gnu-v3-abi.c (std_type_info_gdbarch_data): New global. (build_std_type_info_type, gnuv3_get_typeid_type) (gnuv3_get_typeid): New functions. (init_gnuv3_ops): Initialize std_type_info_gdbarch_data. Set new fields on ABI object. * parse.c (operator_length_standard) : New case. * std-operator.def (OP_TYPEID): New. gdb/testsuite * gdb.cp/typeid.cc: New file. * gdb.cp/typeid.exp: New file. --- gdb/ChangeLog | 22 +++++ gdb/NEWS | 2 + gdb/breakpoint.c | 1 + gdb/c-exp.y | 13 ++- gdb/cp-abi.c | 20 ++++ gdb/cp-abi.h | 12 +++ gdb/eval.c | 17 ++++ gdb/expprint.c | 5 + gdb/gnu-v3-abi.c | 158 ++++++++++++++++++++++++++++++++ gdb/parse.c | 1 + gdb/std-operator.def | 3 + gdb/testsuite/ChangeLog | 5 + gdb/testsuite/gdb.cp/typeid.cc | 60 ++++++++++++ gdb/testsuite/gdb.cp/typeid.exp | 67 ++++++++++++++ 14 files changed, 385 insertions(+), 1 deletion(-) create mode 100644 gdb/testsuite/gdb.cp/typeid.cc create mode 100644 gdb/testsuite/gdb.cp/typeid.exp diff --git a/gdb/ChangeLog b/gdb/ChangeLog index d96ab8b588..8730b7d7d2 100644 --- a/gdb/ChangeLog +++ b/gdb/ChangeLog @@ -1,3 +1,25 @@ +2013-04-15 Tom Tromey + + PR c++/9065: + * NEWS: Update. + * breakpoint.c (watchpoint_exp_is_const): Add OP_TYPEID. + * c-exp.y (TYPEID): New token. + (exp): Add new TYPEID productions. + (ident_tokens): Add "typeid". + * cp-abi.c (cplus_typeid, cplus_typeid_type): New functions. + * cp-abi.h (cplus_typeid, cplus_typeid_type): Declare. + (struct cp_abi_ops) : New fields. + * eval.c (evaluate_subexp_standard) : New case. + * expprint.c (dump_subexp_body_standard) : New + case. + * gnu-v3-abi.c (std_type_info_gdbarch_data): New global. + (build_std_type_info_type, gnuv3_get_typeid_type) + (gnuv3_get_typeid): New functions. + (init_gnuv3_ops): Initialize std_type_info_gdbarch_data. Set + new fields on ABI object. + * parse.c (operator_length_standard) : New case. + * std-operator.def (OP_TYPEID): New. + 2013-04-15 Tom Tromey * elfread.c (elf_symtab_read): Install versioned symbol under diff --git a/gdb/NEWS b/gdb/NEWS index 55f25d8325..c78a4fb70f 100644 --- a/gdb/NEWS +++ b/gdb/NEWS @@ -29,6 +29,8 @@ show remote trace-status-packet * Newly installed $prefix/bin/gcore acts as a shell interface for the GDB command gcore. +* GDB now implements the the C++ 'typeid' operator. + * MI changes ** The -trace-save MI command can optionally save trace buffer in Common diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c index bd1ca5db53..b0dd9b4ba2 100644 --- a/gdb/breakpoint.c +++ b/gdb/breakpoint.c @@ -10317,6 +10317,7 @@ watchpoint_exp_is_const (const struct expression *exp) case OP_TYPE: case OP_TYPEOF: case OP_DECLTYPE: + case OP_TYPEID: case OP_NAME: case OP_OBJC_NSSTRING: diff --git a/gdb/c-exp.y b/gdb/c-exp.y index c3c7f1697b..dd032d2ead 100644 --- a/gdb/c-exp.y +++ b/gdb/c-exp.y @@ -234,6 +234,7 @@ static void c_print_token (FILE *file, int type, YYSTYPE value); %token ENTRY %token TYPEOF %token DECLTYPE +%token TYPEID /* Special type cases, put in to allow the parser to distinguish different legal basetypes. */ @@ -346,6 +347,14 @@ exp : exp DECREMENT %prec UNARY { write_exp_elt_opcode (UNOP_POSTDECREMENT); } ; +exp : TYPEID '(' exp ')' %prec UNARY + { write_exp_elt_opcode (OP_TYPEID); } + ; + +exp : TYPEID '(' type_exp ')' %prec UNARY + { write_exp_elt_opcode (OP_TYPEID); } + ; + exp : SIZEOF exp %prec UNARY { write_exp_elt_opcode (UNOP_SIZEOF); } ; @@ -2290,7 +2299,9 @@ static const struct token ident_tokens[] = {"__typeof", TYPEOF, OP_TYPEOF, 0 }, {"typeof", TYPEOF, OP_TYPEOF, FLAG_SHADOW }, {"__decltype", DECLTYPE, OP_DECLTYPE, FLAG_CXX }, - {"decltype", DECLTYPE, OP_DECLTYPE, FLAG_CXX | FLAG_SHADOW } + {"decltype", DECLTYPE, OP_DECLTYPE, FLAG_CXX | FLAG_SHADOW }, + + {"typeid", TYPEID, OP_TYPEID, FLAG_CXX} }; /* When we find that lexptr (the global var defined in parse.c) is diff --git a/gdb/cp-abi.c b/gdb/cp-abi.c index 8e9d545328..7ac2a204c6 100644 --- a/gdb/cp-abi.c +++ b/gdb/cp-abi.c @@ -179,6 +179,26 @@ cplus_print_vtable (struct value *value) (*current_cp_abi.print_vtable) (value); } +/* See cp-abi.h. */ + +struct value * +cplus_typeid (struct value *value) +{ + if (current_cp_abi.get_typeid == NULL) + error (_("GDB cannot find the typeid on this target")); + return (*current_cp_abi.get_typeid) (value); +} + +/* See cp-abi.h. */ + +struct type * +cplus_typeid_type (struct gdbarch *gdbarch) +{ + if (current_cp_abi.get_typeid_type == NULL) + error (_("GDB cannot find the type for 'typeid' on this target")); + return (*current_cp_abi.get_typeid_type) (gdbarch); +} + int cp_pass_by_reference (struct type *type) { diff --git a/gdb/cp-abi.h b/gdb/cp-abi.h index 96e9aeac22..d68e2ec8be 100644 --- a/gdb/cp-abi.h +++ b/gdb/cp-abi.h @@ -178,6 +178,16 @@ void cplus_make_method_ptr (struct type *type, gdb_byte *CONTENTS, void cplus_print_vtable (struct value *value); +/* Implement 'typeid': find the type info for VALUE, if possible. If + the type info cannot be found, throw an exception. */ + +extern struct value *cplus_typeid (struct value *value); + +/* Return the type of 'typeid' for the current C++ ABI on the given + architecture. */ + +extern struct type *cplus_typeid_type (struct gdbarch *gdbarch); + /* Determine if we are currently in a C++ thunk. If so, get the address of the routine we are thunking to and continue to there instead. */ @@ -219,6 +229,8 @@ struct cp_abi_ops struct value * (*method_ptr_to_value) (struct value **, struct value *); void (*print_vtable) (struct value *); + struct value *(*get_typeid) (struct value *value); + struct type *(*get_typeid_type) (struct gdbarch *gdbarch); CORE_ADDR (*skip_trampoline) (struct frame_info *, CORE_ADDR); int (*pass_by_reference) (struct type *type); }; diff --git a/gdb/eval.c b/gdb/eval.c index a91ba2271a..f04baeee8e 100644 --- a/gdb/eval.c +++ b/gdb/eval.c @@ -2798,6 +2798,23 @@ evaluate_subexp_standard (struct type *expect_type, else error (_("Attempt to use a type as an expression")); + case OP_TYPEID: + { + struct value *result; + enum exp_opcode sub_op = exp->elts[*pos].opcode; + + if (sub_op == OP_TYPE || sub_op == OP_DECLTYPE || sub_op == OP_TYPEOF) + result = evaluate_subexp (NULL_TYPE, exp, pos, + EVAL_AVOID_SIDE_EFFECTS); + else + result = evaluate_subexp (NULL_TYPE, exp, pos, noside); + + if (noside != EVAL_NORMAL) + return allocate_value (cplus_typeid_type (exp->gdbarch)); + + return cplus_typeid (result); + } + default: /* Removing this case and compiling with gcc -Wall reveals that a lot of cases are hitting this case. Some of these should diff --git a/gdb/expprint.c b/gdb/expprint.c index 69055348cf..ea9b5601f5 100644 --- a/gdb/expprint.c +++ b/gdb/expprint.c @@ -953,6 +953,11 @@ dump_subexp_body_standard (struct expression *exp, elt = dump_subexp (exp, stream, elt); fprintf_filtered (stream, ")"); break; + case OP_TYPEID: + fprintf_filtered (stream, "typeid ("); + elt = dump_subexp (exp, stream, elt); + fprintf_filtered (stream, ")"); + break; case STRUCTOP_STRUCT: case STRUCTOP_PTR: { diff --git a/gdb/gnu-v3-abi.c b/gdb/gnu-v3-abi.c index b3585a661f..7649a48ea7 100644 --- a/gdb/gnu-v3-abi.c +++ b/gdb/gnu-v3-abi.c @@ -34,6 +34,12 @@ static struct cp_abi_ops gnu_v3_abi_ops; +/* A gdbarch key for std::type_info, in the event that it can't be + found in the debug info. */ + +static struct gdbarch_data *std_type_info_gdbarch_data; + + static int gnuv3_is_vtable_name (const char *name) { @@ -984,6 +990,154 @@ gnuv3_print_vtable (struct value *value) do_cleanups (cleanup); } +/* Return a GDB type representing `struct std::type_info', laid out + appropriately for ARCH. + + We use this function as the gdbarch per-architecture data + initialization function. */ + +static void * +build_std_type_info_type (struct gdbarch *arch) +{ + struct type *t; + struct field *field_list, *field; + int offset; + struct type *void_ptr_type + = builtin_type (arch)->builtin_data_ptr; + struct type *char_type + = builtin_type (arch)->builtin_char; + struct type *char_ptr_type + = make_pointer_type (make_cv_type (1, 0, char_type, NULL), NULL); + + field_list = xmalloc (sizeof (struct field [2])); + memset (field_list, 0, sizeof (struct field [2])); + field = &field_list[0]; + offset = 0; + + /* The vtable. */ + FIELD_NAME (*field) = "_vptr.type_info"; + FIELD_TYPE (*field) = void_ptr_type; + SET_FIELD_BITPOS (*field, offset * TARGET_CHAR_BIT); + offset += TYPE_LENGTH (FIELD_TYPE (*field)); + field++; + + /* The name. */ + FIELD_NAME (*field) = "__name"; + FIELD_TYPE (*field) = char_ptr_type; + SET_FIELD_BITPOS (*field, offset * TARGET_CHAR_BIT); + offset += TYPE_LENGTH (FIELD_TYPE (*field)); + field++; + + gdb_assert (field == (field_list + 2)); + + t = arch_type (arch, TYPE_CODE_STRUCT, offset, NULL); + TYPE_NFIELDS (t) = field - field_list; + TYPE_FIELDS (t) = field_list; + TYPE_TAG_NAME (t) = "gdb_gnu_v3_type_info"; + INIT_CPLUS_SPECIFIC (t); + + return t; +} + +/* Implement the 'get_typeid_type' method. */ + +static struct type * +gnuv3_get_typeid_type (struct gdbarch *gdbarch) +{ + struct symbol *typeinfo; + struct type *typeinfo_type; + + typeinfo = lookup_symbol ("std::type_info", NULL, STRUCT_DOMAIN, NULL); + if (typeinfo == NULL) + typeinfo_type = gdbarch_data (gdbarch, std_type_info_gdbarch_data); + else + typeinfo_type = SYMBOL_TYPE (typeinfo); + + return typeinfo_type; +} + +/* Implement the 'get_typeid' method. */ + +static struct value * +gnuv3_get_typeid (struct value *value) +{ + struct type *typeinfo_type; + struct type *type; + struct gdbarch *gdbarch; + struct cleanup *cleanup; + struct value *result; + char *typename, *canonical; + + /* We have to handle values a bit trickily here, to allow this code + to work properly with non_lvalue values that are really just + disguised types. */ + if (value_lval_const (value) == lval_memory) + value = coerce_ref (value); + + type = check_typedef (value_type (value)); + + /* In the non_lvalue case, a reference might have slipped through + here. */ + if (TYPE_CODE (type) == TYPE_CODE_REF) + type = check_typedef (TYPE_TARGET_TYPE (type)); + + /* Ignore top-level cv-qualifiers. */ + type = make_cv_type (0, 0, type, NULL); + gdbarch = get_type_arch (type); + + typename = type_to_string (type); + if (typename == NULL) + error (_("cannot find typeinfo for unnamed type")); + cleanup = make_cleanup (xfree, typename); + + /* We need to canonicalize the type name here, because we do lookups + using the demangled name, and so we must match the format it + uses. E.g., GDB tends to use "const char *" as a type name, but + the demangler uses "char const *". */ + canonical = cp_canonicalize_string (typename); + if (canonical != NULL) + { + make_cleanup (xfree, canonical); + typename = canonical; + } + + typeinfo_type = gnuv3_get_typeid_type (gdbarch); + + /* We check for lval_memory because in the "typeid (type-id)" case, + the type is passed via a not_lval value object. */ + if (TYPE_CODE (type) == TYPE_CODE_CLASS + && value_lval_const (value) == lval_memory + && gnuv3_dynamic_class (type)) + { + struct value *vtable, *typeinfo_value; + CORE_ADDR address = value_address (value) + value_embedded_offset (value); + + vtable = gnuv3_get_vtable (gdbarch, type, address); + if (vtable == NULL) + error (_("cannot find typeinfo for object of type '%s'"), typename); + typeinfo_value = value_field (vtable, vtable_field_type_info); + result = value_ind (value_cast (make_pointer_type (typeinfo_type, NULL), + typeinfo_value)); + } + else + { + char *sym_name; + struct minimal_symbol *minsym; + + sym_name = concat ("typeinfo for ", typename, (char *) NULL); + make_cleanup (xfree, sym_name); + minsym = lookup_minimal_symbol (sym_name, NULL, NULL); + + if (minsym == NULL) + error (_("could not find typeinfo symbol for '%s'"), typename); + + result = value_at_lazy (typeinfo_type, SYMBOL_VALUE_ADDRESS (minsym)); + } + + do_cleanups (cleanup); + return result; +} + /* Determine if we are currently in a C++ thunk. If so, get the address of the routine we are thunking to and continue to there instead. */ @@ -1116,6 +1270,8 @@ init_gnuv3_ops (void) { vtable_type_gdbarch_data = gdbarch_data_register_post_init (build_gdb_vtable_type); + std_type_info_gdbarch_data + = gdbarch_data_register_post_init (build_std_type_info_type); gnu_v3_abi_ops.shortname = "gnu-v3"; gnu_v3_abi_ops.longname = "GNU G++ Version 3 ABI"; @@ -1134,6 +1290,8 @@ init_gnuv3_ops (void) gnu_v3_abi_ops.make_method_ptr = gnuv3_make_method_ptr; gnu_v3_abi_ops.method_ptr_to_value = gnuv3_method_ptr_to_value; gnu_v3_abi_ops.print_vtable = gnuv3_print_vtable; + gnu_v3_abi_ops.get_typeid = gnuv3_get_typeid; + gnu_v3_abi_ops.get_typeid_type = gnuv3_get_typeid_type; gnu_v3_abi_ops.skip_trampoline = gnuv3_skip_trampoline; gnu_v3_abi_ops.pass_by_reference = gnuv3_pass_by_reference; } diff --git a/gdb/parse.c b/gdb/parse.c index aa8f09caf5..13699a85e6 100644 --- a/gdb/parse.c +++ b/gdb/parse.c @@ -972,6 +972,7 @@ operator_length_standard (const struct expression *expr, int endpos, case UNOP_TRUNC: case OP_TYPEOF: case OP_DECLTYPE: + case OP_TYPEID: oplen = 1; args = 1; break; diff --git a/gdb/std-operator.def b/gdb/std-operator.def index 467c1411f5..c4f33d90fd 100644 --- a/gdb/std-operator.def +++ b/gdb/std-operator.def @@ -316,3 +316,6 @@ OP (OP_TYPEOF) evaluated solely for its type. This is similar to typeof, but has slight different semantics. */ OP (OP_DECLTYPE) + +/* The typeid operator. This has one expression argument. */ +OP (OP_TYPEID) diff --git a/gdb/testsuite/ChangeLog b/gdb/testsuite/ChangeLog index 6526980583..7da3ff0e23 100644 --- a/gdb/testsuite/ChangeLog +++ b/gdb/testsuite/ChangeLog @@ -1,3 +1,8 @@ +2013-04-15 Tom Tromey + + * gdb.cp/typeid.cc: New file. + * gdb.cp/typeid.exp: New file. + 2013-04-15 Tom Tromey * gdb.cp/exception.exp: Add "catch rethrow" tests. diff --git a/gdb/testsuite/gdb.cp/typeid.cc b/gdb/testsuite/gdb.cp/typeid.cc new file mode 100644 index 0000000000..7120031f03 --- /dev/null +++ b/gdb/testsuite/gdb.cp/typeid.cc @@ -0,0 +1,60 @@ +/* This testcase is part of GDB, the GNU debugger. + + Copyright 2013 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 . */ + +#include + +int i; +char *cp; +const char *ccp; +char ca[5]; + +struct Base +{ + virtual ~Base() { } +}; + +struct VB1 : public virtual Base +{ +}; + +struct VB2 : public virtual Base +{ +}; + +struct Derived : public VB1, VB2 +{ +}; + +Derived d; + +Base *b = &d; +VB1 *vb1 = &d; +VB1 *vb2 = &d; + +const Base *bv = &d; + +int main () +{ + const std::type_info &xi = typeid(i); + const std::type_info &xcp = typeid(cp); + const std::type_info &xccp = typeid(ccp); + const std::type_info &xca = typeid(ca); + const std::type_info &xd = typeid(d); + const std::type_info &xb = typeid(b); + + return 0; +} diff --git a/gdb/testsuite/gdb.cp/typeid.exp b/gdb/testsuite/gdb.cp/typeid.exp new file mode 100644 index 0000000000..2096ad9203 --- /dev/null +++ b/gdb/testsuite/gdb.cp/typeid.exp @@ -0,0 +1,67 @@ +# Copyright 2013 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 . + +standard_testfile .cc + +if {[skip_cplus_tests]} { + return -1 +} + +if {[prepare_for_testing $testfile.exp $testfile $srcfile {debug c++}]} { + return -1 +} + +proc do_typeid_tests {started} { + global hex + + # We might see the standard type or gdb's internal type. + set type_re "(std::type_info|struct gdb_gnu_v3_type_info)" + + + foreach simple_var {i cp ccp ca b} { + gdb_test "print &typeid($simple_var)" \ + " = \\($type_re \\*\\) $hex.*" + + # Note that we test pointer equality rather than object + # equality here. That is because std::type_info's operator== + # is not present in the libstdc++ .so. + gdb_test "print &typeid($simple_var) == &typeid(typeof($simple_var))" \ + " = true" + } + + # typeid for these is Derived. Don't try these tests until the + # inferior has started. + if {$started} { + foreach der_var {*b *vb1 *vb2 *bv d {const Derived} {const Derived &}} { + gdb_test "print &typeid($der_var)" \ + " = \\($type_re \\*\\) $hex.*" + gdb_test "print &typeid($der_var) == &typeid(d)" \ + " = true" + } + } +} + +with_test_prefix "before starting" { + do_typeid_tests 0 +} + +if ![runto_main] { + untested typeid + return -1 +} + +with_test_prefix "after starting" { + do_typeid_tests 1 +} -- 2.34.1