71913 is a case where unsafe_copy_elision_p was being too conservative. We can allow copy elision in a new expression; the only way we could end up initializing a base subobject without knowing it would be through a placement new, in which case we would already be using the wrong (complete object) constructor, so copy elision doesn't make it any worse.
The second patch is for a case where we weren't being conservative enough; we need to beware of tail padding even when the copy constructor is trivial. Tested x86_64-pc-linux-gnu, applying to trunk. 71913 patch also applied to 6 branch.
commit 32e2ba606cd8993bd3de82708711ae26fc251d0a Author: Jason Merrill <ja...@redhat.com> Date: Thu Jul 21 16:55:56 2016 -0400 PR c++/71913 - missing copy elision with new. * call.c (unsafe_copy_elision_p): It's OK to elide when initializing an unknown object. diff --git a/gcc/cp/call.c b/gcc/cp/call.c index d917d9a..061e708 100644 --- a/gcc/cp/call.c +++ b/gcc/cp/call.c @@ -7275,10 +7275,11 @@ unsafe_copy_elision_p (tree target, tree exp) if (TREE_CODE (exp) != TARGET_EXPR) return false; tree type = TYPE_MAIN_VARIANT (TREE_TYPE (exp)); - if (type == CLASSTYPE_AS_BASE (type)) + /* It's safe to elide the copy for a class with no tail padding. */ + if (tree_int_cst_equal (TYPE_SIZE (type), CLASSTYPE_SIZE (type))) return false; - if (!is_base_field_ref (target) - && resolves_to_fixed_type_p (target, NULL)) + /* It's safe to elide the copy if we aren't initializing a base object. */ + if (!is_base_field_ref (target)) return false; tree init = TARGET_EXPR_INITIAL (exp); /* build_compound_expr pushes COMPOUND_EXPR inside TARGET_EXPR. */ diff --git a/gcc/testsuite/g++.dg/init/elide5.C b/gcc/testsuite/g++.dg/init/elide5.C new file mode 100644 index 0000000..0a9978c --- /dev/null +++ b/gcc/testsuite/g++.dg/init/elide5.C @@ -0,0 +1,27 @@ +// PR c++/71913 +// { dg-do link { target c++11 } } + +void* operator new(unsigned long, void* p) { return p; } + +struct IndirectReturn { + IndirectReturn() {} + // Undefined so we get a link error if the indirect return value is copied + IndirectReturn(const IndirectReturn&); + IndirectReturn& operator=(const IndirectReturn&) = delete; + ~IndirectReturn() {} +}; + +IndirectReturn foo() { return IndirectReturn(); } + +void bar(void* ptr) { + new (ptr) IndirectReturn(foo()); +} + +alignas (alignof (IndirectReturn)) +unsigned char c[sizeof(IndirectReturn)]; + +int main() +{ + bar(c); +} +
commit 5d549fb4e1ee815daa678bd369a2650055d61311 Author: Jason Merrill <ja...@redhat.com> Date: Thu Jul 21 15:57:35 2016 -0400 * call.c (build_over_call): Check unsafe_copy_elision_p even for trivial constructors. * method.c (do_build_copy_constructor): Don't copy tail padding even in a trivial constructor. diff --git a/gcc/cp/call.c b/gcc/cp/call.c index f929fb2..d917d9a 100644 --- a/gcc/cp/call.c +++ b/gcc/cp/call.c @@ -7271,6 +7271,9 @@ is_base_field_ref (tree t) static bool unsafe_copy_elision_p (tree target, tree exp) { + /* Copy elision only happens with a TARGET_EXPR. */ + if (TREE_CODE (exp) != TARGET_EXPR) + return false; tree type = TYPE_MAIN_VARIANT (TREE_TYPE (exp)); if (type == CLASSTYPE_AS_BASE (type)) return false; @@ -7726,9 +7729,8 @@ build_over_call (struct z_candidate *cand, int flags, tsubst_flags_t complain) else if (trivial) return force_target_expr (DECL_CONTEXT (fn), arg, complain); } - else if (trivial - || (TREE_CODE (arg) == TARGET_EXPR - && !unsafe_copy_elision_p (fa, arg))) + else if ((trivial || TREE_CODE (arg) == TARGET_EXPR) + && !unsafe_copy_elision_p (fa, arg)) { tree to = cp_stabilize_reference (cp_build_indirect_ref (fa, RO_NULL, diff --git a/gcc/cp/method.c b/gcc/cp/method.c index cd8faaf..63aa53e 100644 --- a/gcc/cp/method.c +++ b/gcc/cp/method.c @@ -542,14 +542,32 @@ do_build_copy_constructor (tree fndecl) if (!inh) parm = convert_from_reference (parm); - if (trivial - && is_empty_class (current_class_type)) - /* Don't copy the padding byte; it might not have been allocated - if *this is a base subobject. */; - else if (trivial) + if (trivial) { - tree t = build2 (INIT_EXPR, void_type_node, current_class_ref, parm); - finish_expr_stmt (t); + if (is_empty_class (current_class_type)) + /* Don't copy the padding byte; it might not have been allocated + if *this is a base subobject. */; + else if (tree_int_cst_equal (TYPE_SIZE (current_class_type), + CLASSTYPE_SIZE (current_class_type))) + { + tree t = build2 (INIT_EXPR, void_type_node, current_class_ref, parm); + finish_expr_stmt (t); + } + else + { + /* We must only copy the non-tail padding parts. */ + tree base_size = CLASSTYPE_SIZE_UNIT (current_class_type); + base_size = size_binop (MINUS_EXPR, base_size, size_int (1)); + tree array_type = build_array_type (unsigned_char_type_node, + build_index_type (base_size)); + tree alias_set = build_int_cst (TREE_TYPE (current_class_ptr), 0); + tree lhs = build2 (MEM_REF, array_type, + current_class_ptr, alias_set); + tree rhs = build2 (MEM_REF, array_type, + TREE_OPERAND (parm, 0), alias_set); + tree t = build2 (INIT_EXPR, void_type_node, lhs, rhs); + finish_expr_stmt (t); + } } else { diff --git a/gcc/testsuite/g++.dg/torture/tail-padding1.C b/gcc/testsuite/g++.dg/torture/tail-padding1.C new file mode 100644 index 0000000..43e26ad --- /dev/null +++ b/gcc/testsuite/g++.dg/torture/tail-padding1.C @@ -0,0 +1,18 @@ +// Test that initializing a non-POD base with a trivial copy ctor doesn't +// clobber tail padding. + +// { dg-do run } + +struct X { ~X() {} int n; char d; }; +struct Y { Y(); char c[3]; }; +struct Z : X, virtual Y { Z(); }; + +X f() { X nrvo; __builtin_memset(&nrvo, 0, sizeof(X)); return nrvo; } +Z::Z() : Y(), X(f()) {} +Y::Y() { c[0] = 1; } + +int main() { + Z z; + if (z.c[0] != 1) + __builtin_abort (); +}