PR middle-end/77608 - missing protection on trivially detectable runtime buffer overflow

gcc/ChangeLog:
2016-11-07  Martin Sebor  <msebor@redhat.com>

	PR middle-end/77608
	* tree-object-size.c (nonconst_offsets): New global.
	(compute_object_offset): New function.
	(addr_object_size): Add an argument.
	(compute_builtin_object_size): Rename...
	(internal_object_size): to this.
	(expr_object_size): Adjust.
	(merge_object_sizes): Handle non-constant offset.
	(plus_stmt_object_size): Same.
	(collect_object_sizes_for): Change to return bool.
	(init_object_sizes): Initialize nonconst_offsets.
	(fini_object_sizes): Release nonconst_offsets.

gcc/testsuite/ChangeLog:
2016-11-07  Martin Sebor  <msebor@redhat.com>

	PR middle-end/77608
	* gcc.c-torture/execute/builtins/lib/chk.c: Add debugging output.
	* gcc.c-torture/execute/builtins/mempcpy-chk.c: Add debugging output.
	(test5): Allow test to emit __mempcpy_chk.
	* gcc.dg/builtin-object-size-18.c: New test.
	* gcc.dg/builtin-stringop-chk-3.c: New test.
	* gcc.dg/pr77608.c: New test.

diff --git a/gcc/testsuite/gcc.c-torture/execute/builtins/lib/chk.c b/gcc/testsuite/gcc.c-torture/execute/builtins/lib/chk.c
index b19d7bf..6966b41 100644
--- a/gcc/testsuite/gcc.c-torture/execute/builtins/lib/chk.c
+++ b/gcc/testsuite/gcc.c-torture/execute/builtins/lib/chk.c
@@ -5,6 +5,15 @@
 
 extern void abort (void);
 
+const char *testfunc;
+int testline;
+
+#define abort()								\
+  (__builtin_printf ("%s:%i: %s: test failure in %s on line %i\n",	\
+		     __FILE__, __LINE__, __FUNCTION__,			\
+		     testfunc, testline),				\
+   __builtin_abort ())
+
 extern int inside_main;
 void *chk_fail_buf[256] __attribute__((aligned (16)));
 volatile int chk_fail_allowed, chk_calls;
diff --git a/gcc/testsuite/gcc.c-torture/execute/builtins/mempcpy-chk.c b/gcc/testsuite/gcc.c-torture/execute/builtins/mempcpy-chk.c
index 7a1737c..b014aff 100644
--- a/gcc/testsuite/gcc.c-torture/execute/builtins/mempcpy-chk.c
+++ b/gcc/testsuite/gcc.c-torture/execute/builtins/mempcpy-chk.c
@@ -9,6 +9,15 @@ extern void *memcpy (void *, const void *, size_t);
 extern void *mempcpy (void *, const void *, size_t);
 extern int memcmp (const void *, const void *, size_t);
 
+extern const char *testfunc;
+extern int testline;
+
+#define memcpy(d, s, n)				\
+  (testfunc = __func__, testline = __LINE__, memcpy (d, s, n))
+
+#define mempcpy(d, s, n)			\
+  (testfunc = __func__, testline = __LINE__, mempcpy (d, s, n))
+
 #include "chk.h"
 
 const char s1[] = "123";
@@ -17,6 +26,8 @@ volatile char *s2 = "defg"; /* prevent constant propagation to happen when whole
 volatile char *s3 = "FGH"; /* prevent constant propagation to happen when whole program assumptions are made.  */
 volatile size_t l1 = 1; /* prevent constant propagation to happen when whole program assumptions are made.  */
 
+#define abort() (__builtin_printf ("failure on line %i\n", __LINE__), __builtin_abort ())
+
 void
 __attribute__((noinline))
 test1 (void)
@@ -326,6 +337,8 @@ test4 (void)
   char buf3[20];
 
   chk_fail_allowed = 1;
+  mempcpy_disallowed = 0;
+
   /* Runtime checks.  */
   if (__builtin_setjmp (chk_fail_buf) == 0)
     {
@@ -343,7 +356,9 @@ test4 (void)
       vx = mempcpy (&buf3[19], "ab", 2);
       abort ();
     }
+
   chk_fail_allowed = 0;
+  mempcpy_disallowed = 1;
 }
 
 #ifndef MAX_OFFSET
@@ -377,6 +392,12 @@ test5 (void)
   int off1, off2, len, i;
   char *p, *q, c;
 
+  /* The call to mempcpy below, even though it's safe, may result in
+     a call to __mempcpy_chk when the (maximum) size of the destination
+     is determined.  */
+  int mempcpy_disallowed_save = mempcpy_disallowed;
+  mempcpy_disallowed = 0;
+
   for (off1 = 0; off1 < MAX_OFFSET; off1++)
     for (off2 = 0; off2 < MAX_OFFSET; off2++)
       for (len = 1; len < MAX_COPY; len++)
@@ -410,6 +431,8 @@ test5 (void)
 	    if (*q != 'a')
 	      abort ();
 	}
+
+  mempcpy_disallowed = mempcpy_disallowed_save;
 }
 
 #define TESTSIZE 80
