Re: [review v3] testsuite, cp: increase the coverage of testing pass-by-ref arguments

Luis Machado
2020-01-13T18:21:35Z

Hi,

I noticed these new tests fail (in large numbers) for GCC 5.4.x. Is it
expected? If so, it may be worth making this test conditional on newer
GCC versions.

On 12/14/19 6:53 AM, Tankut Baris Aktemur (Code Review) wrote:
> Change URL: https://gnutoolchain-gerrit.osci.io/r/c/binutils-gdb/+/142
> ......................................................................
>
> testsuite, cp: increase the coverage of testing pass-by-ref arguments
>
> Extend testcases for GDB's infcall of call-by-value functions that
> take aggregate values as parameters. In particular, existing test has
> been substantially extended with class definitions whose definitions
> of copy constructor, destructor, and move constructor functions are a
> combination of
>
> (1) explicitly defined by the user,
> (2) defaulted inside the class declaration,
> (3) defaulted outside the class declaration,
> (4) deleted
> (5) not defined in the source.
>
> For each combination, a small and a large class is generated as well
> as a derived class and a container class. Additionally, the following
> manually-written cases are provided:
>
> - a dynamic class (i.e. class with a virtual method)
> - classes that contain an array field
> - a class whose copy ctor is inlined
> - a class whose destructor is deleted
> - classes with multiple copy and/or move ctors
>
> Test cases check whether GDB makes the right decision to pass an
> object by value or implicitly by reference, whether really a copy of
> the argument is passed, and whether the copy constructor and
> destructor of the clone of the argument are invoked properly.
>
> The input program pass-by-ref.cc is generated in the test's output
> directory. The input program pass-by-ref-2.cc is manually-written.
>
> Tests have been verified on the X86_64 architecture with
> GCC 7.4.0, 8.2.0, and 9.2.1.
>
> gdb/testsuite/ChangeLog:
> 2019-11-07 Tankut Baris Aktemur <tankut.baris.aktemur@intel.com>
>
> * gdb.cp/pass-by-ref.cc: Delete. Generated in the output
> directory instead.
> * gdb.cp/pass-by-ref.exp: Extend with more cases.
> * gdb.cp/pass-by-ref-2.cc: New file.
> * gdb.cp/pass-by-ref-2.exp: New file.
>
> Change-Id: Ie8ab1f260c6ad5ee4eb34b2c1597ce24af04abb6
> ---
> A gdb/testsuite/gdb.cp/pass-by-ref-2.cc
> A gdb/testsuite/gdb.cp/pass-by-ref-2.exp
> D gdb/testsuite/gdb.cp/pass-by-ref.cc
> M gdb/testsuite/gdb.cp/pass-by-ref.exp
> 4 files changed, 791 insertions(+), 86 deletions(-)
>
>
>
> diff --git a/gdb/testsuite/gdb.cp/pass-by-ref-2.cc b/gdb/testsuite/gdb.cp/pass-by-ref-2.cc
> new file mode 100644
> index 0000000..1cd5a16
> --- /dev/null
> +++ b/gdb/testsuite/gdb.cp/pass-by-ref-2.cc
> @@ -0,0 +1,295 @@
> +/* This testcase is part of GDB, the GNU debugger.
> +
> + Copyright 2019 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 <http://www.gnu.org/licenses/>. */
> +
> +class ByVal {
> +public:
> + ByVal (void);
> +
> + int x;
> +};
> +
> +ByVal::ByVal (void)
> +{
> + x = 2;
> +}
> +
> +class ByRef {
> +public:
> + ByRef (void);
> +
> + ByRef (const ByRef &rhs);
> +
> + int x;
> +};
> +
> +ByRef::ByRef (void)
> +{
> + x = 2;
> +}
> +
> +ByRef::ByRef (const ByRef &rhs)
> +{
> + x = 3; /* ByRef-cctor */
> +}
> +
> +class ArrayContainerByVal {
> +public:
> + ByVal items[2];
> +};
> +
> +int
> +cbvArrayContainerByVal (ArrayContainerByVal arg)
> +{
> + arg.items[0].x += 4; // intentionally modify
> + return arg.items[0].x;
> +}
> +
> +class ArrayContainerByRef {
> +public:
> + ByRef items[2];
> +};
> +
> +int
> +cbvArrayContainerByRef (ArrayContainerByRef arg)
> +{
> + arg.items[0].x += 4; // intentionally modify
> + return arg.items[0].x;
> +}
> +
> +class DynamicBase {
> +public:
> + DynamicBase (void);
> +
> + virtual int get (void);
> +
> + int x;
> +};
> +
> +DynamicBase::DynamicBase (void)
> +{
> + x = 2;
> +}
> +
> +int
> +DynamicBase::get (void)
> +{
> + return 42;
> +}
> +
> +class Dynamic : public DynamicBase {
> +public:
> + virtual int get (void);
> +};
> +
> +int
> +Dynamic::get (void)
> +{
> + return 9999;
> +}
> +
> +int
> +cbvDynamic (DynamicBase arg)
> +{
> + arg.x += 4; // intentionally modify
> + return arg.x + arg.get ();
> +}
> +
> +class Inlined {
> +public:
> + Inlined (void);
> +
> + __attribute__((always_inline))
> + Inlined (const Inlined &rhs)
> + {
> + x = 3;
> + }
> +
> + int x;
> +};
> +
> +Inlined::Inlined (void)
> +{
> + x = 2;
> +}
> +
> +int
> +cbvInlined (Inlined arg)
> +{
> + arg.x += 4; // intentionally modify
> + return arg.x;
> +}
> +
> +class DtorDel {
> +public:
> + DtorDel (void);
> +
> + ~DtorDel (void) = delete;
> +
> + int x;
> +};
> +
> +DtorDel::DtorDel (void)
> +{
> + x = 2;
> +}
> +
> +int
> +cbvDtorDel (DtorDel arg)
> +{
> + // Calling this method should be rejected
> + return arg.x;
> +}
> +
> +class FourCCtor {
> +public:
> + FourCCtor (void);
> +
> + FourCCtor (FourCCtor &rhs);
> + FourCCtor (const FourCCtor &rhs);
> + FourCCtor (volatile FourCCtor &rhs);
> + FourCCtor (const volatile FourCCtor &rhs);
> +
> + int x;
> +};
> +
> +FourCCtor::FourCCtor (void)
> +{
> + x = 2;
> +}
> +
> +FourCCtor::FourCCtor (FourCCtor &rhs)
> +{
> + x = 3;
> +}
> +
> +FourCCtor::FourCCtor (const FourCCtor &rhs)
> +{
> + x = 4;
> +}
> +
> +FourCCtor::FourCCtor (volatile FourCCtor &rhs)
> +{
> + x = 5;
> +}
> +
> +FourCCtor::FourCCtor (const volatile FourCCtor &rhs)
> +{
> + x = 6;
> +}
> +
> +int
> +cbvFourCCtor (FourCCtor arg)
> +{
> + arg.x += 10; // intentionally modify
> + return arg.x;
> +}
> +
> +class TwoMCtor {
> +public:
> + TwoMCtor (void);
> +
> + /* Even though one move ctor is defaulted, the other
> + is explicit. */
> + TwoMCtor (const TwoMCtor &&rhs);
> + TwoMCtor (TwoMCtor &&rhs) = default;
> +
> + int x;
> +};
> +
> +TwoMCtor::TwoMCtor (void)
> +{
> + x = 2;
> +}
> +
> +TwoMCtor::TwoMCtor (const TwoMCtor &&rhs)
> +{
> + x = 3;
> +}
> +
> +int
> +cbvTwoMCtor (TwoMCtor arg)
> +{
> + arg.x += 10; // intentionally modify
> + return arg.x;
> +}
> +
> +class TwoMCtorAndCCtor {
> +public:
> + TwoMCtorAndCCtor (void);
> +
> + TwoMCtorAndCCtor (const TwoMCtorAndCCtor &rhs) = default;
> +
> + /* Even though one move ctor is defaulted, the other
> + is explicit. This makes the type pass-by-ref. */
> + TwoMCtorAndCCtor (const TwoMCtorAndCCtor &&rhs);
> + TwoMCtorAndCCtor (TwoMCtorAndCCtor &&rhs) = default;
> +
> + int x;
> +};
> +
> +TwoMCtorAndCCtor::TwoMCtorAndCCtor (void)
> +{
> + x = 2;
> +}
> +
> +TwoMCtorAndCCtor::TwoMCtorAndCCtor (const TwoMCtorAndCCtor &&rhs)
> +{
> + x = 4;
> +}
> +
> +int
> +cbvTwoMCtorAndCCtor (TwoMCtorAndCCtor arg)
> +{
> + arg.x += 10; // intentionally modify
> + return arg.x;
> +}
> +
> +ArrayContainerByVal arrayContainerByVal;
> +ArrayContainerByRef arrayContainerByRef;
> +Dynamic dynamic;
> +Inlined inlined;
> +// Cannot stack-allocate DtorDel
> +DtorDel *dtorDel;
> +FourCCtor fourCctor_c0v0;
> +const FourCCtor fourCctor_c1v0;
> +volatile FourCCtor fourCctor_c0v1;
> +const volatile FourCCtor fourCctor_c1v1;
> +TwoMCtor twoMctor;
> +TwoMCtorAndCCtor twoMctorAndCctor;
> +
> +int
> +main (void)
> +{
> + int v;
> + dtorDel = new DtorDel;
> + /* Explicitly call the cbv function to make sure the compiler
> + will not omit any code in the binary. */
> + v = cbvArrayContainerByVal (arrayContainerByVal);
> + v = cbvArrayContainerByRef (arrayContainerByRef);
> + v = cbvDynamic (dynamic);
> + v = cbvInlined (inlined);
> + v = cbvFourCCtor (fourCctor_c0v0);
> + v = cbvFourCCtor (fourCctor_c1v0);
> + v = cbvFourCCtor (fourCctor_c0v1);
> + v = cbvFourCCtor (fourCctor_c1v1);
> + /* v = cbvTwoMCtor (twoMctor); */ // This is illegal, cctor is deleted
> + v = cbvTwoMCtorAndCCtor (twoMctorAndCctor);
> +
> + /* stop here */
> +
> + return 0;
> +}
> diff --git a/gdb/testsuite/gdb.cp/pass-by-ref-2.exp b/gdb/testsuite/gdb.cp/pass-by-ref-2.exp
> new file mode 100644
> index 0000000..7cce886
> --- /dev/null
> +++ b/gdb/testsuite/gdb.cp/pass-by-ref-2.exp
> @@ -0,0 +1,114 @@
> +# Copyright 2019 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 <http://www.gnu.org/licenses/>.
> +
> +# Check that GDB can call C++ functions whose parameters have
> +# object type, and are either passed by value or implicitly by reference.
> +#
> +# This is a companion test to pass-by-ref.exp. In this test, the input
> +# is manually-written. In pass-by-ref.exp, the test input is generated.
> +#
> +# We include tests for classes that
> +# - contain arrays as fields,
> +# - are dynamic (i.e. have virtual methods)
> +# - have inlined copy ctor
> +# - have deleted destructor
> +
> +if {[skip_cplus_tests]} {
> + untested "c++ test skipped"
> + continue
> +}
> +
> +standard_testfile .cc
> +
> +set options {debug c++ additional_flags=-std=c++11}
> +if {[prepare_for_testing "failed to prepare" $testfile $srcfile $options]} {
> + return -1
> +}
> +
> +if {![runto_main]} {
> + untested "failed to run to main"
> + return -1
> +}
> +
> +set bp_location [gdb_get_line_number "stop here"]
> +gdb_breakpoint $bp_location
> +gdb_continue_to_breakpoint "end of main" ".*return .*;"
> +
> +gdb_test "print cbvArrayContainerByVal (arrayContainerByVal)" "6" \
> + "call cbvArrayContainerByVal"
> +gdb_test "print arrayContainerByVal.items\[0\].x" "2" \
> + "cbv argument 'arrayContainerByVal' should not change"
> +
> +gdb_test "print cbvArrayContainerByRef (arrayContainerByRef)" "7" \
> + "call cbvArrayContainerByRef"
> +gdb_test "print arrayContainerByRef.items\[0\].x" "2" \
> + "cbv argument 'arrayContainerByRef' should not change"
> +
> +gdb_test "print cbvDynamic (dynamic)" "48" \
> + "call cbvDynamic"
> +gdb_test "print dynamic.x" "2" \
> + "cbv argument 'dynamic' should not change"
> +
> +set sig "\"Inlined\:\:Inlined\\(.*Inlined const\&\\)\""
> +gdb_test "print cbvInlined (inlined)" \
> + "expression cannot be evaluated .* \\(maybe inlined\\?\\)"
> +
> +gdb_test "print cbvDtorDel (*dtorDel)" \
> + ".* cannot be evaluated .* 'DtorDel' is not destructible" \
> + "type not destructible"
> +
> +# Test that GDB calls the correct copy ctor
> +gdb_test "print cbvFourCCtor (fourCctor_c0v0)" "13" \
> + "call cbvFourCCtor (c0v0)"
> +gdb_test "print fourCctor_c0v0.x" "2" \
> + "cbv argument 'twoCctor_c0v0' should not change"
> +
> +gdb_test "print cbvFourCCtor (fourCctor_c1v0)" "14" \
> + "call cbvFourCCtor (c1v0)"
> +gdb_test "print fourCctor_c1v0.x" "2" \
> + "cbv argument 'twoCctor_c1v0' should not change"
> +
> +gdb_test "print cbvFourCCtor (fourCctor_c0v1)" "15" \
> + "call cbvFourCCtor (c0v1)"
> +gdb_test "print fourCctor_c0v1.x" "2" \
> + "cbv argument 'twoCctor_c0v1' should not change"
> +
> +gdb_test "print cbvFourCCtor (fourCctor_c1v1)" "16" \
> + "call cbvFourCCtor (c1v1)"
> +gdb_test "print fourCctor_c1v1.x" "2" \
> + "cbv argument 'twoCctor_c1v1' should not change"
> +
> +gdb_test "print cbvTwoMCtor (twoMctor)" \
> + ".* cannot be evaluated .* 'TwoMCtor' is not copy constructible" \
> + "copy ctor is implicitly deleted"
> +
> +gdb_test "print cbvTwoMCtorAndCCtor (twoMctorAndCctor)" "12" \
> + "call cbvTwoMCtorAndCCtor"
> +gdb_test "print twoMctorAndCctor.x" "2" \
> + "cbv argument 'twoMctorAndCtor' should not change"
> +
> +# Test that we get a breakpoint from the cctor during infcall and
> +# we can examine arguments. This is a test that the dummy frame
> +# of the copy constructor is set up correctly by the infcall mechanism.
> +set bp_location [gdb_get_line_number "ByRef-cctor"]
> +gdb_breakpoint $bp_location
> +gdb_test "print cbvArrayContainerByRef (arrayContainerByRef)" \
> + ".*The program being debugged stopped.*" \
> + "call cbvArrayContainerByRef with BP"
> +gdb_test "backtrace" [multi_line \
> + "#0 ByRef\:\:ByRef .* at .*$srcfile:$bp_location" \
> + "#1 .* ArrayContainerByRef::ArrayContainerByRef .*" \
> + "#2 <function called from gdb>" \
> + "#3 main.*"]
> diff --git a/gdb/testsuite/gdb.cp/pass-by-ref.cc b/gdb/testsuite/gdb.cp/pass-by-ref.cc
> deleted file mode 100644
> index bbe450a..0000000
> --- a/gdb/testsuite/gdb.cp/pass-by-ref.cc
> +++ /dev/null
> @@ -1,79 +0,0 @@
> -/* This testcase is part of GDB, the GNU debugger.
> -
> - Copyright 2007-2019 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 <http://www.gnu.org/licenses/>. */
> -
> -class Obj {
> -public:
> - Obj ();
> - Obj (const Obj &);
> - ~Obj ();
> - int var[2];
> -};
> -
> -int foo (Obj arg)
> -{
> - return arg.var[0] + arg.var[1];
> -}
> -
> -Obj::Obj ()
> -{
> - var[0] = 1;
> - var[1] = 2;
> -}
> -
> -Obj::Obj (const Obj &obj)
> -{
> - var[0] = obj.var[0];
> - var[1] = obj.var[1];
> -}
> -
> -Obj::~Obj ()
> -{
> -
> -}
> -
> -struct Derived : public Obj
> -{
> - int other;
> -};
> -
> -int blap (Derived arg)
> -{
> - return foo (arg);
> -}
> -
> -struct Container
> -{
> - Obj obj;
> -};
> -
> -int blip (Container arg)
> -{
> - return foo (arg.obj);
> -}
> -
> -Obj global_obj;
> -Derived global_derived;
> -Container global_container;
> -
> -int
> -main ()
> -{
> - int bar = foo (global_obj);
> - blap (global_derived);
> - blip (global_container);
> - return bar;
> -}
> diff --git a/gdb/testsuite/gdb.cp/pass-by-ref.exp b/gdb/testsuite/gdb.cp/pass-by-ref.exp
> index 94dd345..f44be77 100644
> --- a/gdb/testsuite/gdb.cp/pass-by-ref.exp
> +++ b/gdb/testsuite/gdb.cp/pass-by-ref.exp
> @@ -14,20 +14,395 @@
> # along with this program. If not, see <http://www.gnu.org/licenses/>.
>
> # Check that GDB can call C++ functions whose parameters have
> -# object type, but are passed by reference.
> +# object type, and are either passed by value or implicitly by reference.
> +#
> +# Suppose F is a function that has a call-by-value parameter whose
> +# type is class C. When calling F with an argument A, a copy of A should
> +# be created and passed to F. If C is a trivially-copyable type, A can
> +# be copied by a straightforward memory copy. However, roughly speaking,
> +# if C has a user-defined copy constructor and/or a user-defined
> +# destructor, the copy ctor should be used to initialize the copy of A
> +# before calling F, and a reference to that copy is passed to F. After
> +# the function returns, the destructor should be called to destruct the
> +# copy. In this case, C is said to be a 'pass-by-reference' type.
> +# Determining whether C is pass-by-ref depends on
> +# how the copy ctor, destructor, and the move ctor of C are defined.
> +# First of all, C is not copy constructible if its copy constructor is
> +# explicitly or implicitly deleted. In this case, it would be illegal
> +# to pass values of type C to a function. C is pass-by-value, if all of
> +# its copy ctor, dtor, and move ctor are trivially defined.
> +# Otherwise, it is pass-by-ref.
> +#
> +# To cover the many possible combinations, this test generates classes
> +# that contain three special functions:
> +# (1) a copy constructor,
> +# (2) a destructor, and
> +# (3) a move constructor.
> +# A special function is in one of the following states:
> +# * explicit: The function is explicitly defined by the user.
> +# * defaultedIn: The function is defaulted inside the class decl,
> +# using the 'default' keyword.
> +# * defaultedOut: The function is declared inside the class decl,
> +# and defaulted outside using the 'default' keyword.
> +# * deleted: The function is explicitly deleted by the user,
> +# using the 'delete' keyword.
> +# * absent: The function is not declared by the user (i.e. it does not
> +# exist in the source. The compiler generates (or deletes) the
> +# definition in this case.
> +#
> +# The C++ ABI decides if a class is pass-by-value or pass-by-ref
> +# (i.e. trivially copyable or not) first at the language level, based
> +# on the state of the special functions. Then, at the target level, a
> +# class may be determined to be pass-by-ref because of its size
> +# (e.g. if it is too large to fit on registers). For this reason, this
> +# test generates both a small and a large version for the same
> +# combination of special function states.
> +#
> +# A class is not trivially-copyable if a base class or a field is not
> +# trivially-copyable, even though the class definition itself seems
> +# trivial. To test these cases, we also generate derived classes and
> +# container classes.
> +#
> +# The generated code is placed in the test output directory.
> +#
> +# The companion test file pass-by-ref-2.exp also contains
> +# manually-written cases.
>
> -if { [skip_cplus_tests] } { continue }
> +if {[skip_cplus_tests]} {
> + untested "c++ test skipped"
> + continue
> +}
>
> +# The program source is generated in the output directory.
> +# We use standard_testfile here to set convenience variables.
> standard_testfile .cc
>
> -if {[prepare_for_testing "failed to prepare" $testfile $srcfile {debug c++}]} {
> +# Some constant values used when generating the source
> +
> +set SMALL 2
> +set LARGE 150
> +set ORIGINAL 2
> +set CUSTOM 3
> +set ADDED 4
> +set TRACE 5
> +
> +
> +# Return 1 if the class whose special function states are STATES
> +# is copyable. Otherwise return 0.
> +
> +proc is_copy_constructible { states } {
> + set cctor [lindex $states 0]
> + set dtor [lindex $states 1]
> + set mctor [lindex $states 2]
> +
> + if {$cctor == "deleted" || ($cctor == "absent" && $mctor != "absent")} {
> + return 0
> + }
> + return 1
> +}
> +
> +# Generate a declaration and an out-of-class definition for a function
> +# with the provided signature. The STATE should be one of the following:
> +# - explicit, defaultedIn, defaultedOut, deleted, absent
> +
> +proc generate_member_function { classname signature length state } {
> + set declaration ""
> + set definition ""
> +
> + global CUSTOM
> + global TRACE
> +
> + switch $state {
> + explicit {
> + set declaration "$signature;\n"
> + set definition "$classname\:\:$signature
> + {
> + data\[0\] = $CUSTOM;
> + data\[[expr $length - 1]\] = $CUSTOM;
> + tracer = $TRACE;
> + }\n"
> + }
> + defaultedIn {
> + set declaration "$signature = default;\n"
> + }
> + defaultedOut {
> + set declaration "$signature;\n"
> + set definition "$classname\:\:$signature = default;\n"
> + }
> + deleted {
> + set declaration "$signature = delete;\n"
> + }
> + default {
> + # function is not user-defined in this case
> + }
> + }
> +
> + return [list $declaration $definition]
> +}
> +
> +# Generate a C++ class with the given CLASSNAME and LENGTH-many
> +# integer elements. The STATES is an array of 3 items
> +# containing the desired state of the special functions
> +# in this order:
> +# copy constructor, destructor, move constructor
> +
> +proc generate_class { classname length states } {
> + set declarations ""
> + set definitions ""
> + set classname "${classname}_[join $states _]"
> +
> + for {set i 0} {$i < [llength $states]} {incr i} {
> + set sig ""
> + switch $i {
> + 0 {set sig "$classname (const $classname \&rhs)"}
> + 1 {set sig "\~$classname (void)"}
> + 2 {set sig "$classname ($classname \&\&rhs)"}
> + }
> +
> + set state [lindex $states $i]
> + set code [generate_member_function $classname $sig $length $state]
> + append declarations [lindex $code 0]
> + append definitions [lindex $code 1]
> + }
> +
> + global ORIGINAL
> +
> + return "
> + /*** C++ class $classname ***/
> + class ${classname} {
> + public:
> + $classname (void);
> + $declarations
> +
> + int data\[$length\];
> + };
> +
> + $classname\:\:$classname (void)
> + {
> + data\[0\] = $ORIGINAL;
> + data\[[expr $length - 1]\] = $ORIGINAL;
> + }
> +
> + $definitions
> +
> + $classname ${classname}_var; /* global var */
> +
> + template int cbv<$classname> ($classname arg);"
> +}
> +
> +# Generate a small C++ class
> +
> +proc generate_small_class { states } {
> + global SMALL
> + return [generate_class Small $SMALL $states];
> +}
> +
> +# Generate a large C++ class
> +
> +proc generate_large_class { states } {
> + global LARGE
> + return [generate_class Large $LARGE $states];
> +}
> +
> +# Generate a class that derives from a small class
> +
> +proc generate_derived_class { states } {
> + set base "Small_[join $states _]"
> + set classname "Derived_[join $states _]"
> +
> + return "
> + /*** Class derived from $base ***/
> + class $classname : public $base {
> + public:
> + };
> +
> + $classname ${classname}_var; /* global var */
> +
> + template int cbv<$classname> ($classname arg);"
> +}
> +
> +# Generate a class that contains a small class item
> +
> +proc generate_container_class { states } {
> + set contained "Small_[join $states _]"
> + set classname "Container_[join $states _]"
> +
> + return "
> + /*** Class that contains $contained ***/
> + class $classname {
> + public:
> + $contained item;
> + };
> +
> + $classname ${classname}_var; /* global var */
> +
> + template int cbv_container<$classname> ($classname arg);"
> +}
> +
> +# Generate useful statements that use a class in the debugee program
> +
> +proc generate_stmts { classprefix states {cbvfun "cbv"}} {
> + set classname "${classprefix}_[join $states _]"
> +
> + # Having an explicit call to the cbv function in the debugee program
> + # ensures that the compiler will emit necessary function in the binary.
> + if {[is_copy_constructible $states]} {
> + set cbvcall "$cbvfun<$classname> (${classname}_var);\n"
> + } else {
> + set cbvcall ""
> + }
> +
> + return "$cbvcall"
> +}
> +
> +# Generate the complete debugee program
> +
> +proc generate_program { classes stmts } {
> + global ADDED
> +
> + return "
> + /*** THIS FILE IS GENERATED BY THE TEST. ***/
> +
> + static int tracer = 0;
> +
> + /* The call-by-value function. */
> + template <class T>
> + int
> + cbv (T arg)
> + {
> + arg.data\[0\] += $ADDED; // intentionally modify the arg
> + return arg.data\[0\];
> + }
> +
> + template <class T>
> + int
> + cbv_container (T arg)
> + {
> + arg.item.data\[0\] += $ADDED; // intentionally modify
> + return arg.item.data\[0\];
> + }
> +
> + $classes
> +
> + int
> + main (void)
> + {
> + $stmts
> +
> + /* stop here */
> +
> + return 0;
> + }"
> +}
> +
> +# Compute all the combinations of special function states.
> +# We do not contain the 'deleted' state for the destructor,
> +# because it is illegal to have stack-allocated objects
> +# whose destructor have been deleted. This case is covered
> +# in pass-by-ref-2 via heap-allocated objects.
> +
> +set options_nodelete [list absent explicit defaultedIn defaultedOut]
> +set options [concat $options_nodelete {deleted}]
> +set all_combinations {}
> +
> +foreach cctor $options {
> + foreach dtor $options_nodelete {
> + foreach mctor $options {
> + lappend all_combinations [list $cctor $dtor $mctor]
> + }
> + }
> +}
> +
> +# Generate the classes.
> +
> +set classes ""
> +set stmts ""
> +
> +foreach state $all_combinations {
> + append classes [generate_small_class $state]
> + append stmts [generate_stmts "Small" $state]
> +
> + append classes [generate_large_class $state]
> + append stmts [generate_stmts "Large" $state]
> +
> + append classes [generate_derived_class $state]
> + append stmts [generate_stmts "Derived" $state]
> +
> + append classes [generate_container_class $state]
> + append stmts [generate_stmts "Container" $state "cbv_container"]
> +}
> +
> +# Generate the program code and compile
> +set program [generate_program $classes $stmts]
> +set srcfile [standard_output_file ${srcfile}]
> +gdb_produce_source $srcfile $program
> +
> +set options {debug c++ additional_flags=-std=c++11}
> +if {[prepare_for_testing "failed to prepare" $testfile $srcfile $options]} {
> return -1
> }
>
> -if ![runto_main] then {
> +if {![runto_main]} {
> + untested "failed to run to main"
> return -1
> }
>
> -gdb_test "print foo (global_obj)" " = 3" "call function in obj"
> -gdb_test "print blap (global_derived)" " = 3" "call function in derived"
> -gdb_test "print blip (global_container)" " = 3" "call function in container"
> +set bp_location [gdb_get_line_number "stop here"]
> +gdb_breakpoint $bp_location
> +gdb_continue_to_breakpoint "end of main" ".*return .*;"
> +
> +# Do the checks for a given class whose name is prefixed with PREFIX,
> +# and whose special functions have the states given in STATES.
> +# The name of the call-by-value function and the expression to access
> +# the data field can be specified explicitly if the default values
> +# do not work.
> +
> +proc test_for_class { prefix states cbvfun data_field length} {
> + set name "${prefix}_[join $states _]"
> +
> + set cctor [lindex $states 0]
> + set dtor [lindex $states 1]
> + set mctor [lindex $states 2]
> +
> + global ORIGINAL
> + global CUSTOM
> + global ADDED
> + global TRACE
> +
> + with_test_prefix $name {
> + if {[is_copy_constructible $states]} {
> + set expected [expr {$ORIGINAL + $ADDED}]
> + if {$cctor == "explicit"} {
> + set expected [expr {$CUSTOM + $ADDED}]
> + }
> + if {$dtor == "explicit"} {
> + gdb_test "print tracer = 0" " = 0" "reset the tracer"
> + }
> + gdb_test "print ${cbvfun}<$name> (${name}_var)" " = $expected" \
> + "call '$cbvfun'"
> + gdb_test "print ${name}_var.${data_field}\[0\]" " = $ORIGINAL" \
> + "cbv argument should not change (item 0)"
> + if {$length > 1} {
> + set last_index [expr $length - 1]
> + gdb_test "print ${name}_var.${data_field}\[$last_index\]" \
> + " = $ORIGINAL" \
> + "cbv argument should not change (item $last_index)"
> + }
> + if {$dtor == "explicit"} {
> + gdb_test "print tracer" " = $TRACE" \
> + "destructor should be called"
> + }
> + } else {
> + gdb_test "print ${cbvfun}<$name> (${name}_var)" \
> + ".* cannot be evaluated .* '${name}' is not copy constructible" \
> + "calling '$cbvfun' should be refused"
> + }
> + }
> +}
> +
> +foreach state $all_combinations {
> + test_for_class "Small" $state "cbv" "data" $SMALL
> + test_for_class "Large" $state "cbv" "data" $LARGE
> + test_for_class "Derived" $state "cbv" "data" 1
> + test_for_class "Container" $state "cbv_container" "item.data" 1
> +}
>