diff mbox

RFC: Add C++ warnings for unnecessarily general return types

Message ID 87lhg7l1v3.fsf@e105548-lin.cambridge.arm.com
State New
Headers show

Commit Message

Richard Sandiford May 29, 2015, 5:39 p.m. UTC
One of the main early aims of the rtl refactoring is to finally separate
instructions (rtx_insns) from other rtxes.  I thought it would help if the
compiler could warn about cases where a function returns rtx when it
could return rtx_insn*, or where a variable has type rtx but could have
type rtx_insn*.

This patch tries to implement something along those lines.  There are two
warning options, one for function returns and one for variables.  There are
obviously many good reasons why a functon or variable might have a more general
type than it might appear to need, so the options certainly aren't -Wall or
-Wextra material.  But it might be worth having them as stage 2 and 3 warnings.

I have a local patch that fixes all instances of the warnings in an
x86_64-linux-gnu bootstrap.

This is very much an RFC.  Maybe the options are too special-purpose
to be worth including.  Or, if they are worth including, there are
probably interesting corner cases that I've not thought about.

The option did find a couple of useful things besides rtx->rtx_insn*
though.  For example, in ipa-comdats.c:enqueue_references we have:

	    symtab_node *node = edge->callee->ultimate_alias_target ();

	    /* Always keep thunks in same sections as target function.  */
	    if (is_a <cgraph_node *>(node))
	      node = dyn_cast <cgraph_node *> (node)->function_symbol ();
	    if (!node->aux && node->definition)
	      {
		 node->aux = *first;
		 *first = node;
	      }

ultimate_alias_target always returns a cgraph_node*, so the is_a is
always true.  There are also two cases where we have unnecessary
safe_as_a <rtx_insn *>s.

Thanks,
Richard


gcc/
	* doc/invoke.texi (-Wupcast-result, -Wupcast-var): Document.
	* doc/extend.texi (no_upcast_warning): Document.

gcc/c-family/
	* c.opt (Wupcast-result, Wupcast-var): New options.
	* c-common.c (c_common_attribute_table): Add no_upcast_warning.
	(handle_no_upcast_warning_attribute): New function.

gcc/cp/
	* cp-tree.h (cp_decl_target_types): New structure.
	(cp_decl_target_types_hasher): Likewise.
	(language_function): Add x_result_target_types and x_decl_target_types.
	(current_function_return_target_types): New macro.
	(current_function_decl_target_types): Likewise.
	(upcast_var_record_type): Declare.
	* decl.c (get_common_target, get_upcast_warning_type): New functions.
	(poplevel): Handle warn_upcast_var.
	(finish_function): Handle warn_upcast_result.  Clear out new
	language_function fields.
	* typeck.c (cp_decl_target_types_hasher::hash): New function.
	(cp_decl_target_types_hasher::equal): Likewise.
	(joust_upcast_types, upcast_var_record_type): Likewise.
	(cp_build_modify_expr): Record upcasts for warn_upcast_var.
	(check_return_expr): Likewise warn_upcast_result.
	* typeck2.c (store_init_value): Record upcasts for warn_upcast_var.

gcc/testsuite/
	* g++.dg/warn/Wupcast-result-1.C, g++.dg/warn/Wupcast-result-2.C,
	g++.dg/warn/Wupcast-result-3.C, g++.dg/warn/Wupcast-result-4.C,
	g++.dg/warn/Wupcast-var-1.C: New tests.

Comments

Jeff Law May 30, 2015, 5:14 a.m. UTC | #1
On 05/29/2015 11:39 AM, Richard Sandiford wrote:
> One of the main early aims of the rtl refactoring is to finally separate
> instructions (rtx_insns) from other rtxes.  I thought it would help if the
> compiler could warn about cases where a function returns rtx when it
> could return rtx_insn*, or where a variable has type rtx but could have
> type rtx_insn*.
>
> This patch tries to implement something along those lines.  There are two
> warning options, one for function returns and one for variables.  There are
> obviously many good reasons why a functon or variable might have a more general
> type than it might appear to need, so the options certainly aren't -Wall or
> -Wextra material.  But it might be worth having them as stage 2 and 3 warnings.
I can't speak to the implementation as it hits the C++ front-end where 
my knowledge approaches zero.  But I can say that I really like what 
you're trying to do here.  I can easily see how it'd be useful for GCC.

The big question in my mind is how useful would this be on other 
codebases --  I have a hard time believing that GCC is the only codebase 
which has these problems.

>
> I have a local patch that fixes all instances of the warnings in an
> x86_64-linux-gnu bootstrap.
Very cool.  It allows us to find a whole class of strengthening cases 
that we can get virtually for free.  Leaving more time for the harder stuff.