diff --git a/gcc/testsuite/gcc.dg/builtin-object-size-18.c b/gcc/testsuite/gcc.dg/builtin-object-size-18.c
new file mode 100644
index 0000000..b1a5666
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/builtin-object-size-18.c
@@ -0,0 +1,115 @@
+/* PR 77608 - missing protection on trivially detectable runtime buffer
+   overflow */
+/* { dg-do compile } */
+/* { dg-options "-O2 -fdump-tree-optimized" } */
+
+#define concat(a, b)   a ## b
+#define CAT(a, b)      concat (a, b)
+
+/* Create a symbol name unique to each tes and object size type.  */
+#define SYM(store, type)  \
+  CAT (CAT (CAT (CAT (CAT (failure_on_line_, __LINE__), \
+		      _), store), _type_), type)
+
+/* References to the following undefined symbol which is unique for each
+   test case are expected to be eliminated.  */
+#define TEST_FAILURE(store, type)			\
+  do {							\
+    extern void SYM (store, type)(void);		\
+    SYM (store, type)();				\
+  } while (0)
+
+#define bos(ptr, type) __builtin_object_size (ptr, type)
+
+#define test(expect, type, ptr, store)		\
+  do {						\
+    if (bos (ptr, type)	!= expect)		\
+      TEST_FAILURE (store, type);		\
+  } while (0)
+
+/* Verify that each of the expressions __builtin_object_size(OBJ, n)
+   is folded into Rn.  */
+#define fold(r0, r1, r2, r3, ptr, store)	\
+  do {						\
+    test (r0, 0, ptr, store);			\
+    test (r1, 1, ptr, store);			\
+    test (r2, 2, ptr, store);			\
+    test (r3, 3, ptr, store);			\
+  } while (0)
+
+/* Verify that __builtin_object_size(xBUF + OFF, n) is folded into Rn
+   for the specified OFF and each of the Rn values.  */
+#define FOLD(ptr, r0, r1, r2, r3)		\
+  do {						\
+    {						\
+      char *buf = gbuf;				\
+      fold (r0, r1, r2, r3, ptr, static);	\
+      sink (buf);				\
+    }						\
+    {						\
+      char *buf = lbuf;				\
+      fold (r0, r1, r2, r3, ptr, auto);		\
+      sink (buf);				\
+    }						\
+    {						\
+      char *buf = abuf;				\
+      fold (r0, r1, r2, r3, ptr, allocated);	\
+      sink (buf);				\
+    }						\
+  } while (0)
+
+#define SCHAR_MAX __SCHAR_MAX__
+#define UCHAR_MAX (__SCHAR_MAX__  * 2 + 1)
+#define N         ((UCHAR_MAX + 1) * 2)
+
+typedef __SIZE_TYPE__ size_t;
+
+void sink (void*, ...);
+
+char gbuf[N];
+
+void test_pointer_plus (char ci, short si, int i, long li)
+{
+  char lbuf[N];
+
+  char *abuf = __builtin_malloc (N);
+
+  FOLD (buf + ci, N, N, N - SCHAR_MAX, N - SCHAR_MAX);
+  FOLD (buf + si, N, N, 0, 0);
+  FOLD (buf +  i, N, N, 0, 0);
+  FOLD (buf + li, N, N, 0, 0);
+
+  FOLD (&buf [1] + ci, N - 1, N - 1, N - UCHAR_MAX - 1, N - UCHAR_MAX - 1);
+}
+
+static inline int
+r (int min, int max)
+{
+  extern int rand (void);
+  int x = rand ();
+  return x < min || max < x ? min : x;
+}
+
+void test_pointer_plus_range (void)
+{
+  char lbuf[N];
+
+  char *abuf = __builtin_malloc (N);
+
+  int i = r (0, 1);
+  FOLD (buf + i, N, N, N - 1, N - 1);
+}
+
+void test_pointer_plus_anti_range (void)
+{
+}
+
+static inline int
+ar (int min, int max)
+{
+  extern int rand (void);
+  int x = rand ();
+  return x < min || max < x ? x : min;
+}
+
+/* { dg-final { scan-tree-dump-not "failue_on_line" "optimized" } } */
diff --git a/gcc/testsuite/gcc.dg/builtin-stringop-chk-3.c b/gcc/testsuite/gcc.dg/builtin-stringop-chk-3.c
new file mode 100644
index 0000000..3b0ba0f
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/builtin-stringop-chk-3.c
@@ -0,0 +1,344 @@
+/* Test exercising buffer overflow warnings emitted for __*_chk builtins
+   in cases where the destination involves a non-constant offset into
+   an object of known size.  */
+/* { dg-do compile } */
+/* { dg-options "-O2 -ftrack-macro-expansion=0" } */
+
+#define INT_MAX      __INT_MAX__
+#define PTRDIFF_MAX  __PTRDIFF_MAX__
+#define SIZE_MAX     __SIZE_MAX__
+
+typedef __PTRDIFF_TYPE__ ptrdiff_t;
+typedef __SIZE_TYPE__    size_t;
+
+void sink (void*);
+
+/* Define memcpy as a macro (as opposed to an inline function) so that
+   warnings point to its invocation in the tests (as opposed to its
+   definition), making sure its first argument is evaluated exactly
+   once.  */
+#define memcpy(d, s, n)							\
+  do {									\
+    __typeof__ (d) __d = (d);						\
+    __builtin___memcpy_chk (__d, s, n, __builtin_object_size (__d, 1));	\
+    sink (__d);								\
+  } while (0)
+
+/* Function to generate a unique offset each time it's called.  Declared
+   (but not defined) and used to prevent GCC from making assumptions about
+   their values based on the variables uses in the tested expressions.  */
+ptrdiff_t random_value (void);
+
+/* For brevity. */
+#define X() random_value ()
+
+/* Test memcpy with a variable offset not known to be in any range
+   and with a constant number of bytes.  */
+
+void test_var_const (const void *p)
+{
+  char buf[5];
+
+  memcpy (buf + X(), p, 6);  /* { dg-warning "writing 6 bytes into a region of size 5" } */
+
+  memcpy (&buf[X()], p, 6);  /* { dg-warning "writing" } */
+
+  /* Since X() below can be assumed to be non-negative (otherwise it would
+     result in forming a pointer before the beginning of BUF), then because
+     of the +1 added to the resulting address there must be at most enough
+     room for (sizeof(buf) - 1) or 4 bytes.  */
+  memcpy (&buf[X()] + 1, p, 4);
+
+  memcpy (&buf[X()] + 1, p, 5);  /* { dg-warning "writing" } */
+
+  memcpy (&buf[X()] + 5, p, 6);  /* { dg-warning "writing" } */
+
+  /* The negative constant offset below must have no effect on the maximum
+     size of the buffer.  */
+  memcpy (&buf[X()] - 1, p, 5);
+
+  memcpy (&buf[X()] - 1, p, 6);  /* { dg-warning "writing" } */
+
+  memcpy (&buf[X()] - 5, p, 6);  /* { dg-warning "writing" } */
+
+  memcpy (&buf[X()] + X(), p, 5);
+
+  memcpy (&buf[X()] + X(), p, 6);  /* { dg-warning "writing" } */
+
+  memcpy (&buf[X()] - X(), p, 5);
+
+  memcpy (&buf[X()] - X(), p, 6);  /* { dg-warning "writing" } */
+}
+
+static inline size_t
+range (size_t min, size_t max)
+{
+  const size_t val = random_value ();
+  return val < min || max < val ? min : val;
+}
+
+/* Test memcpy with a variable offset not known to be in any range
+   and with a number of bytes bounded by a known range.  */
+
+void test_var_range (void *dst, const void *p)
+{
+  char buf[5];
+
+  memcpy (&buf[X()], p, range (0, 5));
+  memcpy (&buf[X()], p, range (1, 5));
+  memcpy (&buf[X()], p, range (2, 5));
+  memcpy (&buf[X()], p, range (3, 5));
+  memcpy (&buf[X()], p, range (4, 5));
+
+  memcpy (&buf[X()], p, range (6, 7));  /* { dg-warning "writing" } */
+
+  const size_t max = SIZE_MAX;
+  memcpy (&buf[X()], p, range (max - 1, max));  /* { dg-warning "specified size between \[0-9\]+ and \[0-9\]+ exceeds maximum object size" } */
+
+  memcpy (dst, p, range (max / 2 + 1, max));  /* { dg-warning "specified size between \[0-9\]+ and \[0-9\]+ exceeds maximum object size" } */
+}
+
+/* Return an ingeger in the range [MIN, MASK].  Use bitwise operations
+   rather than inequality to avoid relying on optimization passes
+   beyond Early Value Range Propagation that __builtin_object_size
+   doesn't make use of (yet).  */
+
+static inline size_t
+simple_range (unsigned min, unsigned mask)
+{
+  return ((unsigned)random_value () & mask) | (min & mask);
+}
+
+/* For brevity. */
+#define R(min, max) simple_range (min, max)
+
+void test_range_auto (const void *p)
+{
+  char buf[5];
+
+  memcpy (buf + R (0, 1), p, 6);  /* { dg-warning "writing" } */
+
+  /* Some of these could be diagnosed as an extension because they would
+     overflow when (if) the non-const index were sufficiently large.
+     The challenge is distinguishing a range where a variable is likely
+     to exceed the minimum required for the overflow to occur from one
+     where it isn't so likely.  */
+  memcpy (buf + R (0, 1), p, 5);
+
+  memcpy (buf + R (0, 1), p, 4);
+
+  memcpy (buf + R (0, 2), p, 3);
+
+  memcpy (buf + R (0, 3), p, 2);
+
+  memcpy (buf + R (0, 7), p, 1);
+
+  memcpy (buf + R (0, 5), p, 0);
+
+  /* The offset is known to be at least 1 so the size of the object
+     is at most 4.  */
+  memcpy (buf + R (1, 2), p, 3);
+
+  memcpy (buf + R (1, 3), p, 3);
+
+  memcpy (buf + R (1, 2), p, 4);
+
+  memcpy (buf + R (1, 3) + 1, p, 4);  /* { dg-warning "writing" } */
+
+  memcpy (buf + R (1, 3), p, 5);  /* { dg-warning "writing" } */
+
+  memcpy (buf + R (2, 3), p, 2);
+
+  memcpy (buf + R (2, 3), p, 3);
+
+  memcpy (buf + R (2, 3) + 1, p, 3);  /* { dg-warning "writing" } */
+
+  memcpy (buf + R (2, 3), p, 4);  /* { dg-warning "writing" } */
+
+  memcpy (buf + R (3, 4), p, 2);
+
+  memcpy (buf + R (3, 7), p, 3);  /* { dg-warning "writing" } */
+
+  memcpy (buf + R (3, 7), p, 9);  /* { dg-warning "writing" } */
+
+  memcpy (&buf[R (0, 1)] + 1, p, 4);
+
+  memcpy (&buf[R (0, 2)] + 1, p, 4);
+
+  memcpy (&buf[R (0, 3)] + 1, p, 4);
+
+  memcpy (&buf[R (0, 4)] + 1, p, 4);
+
+  memcpy (&buf[R (0, 5)] + 1, p, 4);
+
+  memcpy (&buf[R (0, 2)] + 2, p, 4);  /* { dg-warning "writing" } */
+
+  memcpy (&buf[R (0, 2)] - 2, p, 3);
+  memcpy (&buf[R (0, 2)] - 2, p, 3);
+
+  memcpy (&buf[R (0, 2)] - 2, p, 3);
+
+  memcpy (&buf[R (5, 15)], p, 1);  /* { dg-warning "writing" } */
+
+  /* With the offset given by the two ranges below there is at most
+     1 byte left.  */
+  memcpy (buf + R (1, 2) + R (3, 4), p, 1);
+
+  memcpy (buf + R (1, 3) + R (3, 7), p, 2);   /* { dg-warning "writing" } */
+
+  /* Unfortunately, the following isn't handled quite right: only
+     the lower bound given by the first range is used, the second
+     one is disregarded.  */
+  memcpy (&buf [R (1, 2)] + R (3, 4), p, 2);   /* { dg-warning "writing" "index in range plus offset in range" { xfail *-*-* } } */
+
+  memcpy (buf + R (2, 3) + R (2, 3), p, 1);
+
+  memcpy (buf + R (2, 3) + R (2, 3), p, 2);   /* { dg-warning "writing" } */
+}
+
+void test_range_malloc (const void *p)
+{
+  char *buf = __builtin_malloc (5);
+
+  memcpy (buf + R (0, 1), p, 6);  /* { dg-warning "writing" } */
+
+  memcpy (buf + R (0, 1), p, 5);
+
+  memcpy (buf + R (0, 1), p, 4);
+
+  memcpy (buf + R (0, 2), p, 3);
+
+  memcpy (buf + R (0, 3), p, 2);
+
+  memcpy (buf + R (0, 4), p, 1);
+
+  memcpy (buf + R (0, 5), p, 0);
+}
+
+void test_range_schar (signed char i, const void *s)
+{
+  char a [130];
+
+  /* The range of I is [-128, 127] so the size of the destination below
+     is at most 2 (i.e., 130 - 128) bytes. */
+  memcpy (&a [130] + i, s, 2);
+
+  /* Strictly, the range of I below is [0, 127] because a negative value
+     would result in forming an invalid pointer, so the destination is at
+     most 2 bytes. */
+  memcpy (&a [i] + 128, s, 2);
+
+  /* Reset I's range just in case it gets set above.  */
+  i = random_value ();
+  memcpy (&a [130] + i, s, 3);   /* { dg-warning "writing" } */
+
+  i = random_value ();
+  memcpy (&a [i] + 128, s, 3);   /* { dg-warning "writing" } */
+}
+
+void test_range_uchar (unsigned char i, const void *s)
+{
+  char a [260];
+
+  /* The range of I is [0, 255] so the size of the destination below
+     is at most 2 (i.e., 260 - 258 + 0) bytes. */
+  memcpy (&a [258] + i, s, 2);
+
+  memcpy (&a [i] + 128, s, 2);
+
+  /* Reset I's range just in case it gets set above.  */
+  i = random_value ();
+  memcpy (&a [258] + i, s, 3);   /* { dg-warning "writing" } */
+
+  i = random_value ();
+  memcpy (&a [i] + 258, s, 3);   /* { dg-warning "writing" } */
+
+  i = random_value ();
+  memcpy (&a [259] + i, s, 2);   /* { dg-warning "writing" } */
+
+  i = random_value ();
+  memcpy (&a [i] + 259, s, 2);   /* { dg-warning "writing" } */
+
+  i = random_value ();
+  memcpy (&a [260] + i, s, 1);   /* { dg-warning "writing" } */
+
+  i = random_value ();
+  memcpy (&a [i] + 260, s, 1);   /* { dg-warning "writing" } */
+
+  i = random_value ();
+  memcpy (&a [260] + i, s, 0);
+
+  i = random_value ();
+  memcpy (&a [i] + 260, s, 0);
+}
+
+void test_range_int (int i, const void *s)
+{
+  const size_t max = (size_t)INT_MAX * 2 + 1;
+
+  char *a = __builtin_malloc (max);
+
+  memcpy (&a [max] + i, s, INT_MAX);
+  memcpy (&a [max] - i, s, INT_MAX);
+  /* &*/
+  memcpy (&a [max] + i, s, INT_MAX + (size_t)1);
+  memcpy (&a [max] - i, s, INT_MAX + (size_t)1); /* { dg-warning "writing" } */
+  memcpy (&a [max] + i, s, INT_MAX + (size_t)2); /* { dg-warning "writing" } */
+  memcpy (&a [max] - i, s, INT_MAX + (size_t)2); /* { dg-warning "writing" } */
+
+  char *end = &a [max];
+  memcpy (end + i, s, INT_MAX);
+  memcpy (end - i, s, INT_MAX);
+  /* &*/
+  memcpy (end + i, s, INT_MAX + (size_t)1);
+  memcpy (end - i, s, INT_MAX + (size_t)1); /* { dg-warning "writing" } */
+  memcpy (end + i, s, INT_MAX + (size_t)2); /* { dg-warning "writing" } */
+  memcpy (end - i, s, INT_MAX + (size_t)2); /* { dg-warning "writing" } */
+
+  memcpy (&a [i] + max, s, 1);
+}
+
+
+void test_range_ptrdiff_t (ptrdiff_t i, const void *s)
+{
+  const size_t max = PTRDIFF_MAX;
+
+  char *a = __builtin_malloc (max);
+
+  memcpy (&a [max] + i, s, max);
+
+  memcpy (&a [i] + max - 1, s, 1);
+}
+
+void test_range_size_t (size_t i, const void *s)
+{
+  const size_t diffmax = PTRDIFF_MAX;
+
+  char *a = __builtin_malloc (diffmax);
+
+  memcpy (&a [diffmax] + i, s, 0);
+  memcpy (&a [i] + diffmax, s, 0);
+
+  memcpy (&a [diffmax] + i, s, 1);  /* { dg-warning "writing" } */
+
+  memcpy (&a [i] + diffmax, s, 1);  /* { dg-warning "writing" } */
+}
+
+struct S {
+  int i;
+  char a7[7];
+  int b;
+};
+
+void test_range_member_array (struct S *s, const void *p)
+{
+  memcpy (s->a7 + R (0, 1), p, 6);
+
+  memcpy (s->a7 + R (0, 1), p, 7);
+
+  memcpy (s->a7 + R (0, 1), p, 8);  /* { dg-warning "writing" } */
+
+  memcpy (&s->a7 [R (0, 1)], p, 7);
+
+  memcpy (&s->a7 [R (1, 3)], p, 7);  /* { dg-warning "writing" } */
+}
diff --git a/gcc/testsuite/gcc.dg/pr77608.c b/gcc/testsuite/gcc.dg/pr77608.c
new file mode 100644
index 0000000..04f831a
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/pr77608.c
@@ -0,0 +1,19 @@
+/* { dg-do compile } */
+/* { dg-options "-O -Wall -ftrack-macro-expansion=0" } */
+
+#define bos(dest) __builtin_object_size (dest, 0)
+#define memcpy(dest, src, n)				\
+  __builtin___memcpy_chk (dest, src, n, bos (dest))
+
+void f (void*);
+
+void g (int i)
+{
+  char d [3];
+
+  memcpy (d + i, "abcdef", 5);   /* { dg-warning "writing" } */
+
+  f (d);
+}
+
+/* { dg-final { scan-tree-dump-times "__builtin___memcpy_chk" 1 "optimized" } } */
diff --git a/gcc/tree-object-size.c b/gcc/tree-object-size.c
index 1317ad7..b828478 100644
--- a/gcc/tree-object-size.c
+++ b/gcc/tree-object-size.c
@@ -33,6 +33,8 @@ along with GCC; see the file COPYING3.  If not see
 #include "gimple-iterator.h"
 #include "tree-cfg.h"
 
