# Copyright 2007-2021 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 . # Check that GDB can call C++ functions whose parameters have # 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]} { 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 # 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 int cbv (T arg) { arg.data\[0\] += $ADDED; // intentionally modify the arg return arg.data\[0\]; } template 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]} { 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 .*;" # 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 # GCC version <= 6 and Clang do not emit DW_AT_defaulted and DW_AT_deleted. set is_gcc_6_or_older [test_compiler_info {gcc-[0-6]-*}] set is_clang [test_compiler_info {clang-*}] # But Clang version >= 7 emits DW_AT_calling_convention for types. set is_clang_6_or_older [test_compiler_info {clang-[0-6]-*}] 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" } if {$cctor == "defaultedIn" || $dtor == "defaultedIn"} { if {$is_gcc_6_or_older || $is_clang_6_or_older} { setup_xfail "*-*-*" } elseif {$is_clang} { # If this is a pass-by-value case, Clang >= 7's # DW_AT_calling_convention leads to the right decision. # Otherwise, it is expected to fail. if {"defaultedOut" in $states || "explicit" in $states} { setup_xfail "*-*-*" } } } 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"} { if {$cctor == "defaultedIn" && ($is_gcc_6_or_older || $is_clang)} { setup_xfail "*-*-*" } gdb_test "print tracer" " = $TRACE" \ "destructor should be called" } } else { if {$cctor == "deleted" && ($is_gcc_6_or_older || $is_clang)} { setup_xfail "*-*-*" } 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 }