>
> This is very much an RFC.  Maybe the options are too special-purpose
> to be worth including.  Or, if they are worth including, there are
> probably interesting corner cases that I've not thought about.
>
> The option did find a couple of useful things besides rtx->rtx_insn*
> though.  For example, in ipa-comdats.c:enqueue_references we have:
>
> 	    symtab_node *node = edge->callee->ultimate_alias_target ();
>
> 	    /* Always keep thunks in same sections as target function.  */
> 	    if (is_a <cgraph_node *>(node))
> 	      node = dyn_cast <cgraph_node *> (node)->function_symbol ();
> 	    if (!node->aux && node->definition)
> 	      {
> 		 node->aux = *first;
> 		 *first = node;
> 	      }
>
> ultimate_alias_target always returns a cgraph_node*, so the is_a is
> always true.  There are also two cases where we have unnecessary
> safe_as_a <rtx_insn *>s.
And as I've stated before, the is_a, as_a and safe_as_a are in my mind 
are really just markers for the boundaries of where we've pushed the 
rtx_insn * changes.  I hope and expect that as the work as a whole 
progresses many of those will just disappear because they're not needed.

Thanks for working on this.  I hope the C++ maintainers look upon it 
favorably.

jeff
Trevor Saunders May 30, 2015, 9:40 p.m. UTC | #2
On Fri, May 29, 2015 at 11:14:46PM -0600, Jeff Law wrote:
> On 05/29/2015 11:39 AM, Richard Sandiford wrote:
> >One of the main early aims of the rtl refactoring is to finally separate
> >instructions (rtx_insns) from other rtxes.  I thought it would help if the
> >compiler could warn about cases where a function returns rtx when it
> >could return rtx_insn*, or where a variable has type rtx but could have
> >type rtx_insn*.
> >
> >This patch tries to implement something along those lines.  There are two
> >warning options, one for function returns and one for variables.  There are
> >obviously many good reasons why a functon or variable might have a more general
> >type than it might appear to need, so the options certainly aren't -Wall or
> >-Wextra material.  But it might be worth having them as stage 2 and 3 warnings.
> I can't speak to the implementation as it hits the C++ front-end where my
> knowledge approaches zero.  But I can say that I really like what you're
> trying to do here.  I can easily see how it'd be useful for GCC.

Yeah, I did a bit of this manually a while ago, but automating things is
good.

It would also be useful to have something for arguments, but I guess
that's harder and requires LTO, or at least some post processing.

It would also be useful and also much harder to be able to automatically
rewrite as well as detect these cases.

> The big question in my mind is how useful would this be on other codebases
> --  I have a hard time believing that GCC is the only codebase which has
> these problems.

I'm tempted to agree.  I was going to say that I couldn't think of
another example off the top of my head, but actually I think there are
some things I could use it for or at least I'd give it a try for.

The other more generic approach is to make it easier to write plugins to
do this sort of thing.  I've been pondering writing a checker plugin for
gcc's code for a little while but haven't started on anything yet.

Trev
diff mbox

Patch

Index: gcc/doc/invoke.texi
===================================================================
--- gcc/doc/invoke.texi	2015-05-27 14:37:58.553464723 +0100
+++ gcc/doc/invoke.texi	2015-05-27 15:44:05.624385485 +0100
@@ -288,6 +288,7 @@  Objective-C and Objective-C++ Dialects}.
 -Wunused-label  -Wunused-local-typedefs -Wunused-parameter @gol
 -Wno-unused-result -Wunused-value @gol -Wunused-variable @gol
 -Wunused-but-set-parameter -Wunused-but-set-variable @gol
+-Wupcast-result -Wupcast-var @gol
 -Wuseless-cast -Wvariadic-macros -Wvector-operation-performance @gol
 -Wvla -Wvolatile-register-var  -Wwrite-strings @gol
 -Wzero-as-null-pointer-constant}
@@ -4879,6 +4880,43 @@  compilations.
 Warn when deleting a pointer to incomplete type, which may cause
 undefined behavior at runtime.  This warning is enabled by default.
 
+@item -Wupcast-result @r{(C++ and Objective-C++ only)}
+@opindex Wupcast-result
+@opindex Wno-upcast-result
+Warn about functions whose return type is more general than it needs
+to be.  Specifically, if a function returns a pointer or reference
+to some base class @var{B} and all nonnull returns have an implicit
+upcast from a derived class, warn if there is a derived class @var{D}
+that would satisfy all such returns.
+
+The warning does not consider @var{D} to be a candidate if it is in an
+anonymous namespace or if it has the @code{no_upcast_warning} attribute.
+It also ignores returns from virtual functions, where general types are
+likely to be deliberate.
+
+For example, the warning would trigger on the following function,
+which could return @code{D1*} rather than @code{B*}:
+@cindex @code{no_upcast_warning} type attribute
+@smallexample
+struct B @{@};
+struct D1 : public B @{@};
+struct D2 : public D1 @{@};
+B *f(int x) @{ if (x) return new D1; else return new D2; @}
+@end smallexample
+
+@item -Wupcast-var @r{(C++ and Objective-C++ only)}
+@opindex Wupcast-var
+@opindex Wno-upcast-var
+Like @option{-Wupcast-result}, but warn about local variables that are
+more general than they need to be.  For example, the warning would trigger
+on @code{x} in the following function, which could have type @code{D1*}
+rather than @code{B}:
+@smallexample
+struct B @{@};
+struct D1 : public B @{@};
+void f() @{ B *x = new D1; @}
+@end smallexample
+
 @item -Wuseless-cast @r{(C++ and Objective-C++ only)}
 @opindex Wuseless-cast
 @opindex Wno-useless-cast
