diff --git a/gcc/testsuite/gcc.dg/builtin-dynamic-object-size-0.c b/gcc/testsuite/gcc.dg/builtin-dynamic-object-size-0.c
index c72fa0508db..94f8e071e2c 100644
--- a/gcc/testsuite/gcc.dg/builtin-dynamic-object-size-0.c
+++ b/gcc/testsuite/gcc.dg/builtin-dynamic-object-size-0.c
@@ -237,6 +237,33 @@ test_dynarray_struct (size_t sz, size_t off)
   return __builtin_dynamic_object_size (&bin[off].c, 0);
 }
 
+size_t
+__attribute__ ((noinline))
+test_dynarray_struct_subobj (size_t sz, size_t off)
+{
+  struct dynarray_struct bin[sz];
+
+  return __builtin_dynamic_object_size (&bin[off].c[4], 1);
+}
+
+size_t
+__attribute__ ((noinline))
+test_dynarray_struct_subobj2 (size_t sz, size_t off, size_t *objsz)
+{
+  struct dynarray_struct2
+    {
+      long a;
+      int b;
+      char c[sz];
+    };
+
+  struct dynarray_struct2 bin;
+
+  *objsz = sizeof (bin);
+
+  return __builtin_dynamic_object_size (&bin.c[off], 1);
+}
+
 size_t
 __attribute__ ((noinline))
 test_substring (size_t sz, size_t off)
@@ -334,6 +361,13 @@ main (int argc, char **argv)
     FAIL ();
   if (test_substring (128, 142) != 0)
     FAIL ();
+  if (test_dynarray_struct_subobj (42, 4) != 16 - 4)
+    FAIL ();
+  if (test_dynarray_struct_subobj (42, 48) != 0)
+    FAIL ();
+  size_t objsz = 0;
+  if (test_dynarray_struct_subobj2 (42, 4, &objsz) != objsz - 4 - 12)
+    FAIL ();
   if (test_substring_ptrplus (128, 4) != (128 - 4) * sizeof (int))
     FAIL ();
   if (test_substring_ptrplus (128, 142) != 0)
diff --git a/gcc/tree-dynamic-object-size.c b/gcc/tree-dynamic-object-size.c
index 8d7283623dc..ebc2fad7a87 100644
--- a/gcc/tree-dynamic-object-size.c
+++ b/gcc/tree-dynamic-object-size.c
@@ -102,12 +102,21 @@ static bitmap computed[4];
 bool compute_builtin_dyn_object_size (tree, int, tree *,
 				      struct function *fun = NULL);
 
+static tree
+build_cond_branch (tree t, tree low_bound, tree unit_size)
+{
+  tree br = fold_build2 (MINUS_EXPR, TREE_TYPE (t), t, low_bound);
+  br = fold_convert (sizetype, br);
+  br = fold_build2 (MULT_EXPR, sizetype, unit_size, br);
+
+  return br;
+}
+
 /* Compute offset of EXPR within VAR.  Return error_mark_node if unknown.  */
 
 static tree
 compute_object_offset (const_tree expr, const_tree var)
 {
-  enum tree_code code = PLUS_EXPR;
   tree base, off, t;
 
   if (expr == var)
@@ -150,12 +159,16 @@ compute_object_offset (const_tree expr, const_tree var)
       low_bound = array_ref_low_bound (CONST_CAST_TREE (expr));
       unit_size = array_ref_element_size (CONST_CAST_TREE (expr));
       if (! integer_zerop (low_bound))
-	t = fold_build2 (MINUS_EXPR, TREE_TYPE (t), t, low_bound);
-      if (TREE_CODE (t) == INTEGER_CST && tree_int_cst_sgn (t) < 0)
 	{
-	  code = MINUS_EXPR;
-	  t = fold_build1 (NEGATE_EXPR, TREE_TYPE (t), t);
+	  tree cond = fold_build2 (GE_EXPR, TREE_TYPE (t), t, low_bound);
+	  tree then_br = build_cond_branch (t, low_bound, unit_size);
+	  tree else_br = build_cond_branch (low_bound, t, unit_size);
+
+	  then_br = fold_build2 (PLUS_EXPR, sizetype, base, then_br);
+	  else_br = fold_build2 (MINUS_EXPR, sizetype, base, else_br);
+	  return fold_build3 (COND_EXPR, sizetype, cond, then_br, else_br);
 	}
+
       t = fold_convert (sizetype, t);
       off = fold_build2 (MULT_EXPR, sizetype, unit_size, t);
       break;
@@ -168,7 +181,7 @@ compute_object_offset (const_tree expr, const_tree var)
       return error_mark_node;
     }
 