+#include "diagnostic-core.h"
+
 struct object_size_info
 {
   int object_size_type;
@@ -52,11 +54,12 @@ static const unsigned HOST_WIDE_INT unknown[4] = {
 
 static tree compute_object_offset (const_tree, const_tree);
 static bool addr_object_size (struct object_size_info *,
-			      const_tree, int, unsigned HOST_WIDE_INT *);
+			      const_tree, int, unsigned HOST_WIDE_INT *,
+			      bool * = NULL);
 static unsigned HOST_WIDE_INT alloc_object_size (const gcall *, int);
 static tree pass_through_call (const gcall *);
-static void collect_object_sizes_for (struct object_size_info *, tree);
-static void expr_object_size (struct object_size_info *, tree, tree);
+static bool collect_object_sizes_for (struct object_size_info *, tree);
+static bool expr_object_size (struct object_size_info *, tree, tree);
 static bool merge_object_sizes (struct object_size_info *, tree, tree,
 				unsigned HOST_WIDE_INT);
 static bool plus_stmt_object_size (struct object_size_info *, tree, gimple *);
@@ -74,9 +77,18 @@ static void check_for_plus_in_loops_1 (struct object_size_info *, tree,
    the object and object_sizes[3] lower bound for subobject.  */
 static vec<unsigned HOST_WIDE_INT> object_sizes[4];
 
-/* Bitmaps what object sizes have been computed already.  */
+/* Bitmaps of variables whose object sizes have already been computed.  */
 static bitmap computed[4];
 
+/* Bitmaps of variables whose object sizes have been computed without
+   regard to the (non-constant) offset into them.  A bit in each bitmap
+   is valid only if the corresponding bit in the COMPUTED bitmap is
+   non-zero (otherwise it's zero).
+   FIXME: Change this to a vector of offset ranges to make it possible
+   to handle cases like &A[I] + J where both I and J are non-const and
+   bounded by some range.  */
+static bitmap nonconst_offsets[4];
+
 /* Maximum value of offset we consider to be addition.  */
 static unsigned HOST_WIDE_INT offset_limit;
 
@@ -158,14 +170,149 @@ compute_object_offset (const_tree expr, const_tree var)
   return size_binop (code, base, off);
 }
 
+static bool
+operand_unsigned_p (tree op)
+{
+  if (TREE_CODE (op) == SSA_NAME)
+    {
+      gimple *def = SSA_NAME_DEF_STMT (op);
+      if (is_gimple_assign (def))
+	{
+	  tree_code code = gimple_assign_rhs_code (def);
+	  if (code == NOP_EXPR)
+	      op = gimple_assign_rhs1 (def);
+	}
+    }
+
+  return TYPE_UNSIGNED (TREE_TYPE (op));
+}
+
+/* Fill the 2-element OFFRANGE array with the range of values OFF
+   is known to be in.  Postcodition: OFFRANGE[0] <= OFFRANGE[1].  */
+
+static bool
+get_offset_range (tree off, HOST_WIDE_INT offrange[2])
+{
+  STRIP_NOPS (off);
+
+  if (TREE_CODE (off) == NOP_EXPR)
+    {
+      return get_offset_range (TREE_OPERAND (off, 0), offrange);
+    }
+
+  if (TREE_CODE (off) == SSA_NAME)
+    {
+      wide_int min, max;
+      enum value_range_type range_type = get_range_info (off, &min, &max);
+
+      if (range_type == VR_RANGE)
+	{
+	  /* Interpret the range in the variable's type.  */
+	  tree tmin = wide_int_to_tree (TREE_TYPE (off), min);
+	  tree tmax = wide_int_to_tree (TREE_TYPE (off), max);
+
+	  offrange[0] = (tree_fits_shwi_p (tmin)
+			 ? tree_to_shwi (tmin) : tree_to_uhwi (tmin));
+	  offrange[1] = (tree_fits_shwi_p (tmax)
+			  ? tree_to_shwi (tmax) : tree_to_uhwi (tmax));
+
+	  gimple *def = SSA_NAME_DEF_STMT (off);
+	  if (is_gimple_assign (def))
+	    {
+	      tree_code code = gimple_assign_rhs_code (def);
+	      if (code == PLUS_EXPR)
+		{
+		  /* Handle offset in the form VAR + CST where VAR's type
+		     is unsigned so the offset must be the greater of
+		     OFFRANGE[0] and CST.  This assumes the PLUS_EXPR
+		     is in a canonical form with CST second.  */
+		  tree rhs2 = gimple_assign_rhs2 (def);
+		  if (TREE_CODE (rhs2) == INTEGER_CST)
+		    {
+		      tree rhs1 = gimple_assign_rhs1 (def);
+
+		      HOST_WIDE_INT minoff
+			= (tree_fits_shwi_p (rhs2)
+			   ? tree_to_shwi (rhs2) : tree_to_uhwi (rhs2));
+
+		      if (offrange[0] < minoff && operand_unsigned_p (rhs1))
+			offrange[0] = minoff;
+		    }
+		}
+	    }
+	  return true;
+	}
+      else if (range_type == VR_ANTI_RANGE)
+	{
+	  offrange[0] = max.to_uhwi () + 1;
+	  offrange[1] = min.to_uhwi () - 1;
+	  return true;
+	}
+      else if (range_type == VR_VARYING)
+	{
+	  gimple *def = SSA_NAME_DEF_STMT (off);
+	  if (is_gimple_assign (def))
+	    {
+	      tree_code code = gimple_assign_rhs_code (def);
+	      if (code == NOP_EXPR)
+		{
+		  off = gimple_assign_rhs1 (def);
+		  if (offrange[0] != offrange[1])
+		    {
+		      tree type = TREE_TYPE (off);
+		      tree tmin = TYPE_MIN_VALUE (type);
+		      tree tmax = TYPE_MAX_VALUE (type);
+		      offrange[0]
+			= (tree_fits_shwi_p (tmin)
+			   ? tree_to_shwi (tmin) : tree_to_uhwi (tmin));
+		      offrange[1]
+			= (tree_fits_shwi_p (tmax)
+			   ? tree_to_shwi (tmax) : tree_to_uhwi (tmax));
+		    }
+
+		  return true;
+		}
+	    }
+
+	  /* For types smaller than size_t determine their range.  */
+	  tree type = TREE_TYPE (off);
+	  if (TYPE_PRECISION (sizetype) < TYPE_PRECISION (type))
+	    {
+	      tree tmin = TYPE_MIN_VALUE (type);
+	      tree tmax = TYPE_MAX_VALUE (type);
+	      offrange[0]
+		= (tree_fits_shwi_p (tmin)
+		   ? tree_to_shwi (tmin) : tree_to_uhwi (tmin));
+	      offrange[1]
+		= (tree_fits_shwi_p (tmax)
+		   ? tree_to_shwi (tmax) : tree_to_uhwi (tmax));
+
+	      return true;
+	    }
+	}
+    }
+  else if (TREE_CODE (off) == INTEGER_CST && tree_fits_uhwi_p (off))
+    {
+      offrange[0] = offrange[1] = tree_to_uhwi (off);
+      return true;
+    }
+
+  return false;
+}
 
 /* Compute __builtin_object_size for PTR, which is a ADDR_EXPR.
    OBJECT_SIZE_TYPE is the second argument from __builtin_object_size.
-   If unknown, return unknown[object_size_type].  */
+   If known set *PSIZE to it, otherwise set it to unknown[object_size_type].
+   When PIGNORE_OFFSET is non-null and *PIGNORE_OFFSET is true, ignore
+   the offset from PTR in the computation of the size.  When the ADDR_EXPR
+   is determined to involve a non-constant offset, set *PIGNORE_OFFSET to
+   true before returning.  This allows callers to also ignore constant
+   offsets (such as the '3' in &p->a[x] + 3 when x is not constant).  */
 
 static bool
 addr_object_size (struct object_size_info *osi, const_tree ptr,
-		  int object_size_type, unsigned HOST_WIDE_INT *psize)
+		  int object_size_type, unsigned HOST_WIDE_INT *psize,
+		  bool *pignore_offset)
 {
   tree pt_var, pt_var_size = NULL_TREE, var_size, bytes;
 
@@ -346,15 +493,42 @@ addr_object_size (struct object_size_info *osi, const_tree ptr,
 	return false;
       else
 	var_size = pt_var_size;
-      bytes = compute_object_offset (TREE_OPERAND (ptr, 0), var);
+
+      /* Compute the offset into the object given by the operand and
+	 subtract it from the object size unless *PIGNORE_OFFSET is set.  */
+      bytes = (pignore_offset && *pignore_offset
+	       ? size_zero_node
+	       : compute_object_offset (TREE_OPERAND (ptr, 0), var));
+
       if (bytes != error_mark_node)
 	{
-	  if (TREE_CODE (bytes) == INTEGER_CST
-	      && tree_int_cst_lt (var_size, bytes))
-	    bytes = size_zero_node;
+	  HOST_WIDE_INT offrange[2];
+
+	  if (get_offset_range (bytes, offrange))
+	    {
+	      if (pignore_offset)
+		*pignore_offset = offrange[0] != offrange[1];
+
+	      if (offrange[0] < 0 && offrange[1] < 0)
+		bytes = size_zero_node;
+	      else
+		{
+		  unsigned HOST_WIDE_INT minoff
+		    = (offrange[0] < 0 ? 0 : offrange[0]);
+		  unsigned HOST_WIDE_INT vszi = tree_to_shwi (var_size);
+
+		  if (minoff < vszi)
+		    bytes = size_binop (MINUS_EXPR, var_size,
+					build_int_cst (TREE_TYPE (var_size),
+						       minoff));
+		  else
+		    bytes = size_zero_node;
+		}
+	    }
 	  else
 	    bytes = size_binop (MINUS_EXPR, var_size, bytes);
 	}
+
       if (var != pt_var
 	  && pt_var_size
 	  && TREE_CODE (pt_var) == MEM_REF
@@ -379,10 +553,24 @@ addr_object_size (struct object_size_info *osi, const_tree ptr,
 
   if (tree_fits_uhwi_p (bytes))
     {
+      /* When the size of the object is known exactly (including any
+	 constant offset) set *PSIZE to it.  */
       *psize = tree_to_uhwi (bytes);
       return true;
     }
 
+  if (tree_fits_uhwi_p (var_size) && !integer_zerop (var_size))
+    {
+      /* When the size of the object is known and non-zero but
+	 the offset into it isn't set *PSIZE to the size and ignore
+	 the non-constant offset into it but set *PIGNORE_OFFSET to
+	 true to indicate this to the caller.  */
+      *psize = tree_to_uhwi (var_size);
+      if (pignore_offset)
+	*pignore_offset = true;
+      return true;
+    }
+
   return false;
 }
 
@@ -496,9 +684,10 @@ pass_through_call (const gcall *call)
    to __builtin_object_size.  Return true on success and false
    when the object size could not be determined.  */
 
-bool
-compute_builtin_object_size (tree ptr, int object_size_type,
-			     unsigned HOST_WIDE_INT *psize)
+static bool
+internal_builtin_object_size (object_size_info *posi,
+			      tree ptr, int object_size_type,
+			      unsigned HOST_WIDE_INT *psize)
 {
   gcc_assert (object_size_type >= 0 && object_size_type <= 3);
 
@@ -553,7 +742,7 @@ compute_builtin_object_size (tree ptr, int object_size_type,
       unsigned int i;
 
       if (num_ssa_names > object_sizes[object_size_type].length ())
-	object_sizes[object_size_type].safe_grow (num_ssa_names);
+	object_sizes[object_size_type].safe_grow_cleared (num_ssa_names);
       if (dump_file)
 	{
 	  fprintf (dump_file, "Computing %s %sobject size for ",
@@ -563,25 +752,31 @@ compute_builtin_object_size (tree ptr, int object_size_type,
 	  fprintf (dump_file, ":\n");
 	}
 
-      osi.visited = BITMAP_ALLOC (NULL);
-      osi.reexamine = BITMAP_ALLOC (NULL);
-      osi.object_size_type = object_size_type;
-      osi.depths = NULL;
-      osi.stack = NULL;
-      osi.tos = NULL;
-
-      /* First pass: walk UD chains, compute object sizes that
-	 can be computed.  osi.reexamine bitmap at the end will
-	 contain what variables were found in dependency cycles
-	 and therefore need to be reexamined.  */
-      osi.pass = 0;
-      osi.changed = false;
-      collect_object_sizes_for (&osi, ptr);
+      if (!posi)
+	{
+	  osi.visited = BITMAP_ALLOC (NULL);
+	  osi.reexamine = BITMAP_ALLOC (NULL);
+	  osi.object_size_type = object_size_type;
+	  osi.depths = NULL;
+	  osi.stack = NULL;
+	  osi.tos = NULL;
+
+	  /* First pass: walk UD chains, compute object sizes that
+	     can be computed.  osi.reexamine bitmap at the end will
+	     contain what variables were found in dependency cycles
+	     and therefore need to be reexamined.  */
+	  osi.pass = 0;
+	  osi.changed = false;
+
+	  posi = &osi;
+	}
+
+      collect_object_sizes_for (posi, ptr);
 
       /* Second pass: keep recomputing object sizes of variables
 	 that need reexamination, until no object sizes are
 	 increased or all object sizes are computed.  */
-      if (! bitmap_empty_p (osi.reexamine))
+      if (! bitmap_empty_p (posi->reexamine))
 	{
 	  bitmap reexamine = BITMAP_ALLOC (NULL);
 
@@ -593,35 +788,35 @@ compute_builtin_object_size (tree ptr, int object_size_type,
 	     E.g. p = &buf[0]; while (cond) p = p + 4;  */
 	  if (object_size_type & 2)
 	    {
-	      osi.depths = XCNEWVEC (unsigned int, num_ssa_names);
-	      osi.stack = XNEWVEC (unsigned int, num_ssa_names);
-	      osi.tos = osi.stack;
-	      osi.pass = 1;
+	      posi->depths = XCNEWVEC (unsigned int, num_ssa_names);
+	      posi->stack = XNEWVEC (unsigned int, num_ssa_names);
+	      posi->tos = posi->stack;
+	      posi->pass = 1;
 	      /* collect_object_sizes_for is changing
-		 osi.reexamine bitmap, so iterate over a copy.  */
-	      bitmap_copy (reexamine, osi.reexamine);
+		 posi->reexamine bitmap, so iterate over a copy.  */
+	      bitmap_copy (reexamine, posi->reexamine);
 	      EXECUTE_IF_SET_IN_BITMAP (reexamine, 0, i, bi)
-		if (bitmap_bit_p (osi.reexamine, i))
-		  check_for_plus_in_loops (&osi, ssa_name (i));
-
-	      free (osi.depths);
-	      osi.depths = NULL;
-	      free (osi.stack);
-	      osi.stack = NULL;
-	      osi.tos = NULL;
+		if (bitmap_bit_p (posi->reexamine, i))
+		  check_for_plus_in_loops (posi, ssa_name (i));
+
+	      free (posi->depths);
+	      posi->depths = NULL;
+	      free (posi->stack);
+	      posi->stack = NULL;
+	      posi->tos = NULL;
 	    }
 
 	  do
 	    {
-	      osi.pass = 2;
-	      osi.changed = false;
+	      posi->pass = 2;
+	      posi->changed = false;
 	      /* collect_object_sizes_for is changing
-		 osi.reexamine bitmap, so iterate over a copy.  */
-	      bitmap_copy (reexamine, osi.reexamine);
+		 posi->reexamine bitmap, so iterate over a copy.  */
+	      bitmap_copy (reexamine, posi->reexamine);
 	      EXECUTE_IF_SET_IN_BITMAP (reexamine, 0, i, bi)
-		if (bitmap_bit_p (osi.reexamine, i))
+		if (bitmap_bit_p (posi->reexamine, i))
 		  {
-		    collect_object_sizes_for (&osi, ssa_name (i));
+		    collect_object_sizes_for (posi, ssa_name (i));
 		    if (dump_file && (dump_flags & TDF_DETAILS))
 		      {
 			fprintf (dump_file, "Reexamining ");
@@ -631,17 +826,17 @@ compute_builtin_object_size (tree ptr, int object_size_type,
 		      }
 		  }
 	    }
-	  while (osi.changed);
+	  while (posi->changed);
 
 	  BITMAP_FREE (reexamine);
 	}
-      EXECUTE_IF_SET_IN_BITMAP (osi.reexamine, 0, i, bi)
+      EXECUTE_IF_SET_IN_BITMAP (posi->reexamine, 0, i, bi)
 	bitmap_set_bit (computed[object_size_type], i);
 
       /* Debugging dumps.  */
       if (dump_file)
 	{
-	  EXECUTE_IF_SET_IN_BITMAP (osi.visited, 0, i, bi)
+	  EXECUTE_IF_SET_IN_BITMAP (posi->visited, 0, i, bi)
 	    if (object_sizes[object_size_type][i]
 		!= unknown[object_size_type])
 	      {
@@ -656,17 +851,29 @@ compute_builtin_object_size (tree ptr, int object_size_type,
 	      }
 	}
 
-      BITMAP_FREE (osi.reexamine);
-      BITMAP_FREE (osi.visited);
+      if (posi == &osi)
+	{
+	  BITMAP_FREE (posi->reexamine);
+	  BITMAP_FREE (posi->visited);
+	}
     }
 
   *psize = object_sizes[object_size_type][SSA_NAME_VERSION (ptr)];
   return *psize != unknown[object_size_type];
 }
 
-/* Compute object_sizes for PTR, defined to VALUE, which is not an SSA_NAME.  */
+bool
+compute_builtin_object_size (tree ptr, int object_size_type,
+			     unsigned HOST_WIDE_INT *psize)
+{
+  return internal_builtin_object_size (NULL, ptr, object_size_type, psize);
+}
+
+/* Compute object_sizes for PTR, defined to VALUE, which is not an SSA_NAME.
+   Return true if the expression involved a non-constant offset from PTR
+   that has been (unavoidably) ignored, and false otherwise.  */
 
-static void
+static bool
 expr_object_size (struct object_size_info *osi, tree ptr, tree value)
 {
   int object_size_type = osi->object_size_type;
@@ -684,8 +891,9 @@ expr_object_size (struct object_size_info *osi, tree ptr, tree value)
   gcc_assert (TREE_CODE (value) != SSA_NAME
 	      || !POINTER_TYPE_P (TREE_TYPE (value)));
 
+  bool ignore_offset = false;
   if (TREE_CODE (value) == ADDR_EXPR)
-    addr_object_size (osi, value, object_size_type, &bytes);
+    addr_object_size (osi, value, object_size_type, &bytes, &ignore_offset);
   else
     bytes = unknown[object_size_type];
 
@@ -699,6 +907,8 @@ expr_object_size (struct object_size_info *osi, tree ptr, tree value)
       if (object_sizes[object_size_type][varno] > bytes)
 	object_sizes[object_size_type][varno] = bytes;
     }
+
+  return ignore_offset;
 }
 
 
@@ -769,21 +979,27 @@ merge_object_sizes (struct object_size_info *osi, tree dest, tree orig,
 {
   int object_size_type = osi->object_size_type;
   unsigned int varno = SSA_NAME_VERSION (dest);
-  unsigned HOST_WIDE_INT orig_bytes;
 
   if (object_sizes[object_size_type][varno] == unknown[object_size_type])
     return false;
-  if (offset >= offset_limit)
+
+  /* IGNORE_OFFSET will be set to true if the size of the object
+     does not reflect a non-constant offset into it.  */
+  bool ignore_offset = false;
+  if (osi->pass == 0)
+    ignore_offset = collect_object_sizes_for (osi, orig);
+
+  if (offset >= offset_limit && !ignore_offset)
     {
       object_sizes[object_size_type][varno] = unknown[object_size_type];
       return false;
     }
 
-  if (osi->pass == 0)
-    collect_object_sizes_for (osi, orig);
+  unsigned HOST_WIDE_INT orig_bytes
+    = object_sizes[object_size_type][SSA_NAME_VERSION (orig)];
 
-  orig_bytes = object_sizes[object_size_type][SSA_NAME_VERSION (orig)];
-  if (orig_bytes != unknown[object_size_type])
+  if (orig_bytes != unknown[object_size_type]
+      && (!ignore_offset || offset < HOST_WIDE_INT_MAX))
     orig_bytes = (offset > orig_bytes)
 		 ? HOST_WIDE_INT_0U : orig_bytes - offset;
 
@@ -806,7 +1022,6 @@ merge_object_sizes (struct object_size_info *osi, tree dest, tree orig,
   return bitmap_bit_p (osi->reexamine, SSA_NAME_VERSION (orig));
 }
 
-
 /* Compute object_sizes for VAR, defined to the result of an assignment
    with operator POINTER_PLUS_EXPR.  Return true if the object size might
    need reexamination  later.  */
@@ -819,6 +1034,9 @@ plus_stmt_object_size (struct object_size_info *osi, tree var, gimple *stmt)
   unsigned HOST_WIDE_INT bytes;
   tree op0, op1;
 
+  if (object_sizes[object_size_type][varno] == unknown[object_size_type])
+    return false;
+
   if (gimple_assign_rhs_code (stmt) == POINTER_PLUS_EXPR)
     {
       op0 = gimple_assign_rhs1 (stmt);
@@ -834,36 +1052,117 @@ plus_stmt_object_size (struct object_size_info *osi, tree var, gimple *stmt)
   else
     gcc_unreachable ();
 
-  if (object_sizes[object_size_type][varno] == unknown[object_size_type])
-    return false;
+  /* If the offset is not constant, assume it's either zero for the maximum
+     object size or SIZE_MAX for the minimum size of zero.  */
+  HOST_WIDE_INT offrange[2] = { -HOST_WIDE_INT_MAX - 1, HOST_WIDE_INT_MAX };
+  get_offset_range (op1, offrange);
+
+  if (TREE_CODE (op0) == SSA_NAME && offrange[0] == offrange[1])
+    return merge_object_sizes (osi, var, op0, offrange[0]);
 
-  /* Handle PTR + OFFSET here.  */
-  if (TREE_CODE (op1) == INTEGER_CST
-      && (TREE_CODE (op0) == SSA_NAME
-	  || TREE_CODE (op0) == ADDR_EXPR))
+  if (TREE_CODE (op0) == ADDR_EXPR)
+    addr_object_size (osi, op0, object_size_type, &bytes);
+  else if (!internal_builtin_object_size (osi, op0, object_size_type, &bytes))
+    goto set_size;
+
+  if (offrange[0] == offrange[1])
     {
-      if (! tree_fits_uhwi_p (op1))
+      /* Compute the size of the object referenced by &VAR + OP1
+	 with a constant OP1.  */
+      unsigned HOST_WIDE_INT off = offrange[0];
+
+      if (off > offset_limit)
 	bytes = unknown[object_size_type];
-      else if (TREE_CODE (op0) == SSA_NAME)
-	return merge_object_sizes (osi, var, op0, tree_to_uhwi (op1));
+      else if (off > bytes)
+	bytes = 0;
+      else if (bytes != unknown[object_size_type])
+	bytes -= off;
+    }
+  else
+    {
+      /* Compute the size of the largest object referenced by
+	 &VAR + OP1 with OP1's range OFFRANGE.  */
+      unsigned HOST_WIDE_INT maxbytes = bytes;
+      if (TREE_CODE (op0) == SSA_NAME)
+	{
+	  gimple *def = SSA_NAME_DEF_STMT (op0);
+	  if (gimple_code (def) == GIMPLE_ASSIGN)
+	    {
+	      tree_code code = gimple_assign_rhs_code (def);
+	      if (code == ADDR_EXPR)
+		op0 = gimple_assign_rhs1 (def);
+	    }
+	}
+      if (TREE_CODE (op0) == ADDR_EXPR)
+	{
+	  bool ignore_offset = true;
+	  addr_object_size (osi, op0, object_size_type, &maxbytes,
+			    &ignore_offset);
+	}
+
+      if (bytes == maxbytes)
+	{
+	  /* If the lower bound of the range is positive adjust the size
+	     of the object down to reflect the size of the remainder of
+	     the object.  */
+	  if (0 < offrange[0])
+	    bytes = ((unsigned HOST_WIDE_INT) offrange[0] < bytes
+		     ? bytes - offrange[0] : 0);
+	}
       else
 	{
-	  unsigned HOST_WIDE_INT off = tree_to_uhwi (op1);
-
-          /* op0 will be ADDR_EXPR here.  */
-	  addr_object_size (osi, op0, object_size_type, &bytes);
-	  if (bytes == unknown[object_size_type])
-	    ;
-	  else if (off > offset_limit)
-	    bytes = unknown[object_size_type];
-	  else if (off > bytes)
-	    bytes = 0;
+	  unsigned HOST_WIDE_INT firstoff = maxbytes - bytes;
+
+	  /* Compute the largest object size, accounting for
+	     the non-constant offset.  */
+	  if (offrange[0] < 0)
+	    {
+	      if (firstoff <= (unsigned HOST_WIDE_INT) -offrange[0])
+		bytes = maxbytes;
+	      else
+		bytes = maxbytes + offrange[0];
+	    }
 	  else
-	    bytes -= off;
+	    {
+	      if (firstoff + offrange[0] < maxbytes)
+		bytes = maxbytes - (firstoff + offrange[0]);
+	      else
+		bytes = 0;
+	    }
 	}
     }
-  else
-    bytes = unknown[object_size_type];
+
+  if (getenv ("TRACE_OBJECT_SIZE"))
+    {
+      tree type = TREE_TYPE (op1);
+
+      if (TREE_CODE (op1) == SSA_NAME)
+	{
+	  gimple *def = SSA_NAME_DEF_STMT (op1);
+	  if (gimple_code (def) == GIMPLE_ASSIGN)
+	    {
+	      tree_code code = gimple_assign_rhs_code (def);
+	      if (code == ADDR_EXPR || code == NOP_EXPR)
+		type = TREE_TYPE (gimple_assign_rhs1 (def));
+	    }
+	}
+
+      if (offrange[0] == offrange[1])
+	inform (EXPR_LOC_OR_LOC (var, input_location),
+		"objsz for %qE: offset type %qT value %wi, "
+		"assuming type-%i object size is %wu",
+		var, type, offrange[0], object_size_type, bytes);
+      else
+	{
+	  inform (EXPR_LOC_OR_LOC (var, input_location),
+		  "objsz for %qE: offset type %qT range [%wi, %wi], "
+		  "assuming type-%i object size is %wu",
+		  var, type, offrange[0], offrange[1],
+		  object_size_type, bytes);
+	}
+    }
+
+ set_size:
 
   if ((object_size_type & 2) == 0)
     {
@@ -878,7 +1177,6 @@ plus_stmt_object_size (struct object_size_info *osi, tree var, gimple *stmt)
   return false;
 }
 
-
 /* Compute object_sizes for VAR, defined at STMT, which is
    a COND_EXPR.  Return true if the object size might need reexamination
    later.  */
@@ -932,7 +1230,7 @@ cond_expr_object_size (struct object_size_info *osi, tree var, gimple *stmt)
    Otherwise, object size is the maximum of object sizes of variables
    that it might be set to.  */
 
-static void
+static bool
 collect_object_sizes_for (struct object_size_info *osi, tree var)
 {
   int object_size_type = osi->object_size_type;
@@ -940,13 +1238,19 @@ collect_object_sizes_for (struct object_size_info *osi, tree var)
   gimple *stmt;
   bool reexamine;
 
+  /* If the size for the variable has already been computed avoid
+     recomputing it and return true if its computed size involved
+     ignoring a non-constant offset into it and false otherwise.  */
   if (bitmap_bit_p (computed[object_size_type], varno))
-    return;
+    return bitmap_bit_p (nonconst_offsets[object_size_type], varno);
 
   if (osi->pass == 0)
     {
       if (bitmap_set_bit (osi->visited, varno))
 	{
+	  /* Initialize the size to the maximum or minimum, opposite
+	     of the size being computed.  The result will be adjusted
+	     down or up, respectively, as the actual size is determined.  */
 	  object_sizes[object_size_type][varno]
 	    = (object_size_type & 2) ? -1 : 0;
 	}
@@ -961,7 +1265,7 @@ collect_object_sizes_for (struct object_size_info *osi, tree var)
 	      print_generic_expr (dump_file, var, dump_flags);
 	      fprintf (dump_file, "\n");
 	    }
-	  return;
+	  return false;
 	}
     }
 
@@ -975,6 +1279,11 @@ collect_object_sizes_for (struct object_size_info *osi, tree var)
   stmt = SSA_NAME_DEF_STMT (var);
   reexamine = false;
 
+  /* Set to true when the reference to the object whose size is being
+     computed involves a non-constant offset that is not taken into
+     consideration.  */
+  bool ignore_offset = false;
+
   switch (gimple_code (stmt))
     {
     case GIMPLE_ASSIGN:
@@ -993,7 +1302,7 @@ collect_object_sizes_for (struct object_size_info *osi, tree var)
                 && POINTER_TYPE_P (TREE_TYPE (rhs)))
               reexamine = merge_object_sizes (osi, var, rhs, 0);
             else
-              expr_object_size (osi, var, rhs);
+              ignore_offset = expr_object_size (osi, var, rhs);
           }
         else
           unknown_object_size (osi, var);
@@ -1059,6 +1368,12 @@ collect_object_sizes_for (struct object_size_info *osi, tree var)
       || object_sizes[object_size_type][varno] == unknown[object_size_type])
     {
       bitmap_set_bit (computed[object_size_type], varno);
+
+      /* Make a record of a variable whose reference involves a non-constant
+	 offset (that is unavoidably ignored).  */
+      if (ignore_offset)
+	bitmap_set_bit (nonconst_offsets[object_size_type], varno);
+
       bitmap_clear_bit (osi->reexamine, varno);
     }
   else
@@ -1071,6 +1386,8 @@ collect_object_sizes_for (struct object_size_info *osi, tree var)
 	  fprintf (dump_file, "\n");
 	}
     }
+
+  return ignore_offset;
 }
 
 
@@ -1220,8 +1537,9 @@ init_object_sizes (void)
 
   for (object_size_type = 0; object_size_type <= 3; object_size_type++)
     {
-      object_sizes[object_size_type].safe_grow (num_ssa_names);
+      object_sizes[object_size_type].safe_grow_cleared (num_ssa_names);
       computed[object_size_type] = BITMAP_ALLOC (NULL);
+      nonconst_offsets[object_size_type] = BITMAP_ALLOC (NULL);
     }
 
   init_offset_limit ();
@@ -1239,6 +1557,7 @@ fini_object_sizes (void)
     {
       object_sizes[object_size_type].release ();
       BITMAP_FREE (computed[object_size_type]);
+      BITMAP_FREE (nonconst_offsets[object_size_type]);
     }
 }
 