Index: gcc/doc/extend.texi
===================================================================
--- gcc/doc/extend.texi	2015-05-26 12:00:59.000225870 +0100
+++ gcc/doc/extend.texi	2015-05-27 15:45:25.975471274 +0100
@@ -6179,6 +6179,19 @@  the case with lock or thread classes, wh
 not referenced, but contain constructors and destructors that have
 nontrivial bookkeeping functions.
 
+@item no_upcast_warning
+@cindex @code{no_upcast_warning} type attribute
+Specifies that a struct or class should be ignored by the C++ options
+@option{-Wupcast-result} and @option{-Wupcast-var} when searching for
+a more derived class.  For example:
+@smallexample
+struct B @{@};
+struct __attribute__((no_upcast_warning)) D1 : public B @{@};
+B *f(int x) @{ return new D1; @}
+@end smallexample
+would not trigger a warning even when @option{-Wupcast-result} is
+in effect.
+
 @item visibility
 @cindex @code{visibility} type attribute
 In C++, attribute visibility (@pxref{Function Attributes}) can also be
@@ -6192,7 +6205,6 @@  particular, if a class is thrown as an e
 and caught in another, the class must have default visibility.
 Otherwise the two shared objects are unable to use the same
 typeinfo node and exception handling will break.
-
 @end table
 
 To specify multiple attributes, separate them by commas within the
Index: gcc/c-family/c.opt
===================================================================
--- gcc/c-family/c.opt	2015-05-27 14:37:58.553464723 +0100
+++ gcc/c-family/c.opt	2015-05-27 14:37:58.541464861 +0100
@@ -896,6 +896,14 @@  Wunused-result
 C ObjC C++ ObjC++ Var(warn_unused_result) Init(1) Warning
 Warn if a caller of a function, marked with attribute warn_unused_result, does not use its return value
 
+Wupcast-result
+C++ ObjC++ Var(warn_upcast_result) Warning
+Warn when every nonnull return from a function performs an upcast from the same derived type to a base type.
+
+Wupcast-var
+C++ ObjC++ Var(warn_upcast_var) Warning
+Warn when every nonnull assignment to a variable performs an upcast from the same derived type to a base type.
+
 Wvariadic-macros
 C ObjC C++ ObjC++ CPP(warn_variadic_macros) CppReason(CPP_W_VARIADIC_MACROS) Var(cpp_warn_variadic_macros) Init(0) Warning LangEnabledBy(C ObjC C++ ObjC++,Wpedantic || Wtraditional)
 Warn about using variadic macros
Index: gcc/c-family/c-common.c
===================================================================
--- gcc/c-family/c-common.c	2015-05-27 14:37:58.553464723 +0100
+++ gcc/c-family/c-common.c	2015-05-27 14:37:58.541464861 +0100
@@ -404,6 +404,8 @@  static tree handle_designated_init_attri
 static tree handle_bnd_variable_size_attribute (tree *, tree, tree, int, bool *);
 static tree handle_bnd_legacy (tree *, tree, tree, int, bool *);
 static tree handle_bnd_instrument (tree *, tree, tree, int, bool *);
+static tree handle_no_upcast_warning_attribute (tree *, tree, tree, int,
+						bool *);
 
 static void check_function_nonnull (tree, int, tree *);
 static void check_nonnull_arg (void *, tree, unsigned HOST_WIDE_INT);
@@ -823,6 +825,8 @@  const struct attribute_spec c_common_att
 			      handle_bnd_legacy, false },
   { "bnd_instrument",         0, 0, true, false, false,
 			      handle_bnd_instrument, false },
+  { "no_upcast_warning",      0, 0, false, true, false,
+			      handle_no_upcast_warning_attribute, false },
   { NULL,                     0, 0, false, false, false, NULL, false }
 };
 
@@ -8718,6 +8722,22 @@  handle_bnd_instrument (tree *node, tree
     {
       warning (OPT_Wattributes, "%qE attribute ignored", name);
       *no_add_attrs = true;
+    }
+
+  return NULL_TREE;
+}
+
+/* Handle a "no_upcast_warning" attribute; arguments as in
+   struct attribute_spec.handler.  */
+
+static tree
+handle_no_upcast_warning_attribute (tree *node, tree name, tree, int,
+				    bool *no_add_attrs)
+{
+  if (TREE_CODE (*node) != RECORD_TYPE)
+    {
+      warning (OPT_Wattributes, "%qE attribute ignored", name);
+      *no_add_attrs = true;
     }
 
   return NULL_TREE;
Index: gcc/cp/cp-tree.h
===================================================================
--- gcc/cp/cp-tree.h	2015-05-27 14:37:58.553464723 +0100
+++ gcc/cp/cp-tree.h	2015-05-27 15:26:56.200086947 +0100
@@ -1200,6 +1200,23 @@  struct named_label_hasher : ggc_hasher<n
   static bool equal (named_label_entry *, named_label_entry *);
 };
 