-  return fold_build2 (code, sizetype, base, off);
+  return fold_build2 (PLUS_EXPR, sizetype, base, off);
 }
 
 /* Given an object size SZ and offset OFF into it, compute the usable object
@@ -328,6 +341,105 @@ whole_var_size (struct object_size_info *osi, tree pt_var,
   return true;
 }
 
+/* Get the most immediate subobject encapsulating the pointer PTR, given the
+   whole_var object WHOLE_VAR.  */
+
+static tree
+get_closest_subobject (const_tree ptr, tree whole_var)
+{
+  tree var = TREE_OPERAND (ptr, 0);
+
+  while (var != whole_var
+	 && TREE_CODE (var) != BIT_FIELD_REF
+	 && TREE_CODE (var) != COMPONENT_REF
+	 && TREE_CODE (var) != ARRAY_REF
+	 && TREE_CODE (var) != ARRAY_RANGE_REF
+	 && TREE_CODE (var) != REALPART_EXPR
+	 && TREE_CODE (var) != IMAGPART_EXPR)
+    var = TREE_OPERAND (var, 0);
+
+  if (var != whole_var && TREE_CODE (var) == ARRAY_REF)
+    var = TREE_OPERAND (var, 0);
+
+  if (! TYPE_SIZE_UNIT (TREE_TYPE (var)))
+    var = whole_var;
+  else if (var != whole_var && TREE_CODE (whole_var) == MEM_REF)
+    {
+      tree v = var;
+      while (v && v != whole_var)
+	switch (TREE_CODE (v))
+	  {
+	  case ARRAY_REF:
+	    if (TYPE_SIZE_UNIT (TREE_TYPE (TREE_OPERAND (v, 0))))
+	      {
+		tree domain
+		  = TYPE_DOMAIN (TREE_TYPE (TREE_OPERAND (v, 0)));
+		if (domain && TYPE_MAX_VALUE (domain))
+		  {
+		    v = NULL_TREE;
+		    break;
+		  }
+	      }
+	    v = TREE_OPERAND (v, 0);
+	    break;
+	  case REALPART_EXPR:
+	  case IMAGPART_EXPR:
+	    v = NULL_TREE;
+	    break;
+	  case COMPONENT_REF:
+	    if (TREE_CODE (TREE_TYPE (v)) != ARRAY_TYPE)
+	      {
+		v = NULL_TREE;
+		break;
+	      }
+	    while (v != whole_var && TREE_CODE (v) == COMPONENT_REF)
+	      if (TREE_CODE (TREE_TYPE (TREE_OPERAND (v, 0)))
+		  != UNION_TYPE
+		  && TREE_CODE (TREE_TYPE (TREE_OPERAND (v, 0)))
+		  != QUAL_UNION_TYPE)
+		break;
+	      else
+		v = TREE_OPERAND (v, 0);
+	    if (TREE_CODE (v) == COMPONENT_REF
+		&& TREE_CODE (TREE_TYPE (TREE_OPERAND (v, 0)))
+		== RECORD_TYPE)
+	      {
+		tree fld_chain = DECL_CHAIN (TREE_OPERAND (v, 1));
+		for (; fld_chain; fld_chain = DECL_CHAIN (fld_chain))
+		  if (TREE_CODE (fld_chain) == FIELD_DECL)
+		    break;
+
+		if (fld_chain)
+		  {
+		    v = NULL_TREE;
+		    break;
+		  }
+		v = TREE_OPERAND (v, 0);
+	      }
+	    while (v != whole_var && TREE_CODE (v) == COMPONENT_REF)
+	      if (TREE_CODE (TREE_TYPE (TREE_OPERAND (v, 0)))
+		  != UNION_TYPE
+		  && TREE_CODE (TREE_TYPE (TREE_OPERAND (v, 0)))
+		  != QUAL_UNION_TYPE)
+		break;
+	      else
+		v = TREE_OPERAND (v, 0);
+	    if (v != whole_var)
+	      v = NULL_TREE;
+	    else
+	      v = whole_var;
+	    break;
+	  default:
+	    v = whole_var;
+	    break;
+	  }
+      if (v == whole_var)
+	var = whole_var;
+    }
+
+  return var;
+}
+
 /* Compute an object size estimate for PTR, which is a ADDR_EXPR.
    OBJECT_SIZE_TYPE is the second argument from __builtin_dynamic_object_size.
    If unknown, return false, setting PSIZE to NULL_TREE.  */
