261 lines
9.2 KiB
C
261 lines
9.2 KiB
C
/* Test exercising -Wrawmem-overflow and -Wstringop-overflow warnings. */
|
|
/* { dg-do compile } */
|
|
/* { dg-options "-O2 -Wstringop-overflow=1" } */
|
|
|
|
#define offsetof(type, mem) __builtin_offsetof (type, mem)
|
|
|
|
/* Return the number of bytes from member MEM of TYPE to the end
|
|
of object OBJ. */
|
|
#define offsetfrom(type, obj, mem) (sizeof (obj) - offsetof (type, mem))
|
|
|
|
|
|
typedef __SIZE_TYPE__ size_t;
|
|
extern void* memcpy (void*, const void*, size_t);
|
|
extern void* memset (void*, int, __SIZE_TYPE__);
|
|
|
|
|
|
struct A { char a, b; };
|
|
struct B { struct A a; char c, d; };
|
|
|
|
/* Function to call to "escape" pointers from tests below to prevent
|
|
GCC from assuming the values of the objects they point to stay
|
|
the unchanged. */
|
|
void escape (void*, ...);
|
|
|
|
/* Function to "generate" a random number 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. */
|
|
size_t random_unsigned_value (void);
|
|
|
|
/* Return a random unsigned value between MIN and MAX. */
|
|
|
|
static inline size_t
|
|
range (size_t min, size_t max)
|
|
{
|
|
const size_t val = random_unsigned_value ();
|
|
return val < min || max < val ? min : val;
|
|
}
|
|
|
|
/* Verify that writing past the end of a local array is diagnosed. */
|
|
|
|
void test_memop_warn_local (const void *src)
|
|
{
|
|
size_t n;
|
|
|
|
n = range (8, 32);
|
|
|
|
struct A a[2];
|
|
|
|
memcpy (a, src, n); /* { dg-warning "writing between 8 and 32 bytes into a region of size 4 overflows the destination" } */
|
|
escape (a, src);
|
|
|
|
/* At -Wrawmem-overflow=1 the destination is considered to be
|
|
the whole array and its size is therefore sizeof a. */
|
|
memcpy (&a[0], src, n); /* { dg-warning "writing between 8 and 32 bytes into a region of size 4 overflows the destination" } */
|
|
escape (a, src);
|
|
|
|
/* Verify the same as above but by writing into the first mmeber
|
|
of the first element of the array. */
|
|
memcpy (&a[0].a, src, n); /* { dg-warning "writing between 8 and 32 bytes into a region of size 4 overflows the destination" } */
|
|
escape (a, src);
|
|
|
|
n = range (12, 32);
|
|
|
|
struct B b[2];
|
|
|
|
memcpy (&b[0], src, n); /* { dg-warning "writing between 12 and 32 bytes into a region of size 8 overflows the destination" } */
|
|
escape (b);
|
|
|
|
/* The following idiom of clearing multiple members of a struct is
|
|
used in a few places in the Linux kernel. Verify that a warning
|
|
is issued for it when it writes past the end of the array object. */
|
|
memset (&b[0].a.b, 0, offsetfrom (struct B, b, a.b) + 1); /* { dg-warning "writing 8 bytes into a region of size 7" } */
|
|
escape (b);
|
|
|
|
memset (&b->a.b, 0, offsetfrom (struct B, b, a.b) + 1); /* { dg-warning "writing 8 bytes into a region of size 7" } */
|
|
escape (b);
|
|
|
|
memset (&b[0].c, 0, offsetfrom (struct B, b, c) + 1); /* { dg-warning "writing 7 bytes into a region of size 6" } */
|
|
escape (b);
|
|
|
|
memset (&b->c, 0, offsetfrom (struct B, b, c) + 1); /* { dg-warning "writing 7 bytes into a region of size 6" } */
|
|
escape (b);
|
|
|
|
memset (&b[0].d, 0, offsetfrom (struct B, b, d) + 1); /* { dg-warning "writing 6 bytes into a region of size 5" } */
|
|
escape (b);
|
|
|
|
memset (&b->d, 0, offsetfrom (struct B, b, d) + 1); /* { dg-warning "writing 6 bytes into a region of size 5" } */
|
|
escape (b);
|
|
|
|
/* Same as above but clearing just elements of the second element
|
|
of the array. */
|
|
memset (&b[1].a.b, 0, offsetfrom (struct B, b[1], a.b) + 1); /* { dg-warning "writing 4 bytes into a region of size 3" } */
|
|
escape (b);
|
|
|
|
memset (&b[1].c, 0, offsetfrom (struct B, b[1], c) + 1); /* { dg-warning "writing 3 bytes into a region of size 2" } */
|
|
escape (b);
|
|
|
|
memset (&b[1].d, 0, offsetfrom (struct B, b[1], d) + 1); /* { dg-warning "writing 2 bytes into a region of size 1" } */
|
|
escape (b);
|
|
}
|
|
|
|
/* Verify that writing past the end of a dynamically allocated array
|
|
of known size is diagnosed. */
|
|
|
|
void test_memop_warn_alloc (const void *src)
|
|
{
|
|
size_t n;
|
|
|
|
n = range (8, 32);
|
|
|
|
struct A *a = __builtin_malloc (sizeof *a * 2);
|
|
|
|
memcpy (a, src, n); /* { dg-warning "writing between 8 and 32 bytes into a region of size 4 overflows the destination" "memcpy into allocated" { xfail *-*-*} } */
|
|
escape (a, src);
|
|
|
|
/* At -Wrawmem-overflow=1 the destination is considered to be
|
|
the whole array and its size is therefore sizeof a. */
|
|
memcpy (&a[0], src, n); /* { dg-warning "writing between 8 and 32 bytes into a region of size 4 overflows the destination" "memcpy into allocated" { xfail *-*-*} } */
|
|
escape (a, src);
|
|
|
|
/* Verify the same as above but by writing into the first mmeber
|
|
of the first element of the array. */
|
|
memcpy (&a[0].a, src, n); /* { dg-warning "writing between 8 and 32 bytes into a region of size 4 overflows the destination" "memcpy into allocated" { xfail *-*-*} } */
|
|
escape (a, src);
|
|
|
|
n = range (12, 32);
|
|
|
|
struct B *b = __builtin_malloc (sizeof *b * 2);
|
|
|
|
memcpy (&b[0], src, n); /* { dg-warning "writing between 12 and 32 bytes into a region of size 8 overflows the destination" "memcpy into allocated" { xfail *-*-*} } */
|
|
escape (b);
|
|
|
|
/* The following idiom of clearing multiple members of a struct is
|
|
used in a few places in the Linux kernel. Verify that a warning
|
|
is issued for it when it writes past the end of the array object. */
|
|
memset (&b[0].a.b, 0, offsetfrom (struct B, b, a.b) + 1); /* { dg-warning "writing 8 bytes into a region of size 7" "memcpy into allocated" { xfail *-*-*} } */
|
|
escape (b);
|
|
|
|
memset (&b->a.b, 0, offsetfrom (struct B, b, a.b) + 1); /* { dg-warning "writing 8 bytes into a region of size 7" "memcpy into allocated" { xfail *-*-*} } */
|
|
escape (b);
|
|
|
|
memset (&b[0].c, 0, offsetfrom (struct B, b, c) + 1); /* { dg-warning "writing 7 bytes into a region of size 6" "memcpy into allocated" { xfail *-*-*} } */
|
|
escape (b);
|
|
|
|
memset (&b->c, 0, offsetfrom (struct B, b, c) + 1); /* { dg-warning "writing 7 bytes into a region of size 6" "memcpy into allocated" { xfail *-*-*} } */
|
|
escape (b);
|
|
|
|
memset (&b[0].d, 0, offsetfrom (struct B, b, d) + 1); /* { dg-warning "writing 6 bytes into a region of size 5" "memcpy into allocated" { xfail *-*-*} } */
|
|
escape (b);
|
|
|
|
memset (&b->d, 0, offsetfrom (struct B, b, d) + 1); /* { dg-warning "writing 6 bytes into a region of size 5" "memcpy into allocated" { xfail *-*-*} } */
|
|
escape (b);
|
|
|
|
/* Same as above but clearing just elements of the second element
|
|
of the array. */
|
|
memset (&b[1].a.b, 0, offsetfrom (struct B, b[1], a.b) + 1); /* { dg-warning "writing 4 bytes into a region of size 3" "memcpy into allocated" { xfail *-*-*} } */
|
|
escape (b);
|
|
|
|
memset (&b[1].c, 0, offsetfrom (struct B, b[1], c) + 1); /* { dg-warning "writing 3 bytes into a region of size 2" "memcpy into allocated" { xfail *-*-*} } */
|
|
escape (b);
|
|
|
|
memset (&b[1].d, 0, offsetfrom (struct B, b[1], d) + 1); /* { dg-warning "writing 2 bytes into a region of size 1" "memcpy into allocated" { xfail *-*-*} } */
|
|
escape (b);
|
|
}
|
|
|
|
|
|
void test_memop_nowarn (const void *src)
|
|
{
|
|
struct B b[2];
|
|
|
|
size_t n = range (sizeof b, 32);
|
|
|
|
/* Verify that clearing the whole array is not diagnosed regardless
|
|
of whether the expression pointing to its beginning is obtained
|
|
from the array itself or its first member(s). */
|
|
memcpy (b, src, n);
|
|
escape (b);
|
|
|
|
memcpy (&b[0], src, n);
|
|
escape (b);
|
|
|
|
memcpy (&b[0].a, src, n);
|
|
escape (b, src);
|
|
|
|
memcpy (&b[0].a.a, src, n);
|
|
escape (b, src);
|
|
|
|
/* Clearing multiple elements of an array of structs. */
|
|
memset (&b[0].a.b, 0, sizeof b - offsetof (struct B, a.b));
|
|
escape (b);
|
|
|
|
memset (&b->a.b, 0, sizeof b - offsetof (struct B, a.b));
|
|
escape (b);
|
|
|
|
memset (&b[0].c, 0, sizeof b - offsetof (struct B, c));
|
|
escape (b);
|
|
|
|
memset (&b->c, 0, sizeof b - offsetof (struct B, c));
|
|
escape (b);
|
|
|
|
memset (&b[0].d, 0, sizeof b - offsetof (struct B, d));
|
|
escape (b);
|
|
|
|
memset (&b->d, 0, sizeof b - offsetof (struct B, d));
|
|
escape (b);
|
|
|
|
/* Same as above but clearing just elements of the second element
|
|
of the array. */
|
|
memset (&b[1].a.b, 0, sizeof b[1] - offsetof (struct B, a.b));
|
|
escape (b);
|
|
|
|
memset (&b[1].c, 0, sizeof b[1] - offsetof (struct B, c));
|
|
escape (b);
|
|
|
|
memset (&b[1].d, 0, sizeof b[1] - offsetof (struct B, d));
|
|
escape (b);
|
|
}
|
|
|
|
|
|
/* The foollowing function could specify in its API that it takes
|
|
an array of exactly two elements, as shown below. Verify that
|
|
writing into both elements is not diagnosed. */
|
|
void test_memop_nowarn_arg (struct A[2], const void*);
|
|
|
|
void test_memop_nowarn_arg (struct A *a, const void *src)
|
|
{
|
|
memcpy (a, src, 2 * sizeof *a);
|
|
escape (a, src);
|
|
|
|
memcpy (a, src, range (2 * sizeof *a, 123));
|
|
escape (a, src);
|
|
}
|
|
|
|
|
|
struct C { char a[3], b; };
|
|
struct D { struct C c; char d, e; };
|
|
|
|
extern char* strncpy (char*, const char*, __SIZE_TYPE__);
|
|
|
|
void test_stringop_warn (void)
|
|
{
|
|
size_t n = range (2 * sizeof (struct D) + 1, 33);
|
|
|
|
struct C c[2];
|
|
|
|
/* Similarly, at -Wstringop-overflow=1 the destination is considered
|
|
to be the whole array and its size is therefore sizeof c. */
|
|
strncpy (c[0].a, "123", n); /* { dg-warning "writing between 13 and 33 bytes into a region of size 8 overflows the destination" } */
|
|
|
|
escape (c);
|
|
}
|
|
|
|
|
|
void test_stringop_nowarn (void)
|
|
{
|
|
struct D d[2];
|
|
|
|
strncpy (d[0].c.a, "123", range (sizeof d, 32));
|
|
escape (d);
|
|
}
|