From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (qmail 6937 invoked by alias); 3 Aug 2004 14:09:13 -0000 Mailing-List: contact gdb-help@sources.redhat.com; run by ezmlm Precedence: bulk List-Subscribe: List-Archive: List-Post: List-Help: , Sender: gdb-owner@sources.redhat.com Received: (qmail 6909 invoked from network); 3 Aug 2004 14:09:09 -0000 Received: from unknown (HELO mclean.mail.mindspring.net) (207.69.200.57) by sourceware.org with SMTP; 3 Aug 2004 14:09:09 -0000 Received: from user-119a90a.biz.mindspring.com ([66.149.36.10] helo=berman.michael-chastain.com) by mclean.mail.mindspring.net with esmtp (Exim 3.33 #1) id 1BrzyV-00034L-00; Tue, 03 Aug 2004 10:09:07 -0400 Received: from mindspring.com (localhost [127.0.0.1]) by berman.michael-chastain.com (Postfix) with SMTP id 40B574B102; Tue, 3 Aug 2004 10:09:24 -0400 (EDT) Date: Tue, 03 Aug 2004 14:09:00 -0000 From: Michael Chastain To: gdb@sources.redhat.com, david.carlton@sun.com, carlton@bactrian.org Subject: new testsuite function for c++ ptype Message-ID: <410F9C94.nail8KU11N4AM@mindspring.com> User-Agent: nail 10.8 6/28/04 MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Transfer-Encoding: 7bit X-SW-Source: 2004-08/txt/msg00006.txt.bz2 Here is what I have been working on lately. This isn't ready for code review yet, but I want to share my design and get comments on that. The problem: gdb.cp/*.exp contains 160 ptype tests. Each of these tests needs about five or six different output patterns to accommodate various C++ compilers: four versions of g++, dwarf-2 and stabs+, plus the hp ac++ compiler. g++ changes with time. The most recent change in g++ HEAD caused several dozen new FAILs and I had to patch up a lot of test scripts. Plus, some test scripts do not have any code at all to handle the vagaries of virtual base pointers and synthetic operators, so they just FAIL on those configurations. I have tried to handle this variation with some regular expression machinery but it's getting to be too much. So I wrote a new procedure, "cp_test_ptype_class", that actually reads each line of the ptype body and processes it against a description of the class contents. "cp_test_ptype_class" takes a lot of parameters, but it's not actually that bad if you're cut-and-pasting from existing calls, which is how everybody will write these things after I write the first 160. The new "cp_test_ptype_class" will live in a new file, lib/cp-support.exp. Here is a sample. Consider the C++ class: class A : virtual public V { public: virtual int f(); private: int a; }; The call to test this is: cp_test_ptype_class "ptype A" "ptype A" \ "class" "VA" { "public virtual V" } \ { "V" } \ { { "private" "int a;" } } \ { { "public" "virtual int f();" } } \ "" If you look in the comments in cp-support.exp (below), you will see all the variations that cp_test_ptype_class handles. My goal is that the calls will be more stable than they have been, and all the variation will happen inside the implementation of cp_test_ptype_class as new things happen. Surprisingly, the performance is about the same as the old code. My speculation is that the old code spent a lot more time compiling regular expressions, while this new code just does a lot more direct string matching. I've written a new version of virtfunc.exp which calls cp_test_ptype_class instead of test_one_ptype. I've tested it on native i686-pc-linux-gnu with gcc 2.95.3, gcc 3.3.4, gcc 3.4.1, and gcc HEAD, with dwarf-2 and stabs+. I haven't tried it on HP yet but my HP logs say that this approach ought to work fine. But it's still in the design phase. This is where I take a checkpoint for people to comment on this, if you want to. Michael C === # This test code is part of GDB, the GNU debugger. # Copyright 2003, 2004 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 2 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, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. set wsopt "\[\r\n\t \]*" set ws "\[\r\n\t \]+" set nl "\[\r\n\]+" # Test ptype of a class. # # Different C++ compilers produce different output. To accommodate all # the variations listed below, I read the output of "ptype" and process # each line, matching it to the class description given in the # parameters. # # COMMAND and TESTNAME are as usual for gdb_test_multiple. # # IN_KEY is "class" or "struct". For now, I ignore it, and allow either # "class" or "struct" in the output, as long as the access specifiers all # work out okay. # # IN_BASES is a list of base classes. I just match these to the output. # # IN_VBASES is a list of direct and indirect virtual base classes. # With gcc 2.95.3, gdb shows the virtual base class pointers. # # IN_FIELDS is a list of data fields. # # IN_METHODS is a list of methods. # # IN_TAIL is the expected text after the close brace, specifically the "*" # in "struct { ... } *". # # gdb can vary the output of ptype in several ways: # # . CLASS/STRUCT # # The output can start with either "class" or "struct", depending on # what the symbol table reader in gdb decides. This is usually # unrelated to the original source code. # # dwarf-2 debug info distinguishes class/struct, but gdb ignores it # stabs+ debug info does not distinguish class/struct # hp debug info distinguishes class/struct, and gdb honors it # # I tried to accommodate this with regular expressions such as # "((class|struct) A \{ public:|struct A \{)", but that turns into a # hairy mess because of optional private virtual base pointers and # optional public synthetic operators. This is the big reason I gave # up on regular expressions and started parsing the output. # # . REDUNDANT ACCESS SPECIFIER # # In "class { private: ... }" or "struct { public: ... }", gdb might # elide the redundant initial access specifier. I have not checked # lately to see how gdb actually behaves; I just accept all forms # where the access specifiers work out okay. # # . VIRTUAL BASE POINTERS # # If a class has virtual bases, either direct or indirect, the class # will have virtual base pointers. With gcc 2.95.3, gdb prints lines # for these virtual base pointers. This does not happen with gcc # 3.3.4, gcc 3.4.1, or hp acc A.03.45. # # I accept these lines. These lines are optional; but if I see one of # these lines, then I expect to see all of them. # # Note: drow considers printing these lines to be a bug in gdb. # # . SYNTHETIC METHODS # # A C++ compiler may synthesize some methods: an assignment # operator, a copy constructor, a constructor, and a destructor. The # compiler might include debug information for these methods. # # dwarf-2 gdb does not show these methods # stabs+ gdb shows these methods # hp gdb does not show these methods # # I accept these methods. These lines are optional, and any or # all of them might appear, mixed in anywhere in the regular methods. # # With gcc v2, the synthetic copy-ctor and ctor have an additional # "int" parameter at the beginning, the "in-charge" flag. # # . DEMANGLER SYNTAX VARIATIONS # # Different demanglers produce "int foo(void)" versus "int foo()", # "const A&" versus "const A &", and so on. # # TODO # # Tagless structs. # # "A*" versus "A *" and "A&" versus "A &" in user methods. # # -- chastain 2004-08-03 proc cp_test_ptype_class { command testname in_key in_tag in_bases in_vbases in_fields in_methods in_tail } { global gdb_prompt global wsopt global ws global nl # Construct a list of synthetic operators. # These are: { access-type regular-expression }. set list_synth_possible { } lappend list_synth_possible [ list "public" "$in_tag & operator=\\($in_tag const ?&\\);" ] lappend list_synth_possible [ list "public" "$in_tag\\((int,|) ?$in_tag const ?&\\);" ] lappend list_synth_possible [ list "public" "$in_tag\\((int|void|)\\);" ] # Actually do the ptype. set parse_okay 0 gdb_test_multiple "$command" "$testname // parse failed" { -re "type = (struct|class)${wsopt}(\[A-Za-z0-9_\]*)${wsopt}((:\[^\{\]*)?)${wsopt}\{(.*)\}${wsopt}(\[^\r\n\]*)$nl$gdb_prompt $" { set parse_okay 1 set actual_key $expect_out(1,string) set actual_tag $expect_out(2,string) set actual_base_string $expect_out(3,string) set actual_body $expect_out(5,string) set actual_tail $expect_out(6,string) } } if { ! $parse_okay } then { return } # Check the actual key. It would be nice to require that it match # the input key, but gdb does not support that. For now, accept any # $actual_key as long as the access property of each field/method # matches. switch "$actual_key" { "class" { set access "private" } "struct" { set access "public" } default { fail "$testname // wrong key: $actual_key"; return; } } # Check the actual tag. # TODO: accommodate tagless structs. if { "$actual_tag" != "$in_tag" } then { fail "$testname // wrong tag: $actual_tag" return } # Check the actual bases. # First parse them into a list. set list_actual_bases { } if { "$actual_base_string" != "" } then { regsub "^:${wsopt}" $actual_base_string "" actual_base_string set list_actual_bases [split $actual_base_string ","] } # Check the base count. if { [llength $list_actual_bases] < [llength $in_bases] } then { fail "$testname // too few bases" return } if { [llength $list_actual_bases] > [llength $in_bases] } then { fail "$testname // too many bases" return } # Check each base. set ibase 0 foreach actual_base $list_actual_bases { regsub "^${ws}" $actual_base "" actual_base regsub "${ws}\$" $actual_base "" actual_base if { "$actual_base" != [lindex $in_bases $ibase] } then { fail "$testname // wrong base: $actual_base" return } incr ibase } # Parse each line in the body. set last_was_access 0 set vbase_match 0 foreach actual_line [split $actual_body "\r\n"] { # Chomp the line. regsub "^${ws}" $actual_line "" actual_line regsub "${ws}\$" $actual_line "" actual_line if { "$actual_line" == "" } then { continue; } # Access specifiers. if { [regexp "^(public|protected|private)${wsopt}:\$" "$actual_line" s0 s1] } then { set access "$s1" if { $last_was_access } then { fail "$testname // redundant access specifier" return } set last_was_access 1 continue } else { set last_was_access 0 } # Optional virtual base pointer. if { [ llength $in_vbases ] > 0 } then { set in_vbase [lindex $in_vbases 0] if { [ regexp "$in_vbase \\*(_vb.|_vb\\\$|__vb_)\[0-9\]*$in_vbase;" $actual_line ] } then { if { "$access" != "private" } then { fail "$testname // wrong access specifier: $access" return } set in_vbases [lreplace $in_vbases 0 0] set vbase_match 1 continue } } # Data fields. if { [llength $in_fields] > 0 } then { set in_access [lindex [lindex $in_fields 0] 0] set in_data [lindex [lindex $in_fields 0] 1] if { "$actual_line" == "$in_data" } then { if { "$access" != "$in_access" } then { fail "$testname // wrong access specifier: $access" return } set in_fields [lreplace $in_fields 0 0] continue } # Data fields must appear before synths and methods. fail "$testname // unrecognized line: $actual_line" } # Synthetic operators. These are optional and can be mixed in # with the methods in any order. TODO: it would be nice to # detect duplicates. set synth_match 0 foreach synth $list_synth_possible { set synth_access [lindex $synth 0] set synth_re [lindex $synth 1] if { [ regexp "$synth_re" "$actual_line" ] } then { if { "$access" != "$synth_access" } then { fail "$testname // wrong access specifier: $access" return } set synth_match 1 break } } if { $synth_match } then { continue; } # Methods. if { [llength $in_methods] > 0 } then { set in_access [lindex [lindex $in_methods 0] 0] set in_member [lindex [lindex $in_methods 0] 1] if { "$actual_line" == "$in_member" } then { if { "$access" != "$in_access" } then { fail "$testname // wrong access specifier: $access" return } set in_methods [lreplace $in_methods 0 0] continue } # gcc 2.95.3 shows "foo()" as "foo(void)". regsub -all "\\(\\)" $in_member "(void)" in_member if { "$actual_line" == "$in_member" } then { if { "$access" != "$in_access" } then { fail "$testname // wrong access specifier: $access" return } set in_methods [lreplace $in_methods 0 0] continue } } # Unrecognized line. fail "$testname // unrecognized line: $actual_line" return } # Check for missing elements. if { $vbase_match } then { if { [llength $in_vbases] > 0 } then { fail "$testname // missing virtual base pointers" } } if { [llength $in_fields] > 0 } then { fail "$testname // missing fields" return } if { [llength $in_methods] > 0 } then { fail "$testname // missing methods" return } # Check the tail. regsub "^${ws}" $actual_tail "" actual_tail regsub "${ws}\$" $actual_tail "" actual_tail if { "$actual_tail" != "$in_tail" } then { fail "$testname // wrong tail: $actual_tail" return } # It all worked! pass "$testname" return }