@@ -337,7 +449,8 @@ addr_dyn_object_size (struct object_size_info *osi, const_tree ptr,
 		      int object_size_type, tree *psize,
 		      tree *wholesizep = NULL)
 {
-  tree pt_var, pt_var_size = NULL_TREE, bytes;
+  struct function *fun = osi ? osi->fun : cfun;
+  tree pt_var, pt_var_size = NULL_TREE, var_size, bytes;
 
   gcc_assert (TREE_CODE (ptr) == ADDR_EXPR);
 
@@ -353,22 +466,57 @@ addr_dyn_object_size (struct object_size_info *osi, const_tree ptr,
   if (!whole_var_size (osi, pt_var, object_size_type, &pt_var_size))
     return false;
 
-  if (!pt_var_size)
-    return false;
+  var_size = pt_var_size;
 
   /* PTR points to a subobject of whole variable PT_VAR.  */
   if (pt_var != TREE_OPERAND (ptr, 0))
     {
-      bytes = compute_object_offset (TREE_OPERAND (ptr, 0), pt_var);
+      tree var;
+
+      if (object_size_type & 1)
+	var = get_closest_subobject (ptr, pt_var);
+      else
+	var = pt_var;
+
+      if (var != pt_var)
+	{
+	  var_size = TYPE_SIZE_UNIT (TREE_TYPE (var));
+	  if (!TREE_CONSTANT (var_size))
+	    var_size = get_or_create_ssa_default_def (fun, var_size);
+	  if (!var_size)
+	    return false;
+	}
+      else if (!pt_var_size)
+	return false;
+
+      bytes = compute_object_offset (TREE_OPERAND (ptr, 0), var);
       if (bytes != error_mark_node)
-	bytes = size_for_offset (pt_var_size, bytes, pt_var_size);
+	{
+	  tree cap_size = pt_var_size;
+
+	  /* For subobject sizes where the whole object is a structure,
+	     restrict size to the size of the struct less the offset of the
+	     subobject in the struct.  */
+	  if (var != pt_var && pt_var_size && TREE_CODE (pt_var) == MEM_REF)
+	    {
+	      tree off = compute_object_offset (TREE_OPERAND (ptr, 0), pt_var);
+	      if (off == error_mark_node)
+		off = size_zero_node;
+	      else
+		off = fold_build2 (MIN_EXPR, sizetype, pt_var_size, off);
+	      cap_size = fold_build2 (MINUS_EXPR, sizetype, pt_var_size, off);
+	    }
+	  bytes = size_for_offset (var_size, bytes, cap_size);
+	}
     }
+  else if (!pt_var_size)
+    return false;
   else
     bytes = pt_var_size;
 
   *psize = bytes == error_mark_node ? NULL_TREE : bytes;
   if (wholesizep)
-    *wholesizep = pt_var_size;
+    *wholesizep = var_size;
   return true;
 }
 
