This is the mail archive of the gdb@sources.redhat.com mailing list for the GDB project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

new testsuite function for c++ ptype


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
}


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]