+/* Used to implement -Wupcast-var for VAR_DECL VAR, which has a pointer
+   or reference type.  TARGET_TYPES is a list of types with which
+   TREE_TYPE (TREE_TYPE (VAR)) must be compatible.  Each one is either
+   TREE_TYPE (TREE_TYPE (VAR)) or is derived from it.  */
+
+struct GTY((for_user)) cp_decl_target_types
+{
+  tree var;
+  vec<tree, va_gc> *target_types;
+};
+
+struct cp_decl_target_types_hasher : ggc_hasher<cp_decl_target_types *>
+{
+  static hashval_t hash (cp_decl_target_types *);
+  static bool equal (cp_decl_target_types *, cp_decl_target_types *);
+};
+
 /* Global state pertinent to the current function.  */
 
 struct GTY(()) language_function {
@@ -1232,6 +1249,8 @@  struct GTY(()) language_function {
   /* Tracking possibly infinite loops.  This is a vec<tree> only because
      vec<bool> doesn't work with gtype.  */
   vec<tree, va_gc> *infinite_loops;
+  vec<tree, va_gc> *x_result_target_types;
+  hash_table<cp_decl_target_types_hasher> *x_decl_target_types;
   hash_table<cxx_int_tree_map_hasher> *extern_decl_map;
 };
 
@@ -1307,6 +1326,20 @@  #define in_function_try_handler cp_funct
 #define current_function_return_value \
   (cp_function_chain->x_return_value)
 
+/* Used to implement -Wupcast-result.  If the current function returns a
+   pointer or reference, this is the list of types with which the
+   pointer or reference target (X) must be compatible.  Each one is either
+   X or is derived from X.  */
+
+#define current_function_result_target_types \
+  (cp_function_chain->x_result_target_types)
+
+/* Used to implement -Wupcast-var.  It is a hash table of all variables
+   in the current function that might be relevant to the warning.  */
+
+#define current_function_decl_target_types \
+  (cp_function_chain->x_decl_target_types)
+
 /* A type involving 'auto' to be used for return type deduction.  */
 
 #define current_function_auto_return_pattern \
@@ -6241,6 +6274,7 @@  extern tree cp_build_c_cast			(tree, tre
 extern tree build_x_modify_expr			(location_t, tree,
 						 enum tree_code, tree,
 						 tsubst_flags_t);
+extern void upcast_var_record_type		(tree, tree);
 extern tree cp_build_modify_expr		(tree, enum tree_code, tree,
 						 tsubst_flags_t);
 extern tree convert_for_initialization		(tree, tree, tree, int,
Index: gcc/cp/decl.c
===================================================================
--- gcc/cp/decl.c	2015-05-27 14:37:58.553464723 +0100
+++ gcc/cp/decl.c	2015-05-27 15:32:12.872489637 +0100
@@ -278,6 +278,76 @@  typedef struct GTY(()) incomplete_var_d
 
 static GTY(()) vec<incomplete_var, va_gc> *incomplete_vars;
 
+/* On entry:
+
+   - Each element of TARGETS is, or is derived from, BASE.
+   - TARGETS[0] is, or is derived from, CANDIDATE; and
+   - CANDIDATE is, or is derived from, BASE
+
+   Find and return a CANDIDATE that satisfies the additional properties:
+
+   - Each element of TARGETS is, or is derived from, CANDIDATE
+   - CANDIDATE does not have the no_upcast_warning attribute
+
+   In hierarchies with no virtual bases, always return the most derived
+   such type (effectively the maximum lower bound).  In hierarchies with
+   virtual bases the choice is more arbitrary, but is never BASE if an
+   alternative exists.  */
+
+static tree
+get_common_target (vec<tree, va_gc> &targets, tree base, tree candidate)
+{
+  if (!lookup_attribute ("no_upcast_warning", TYPE_ATTRIBUTES (candidate)))
+    {
+      /* We already know CANDIDATE satisfies the condition for TARGETS[0].  */
+      unsigned int i;
+      for (i = 1; i < targets.length (); ++i)
+	{
+	  tree other = targets[i];
+	  if (candidate != other && !DERIVED_FROM_P (candidate, other))
+	    break;
+	}
+      if (i == targets.length ())
+	return candidate;
+    }
+  tree binfo = TYPE_BINFO (candidate);
+  tree next_binfo;
+  for (unsigned int i = 0; BINFO_BASE_ITERATE (binfo, i, next_binfo); ++i)
+    {
+      tree next_try = BINFO_TYPE (next_binfo);
+      if (DERIVED_FROM_P (base, next_try))
+	{
+	  tree res = get_common_target (targets, base, next_try);
+	  if (res != base)
+	    return res;
+	}
+    }
+  return base;
+}
+
+/* LHS_TYPE is a pointer or reference type and TARGETS is a list of
+   types that are, or are derived from, TREE_TYPE (LHS_TYPE).  If the
+   same condition would hold for a derived class of TREE_TYPE (LHS_TYPE),
+   pick one such type -- as for get_common_target -- and return a pointer
+   or reference like LHS_TYPE that has that target.  Return null otherwise.  */
+
+static tree
+get_upcast_warning_type (vec<tree, va_gc> &targets, tree lhs_type)
+{
+  tree lhs_target = TYPE_MAIN_VARIANT (TREE_TYPE (lhs_type));
+  tree alt_target = get_common_target (targets, lhs_target, targets[0]);
+  if (alt_target == lhs_target
+      || decl_anon_ns_mem_p (alt_target))
+    return NULL_TREE;
+
+  alt_target = cp_build_qualified_type
+    (alt_target, cp_type_quals (TREE_TYPE (lhs_type)));
+  if (TREE_CODE (lhs_type) == POINTER_TYPE)
+    return build_pointer_type (alt_target);
+  else
+    return build_reference_type (alt_target);
+}
+
 /* Returns the kind of template specialization we are currently
    processing, given that it's declaration contained N_CLASS_SCOPES
    explicit scope qualifications.  */
@@ -633,8 +703,13 @@  poplevel (int keep, int reverse, int fun
   leaving_for_scope
     = current_binding_level->kind == sk_for && flag_new_for_scope == 1;
 
-  /* Before we remove the declarations first check for unused variables.  */
-  if ((warn_unused_variable || warn_unused_but_set_variable)
+  /* Before we remove the declarations first check for unused variables
+     and variables with lax types.  */
+  if ((warn_unused_variable
+       || warn_unused_but_set_variable
+       || (warn_upcast_var
+	   && cfun
+	   && current_function_decl_target_types))
       && current_binding_level->kind != sk_template_parms
       && !processing_template_decl)
     for (tree d = getdecls (); d; d = TREE_CHAIN (d))
@@ -644,7 +719,8 @@  poplevel (int keep, int reverse, int fun
 	   getdecls is built.  */
 	decl = TREE_CODE (d) == TREE_LIST ? TREE_VALUE (d) : d;
 	tree type = TREE_TYPE (decl);
-	if (VAR_P (decl)
+	if ((warn_unused_variable || warn_unused_but_set_variable)
+	    && VAR_P (decl)
 	    && (! TREE_USED (decl) || !DECL_READ_P (decl))
 	    && ! DECL_IN_SYSTEM_HEADER (decl)
 	    && DECL_NAME (decl) && ! DECL_ARTIFICIAL (decl)
@@ -666,6 +742,25 @@  poplevel (int keep, int reverse, int fun
 		unused_but_set_errorcount = errorcount;
 	      }
 	  }
+	if (VAR_P (decl)
+	    && !TREE_ADDRESSABLE (decl)
+	    && warn_upcast_var
+	    && cfun
+	    && current_function_decl_target_types)
+	  {
+	    cp_decl_target_types dummy = { decl, NULL };
+	    cp_decl_target_types *entry
+	      = current_function_decl_target_types->find (&dummy);
+	    if (entry && !vec_safe_is_empty (entry->target_types))
+	      {
+		tree var_type = TREE_TYPE (decl);
+		tree alt_type = get_upcast_warning_type (*entry->target_types,
+							 var_type);
+		if (alt_type)
+		  warning (OPT_Wupcast_var, "%q+D could have type %qT"
+			   " instead of %qT", decl, alt_type, var_type);
+	      }
+	  }
       }
 
   /* Remove declarations for all the DECLs in this level.  */
@@ -14294,6 +14389,19 @@  finish_function (int flags)
       TREE_NO_WARNING (fndecl) = 1;
     }
 
+  if (!DECL_VIRTUAL_P (fndecl)
+      && !vec_safe_is_empty (current_function_result_target_types)
+      && warn_upcast_result)
+    {
+      tree ret_type = TREE_TYPE (fntype);
+      tree alt_type = get_upcast_warning_type
+	(*current_function_result_target_types, ret_type);
+      if (alt_type)
+	warning_at (DECL_SOURCE_LOCATION (fndecl), OPT_Wupcast_result,
+		    "function could return %qT instead of %qT",
+		    alt_type, ret_type);
+    }
+
   /* Store the end of the function, so that we get good line number
      info for the epilogue.  */
   cfun->function_end_locus = input_location;
@@ -14345,6 +14453,8 @@  finish_function (int flags)
       f->bindings = NULL;
       f->extern_decl_map = NULL;
       f->infinite_loops = NULL;
+      f->x_result_target_types = NULL;
+      f->x_decl_target_types = NULL;
     }
   /* Clear out the bits we don't need.  */
   local_names = NULL;
Index: gcc/cp/typeck.c
===================================================================
--- gcc/cp/typeck.c	2015-05-27 14:37:58.553464723 +0100
+++ gcc/cp/typeck.c	2015-05-27 15:29:37.190258405 +0100
@@ -74,6 +74,77 @@  static void warn_args_num (location_t, t
 static int convert_arguments (tree, vec<tree, va_gc> **, tree, int,
                               tsubst_flags_t);
 
+hashval_t
+cp_decl_target_types_hasher::hash (cp_decl_target_types *entry)
+{
+  return DECL_UID (entry->var);
+}
+
+bool
+cp_decl_target_types_hasher::equal (cp_decl_target_types *a,
+				   cp_decl_target_types *b)
+{
+  return a->var == b->var;
+}
+
+/* RHS is being assigned to a variable of type LHS_TYPE or is being returned
+   from a function that returns LHS_TYPE.  If LHS_TYPE is a pointer to a class,
+   update TARGET_TYPES so that it would be safe to replace TREE_TYPE (LHS_TYPE)
+   with some type X if each member of TARGET_TYPES is, or is derived
+   from, X.  */
+
+static void
+joust_upcast_types (vec<tree, va_gc> *&target_types, tree lhs_type, tree rhs)
+{
+  /* Only consider pointers to classes and references to classes.  */
+  if (!POINTER_TYPE_P (lhs_type))
+    return;
+  tree lhs_target = TYPE_MAIN_VARIANT (TREE_TYPE (lhs_type));
+  if (!CLASS_TYPE_P (lhs_target))
+    return;
+
+  /* Ignore null values, since they could come from type-specific
+     macros (like GCC's NULL_RTX).  */
+  if (null_ptr_cst_p (rhs) || integer_zerop (rhs))
+    return;
+
+  tree rhs_type = TREE_TYPE (rhs);
+  if (TREE_CODE (lhs_type) == TREE_CODE (rhs_type)
+      || TREE_CODE (lhs_type) == REFERENCE_TYPE)
+    {
+      /* We're only interested in cases where the target of RHS_TYPE
+	 is derived from the target of LHS_TYPE.  */
+      tree rhs_target = (TREE_CODE (lhs_type) == TREE_CODE (rhs_type)
+			 ? TREE_TYPE (rhs_type) : rhs_type);
+      rhs_target = TYPE_MAIN_VARIANT (rhs_target);
+      if (CLASS_TYPE_P (rhs_target)
+	  && lhs_target != rhs_target
+	  && DERIVED_FROM_P (lhs_target, rhs_target))
+	{
+	  /* Do nothing if RHS_TARGET is derived from an existing type.  */
+	  unsigned int i;
+	  tree other_target;
+	  FOR_EACH_VEC_SAFE_ELT (target_types, i, other_target)
+	    if (other_target == rhs_target
+		|| DERIVED_FROM_P (other_target, rhs_target))
+	      return;
+
+	  /* Remove any entries derived from RHS_TARGET.  */
+	  unsigned int j = 0;
+	  FOR_EACH_VEC_SAFE_ELT (target_types, i, other_target)
+	    if (!DERIVED_FROM_P (rhs_target, other_target))
+	      (*target_types)[j++] = other_target;
+
+	  vec_safe_truncate (target_types, j);
+	  vec_safe_push (target_types, rhs_target);
+	  return;
+	}
+    }
+  /* The worst case: keep the original target.  */
+  vec_safe_truncate (target_types, 0);
+  vec_safe_push (target_types, lhs_target);
+}
+
 /* Do `exp = require_complete_type (exp);' to make sure exp
    does not have an incomplete type.  (That includes void types.)
    Returns error_mark_node if the VALUE does not have
@@ -7307,6 +7378,33 @@  build_modify_expr (location_t /*location
   return cp_build_modify_expr (lhs, modifycode, rhs, tf_warning_or_error);
 }
 
+/* Record an assignment from RHS to LHS for -Wupcast-var.  */
+
+void
+upcast_var_record_type (tree lhs, tree rhs)
+{
+  tree lhstype = TREE_TYPE (lhs);
+  if (VAR_P (lhs)
+      && !TREE_STATIC (lhs)
+      && DECL_CONTEXT (lhs) == current_function_decl
+      && POINTER_TYPE_P (lhstype)
+      && CLASS_TYPE_P (TREE_TYPE (lhstype)))
+    {
+      cp_decl_target_types dummy = { lhs, NULL };
+      if (!current_function_decl_target_types)
+	current_function_decl_target_types
+	  = hash_table<cp_decl_target_types_hasher>::create_ggc (32);
+      cp_decl_target_types **slot
+	= current_function_decl_target_types->find_slot (&dummy, INSERT);
+      if (!*slot)
+	{
+	  *slot = ggc_alloc <cp_decl_target_types> ();
+	  **slot = dummy;
+	}
+      joust_upcast_types ((*slot)->target_types, lhstype, rhs);
+    }
+}
+
 /* Build an assignment expression of lvalue LHS from value RHS.
    MODIFYCODE is the code for a binary operator that we use
    to combine the old value of LHS with RHS to get the new value.
@@ -7427,6 +7525,9 @@  cp_build_modify_expr (tree lhs, enum tre
       break;
     }
 
+  if (warn_upcast_var)
+    upcast_var_record_type (lhs, rhs);
+
   if (modifycode == INIT_EXPR)
     {
       if (BRACE_ENCLOSED_INITIALIZER_P (rhs))
@@ -8537,6 +8638,10 @@  check_return_expr (tree retval, bool *no
       functype = type;
     }
 
+  if (warn_upcast_result)
+    joust_upcast_types (current_function_result_target_types,
+			functype, retval);
+
   /* When no explicit return-value is given in a function with a named
      return value, the named return value is used.  */
   result = DECL_RESULT (current_function_decl);
Index: gcc/cp/typeck2.c
===================================================================
--- gcc/cp/typeck2.c	2015-05-27 14:37:58.553464723 +0100
+++ gcc/cp/typeck2.c	2015-05-27 14:37:58.549464769 +0100
@@ -814,6 +814,9 @@  store_init_value (tree decl, tree init,
 
   value = extend_ref_init_temps (decl, value, cleanups);
 
+  if (warn_upcast_var)
+    upcast_var_record_type (decl, init);
+
   /* In C++11 constant expression is a semantic, not syntactic, property.
      In C++98, make sure that what we thought was a constant expression at
      template definition time is still constant and otherwise perform this
Index: gcc/testsuite/g++.dg/warn/Wupcast-result-1.C
===================================================================
--- /dev/null	2015-05-18 19:38:59.338844008 +0100
+++ gcc/testsuite/g++.dg/warn/Wupcast-result-1.C	2015-05-27 14:37:58.549464769 +0100
@@ -0,0 +1,136 @@ 
+// { dg-options "-Wupcast-result -std=c++11" }
+
+struct B {};
+struct B1 : public B {};
+struct B11 : public B1 {};
+struct B12 : public B1 {};
+struct B121 : public B12 {};
+struct B2 : public B {};
+namespace { struct B3 : public B {}; }
+
+B *
+f1 (int x) // { dg-warning "function could return 'B1\\*'" }
+{
+  if (x == 1)
+    return new B11;
+  if (x == 2)
+    return new B12;
+  if (x == 3)
+    return (B *) 0;
+  if (x == 4)
+    return nullptr;
+  if (x == 5)
+    return 0;
+  if (x == 6)
+    return (B1 *) 0;
+  if (x == 7)
+    return (B11 *) 0;
+  if (x == 8)
+    return (B12 *) 0;
+  return new B1;
+}
+
+B *
+f2 (int x) // { dg-warning "function could return 'B1\\*'" }
+{
+  if (x == 1)
+    return new B12;
+  if (x == 2)
+    return new B11;
+  return new B1;
+}
+
+B *
+f3 (int x) // { dg-warning "function could return 'B1\\*'" }
+{
+  if (x == 1)
+    return new B1;
+  if (x == 2)
+    return new B11;
+  return new B12;
+}
+
+B *
+f4 (int x) // { dg-warning "function could return 'B1\\*'" }
+{
+  if (x == 1)
+    return new B11;
+  return new B12;
+}
+
+B *
+f5 (int x)
+{
+  if (x == 1)
+    return new B1;
+  return new B2;
+}
+
+B *
+f6 (int x)
+{
+  if (x == 1)
+    return new B11;
+  return new B2;
+}
+
+B *
+f7 (int x)
+{
+  if (x == 1)
+    return new B12;
+  return new B2;
+}
+
+B *
+f8 (int x) // { dg-warning "function could return 'B12\\*'" }
+{
+  if (x == 1)
+    return new B12;
+  return new B121;
+}
+
+B *
+f9 ()
+{
+  return (B *) new B1;
+}
+
+B *
+f10 ()
+{
+  return new B3;
+}
+
+B *
+f11 ()
+{
+  return new B;
+}
+
+B *
+f12 ()
+{
+  return (B2 *) 0;
+}
+
+int *
+f13 ()
+{
+  return new int;
+}
+
+int
+f14 ()
+{
+  return 0;
+}
+
+B **
+f15 (B **x)
+{
+  return x;
+}
+
+struct X1 { B *foo () { return new B1; } }; // { dg-warning "function could return 'B1\\*'" }
+struct X2 { virtual B *foo () { return new B1; } };
Index: gcc/testsuite/g++.dg/warn/Wupcast-result-2.C
===================================================================
--- /dev/null	2015-05-18 19:38:59.338844008 +0100
+++ gcc/testsuite/g++.dg/warn/Wupcast-result-2.C	2015-05-27 14:37:58.549464769 +0100
@@ -0,0 +1,83 @@ 
+// { dg-options "-Wupcast-result" }
+
+struct B {};
+struct B1 : virtual public B {};
+struct B2 : virtual public B {};
+struct B3 : virtual public B {};
+struct B4 : virtual public B {};
+struct B1B2 : virtual public B1, virtual public B2 {};
+struct B1B2B3 : virtual public B1, virtual public B2, virtual public B3 {};
+struct B1B2B4 : virtual public B1, virtual public B2, virtual public B4 {};
+struct B2B3 : virtual public B2, virtual public B3 {};
+struct B3B4 : virtual public B3, virtual public B4 {};
+
+B *
+f1 (int x) // { dg-warning "function could return 'B1\\*'" }
+{
+  if (x == 1)
+    return new B1();
+  return new B1B2();
+}
+
+B *
+f2 (int x) // { dg-warning "function could return 'B2\\*'" }
+{
+  if (x == 1)
+    return new B2();
+  return new B1B2();
+}
+
+B *
+f3 (int x) // { dg-warning "function could return 'B2\\*'" }
+{
+  if (x == 1)
+    return new B1B2();
+  if (x == 2)
+    return new B1B2B3();
+  return new B2();
+}
+
+B *
+f4 (int x) // { dg-warning "function could return 'B2\\*'" }
+{
+  if (x == 1)
+    return new B1B2B3();
+  if (x == 2)
+    return new B1B2B4();
+  return new B2();
+}
+
+const volatile B *
+f5 (int x) // { dg-warning "function could return 'const volatile B1\\*'" }
+{
+  if (x == 1)
+    return new B1B2B3();
+  if (x == 2)
+    return new B1B2B4();
+  return new B1();
+}
+
+// Expect B1 to picked over B2, since it's listed first.
+const B *
+f6 (int x) // { dg-warning "function could return 'const B1\\*'" }
+{
+  if (x == 1)
+    return new B1B2;
+  return new B1B2B3;
+}
+
+volatile B *
+f7 (int x) // { dg-warning "function could return 'volatile B2\\*'" }
+{
+  if (x == 1)
+    return new B1B2;
+  return new B2B3;
+}
+
+B *
+f8 (int x)
+{
+  if (x == 1)
+    return new B1B2;
+  return new B3B4;
+}
Index: gcc/testsuite/g++.dg/warn/Wupcast-result-3.C
===================================================================
--- /dev/null	2015-05-18 19:38:59.338844008 +0100
+++ gcc/testsuite/g++.dg/warn/Wupcast-result-3.C	2015-05-27 14:37:58.553464723 +0100
@@ -0,0 +1,31 @@ 
+// { dg-options "-Wupcast-result" }
+
+struct B {};
+struct B1 : public B {};
+struct B2 : public B {};
+struct B21 : public B2 {};
+
+B2 b2;
+const B2 cb2;
+B21 b21;
+const B21 cb21;
+
+const B &
+f1 (int x) // { dg-warning "function could return 'const B2&'" }
+{
+  if (x == 1)
+    return cb2;
+  return cb21;
+}
+
+B &
+f2 (B1 &y) // { dg-warning "function could return 'B1&'" }
+{
+  return y;
+}
+
+B &
+f3 (B1 &y)
+{
+  return (B &) y;
+}
Index: gcc/testsuite/g++.dg/warn/Wupcast-result-4.C
===================================================================
--- /dev/null	2015-05-18 19:38:59.338844008 +0100
+++ gcc/testsuite/g++.dg/warn/Wupcast-result-4.C	2015-05-27 14:37:58.553464723 +0100
@@ -0,0 +1,15 @@ 
+// { dg-options "-Wupcast-result" }
+
+struct B {};
+struct B1 : public B {};
+struct __attribute__((no_upcast_warning)) B11 : public B1 {};
+struct B111 : public B11 {};
+struct B112 : public B11 {};
+
+B *
+f1 (int x) // { dg-warning "function could return 'B1\\*'" }
+{
+  if (x == 1)
+    return new B111;
+  return new B112;
+}
Index: gcc/testsuite/g++.dg/warn/Wupcast-var-1.C
===================================================================
--- /dev/null	2015-05-18 19:38:59.338844008 +0100
+++ gcc/testsuite/g++.dg/warn/Wupcast-var-1.C	2015-05-27 14:37:58.553464723 +0100
@@ -0,0 +1,160 @@ 
+// { dg-options "-Wupcast-var -std=c++11" }
+
+struct B {};
+struct B1 : public B {};
+struct B11 : public B1 {};
+struct B12 : public B1 {};
+struct B121 : public B12 {};
+struct B2 : public B {};
+namespace { struct B3 : public B {}; }
+
+void
+f1 ()
+{
+  B *x; // { dg-warning "could have type 'B1\\*'" }
+  x = new B11();
+  x = new B12();
+  x = (B *) 0;
+  x = nullptr;
+  x = 0;
+  x = (B1 *) 0;
+  x = (B11 *) 0;
+  x = (B12 *) 0;
+  x = new B1;
+}
+
+void
+f2 (void)
+{
+  B *x = new B12(); // { dg-warning "could have type 'B1\\*'" }
+  x = new B11();
+  x = new B1();
+}
+
+void
+f3 (void)
+{
+  B *x = new B1(); // { dg-warning "could have type 'B1\\*'" }
+  x = new B12();
+  x = new B11();
+}
+
+void
+f4 ()
+{
+  B *x = new B11(); // { dg-warning "could have type 'B1\\*'" }
+  x = new B12();
+}
+
+void
+f5 ()
+{
+  B *x = new B1;
+  x = new B2;
+}
+
+void
+f6 ()
+{
+  B *x = new B11;
+  x = new B2;
+}
+
+void
+f7 ()
+{
+  B *x = new B12;
+  x = new B2;
+}
+
+void
+f8 ()
+{
+  B *x = new B12(); // { dg-warning "could have type 'B12\\*'" }
+  x = new B121();
+}
+
+void
+f9 ()
+{
+  B *x = (B *) new B1;
+}
+
+void
+f10 ()
+{
+  B *x = new B3;
+}
+
+void
+f11 ()
+{
+  B *x = new B;
+}
+
+void
+f12 ()
+{
+  B *x = (B2 *) 0;
+}
+
+void
+f13 ()
+{
+  int *x = new int;
+}
+
+void
+f14 ()
+{
+  int x = 0;
+}
+
+void
+f15 (B **y)
+{
+  B **x = y;
+}
+
+void
+f15 (B *y)
+{
+  y = new B1;
+}
+
+void foo (B **x);
+
+void
+f16 ()
+{
+  B *x = new B11;
+  foo (&x);
+}
+
+void
+f17 ()
+{
+  static B *x = new B11;
+}
+
+void
+f18 ()
+{
+  static B *x = new B11;
+  foo (&x);
+}
+
+void
+f19 ()
+{
+  extern B *x;
+  x = new B11;
+}
+
+void
+f20 ()
+{
+  extern B *x;
+  x = new B11;
+  foo (&x);
+}