Message ID | Y1lb4uJuWVdEF0x0@redhat.com |
---|---|
State | New |
Headers | show |
Series | [v3] c++: Implement -Wdangling-reference [PR106393] | expand |
On 10/26/22 12:10, Marek Polacek wrote: > On Tue, Oct 25, 2022 at 11:53:51AM -0400, Jason Merrill via Gcc-patches wrote: >> On 10/25/22 11:21, Marek Polacek wrote: >>> On Mon, Oct 24, 2022 at 01:30:42PM -0400, Jason Merrill wrote: >>>> On 10/21/22 19:28, Marek Polacek wrote: >>>>> It doesn't warn when the function in question is a member function, otherwise >>>>> it'd emit loads of warnings for valid code like obj.emplace<T>({0}, 0). >>>> >>>> We had discussed warning if the object argument is a temporary (and for the >>>> above check, the function returns *this)? >>> >>> Presumably you mean detecting something like this: >>> >>> struct S { >>> const S& self () { return *this; } >>> }; >>> const S& s = S().self(); >> >> Yes. Or >> >> struct S { >> int ar[4]; >> int& operator[](int i) { return ar[i]; } >> }; >> const int &r = S()[2]; > > Ok, I've extended the warning to check if the object argument is a temporary > as well, so we get a warning here now. > >>> I don't currently have a way to detect it, can I steal a METHOD_TYPE flag >>> that says "this member function returns *this"? Alternatively, walk its >>> DECL_SAVED_TREE and look for RETURN_EXPR? >> >> Like you limited the above check to TREE_STATIC, let's also forget about >> checking for return *this. > > I don't follow, but it looks like I don't need to change anything and just > keep the TREE_STATIC check? > >>>>> It warns in member initializer lists as well: >>>>> >>>>> const int& f(const int& i) { return i; } >>>>> struct S { >>>>> const int &r; // { dg-warning "dangling reference" } >>>>> S() : r(f(10)) { } // { dg-message "destroyed" } >>>>> }; >>>>> >>>>> I've run the testsuite/bootstrap with the warning enabled by default. >>>>> There were just a few FAILs: >>>>> * g++.dg/warn/Wdangling-pointer-2.C >>>>> * 20_util/any/misc/any_cast.cc >>>>> * 20_util/forward/c_neg.cc >>>>> * 20_util/forward/f_neg.cc >>>>> * experimental/any/misc/any_cast.cc >>>>> all of these look like genuine bugs. A bootstrap with the warning >>>>> enabled by default passed. >>>>> >>>>> When testing a previous version of the patch, there were many FAILs in >>>>> libstdc++'s 22_locale/; all of them because the warning triggered on >>>>> >>>>> const test_type& obj = std::use_facet<test_type>(std::locale()); >>>>> >>>>> but this code looks valid -- std::use_facet doesn't return a reference >>>>> to its parameter. Therefore I added code to suppress the warning when >>>>> the call is std::use_facet. Now 22_locale/* pass even with the warning >>>>> on. We could exclude more std:: functions like this if desirable. >>>> >>>> Instead of adding special cases in the compiler, let's disable the warning >>>> around the definition of use_facet (and adjust the compiler as needed so >>>> that avoids the warning). >>> >>> As I said in >>> <https://gcc.gnu.org/pipermail/gcc-patches/2022-October/604307.html> >>> I don't think it's possible without inventing an attribute (?). >> >> Replied. > > Fixed. > >>>>> + tree fndecl = cp_get_callee_fndecl_nofold (call); >>>>> + if (!fndecl >>>>> + || warning_suppressed_p (fndecl, OPT_Wdangling_reference) >>>>> + /* Don't warn about member functions; the warning would trigger in >>>>> + valid code like >>>>> + std::any a(...); >>>>> + S& s = a.emplace<S>({0}, 0); >>>>> + which constructs a new object and returns a reference to it. */ >>>>> + || DECL_NONSTATIC_MEMBER_FUNCTION_P (fndecl) >>>>> + /* It seems unreasonable to warn about operator functions. */ >>>>> + || DECL_OVERLOADED_OPERATOR_P (fndecl) >>>> >>>> I guess I'd expect false positives on << and >> because of iostreams, do you >>>> see false positives with other operators? >>> >>> This was just a guess. The warning triggered in g++.dg/overload/operator6.C. >> >> That looks like a true positive. > > Ok, then... > >>> I suppose this could be limited to << and >>? I'm not sure. >> >> I'm not sure either. Another possibility would be to only consider the LHS >> of binary operators, since returning a reference to the RHS is very unusual. > > ...I've removed the DECL_OVERLOADED_OPERATOR_P check altogether and don't > really see any false positives. It's probably better to start with a more > general warning and then tweak it based on empirical evidence. > >>>>> +// Invalid, but we don't warn here yet. >>>>> +// r12 = f (f ((const int &) &TARGET_EXPR <D.2459, 1>)) >>>>> +const int& r12 = f(f(1)); >>>> >>>> This should be a simple recursion? >>> >>> Hmm, the inner call is just a sub-expression of the full-expression so >>> there you can still use the returned temporary. But in this case the >>> temporary is used beyond the full-expression so it's invalid. I've added >>> >>> if (tree r = find_initializing_call_expr (arg)) >>> if (cp_tree_equal (CALL_EXPR_FN (r), CALL_EXPR_FN (expr))) >>> return expr; >>> >>> so that we detect f(f(1)) but I'm dubious that this is actually useful. >> >> Indeed, but why limit it to checking for the same function rather than also >> warning for >> >> f(g(1)) >> >> or >> >> A().f().g() >> >> ? > > OK, I've made the warning even more recursive so that now we detect > both of these, see Wdangling-reference3.C. While at it, I've renamed > the functions. > > As before, I've tested this patch twice, once with -Wdangling-reference > enabled by default, once with -Wdangling-reference enabled by -Wextra. > The couple of FAILs I saw were true positives (e.g., rv-conv1.C, rv-func2.C). I might actually add it to -Wall, the false positive rate sounds pretty low at this point. > Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk? > > -- >8 -- > This patch implements a new experimental warning (enabled by -Wextra) to > detect references bound to temporaries whose lifetime has ended. The > primary motivation is the Note in > <https://en.cppreference.com/w/cpp/algorithm/max>: > > Capturing the result of std::max by reference produces a dangling reference > if one of the parameters is a temporary and that parameter is returned: > > int n = 1; > const int& r = std::max(n-1, n+1); // r is dangling > > That's because both temporaries for n-1 and n+1 are destroyed at the end > of the full expression. With this warning enabled, you'll get: > > g.C:3:12: warning: possibly dangling reference to a temporary [-Wdangling-reference] > 3 | const int& r = std::max(n-1, n+1); > | ^ > g.C:3:24: note: the temporary was destroyed at the end of the full expression 'std::max<int>((n - 1), (n + 1))' > 3 | const int& r = std::max(n-1, n+1); > | ~~~~~~~~^~~~~~~~~~ > > The warning works by checking if a reference is initialized with a function > that returns a reference, and at least one parameter of the function is > a reference that is bound to a temporary. It assumes that such a function > actually returns one of its arguments! (I added code to check_return_expr > to suppress the warning when we've seen the definition of the function > and we can say that it can return a variable with static storage > duration.) > > It warns when the function in question is a member function, but only if > the function is invoked on a temporary object, otherwise the warning > would emit loads of warnings for valid code like obj.emplace<T>({0}, 0). > It does detect the dangling reference in: > > struct S { > const S& self () { return *this; } > }; > const S& s = S().self(); > > It warns in member initializer lists as well: > > const int& f(const int& i) { return i; } > struct S { > const int &r; > S() : r(f(10)) { } > }; > > I've run the testsuite/bootstrap with the warning enabled by default. > There were just a few FAILs, all of which look like genuine bugs. > A bootstrap with the warning enabled by default passed as well. > > When testing a previous version of the patch, there were many FAILs in > libstdc++'s 22_locale/; all of them because the warning triggered on > > const test_type& obj = std::use_facet<test_type>(std::locale()); > > but this code looks valid -- std::use_facet doesn't return a reference > to its parameter. Therefore I added a #pragma and code to suppress the > warning. > > PR c++/106393 > > gcc/c-family/ChangeLog: > > * c.opt (Wdangling-reference): New. > > gcc/cp/ChangeLog: > > * call.cc (expr_represents_temporary_p): New, factored out of... > (conv_binds_ref_to_temporary): ...here. Don't return false just > because a ck_base is missing. Use expr_represents_temporary_p. > (do_warn_dangling_reference): New. > (maybe_warn_dangling_reference): New. > (extend_ref_init_temps): Call maybe_warn_dangling_reference. > * typeck.cc (check_return_expr): Suppress -Wdangling-reference > warnings. > > gcc/ChangeLog: > > * doc/invoke.texi: Document -Wdangling-reference. > > libstdc++-v3/ChangeLog: > > * include/bits/locale_classes.tcc: Add #pragma to disable > -Wdangling-reference with std::use_facet. > > gcc/testsuite/ChangeLog: > > * g++.dg/cpp23/elision4.C: Use -Wdangling-reference, add dg-warning. > * g++.dg/cpp23/elision7.C: Likewise. > * g++.dg/warn/Wdangling-reference1.C: New test. > * g++.dg/warn/Wdangling-reference2.C: New test. > * g++.dg/warn/Wdangling-reference3.C: New test. > --- > gcc/c-family/c.opt | 4 + > gcc/cp/call.cc | 148 ++++++++++++++++-- > gcc/cp/cp-tree.h | 4 +- > gcc/cp/typeck.cc | 10 ++ > gcc/doc/invoke.texi | 40 ++++- > gcc/testsuite/g++.dg/cpp23/elision4.C | 5 +- > gcc/testsuite/g++.dg/cpp23/elision7.C | 3 +- > .../g++.dg/warn/Wdangling-reference1.C | 144 +++++++++++++++++ > .../g++.dg/warn/Wdangling-reference2.C | 28 ++++ > .../g++.dg/warn/Wdangling-reference3.C | 24 +++ > libstdc++-v3/include/bits/locale_classes.tcc | 3 + > 11 files changed, 396 insertions(+), 17 deletions(-) > create mode 100644 gcc/testsuite/g++.dg/warn/Wdangling-reference1.C > create mode 100644 gcc/testsuite/g++.dg/warn/Wdangling-reference2.C > create mode 100644 gcc/testsuite/g++.dg/warn/Wdangling-reference3.C > > diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt > index 01d480759ae..02d79991aeb 100644 > --- a/gcc/c-family/c.opt > +++ b/gcc/c-family/c.opt > @@ -555,6 +555,10 @@ Wdangling-pointer= > C ObjC C++ ObjC++ Joined RejectNegative UInteger Var(warn_dangling_pointer) Warning LangEnabledBy(C ObjC C++ ObjC++,Wall, 2, 0) IntegerRange(0, 2) > Warn for uses of pointers to auto variables whose lifetime has ended. > > +Wdangling-reference > +C++ ObjC++ Var(warn_dangling_reference) Warning LangEnabledBy(C++ ObjC++, Wextra) > +Warn when a reference is bound to a temporary whose lifetime has ended. > + > Wdate-time > C ObjC C++ ObjC++ CPP(warn_date_time) CppReason(CPP_W_DATE_TIME) Var(cpp_warn_date_time) Init(0) Warning > Warn about __TIME__, __DATE__ and __TIMESTAMP__ usage. > diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc > index 6a34e9c2ae1..dae7a33fb89 100644 > --- a/gcc/cp/call.cc > +++ b/gcc/cp/call.cc > @@ -9313,6 +9313,16 @@ conv_binds_ref_to_prvalue (conversion *c) > return conv_is_prvalue (next_conversion (c)); > } > > +/* True iff EXPR represents a (subobject of a) temporary. */ > + > +static bool > +expr_represents_temporary_p (tree expr) > +{ > + while (handled_component_p (expr)) > + expr = TREE_OPERAND (expr, 0); > + return TREE_CODE (expr) == TARGET_EXPR; > +} > + > /* True iff C is a conversion that binds a reference to a temporary. > This is a superset of conv_binds_ref_to_prvalue: here we're also > interested in xvalues. */ > @@ -9330,18 +9340,14 @@ conv_binds_ref_to_temporary (conversion *c) > struct Derived : Base {}; > const Base& b(Derived{}); > where we bind 'b' to the Base subobject of a temporary object of type > - Derived. The subobject is an xvalue; the whole object is a prvalue. */ > - if (c->kind != ck_base) > - return false; > - c = next_conversion (c); > - if (c->kind == ck_identity && c->u.expr) > - { > - tree expr = c->u.expr; > - while (handled_component_p (expr)) > - expr = TREE_OPERAND (expr, 0); > - if (TREE_CODE (expr) == TARGET_EXPR) > - return true; > - } > + Derived. The subobject is an xvalue; the whole object is a prvalue. > + > + The ck_base doesn't have to be present for cases like X{}.m. */ > + if (c->kind == ck_base) > + c = next_conversion (c); > + if (c->kind == ck_identity && c->u.expr > + && expr_represents_temporary_p (c->u.expr)) > + return true; > return false; > } > > @@ -13428,6 +13434,121 @@ initialize_reference (tree type, tree expr, > return expr; > } > > +/* Helper for maybe_warn_dangling_reference to find a problematic CALL_EXPR > + that initializes the LHS (and at least one of its arguments represents > + a temporary, as outlined in maybe_warn_dangling_reference), or NULL_TREE > + if none found. For instance: > + > + const S& s = S().self(); // S::self (&TARGET_EXPR <...>) > + const int& r = (42, f(1)); // f(1) > + const int& t = b ? f(1) : f(2); // f(1) > + const int& u = b ? f(1) : f(g); // f(1) > + const int& v = b ? f(g) : f(2); // f(2) > + const int& w = b ? f(g) : f(g); // NULL_TREE > + const int& y = (f(1), 42); // NULL_TREE > + const int& z = f(f(1)); // f(f(1)) > + > + EXPR is the initializer. */ > + > +static tree > +do_warn_dangling_reference (tree expr) > +{ > + STRIP_NOPS (expr); > + switch (TREE_CODE (expr)) > + { > + case CALL_EXPR: > + { > + tree fndecl = cp_get_callee_fndecl_nofold (expr); > + if (!fndecl > + || warning_suppressed_p (fndecl, OPT_Wdangling_reference) > + || !warning_enabled_at (DECL_SOURCE_LOCATION (fndecl), > + OPT_Wdangling_reference) > + /* If the function doesn't return a reference, don't warn. This > + can be e.g. > + const int& z = std::min({1, 2, 3, 4, 5, 6, 7}); > + which doesn't dangle: std::min here returns an int. */ > + || !TYPE_REF_P (TREE_TYPE (TREE_TYPE (fndecl)))) Maybe TYPE_REF_OBJ_P? Either way is fine. > + return NULL_TREE; > + > + /* Here we're looking to see if any of the arguments is a temporary > + initializing a reference parameter. */ > + for (int i = 0; i < call_expr_nargs (expr); ++i) > + { > + tree arg = CALL_EXPR_ARG (expr, i); > + /* Check that this argument initializes a reference, except for > + the argument initializing the object of a member function. */ > + if (!DECL_NONSTATIC_MEMBER_FUNCTION_P (fndecl) > + && !TYPE_REF_P (TREE_TYPE (arg))) > + continue; > + /* It could also be another call taking a temporary and returning > + it and initializing this reference parameter. */ > + if (do_warn_dangling_reference (arg)) > + return expr; > + STRIP_NOPS (arg); > + if (TREE_CODE (arg) == ADDR_EXPR) > + arg = TREE_OPERAND (arg, 0); > + if (expr_represents_temporary_p (arg)) > + return expr; > + /* Don't warn about member function like: > + std::any a(...); > + S& s = a.emplace<S>({0}, 0); > + which constructs a new object and returns a reference to it, but > + we still want to detect: > + struct S { const S& self () { return *this; } }; > + const S& s = S().self(); > + where 's' dangles. If we've gotten here, the object this function > + is invoked on is not a temporary. */ > + if (DECL_NONSTATIC_MEMBER_FUNCTION_P (fndecl)) > + break; > + } > + return NULL_TREE; > + } > + case COMPOUND_EXPR: > + return do_warn_dangling_reference (TREE_OPERAND (expr, 1)); > + case COND_EXPR: > + if (tree t = do_warn_dangling_reference (TREE_OPERAND (expr, 1))) > + return t; > + return do_warn_dangling_reference (TREE_OPERAND (expr, 2)); > + case PAREN_EXPR: > + return do_warn_dangling_reference (TREE_OPERAND (expr, 0)); > + default: > + return NULL_TREE; > + } > +} > + > +/* Implement -Wdangling-reference, to detect cases like > + > + int n = 1; > + const int& r = std::max(n - 1, n + 1); // r is dangling > + > + This creates temporaries from the arguments, returns a reference to > + one of the temporaries, but both temporaries are destroyed at the end > + of the full expression. > + > + This works by checking if a reference is initialized with a function > + that returns a reference, and at least one parameter of the function > + is a reference that is bound to a temporary. It assumes that such a > + function actually returns one of its arguments. > + > + DECL is the reference being initialized, INIT is the initializer. */ > + > +static void > +maybe_warn_dangling_reference (const_tree decl, tree init) > +{ > + if (!warn_dangling_reference) > + return; > + if (!TYPE_REF_P (TREE_TYPE (decl))) > + return; > + if (tree call = do_warn_dangling_reference (init)) > + { > + auto_diagnostic_group d; > + if (warning_at (DECL_SOURCE_LOCATION (decl), OPT_Wdangling_reference, > + "possibly dangling reference to a temporary")) > + inform (EXPR_LOCATION (call), "the temporary was destroyed at " > + "the end of the full expression %qE", call); > + } > +} > + > /* If *P is an xvalue expression, prevent temporary lifetime extension if it > gets used to initialize a reference. */ > > @@ -13525,6 +13646,9 @@ extend_ref_init_temps (tree decl, tree init, vec<tree, va_gc> **cleanups, > tree type = TREE_TYPE (init); > if (processing_template_decl) > return init; > + > + maybe_warn_dangling_reference (decl, init); > + > if (TYPE_REF_P (type)) > init = extend_ref_init_temps_1 (decl, init, cleanups, cond_guard); > else > diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h > index 867096b08c6..40f5bf802c3 100644 > --- a/gcc/cp/cp-tree.h > +++ b/gcc/cp/cp-tree.h > @@ -459,7 +459,6 @@ extern GTY(()) tree cp_global_trees[CPTI_MAX]; > TI_PENDING_TEMPLATE_FLAG. > TEMPLATE_PARMS_FOR_INLINE. > DELETE_EXPR_USE_VEC (in DELETE_EXPR). > - (TREE_CALLS_NEW) (in _EXPR or _REF) (commented-out). > ICS_ELLIPSIS_FLAG (in _CONV) > DECL_INITIALIZED_P (in VAR_DECL) > TYPENAME_IS_CLASS_P (in TYPENAME_TYPE) > @@ -4567,6 +4566,9 @@ get_vec_init_expr (tree t) > When appearing in a CONSTRUCTOR, the expression is an unconverted > compound literal. > > + When appearing in a CALL_EXPR, it means that it is a call to > + a constructor. > + > When appearing in a FIELD_DECL, it means that this field > has been duly initialized in its constructor. */ > #define TREE_HAS_CONSTRUCTOR(NODE) (TREE_LANG_FLAG_4 (NODE)) > diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc > index ab6979bcc50..54fac880d8c 100644 > --- a/gcc/cp/typeck.cc > +++ b/gcc/cp/typeck.cc > @@ -11246,6 +11246,16 @@ check_return_expr (tree retval, bool *no_warning) > if (processing_template_decl) > return saved_retval; > > + /* A naive attempt to reduce the number of -Wdangling-reference false > + positives: if we know that this function can return a variable with > + static storage duration rather than one of its parameters, suppress > + the warning. */ > + if (warn_dangling_reference > + && TYPE_REF_P (functype) > + && bare_retval You probably need to check VAR_P (bare_retval) here, static_flag is used for various other things in other tree codes. > + && TREE_STATIC (bare_retval)) > + suppress_warning (current_function_decl, OPT_Wdangling_reference); > + > /* Actually copy the value returned into the appropriate location. */ > if (retval && retval != result) > retval = cp_build_init_expr (result, retval); > diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi > index 64f77e8367a..0a3a174cdc8 100644 > --- a/gcc/doc/invoke.texi > +++ b/gcc/doc/invoke.texi > @@ -249,7 +249,8 @@ in the following sections. > -Wno-class-conversion -Wclass-memaccess @gol > -Wcomma-subscript -Wconditionally-supported @gol > -Wno-conversion-null -Wctad-maybe-unsupported @gol > --Wctor-dtor-privacy -Wno-delete-incomplete @gol > +-Wctor-dtor-privacy -Wdangling-reference @gol > +-Wno-delete-incomplete @gol > -Wdelete-non-virtual-dtor -Wno-deprecated-array-compare @gol > -Wdeprecated-copy -Wdeprecated-copy-dtor @gol > -Wno-deprecated-enum-enum-conversion -Wno-deprecated-enum-float-conversion @gol > @@ -3627,6 +3628,42 @@ public static member functions. Also warn if there are no non-private > methods, and there's at least one private member function that isn't > a constructor or destructor. > > +@item -Wdangling-reference @r{(C++ and Objective-C++ only)} > +@opindex Wdangling-reference > +@opindex Wno-dangling-reference > +Warn when a reference is bound to a temporary whose lifetime has ended. > +For example: > + > +@smallexample > +int n = 1; > +const int& r = std::max(n - 1, n + 1); // r is dangling > +@end smallexample > + > +In the example above, two temporaries are created, one for each > +argument, and a reference to one of the temporaries is returned. > +However, both temporaries are destroyed at the end of the full > +expression, so the reference @code{r} is dangling. This warning > +also detects dangling references in member initializer lists: > + > +@smallexample > +const int& f(const int& i) @{ return i; @} > +struct S @{ > + const int &r; // r is dangling > + S() : r(f(10)) @{ @} > +@}; > +@end smallexample > + > +Member functions are checked as well, but only their object argument: > + > +@smallexample > +struct S @{ > + const S& self () @{ return *this; @} > +@}; > +const S& s = S().self(); // s is dangling > +@end smallexample > + > +This warning is enabled by @option{-Wextra}. It would be good to mention using #pragma to disable the warning around safe functions. > @item -Wdelete-non-virtual-dtor @r{(C++ and Objective-C++ only)} > @opindex Wdelete-non-virtual-dtor > @opindex Wno-delete-non-virtual-dtor > @@ -5936,6 +5973,7 @@ name is still supported, but the newer name is more descriptive.) > > @gccoptlist{-Wclobbered @gol > -Wcast-function-type @gol > +-Wdangling-reference @r{(C++ only)} @gol > -Wdeprecated-copy @r{(C++ only)} @gol > -Wempty-body @gol > -Wenum-conversion @r{(C only)} @gol > diff --git a/gcc/testsuite/g++.dg/cpp23/elision4.C b/gcc/testsuite/g++.dg/cpp23/elision4.C > index c19b86b8b5f..d39053ad741 100644 > --- a/gcc/testsuite/g++.dg/cpp23/elision4.C > +++ b/gcc/testsuite/g++.dg/cpp23/elision4.C > @@ -1,5 +1,6 @@ > // PR c++/101165 - P2266R1 - Simpler implicit move > // { dg-do compile { target c++23 } } > +// { dg-options "-Wdangling-reference" } > // Test from P2266R1, $ 5.2. LibreOffice OString constructor. > > struct X { > @@ -33,6 +34,6 @@ T& temporary2(T&& x) { return static_cast<T&>(x); } > void > test () > { > - int& r1 = temporary1 (42); > - int& r2 = temporary2 (42); > + int& r1 = temporary1 (42); // { dg-warning "dangling reference" } > + int& r2 = temporary2 (42); // { dg-warning "dangling reference" } > } > diff --git a/gcc/testsuite/g++.dg/cpp23/elision7.C b/gcc/testsuite/g++.dg/cpp23/elision7.C > index 19fa89ae133..0045842b34f 100644 > --- a/gcc/testsuite/g++.dg/cpp23/elision7.C > +++ b/gcc/testsuite/g++.dg/cpp23/elision7.C > @@ -1,5 +1,6 @@ > // PR c++/101165 - P2266R1 - Simpler implicit move > // { dg-do compile { target c++23 } } > +// { dg-options "-Wdangling-reference" } > > struct X { > X (); > @@ -68,5 +69,5 @@ f7 (T &&t) > void > do_f7 () > { > - const int &x = f7 (0); > + const int &x = f7 (0); // { dg-warning "dangling reference" } > } > diff --git a/gcc/testsuite/g++.dg/warn/Wdangling-reference1.C b/gcc/testsuite/g++.dg/warn/Wdangling-reference1.C > new file mode 100644 > index 00000000000..97c81ee716c > --- /dev/null > +++ b/gcc/testsuite/g++.dg/warn/Wdangling-reference1.C > @@ -0,0 +1,144 @@ > +// PR c++/106393 > +// { dg-do compile { target c++11 } } > +// { dg-options "-Wdangling-reference" } > + > +const int& f(const int& i) { return i; } > +const int& f_(const int& i) { return i; } > +const int& h(int); > +const int& rp(const int *); > +int g; > +const int& globref(const int&) { return g; } > +struct X { > + int* i; > + operator const int&() const { return *i; } > +}; > +X x{&g}; > + > +const int& r1 = f(10); // { dg-warning "dangling reference" } > +// r2 = _ZGR2r2_ = (int) *f ((const int &) &TARGET_EXPR <D.2429, 10>) + 1; (const int &) &_ZGR2r2_ > +const int& r2 = f(10) + 1; > +// Don't warn here, we have > +// r3 = f (X::operator const int& (&x)) > +const int& r3 = f(x); > +// Don't warn here, because we've seen the definition of globref > +// and could figure out that it may not return one of its parms. > +// Questionable -- it can also hide bugs --, but it helps here. > +const int& r4 = globref(1); > +const int& r5 = (42, f(10)); // { dg-warning "dangling reference" } > +const int& r6 = (f(10), 42); > +const int& r7 = (f(10)); // { dg-warning "dangling reference" } > +const int& r8 = g ? f(10) : f(9); // { dg-warning "dangling reference" } > +const int& r9 = (42, g ? f(10) : f(9)); // { dg-warning "dangling reference" } > +const int& r10 = (g ? f(10) : f(9), 42); > +// Binds to a reference temporary for r11. No dangling reference. > +const int& r11 = g ? f(10) : 9; > +const int& r12 = g ? 9 : f(10); > +// r12 = f (f ((const int &) &TARGET_EXPR <D.2459, 1>)) > +const int& r13 = f(f(1)); // { dg-warning "dangling reference" } > +const int& r14 = f(f_(1)); // { dg-warning "dangling reference" } > +const int& r15 = f(g ? f(1) : f(2)); // { dg-warning "dangling reference" } > +const int& r16 = f(*&f(1)); // { dg-warning "dangling reference" } > +const int& r17 = rp(&f(1)); > +const int& r18 = rp(&f(g)); > +const int& r19 = h(f(1)); > +// Other forms of initializers. > +const int& r20(f(10)); // { dg-warning "dangling reference" } > +const int& r21(f(10)); // { dg-warning "dangling reference" } > +// Returns a ref, but doesn't have a parameter of reference type. > +const int& r22 = h(10); > +const int& r23 = g ? h(10) : f(10); // { dg-warning "dangling reference" } > +const int& r24 = g ? f(10) : h(10); // { dg-warning "dangling reference" } > +const int& r25 = g ? h(10) : (1, f(10)); // { dg-warning "dangling reference" } > +const int& r26 = g ? (1, f(10)) : h(10); // { dg-warning "dangling reference" } > +const int& r29 = f((f_(1), 1)); // { dg-warning "dangling reference" } > +const int& r30 = f((f_(1), g)); > + > +struct Z { > + operator int() { return 42; } > +}; > + > +const int& r27 = f(Z()); // { dg-warning "dangling reference" } > +const int& r28 = f(true ? Z() : Z()); // { dg-warning "dangling reference" } > + > +const int& operator|(const int &, Z); > +const int& r31 = 1 | Z(); // { dg-warning "dangling reference" } > + > +// OK: the reference is bound to the 10 so still valid at the point > +// where it's copied into i1. > +int i1 = f(10); > + > +int > +test1 () > +{ > + const int &lr = f(10); // { dg-warning "dangling reference" } > + int i2 = f(10); > + return lr; > +} > + > +struct B { }; > +struct D : B { }; > +struct C { > + D d; > +}; > + > +C c; > +D d; > + > +using U = D[3]; > + > +const B& frotz(const D&); > +const B& b1 = frotz(C{}.d); // { dg-warning "dangling reference" } > +const B& b2 = frotz(D{}); // { dg-warning "dangling reference" } > +const B& b3 = frotz(c.d); > +const B& b4 = frotz(d); > +const B& b5 = frotz(U{}[0]); // { dg-warning "dangling reference" } > + > +// Try returning a subobject. > +const B& bar (const D& d) { return d; } > +const B& b6 = bar (D{}); // { dg-warning "dangling reference" } > +const B& baz (const C& c) { return c.d; } > +const B& b7 = baz (C{}); // { dg-warning "dangling reference" } > +const D& qux (const C& c) { return c.d; } > +const D& d1 = qux (C{}); // { dg-warning "dangling reference" } > + > +struct E { > + E(int); > +}; > +const E& operator*(const E&); > +const E& b8 = *E(1); // { dg-warning "dangling reference" } > + > +struct F : virtual B { }; > +struct G : virtual B { }; > +struct H : F, G { }; > +const B& yum (const F& f) { return f; } > +const B& b9 = yum (F{}); // { dg-warning "dangling reference" } > +const B& lox (const H& h) { return h; } > +const B& b10 = lox (H{}); // { dg-warning "dangling reference" } > + > +struct S { > + const int &r; // { dg-warning "dangling reference" } > + S() : r(f(10)) { } // { dg-message "destroyed" } > +}; > + > +// From cppreference. > +template<class T> > +const T& max(const T& a, const T& b) > +{ > + return (a < b) ? b : a; > +} > + > +int n = 1; > +const int& refmax = max(n - 1, n + 1); // { dg-warning "dangling reference" } > + > +struct Y { > + operator int&(); > + operator int&&(); > + const int& foo(const int&); > +}; > + > +// x1 = Y::operator int&& (&TARGET_EXPR <D.2410, {}>) > +int&& x1 = Y(); // { dg-warning "dangling reference" } > +int&& x2 = Y{}; // { dg-warning "dangling reference" } > +int& x3 = Y(); // { dg-warning "dangling reference" } > +int& x4 = Y{}; // { dg-warning "dangling reference" } > +const int& t1 = Y().foo(10); // { dg-warning "dangling reference" } > diff --git a/gcc/testsuite/g++.dg/warn/Wdangling-reference2.C b/gcc/testsuite/g++.dg/warn/Wdangling-reference2.C > new file mode 100644 > index 00000000000..dafdb43f1b9 > --- /dev/null > +++ b/gcc/testsuite/g++.dg/warn/Wdangling-reference2.C > @@ -0,0 +1,28 @@ > +// PR c++/106393 > +// { dg-do compile { target c++11 } } > +// { dg-options "-Wdangling-reference" } > + > +namespace std { > +struct any {}; > +template <typename _ValueType> _ValueType any_cast(any &&); > +template <typename _Tp> struct remove_reference { using type = _Tp; }; > +template <typename _Tp> _Tp forward(typename remove_reference<_Tp>::type); > +template <typename _Tp> typename remove_reference<_Tp>::type move(_Tp); > +} // namespace std > + > +const int &r = std::any_cast<int&>(std::any()); // { dg-warning "dangling reference" } > + > +template <class T> struct C { > + T t_; // { dg-warning "dangling reference" } > + C(T); > + template <class U> C(U c) : t_(std::forward<T>(c.t_)) {} > +}; > +struct A {}; > +struct B { > + B(A); > +}; > +int main() { > + A a; > + C<A> ca(a); > + C<B &&>(std::move(ca)); > +} > diff --git a/gcc/testsuite/g++.dg/warn/Wdangling-reference3.C b/gcc/testsuite/g++.dg/warn/Wdangling-reference3.C > new file mode 100644 > index 00000000000..4bc20c13b3f > --- /dev/null > +++ b/gcc/testsuite/g++.dg/warn/Wdangling-reference3.C > @@ -0,0 +1,24 @@ > +// PR c++/106393 > +// { dg-do compile { target c++11 } } > +// { dg-options "-Wdangling-reference" } > + > +struct A { > + int ar[4]; > + int& operator[](int i) { return ar[i]; } > +}; > +const int &r = A()[2]; // { dg-warning "dangling reference" } > + > +struct S { > + const S& self () { return *this; } > +}; > +const S& s = S().self(); // { dg-warning "dangling reference" } > + > +struct G { > + const G& g() { return *this; } > +}; > + > +struct F { > + G& f(); > +}; > + > +const G& g = F().f().g(); // { dg-warning "dangling reference" } > diff --git a/libstdc++-v3/include/bits/locale_classes.tcc b/libstdc++-v3/include/bits/locale_classes.tcc > index 64cd7534dc6..9cc4f238ee7 100644 > --- a/libstdc++-v3/include/bits/locale_classes.tcc > +++ b/libstdc++-v3/include/bits/locale_classes.tcc > @@ -127,6 +127,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > * @return Reference to facet of type Facet. > * @throw std::bad_cast if @p __loc doesn't contain a facet of type _Facet. > */ > +#pragma GCC diagnostic push > +#pragma GCC diagnostic ignored "-Wdangling-reference" > template<typename _Facet> > const _Facet& > use_facet(const locale& __loc) > @@ -141,6 +143,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > return static_cast<const _Facet&>(*__facets[__i]); > #endif > } > +#pragma GCC diagnostic pop > > > // Generic version does nothing. > > base-commit: a87819b8f1b890d36a3f05bd9de80be20e9525dd
diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt index 01d480759ae..02d79991aeb 100644 --- a/gcc/c-family/c.opt +++ b/gcc/c-family/c.opt @@ -555,6 +555,10 @@ Wdangling-pointer= C ObjC C++ ObjC++ Joined RejectNegative UInteger Var(warn_dangling_pointer) Warning LangEnabledBy(C ObjC C++ ObjC++,Wall, 2, 0) IntegerRange(0, 2) Warn for uses of pointers to auto variables whose lifetime has ended. +Wdangling-reference +C++ ObjC++ Var(warn_dangling_reference) Warning LangEnabledBy(C++ ObjC++, Wextra) +Warn when a reference is bound to a temporary whose lifetime has ended. + Wdate-time C ObjC C++ ObjC++ CPP(warn_date_time) CppReason(CPP_W_DATE_TIME) Var(cpp_warn_date_time) Init(0) Warning Warn about __TIME__, __DATE__ and __TIMESTAMP__ usage. diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc index 6a34e9c2ae1..dae7a33fb89 100644 --- a/gcc/cp/call.cc +++ b/gcc/cp/call.cc @@ -9313,6 +9313,16 @@ conv_binds_ref_to_prvalue (conversion *c) return conv_is_prvalue (next_conversion (c)); } +/* True iff EXPR represents a (subobject of a) temporary. */ + +static bool +expr_represents_temporary_p (tree expr) +{ + while (handled_component_p (expr)) + expr = TREE_OPERAND (expr, 0); + return TREE_CODE (expr) == TARGET_EXPR; +} + /* True iff C is a conversion that binds a reference to a temporary. This is a superset of conv_binds_ref_to_prvalue: here we're also interested in xvalues. */ @@ -9330,18 +9340,14 @@ conv_binds_ref_to_temporary (conversion *c) struct Derived : Base {}; const Base& b(Derived{}); where we bind 'b' to the Base subobject of a temporary object of type - Derived. The subobject is an xvalue; the whole object is a prvalue. */ - if (c->kind != ck_base) - return false; - c = next_conversion (c); - if (c->kind == ck_identity && c->u.expr) - { - tree expr = c->u.expr; - while (handled_component_p (expr)) - expr = TREE_OPERAND (expr, 0); - if (TREE_CODE (expr) == TARGET_EXPR) - return true; - } + Derived. The subobject is an xvalue; the whole object is a prvalue. + + The ck_base doesn't have to be present for cases like X{}.m. */ + if (c->kind == ck_base) + c = next_conversion (c); + if (c->kind == ck_identity && c->u.expr + && expr_represents_temporary_p (c->u.expr)) + return true; return false; } @@ -13428,6 +13434,121 @@ initialize_reference (tree type, tree expr, return expr; } +/* Helper for maybe_warn_dangling_reference to find a problematic CALL_EXPR + that initializes the LHS (and at least one of its arguments represents + a temporary, as outlined in maybe_warn_dangling_reference), or NULL_TREE + if none found. For instance: + + const S& s = S().self(); // S::self (&TARGET_EXPR <...>) + const int& r = (42, f(1)); // f(1) + const int& t = b ? f(1) : f(2); // f(1) + const int& u = b ? f(1) : f(g); // f(1) + const int& v = b ? f(g) : f(2); // f(2) + const int& w = b ? f(g) : f(g); // NULL_TREE + const int& y = (f(1), 42); // NULL_TREE + const int& z = f(f(1)); // f(f(1)) + + EXPR is the initializer. */ + +static tree +do_warn_dangling_reference (tree expr) +{ + STRIP_NOPS (expr); + switch (TREE_CODE (expr)) + { + case CALL_EXPR: + { + tree fndecl = cp_get_callee_fndecl_nofold (expr); + if (!fndecl + || warning_suppressed_p (fndecl, OPT_Wdangling_reference) + || !warning_enabled_at (DECL_SOURCE_LOCATION (fndecl), + OPT_Wdangling_reference) + /* If the function doesn't return a reference, don't warn. This + can be e.g. + const int& z = std::min({1, 2, 3, 4, 5, 6, 7}); + which doesn't dangle: std::min here returns an int. */ + || !TYPE_REF_P (TREE_TYPE (TREE_TYPE (fndecl)))) + return NULL_TREE; + + /* Here we're looking to see if any of the arguments is a temporary + initializing a reference parameter. */ + for (int i = 0; i < call_expr_nargs (expr); ++i) + { + tree arg = CALL_EXPR_ARG (expr, i); + /* Check that this argument initializes a reference, except for + the argument initializing the object of a member function. */ + if (!DECL_NONSTATIC_MEMBER_FUNCTION_P (fndecl) + && !TYPE_REF_P (TREE_TYPE (arg))) + continue; + /* It could also be another call taking a temporary and returning + it and initializing this reference parameter. */ + if (do_warn_dangling_reference (arg)) + return expr; + STRIP_NOPS (arg); + if (TREE_CODE (arg) == ADDR_EXPR) + arg = TREE_OPERAND (arg, 0); + if (expr_represents_temporary_p (arg)) + return expr; + /* Don't warn about member function like: + std::any a(...); + S& s = a.emplace<S>({0}, 0); + which constructs a new object and returns a reference to it, but + we still want to detect: + struct S { const S& self () { return *this; } }; + const S& s = S().self(); + where 's' dangles. If we've gotten here, the object this function + is invoked on is not a temporary. */ + if (DECL_NONSTATIC_MEMBER_FUNCTION_P (fndecl)) + break; + } + return NULL_TREE; + } + case COMPOUND_EXPR: + return do_warn_dangling_reference (TREE_OPERAND (expr, 1)); + case COND_EXPR: + if (tree t = do_warn_dangling_reference (TREE_OPERAND (expr, 1))) + return t; + return do_warn_dangling_reference (TREE_OPERAND (expr, 2)); + case PAREN_EXPR: + return do_warn_dangling_reference (TREE_OPERAND (expr, 0)); + default: + return NULL_TREE; + } +} + +/* Implement -Wdangling-reference, to detect cases like + + int n = 1; + const int& r = std::max(n - 1, n + 1); // r is dangling + + This creates temporaries from the arguments, returns a reference to + one of the temporaries, but both temporaries are destroyed at the end + of the full expression. + + This works by checking if a reference is initialized with a function + that returns a reference, and at least one parameter of the function + is a reference that is bound to a temporary. It assumes that such a + function actually returns one of its arguments. + + DECL is the reference being initialized, INIT is the initializer. */ + +static void +maybe_warn_dangling_reference (const_tree decl, tree init) +{ + if (!warn_dangling_reference) + return; + if (!TYPE_REF_P (TREE_TYPE (decl))) + return; + if (tree call = do_warn_dangling_reference (init)) + { + auto_diagnostic_group d; + if (warning_at (DECL_SOURCE_LOCATION (decl), OPT_Wdangling_reference, + "possibly dangling reference to a temporary")) + inform (EXPR_LOCATION (call), "the temporary was destroyed at " + "the end of the full expression %qE", call); + } +} + /* If *P is an xvalue expression, prevent temporary lifetime extension if it gets used to initialize a reference. */ @@ -13525,6 +13646,9 @@ extend_ref_init_temps (tree decl, tree init, vec<tree, va_gc> **cleanups, tree type = TREE_TYPE (init); if (processing_template_decl) return init; + + maybe_warn_dangling_reference (decl, init); + if (TYPE_REF_P (type)) init = extend_ref_init_temps_1 (decl, init, cleanups, cond_guard); else diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h index 867096b08c6..40f5bf802c3 100644 --- a/gcc/cp/cp-tree.h +++ b/gcc/cp/cp-tree.h @@ -459,7 +459,6 @@ extern GTY(()) tree cp_global_trees[CPTI_MAX]; TI_PENDING_TEMPLATE_FLAG. TEMPLATE_PARMS_FOR_INLINE. DELETE_EXPR_USE_VEC (in DELETE_EXPR). - (TREE_CALLS_NEW) (in _EXPR or _REF) (commented-out). ICS_ELLIPSIS_FLAG (in _CONV) DECL_INITIALIZED_P (in VAR_DECL) TYPENAME_IS_CLASS_P (in TYPENAME_TYPE) @@ -4567,6 +4566,9 @@ get_vec_init_expr (tree t) When appearing in a CONSTRUCTOR, the expression is an unconverted compound literal. + When appearing in a CALL_EXPR, it means that it is a call to + a constructor. + When appearing in a FIELD_DECL, it means that this field has been duly initialized in its constructor. */ #define TREE_HAS_CONSTRUCTOR(NODE) (TREE_LANG_FLAG_4 (NODE)) diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc index ab6979bcc50..54fac880d8c 100644 --- a/gcc/cp/typeck.cc +++ b/gcc/cp/typeck.cc @@ -11246,6 +11246,16 @@ check_return_expr (tree retval, bool *no_warning) if (processing_template_decl) return saved_retval; + /* A naive attempt to reduce the number of -Wdangling-reference false + positives: if we know that this function can return a variable with + static storage duration rather than one of its parameters, suppress + the warning. */ + if (warn_dangling_reference + && TYPE_REF_P (functype) + && bare_retval + && TREE_STATIC (bare_retval)) + suppress_warning (current_function_decl, OPT_Wdangling_reference); + /* Actually copy the value returned into the appropriate location. */ if (retval && retval != result) retval = cp_build_init_expr (result, retval); diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index 64f77e8367a..0a3a174cdc8 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -249,7 +249,8 @@ in the following sections. -Wno-class-conversion -Wclass-memaccess @gol -Wcomma-subscript -Wconditionally-supported @gol -Wno-conversion-null -Wctad-maybe-unsupported @gol --Wctor-dtor-privacy -Wno-delete-incomplete @gol +-Wctor-dtor-privacy -Wdangling-reference @gol +-Wno-delete-incomplete @gol -Wdelete-non-virtual-dtor -Wno-deprecated-array-compare @gol -Wdeprecated-copy -Wdeprecated-copy-dtor @gol -Wno-deprecated-enum-enum-conversion -Wno-deprecated-enum-float-conversion @gol @@ -3627,6 +3628,42 @@ public static member functions. Also warn if there are no non-private methods, and there's at least one private member function that isn't a constructor or destructor. +@item -Wdangling-reference @r{(C++ and Objective-C++ only)} +@opindex Wdangling-reference +@opindex Wno-dangling-reference +Warn when a reference is bound to a temporary whose lifetime has ended. +For example: + +@smallexample +int n = 1; +const int& r = std::max(n - 1, n + 1); // r is dangling +@end smallexample + +In the example above, two temporaries are created, one for each +argument, and a reference to one of the temporaries is returned. +However, both temporaries are destroyed at the end of the full +expression, so the reference @code{r} is dangling. This warning +also detects dangling references in member initializer lists: + +@smallexample +const int& f(const int& i) @{ return i; @} +struct S @{ + const int &r; // r is dangling + S() : r(f(10)) @{ @} +@}; +@end smallexample + +Member functions are checked as well, but only their object argument: + +@smallexample +struct S @{ + const S& self () @{ return *this; @} +@}; +const S& s = S().self(); // s is dangling +@end smallexample + +This warning is enabled by @option{-Wextra}. + @item -Wdelete-non-virtual-dtor @r{(C++ and Objective-C++ only)} @opindex Wdelete-non-virtual-dtor @opindex Wno-delete-non-virtual-dtor @@ -5936,6 +5973,7 @@ name is still supported, but the newer name is more descriptive.) @gccoptlist{-Wclobbered @gol -Wcast-function-type @gol +-Wdangling-reference @r{(C++ only)} @gol -Wdeprecated-copy @r{(C++ only)} @gol -Wempty-body @gol -Wenum-conversion @r{(C only)} @gol diff --git a/gcc/testsuite/g++.dg/cpp23/elision4.C b/gcc/testsuite/g++.dg/cpp23/elision4.C index c19b86b8b5f..d39053ad741 100644 --- a/gcc/testsuite/g++.dg/cpp23/elision4.C +++ b/gcc/testsuite/g++.dg/cpp23/elision4.C @@ -1,5 +1,6 @@ // PR c++/101165 - P2266R1 - Simpler implicit move // { dg-do compile { target c++23 } } +// { dg-options "-Wdangling-reference" } // Test from P2266R1, $ 5.2. LibreOffice OString constructor. struct X { @@ -33,6 +34,6 @@ T& temporary2(T&& x) { return static_cast<T&>(x); } void test () { - int& r1 = temporary1 (42); - int& r2 = temporary2 (42); + int& r1 = temporary1 (42); // { dg-warning "dangling reference" } + int& r2 = temporary2 (42); // { dg-warning "dangling reference" } } diff --git a/gcc/testsuite/g++.dg/cpp23/elision7.C b/gcc/testsuite/g++.dg/cpp23/elision7.C index 19fa89ae133..0045842b34f 100644 --- a/gcc/testsuite/g++.dg/cpp23/elision7.C +++ b/gcc/testsuite/g++.dg/cpp23/elision7.C @@ -1,5 +1,6 @@ // PR c++/101165 - P2266R1 - Simpler implicit move // { dg-do compile { target c++23 } } +// { dg-options "-Wdangling-reference" } struct X { X (); @@ -68,5 +69,5 @@ f7 (T &&t) void do_f7 () { - const int &x = f7 (0); + const int &x = f7 (0); // { dg-warning "dangling reference" } } diff --git a/gcc/testsuite/g++.dg/warn/Wdangling-reference1.C b/gcc/testsuite/g++.dg/warn/Wdangling-reference1.C new file mode 100644 index 00000000000..97c81ee716c --- /dev/null +++ b/gcc/testsuite/g++.dg/warn/Wdangling-reference1.C @@ -0,0 +1,144 @@ +// PR c++/106393 +// { dg-do compile { target c++11 } } +// { dg-options "-Wdangling-reference" } + +const int& f(const int& i) { return i; } +const int& f_(const int& i) { return i; } +const int& h(int); +const int& rp(const int *); +int g; +const int& globref(const int&) { return g; } +struct X { + int* i; + operator const int&() const { return *i; } +}; +X x{&g}; + +const int& r1 = f(10); // { dg-warning "dangling reference" } +// r2 = _ZGR2r2_ = (int) *f ((const int &) &TARGET_EXPR <D.2429, 10>) + 1; (const int &) &_ZGR2r2_ +const int& r2 = f(10) + 1; +// Don't warn here, we have +// r3 = f (X::operator const int& (&x)) +const int& r3 = f(x); +// Don't warn here, because we've seen the definition of globref +// and could figure out that it may not return one of its parms. +// Questionable -- it can also hide bugs --, but it helps here. +const int& r4 = globref(1); +const int& r5 = (42, f(10)); // { dg-warning "dangling reference" } +const int& r6 = (f(10), 42); +const int& r7 = (f(10)); // { dg-warning "dangling reference" } +const int& r8 = g ? f(10) : f(9); // { dg-warning "dangling reference" } +const int& r9 = (42, g ? f(10) : f(9)); // { dg-warning "dangling reference" } +const int& r10 = (g ? f(10) : f(9), 42); +// Binds to a reference temporary for r11. No dangling reference. +const int& r11 = g ? f(10) : 9; +const int& r12 = g ? 9 : f(10); +// r12 = f (f ((const int &) &TARGET_EXPR <D.2459, 1>)) +const int& r13 = f(f(1)); // { dg-warning "dangling reference" } +const int& r14 = f(f_(1)); // { dg-warning "dangling reference" } +const int& r15 = f(g ? f(1) : f(2)); // { dg-warning "dangling reference" } +const int& r16 = f(*&f(1)); // { dg-warning "dangling reference" } +const int& r17 = rp(&f(1)); +const int& r18 = rp(&f(g)); +const int& r19 = h(f(1)); +// Other forms of initializers. +const int& r20(f(10)); // { dg-warning "dangling reference" } +const int& r21(f(10)); // { dg-warning "dangling reference" } +// Returns a ref, but doesn't have a parameter of reference type. +const int& r22 = h(10); +const int& r23 = g ? h(10) : f(10); // { dg-warning "dangling reference" } +const int& r24 = g ? f(10) : h(10); // { dg-warning "dangling reference" } +const int& r25 = g ? h(10) : (1, f(10)); // { dg-warning "dangling reference" } +const int& r26 = g ? (1, f(10)) : h(10); // { dg-warning "dangling reference" } +const int& r29 = f((f_(1), 1)); // { dg-warning "dangling reference" } +const int& r30 = f((f_(1), g)); + +struct Z { + operator int() { return 42; } +}; + +const int& r27 = f(Z()); // { dg-warning "dangling reference" } +const int& r28 = f(true ? Z() : Z()); // { dg-warning "dangling reference" } + +const int& operator|(const int &, Z); +const int& r31 = 1 | Z(); // { dg-warning "dangling reference" } + +// OK: the reference is bound to the 10 so still valid at the point +// where it's copied into i1. +int i1 = f(10); + +int +test1 () +{ + const int &lr = f(10); // { dg-warning "dangling reference" } + int i2 = f(10); + return lr; +} + +struct B { }; +struct D : B { }; +struct C { + D d; +}; + +C c; +D d; + +using U = D[3]; + +const B& frotz(const D&); +const B& b1 = frotz(C{}.d); // { dg-warning "dangling reference" } +const B& b2 = frotz(D{}); // { dg-warning "dangling reference" } +const B& b3 = frotz(c.d); +const B& b4 = frotz(d); +const B& b5 = frotz(U{}[0]); // { dg-warning "dangling reference" } + +// Try returning a subobject. +const B& bar (const D& d) { return d; } +const B& b6 = bar (D{}); // { dg-warning "dangling reference" } +const B& baz (const C& c) { return c.d; } +const B& b7 = baz (C{}); // { dg-warning "dangling reference" } +const D& qux (const C& c) { return c.d; } +const D& d1 = qux (C{}); // { dg-warning "dangling reference" } + +struct E { + E(int); +}; +const E& operator*(const E&); +const E& b8 = *E(1); // { dg-warning "dangling reference" } + +struct F : virtual B { }; +struct G : virtual B { }; +struct H : F, G { }; +const B& yum (const F& f) { return f; } +const B& b9 = yum (F{}); // { dg-warning "dangling reference" } +const B& lox (const H& h) { return h; } +const B& b10 = lox (H{}); // { dg-warning "dangling reference" } + +struct S { + const int &r; // { dg-warning "dangling reference" } + S() : r(f(10)) { } // { dg-message "destroyed" } +}; + +// From cppreference. +template<class T> +const T& max(const T& a, const T& b) +{ + return (a < b) ? b : a; +} + +int n = 1; +const int& refmax = max(n - 1, n + 1); // { dg-warning "dangling reference" } + +struct Y { + operator int&(); + operator int&&(); + const int& foo(const int&); +}; + +// x1 = Y::operator int&& (&TARGET_EXPR <D.2410, {}>) +int&& x1 = Y(); // { dg-warning "dangling reference" } +int&& x2 = Y{}; // { dg-warning "dangling reference" } +int& x3 = Y(); // { dg-warning "dangling reference" } +int& x4 = Y{}; // { dg-warning "dangling reference" } +const int& t1 = Y().foo(10); // { dg-warning "dangling reference" } diff --git a/gcc/testsuite/g++.dg/warn/Wdangling-reference2.C b/gcc/testsuite/g++.dg/warn/Wdangling-reference2.C new file mode 100644 index 00000000000..dafdb43f1b9 --- /dev/null +++ b/gcc/testsuite/g++.dg/warn/Wdangling-reference2.C @@ -0,0 +1,28 @@ +// PR c++/106393 +// { dg-do compile { target c++11 } } +// { dg-options "-Wdangling-reference" } + +namespace std { +struct any {}; +template <typename _ValueType> _ValueType any_cast(any &&); +template <typename _Tp> struct remove_reference { using type = _Tp; }; +template <typename _Tp> _Tp forward(typename remove_reference<_Tp>::type); +template <typename _Tp> typename remove_reference<_Tp>::type move(_Tp); +} // namespace std + +const int &r = std::any_cast<int&>(std::any()); // { dg-warning "dangling reference" } + +template <class T> struct C { + T t_; // { dg-warning "dangling reference" } + C(T); + template <class U> C(U c) : t_(std::forward<T>(c.t_)) {} +}; +struct A {}; +struct B { + B(A); +}; +int main() { + A a; + C<A> ca(a); + C<B &&>(std::move(ca)); +} diff --git a/gcc/testsuite/g++.dg/warn/Wdangling-reference3.C b/gcc/testsuite/g++.dg/warn/Wdangling-reference3.C new file mode 100644 index 00000000000..4bc20c13b3f --- /dev/null +++ b/gcc/testsuite/g++.dg/warn/Wdangling-reference3.C @@ -0,0 +1,24 @@ +// PR c++/106393 +// { dg-do compile { target c++11 } } +// { dg-options "-Wdangling-reference" } + +struct A { + int ar[4]; + int& operator[](int i) { return ar[i]; } +}; +const int &r = A()[2]; // { dg-warning "dangling reference" } + +struct S { + const S& self () { return *this; } +}; +const S& s = S().self(); // { dg-warning "dangling reference" } + +struct G { + const G& g() { return *this; } +}; + +struct F { + G& f(); +}; + +const G& g = F().f().g(); // { dg-warning "dangling reference" } diff --git a/libstdc++-v3/include/bits/locale_classes.tcc b/libstdc++-v3/include/bits/locale_classes.tcc index 64cd7534dc6..9cc4f238ee7 100644 --- a/libstdc++-v3/include/bits/locale_classes.tcc +++ b/libstdc++-v3/include/bits/locale_classes.tcc @@ -127,6 +127,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION * @return Reference to facet of type Facet. * @throw std::bad_cast if @p __loc doesn't contain a facet of type _Facet. */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdangling-reference" template<typename _Facet> const _Facet& use_facet(const locale& __loc) @@ -141,6 +143,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION return static_cast<const _Facet&>(*__facets[__i]); #endif } +#pragma GCC diagnostic pop // Generic version does nothing.