@@ -142,6 +142,10 @@ Wanalyzer-putenv-of-auto-var
Common Var(warn_analyzer_putenv_of_auto_var) Init(1) Warning
Warn about code paths in which an on-stack buffer is passed to putenv.
+Wanalyzer-restrict
+Common Var(warn_analyzer_restrict) Init(1) Warning
+Warn about code paths in which an argument passed to a restrict-qualified parameter aliases with another argument.
+
Wanalyzer-shift-count-negative
Common Var(warn_analyzer_shift_count_negative) Init(1) Warning
Warn about code paths in which a shift with negative count is attempted.
@@ -502,7 +502,6 @@ region_model::impl_call_malloc (const call_details &cd)
}
/* Handle the on_call_pre part of "memcpy" and "__builtin_memcpy". */
-// TODO: complain about overlapping src and dest.
void
region_model::impl_call_memcpy (const call_details &cd)
@@ -516,6 +515,9 @@ region_model::impl_call_memcpy (const call_details &cd)
const region *src_reg = deref_rvalue (src_ptr_sval, cd.get_arg_tree (1),
cd.get_ctxt ());
+ check_region_overlap (src_reg, /* src_idx */ 1, dest_reg, /* dst_idx */ 0,
+ num_bytes_sval, cd);
+
cd.maybe_set_lhs (dest_ptr_sval);
const region *sized_src_reg
@@ -527,6 +529,27 @@ region_model::impl_call_memcpy (const call_details &cd)
set_value (sized_dest_reg, src_contents_sval, cd.get_ctxt ());
}
+/* Handle the on_call_pre part of "mempcpy" and "__builtin_mempcpy". */
+
+void
+region_model::impl_call_mempcpy (const call_details &cd)
+{
+ const svalue *dest_ptr_sval = cd.get_arg_svalue (0);
+ const svalue *src_ptr_sval = cd.get_arg_svalue (1);
+ const svalue *num_bytes_sval = cd.get_arg_svalue (2);
+
+ const region *dest_reg = deref_rvalue (dest_ptr_sval, cd.get_arg_tree (0),
+ cd.get_ctxt ());
+ const region *src_reg = deref_rvalue (src_ptr_sval, cd.get_arg_tree (1),
+ cd.get_ctxt ());
+
+ check_region_for_write (dest_reg, cd.get_ctxt ());
+ check_region_overlap (src_reg, /* src_idx */ 1, dest_reg, /* dst_idx */ 0,
+ num_bytes_sval, cd);
+
+ /* TODO: model the return value behavior. */
+}
+
/* Handle the on_call_pre part of "memset" and "__builtin_memset". */
void
@@ -1676,6 +1676,277 @@ void region_model::check_region_bounds (const region *reg,
}
}
+/* Concrete subclass of pending_diagnostic_subclass complaining about arguments
+ passed to restrict-qualified parameters aliasing with another argument. */
+
+class restrict_alias_diagnostic
+ : public pending_diagnostic_subclass<restrict_alias_diagnostic>
+{
+public:
+ restrict_alias_diagnostic (tree src_tree, unsigned src_idx,
+ tree dst_tree, unsigned dst_idx, tree fndecl)
+ : m_src_idx (src_idx), m_dst_idx (dst_idx), m_fndecl (fndecl)
+ {
+ m_src_tree = fixup_tree_for_diagnostic (src_tree);
+ m_dst_tree = fixup_tree_for_diagnostic (dst_tree);
+ }
+
+ const char *get_kind () const final override
+ {
+ return "restrict_diagnostic";
+ }
+
+ bool operator== (const restrict_alias_diagnostic &other) const
+ {
+ return m_src_idx == other.m_src_idx && m_dst_idx == other.m_dst_idx
+ && pending_diagnostic::same_tree_p (m_src_tree, other.m_src_tree)
+ && pending_diagnostic::same_tree_p (m_dst_tree, other.m_dst_tree)
+ && pending_diagnostic::same_tree_p (m_fndecl, other.m_fndecl);
+ }
+
+ int get_controlling_option () const final override
+ {
+ return OPT_Wanalyzer_restrict;
+ }
+
+ bool emit (rich_location *rich_loc) override
+ {
+ diagnostic_metadata m;
+ bool warned = warning_meta (rich_loc, m, get_controlling_option (),
+ "argument %u passed to %<restrict%>-qualified"
+ " parameter aliases with argument %u",
+ m_src_idx + 1, m_dst_idx + 1);
+
+ if (warned)
+ inform (DECL_SOURCE_LOCATION (m_fndecl), "declared here");
+
+ return warned;
+ }
+
+ label_text describe_final_event (const evdesc::final_event &ev) override
+ {
+ if (m_src_tree && m_dst_tree)
+ return ev.formatted_print ("%qE and %qE point to the same memory"
+ " location", m_src_tree, m_dst_tree);
+ return ev.formatted_print ("argument %u and %u point to the same location",
+ m_src_idx + 1, m_dst_idx + 1);
+ }
+
+protected:
+ tree m_src_tree;
+ unsigned m_src_idx;
+ tree m_dst_tree;
+ unsigned m_dst_idx;
+ tree m_fndecl;
+};
+
+/* Concrete subclass of restrict_alias to warn on the special case where a
+ number of bytes are copied and the buffers shall not overlap. */
+
+class region_overlap_diagnostic : public restrict_alias_diagnostic
+{
+public:
+ region_overlap_diagnostic (tree src_tree, unsigned src_idx,
+ tree dst_tree, unsigned dst_idx, tree num,
+ tree overlapping_bytes, tree fndecl)
+ : restrict_alias_diagnostic (src_tree, src_idx, dst_tree, dst_idx, fndecl),
+ m_num (num), m_overlapping_bytes (overlapping_bytes)
+ {}
+
+ bool operator== (const region_overlap_diagnostic &other) const
+ {
+ return restrict_alias_diagnostic::operator== (other)
+ && pending_diagnostic::same_tree_p (m_num, other.m_num)
+ && pending_diagnostic::same_tree_p (m_overlapping_bytes,
+ other.m_overlapping_bytes);
+ }
+
+ bool emit (rich_location *rich_loc) final override
+ {
+ diagnostic_metadata m;
+ if (m_fndecl)
+ {
+ bool warned = warning_meta (rich_loc, m, get_controlling_option (),
+ "calling %qE with overlapping buffers"
+ " results in undefined behavior",
+ m_fndecl);
+ if (warned)
+ inform (rich_loc->get_loc (),
+ "use %<memmove%> instead of %qE with overlapping buffers",
+ m_fndecl);
+ return warned;
+ }
+
+ bool warned = warning_meta (rich_loc, m, get_controlling_option (),
+ "calling with overlapping buffers"
+ " results in undefined behavior");
+ if (warned)
+ inform (rich_loc->get_loc (),
+ "use %<memmove%> instead with overlapping buffers");
+ return warned;
+ }
+
+ label_text describe_final_event (const evdesc::final_event &ev)
+ final override
+ {
+ const char *unit = integer_onep (m_overlapping_bytes) ? "byte" : "bytes";
+ if (m_num && m_src_tree && m_dst_tree)
+ return ev.formatted_print ("copying %E bytes from %qE to %qE overlaps by"
+ " %E %s",
+ m_num, m_src_tree, m_dst_tree,
+ m_overlapping_bytes, unit);
+ return ev.formatted_print ("copying %E bytes from argument %u to argument"
+ " %u overlaps by %E %s",
+ m_num, m_src_idx + 1, m_dst_idx + 1,
+ m_overlapping_bytes, unit);
+ }
+
+private:
+ tree m_num;
+ tree m_overlapping_bytes;
+};
+
+/* Check whether RQ_PARAM and OTHER_PARAM point to the same memory location
+ and might emit a warning.
+
+ For correct output in the diagnostics, provide the region of the
+ restrict-qualified parameter in RQ_PARAM and the other parameter in
+ OTHER_PARAM. RQ_PARAM_IDX and OTHER_PARAM_IDX are 0-based indices to
+ retrieve the argument from CD. */
+
+void region_model::check_region_aliases (const region *rq_param,
+ unsigned rq_param_idx,
+ const region *other_param,
+ unsigned other_param_idx,
+ const call_details &cd) const
+{
+ /* Do not warn again if Wrestrict already warned at this statement. */
+ if (warning_suppressed_p (cd.get_call_stmt (), OPT_Wrestrict))
+ return;
+
+ region_model_context *ctxt = cd.get_ctxt ();
+ if (!ctxt)
+ return;
+
+ /* Remove possibly wrapping casts and check whether SRC and DST are equal
+ and not symbolic. */
+ rq_param = rq_param->unwrap_cast ();
+ other_param = other_param->unwrap_cast ();
+ if (rq_param == other_param && !rq_param->symbolic_p ())
+ {
+ tree rq_param_tree = cd.get_arg_tree (rq_param_idx);
+ tree other_param_tree = cd.get_arg_tree (other_param_idx);
+ ctxt->warn (new restrict_alias_diagnostic (rq_param_tree, rq_param_idx,
+ other_param_tree,
+ other_param_idx,
+ cd.get_fndecl_for_call ()));
+ }
+}
+
+/* Checks whether SRC and DST overlap and might emit a warning.
+
+ src_idx and dst_idx are 0-based indices to retrieve the argument from CD.
+ NUM_BYTES_SVAL is the number of bytes that are copied starting from SRC,
+ i.e. the third argument of memcpy. If NUM_BYTES_SVAL is non-constant, it
+ falls back to check whether SRC and DST point to the same location. */
+
+void region_model::check_region_overlap (const region *src,
+ unsigned src_idx,
+ const region *dst,
+ unsigned dst_idx,
+ const svalue *num_bytes_sval,
+ const call_details &cd) const
+{
+ gcc_assert (num_bytes_sval);
+
+ /* Do not warn again if Wrestrict already warned at this statement. */
+ if (warning_suppressed_p (cd.get_call_stmt (), OPT_Wrestrict))
+ return;
+
+ region_model_context *ctxt = cd.get_ctxt ();
+ if (!ctxt)
+ return;
+
+ num_bytes_sval = num_bytes_sval->unwrap_cast ();
+ if (tree num_bytes_tree = num_bytes_sval->maybe_get_constant ())
+ {
+ /* Bail out if the constant is no integer. */
+ if (!INTEGRAL_TYPE_P (TREE_TYPE (num_bytes_tree)))
+ return;
+ byte_size_t num_bytes = TREE_INT_CST_LOW (num_bytes_tree);
+
+ region_offset src_offset = src->get_offset ();
+ region_offset dst_offset = dst->get_offset ();
+ const region *src_base_reg = src_offset.get_base_region ();
+ const region *dst_base_reg = dst_offset.get_base_region ();
+
+ /* Check that the base_regions are the same, not symbolic and that
+ both offsets are also not symbolic. */
+ if (src_base_reg == dst_base_reg && !src_base_reg->symbolic_p ()
+ && !src_offset.symbolic_p () && !dst_offset.symbolic_p ())
+ {
+ /* It is prohibited to get the pointer address of bit fields, thus
+ we can assume that the offset here is always a multiple of 8. */
+ byte_size_t src_byte_offset
+ = src_offset.get_bit_offset () >> LOG2_BITS_PER_UNIT;
+ byte_size_t dst_byte_offset
+ = dst_offset.get_bit_offset () >> LOG2_BITS_PER_UNIT;
+
+ /* If the SRC pointer < DST pointer, we need to check that
+ SRC + NUM_BYTES_SVAL is still before the beginning of DST.
+ Overlapping buffers would yield unexpected behavior if the
+ implementation copies in the forward direction.
+
+ Otherwise, vice versa. */
+ byte_range src_range (src_byte_offset, num_bytes);
+ byte_range dst_range (dst_byte_offset, num_bytes);
+ byte_size_t num_overlap_bytes;
+ /* Emit a warning if the buffers overlap. */
+ if (src_range.intersects_p (dst_range, &num_overlap_bytes))
+ {
+ tree src_tree = cd.get_arg_tree (src_idx);
+ tree dst_tree = cd.get_arg_tree (dst_idx);
+ tree num_overlap_tree
+ = wide_int_to_tree (size_type_node, num_overlap_bytes);
+ tree fndecl = cd.get_fndecl_for_call ();
+ ctxt->warn (new region_overlap_diagnostic (src_tree, src_idx,
+ dst_tree, dst_idx,
+ num_bytes_tree,
+ num_overlap_tree,
+ fndecl));
+ }
+ }
+ }
+ else if (const widening_svalue *w_sval
+ = dyn_cast <const widening_svalue *> (num_bytes_sval))
+ {
+ /* Recheck for both values of widening_svalue. */
+ region_model::check_region_overlap (src, src_idx, dst, dst_idx,
+ w_sval->get_base_svalue (), cd);
+ region_model::check_region_overlap (src, src_idx, dst, dst_idx,
+ w_sval->get_iter_svalue (), cd);
+ }
+ else
+ {
+ /* Fall back and only check for aliases. */
+ src = src->unwrap_cast ();
+ dst = dst->unwrap_cast ();
+ if (src == dst && !src->symbolic_p ())
+ {
+ tree src_tree = cd.get_arg_tree (src_idx);
+ tree dst_tree = cd.get_arg_tree (dst_idx);
+ /* The number of copied bytes is equal to the overlapping bytes. */
+ tree num_bytes_tree = get_representative_tree (num_bytes_sval);
+ tree fndecl = cd.get_fndecl_for_call ();
+ ctxt->warn (new region_overlap_diagnostic (src_tree, src_idx,
+ dst_tree, dst_idx,
+ num_bytes_tree,
+ num_bytes_tree,
+ fndecl));
+ }
+ }
+}
+
/* Ensure that all arguments at the call described by CD are checked
for poisoned values, by calling get_rvalue on each argument. */
@@ -1844,6 +2115,10 @@ region_model::on_call_pre (const gcall *call, region_model_context *ctxt,
case BUILT_IN_MEMCPY_CHK:
impl_call_memcpy (cd);
return false;
+ case BUILT_IN_MEMPCPY:
+ case BUILT_IN_MEMPCPY_CHK:
+ impl_call_mempcpy (cd);
+ return false;
case BUILT_IN_MEMSET:
case BUILT_IN_MEMSET_CHK:
impl_call_memset (cd);
@@ -1991,6 +2266,15 @@ region_model::on_call_pre (const gcall *call, region_model_context *ctxt,
&& (!(callee_fndecl_flags & (ECF_CONST | ECF_PURE)))
&& !fndecl_built_in_p (callee_fndecl))
unknown_side_effects = true;
+
+ /* TODO: Check for function calls that any restrict-qualified parameter
+ does not alias with another parameter with the
+ region_model::check_region_aliases method.
+
+ The C standard states that aliases to restrict-qualified
+ parameters are defined behavior if neither the alias nor the
+ restrict-qualified parameter is written. We did not come to a
+ conclusion on how to handle this case yet. */
}
else
unknown_side_effects = true;
@@ -629,6 +629,7 @@ class region_model
void impl_call_free (const call_details &cd);
void impl_call_malloc (const call_details &cd);
void impl_call_memcpy (const call_details &cd);
+ void impl_call_mempcpy (const call_details &cd);
void impl_call_memset (const call_details &cd);
void impl_call_putenv (const call_details &cd);
void impl_call_realloc (const call_details &cd);
@@ -873,6 +874,13 @@ class region_model
region_model_context *ctxt) const;
void check_region_bounds (const region *reg, enum access_direction dir,
region_model_context *ctxt) const;
+ void check_region_aliases (const region *src, unsigned src_idx,
+ const region *dst, unsigned dst_idx,
+ const call_details &cd) const;
+ void check_region_overlap (const region *src, unsigned src_idx,
+ const region *dst, unsigned dst_idx,
+ const svalue *num_sval,
+ const call_details &cd) const;
void check_call_args (const call_details &cd) const;
void check_external_function_for_access_attr (const gcall *call,
@@ -275,6 +275,17 @@ region::can_have_initial_svalue_p () const
}
}
+/* If this region is a cast_region, return the original region.
+ Otherwise, return this region. */
+
+const region *
+region::unwrap_cast () const
+{
+ if (const cast_region *cast_reg = dyn_cast_cast_region ())
+ return cast_reg->get_original_region ();
+ return this;
+}
+
/* If this region is a decl_region, return the decl.
Otherwise return NULL. */
@@ -155,6 +155,7 @@ public:
const frame_region *maybe_get_frame_region () const;
enum memory_space get_memory_space () const;
bool can_have_initial_svalue_p () const;
+ const region *unwrap_cast () const;
tree maybe_get_decl () const;
@@ -153,6 +153,17 @@ svalue::unwrap_any_unmergeable () const
return this;
}
+/* If this svalue is a cast (i.e a unaryop NOP_EXPR or VIEW_CONVERT_EXPR),
+ return the underlying svalue.
+ Otherwise return this svalue. */
+
+const svalue *
+svalue::unwrap_cast () const
+{
+ const svalue *sval = maybe_undo_cast ();
+ return sval ? sval : this;
+}
+
/* Attempt to merge THIS with OTHER, returning the merged svalue.
Return NULL if not mergeable. */
@@ -139,6 +139,7 @@ public:
const region *maybe_get_region () const;
const svalue *maybe_undo_cast () const;
const svalue *unwrap_any_unmergeable () const;
+ const svalue *unwrap_cast () const;
const svalue *can_merge_p (const svalue *other,
region_model_manager *mgr,
@@ -463,6 +463,7 @@ Objective-C and Objective-C++ Dialects}.
-Wno-analyzer-possible-null-argument @gol
-Wno-analyzer-possible-null-dereference @gol
-Wno-analyzer-putenv-of-auto-var @gol
+-Wno-analyzer-restrict @gol
-Wno-analyzer-shift-count-negative @gol
-Wno-analyzer-shift-count-overflow @gol
-Wno-analyzer-stale-setjmp-buffer @gol
@@ -9769,6 +9770,7 @@ Enabling this option effectively enables the following warnings:
-Wanalyzer-possible-null-argument @gol
-Wanalyzer-possible-null-dereference @gol
-Wanalyzer-putenv-of-auto-var @gol
+-Wanalyzer-restrict @gol
-Wanalyzer-shift-count-negative @gol
-Wanalyzer-shift-count-overflow @gol
-Wanalyzer-stale-setjmp-buffer @gol
@@ -10072,6 +10074,21 @@ or an on-stack buffer.
See @uref{https://wiki.sei.cmu.edu/confluence/x/6NYxBQ, POS34-C. Do not call putenv() with a pointer to an automatic variable as the argument}.
+@item -Wno-analyzer-restrict
+@opindex Wanalyzer-restrict
+@opindex Wno-analyzer-restrict
+This warning requires @option{-fanalyzer}, which enables it; use
+@option{-Wno-analyzer-restrict} to disable it.
+
+This diagnostic warns for paths through the code in which the destination
+and source arguments passed to @code{memcpy} and @code{mempcpy} overlap.
+
+This diagnostic similar to @option{-Wrestrict} but inside the analyzer.
+The region model of the analyzer allows it to catch more bugs than
+@option{-Wrestrict} in presence of aliases and interprocedural flows.
+Note that this diagnostic, unlike @option{-Wrestrict}, does only warn on
+calls to @code{memcpy} and @code{mempcpy}.
+
@item -Wno-analyzer-shift-count-negative
@opindex Wanalyzer-shift-count-negative
@opindex Wno-analyzer-shift-count-negative
new file mode 100644
@@ -0,0 +1,413 @@
+#define _GNU_SOURCE
+#include <string.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+/* Wanalyzer-restrict tests for memcpy. */
+
+/* Avoid folding of memcpy. */
+typedef void * (*memcpy_t) (void *dst, const void *src, size_t n);
+
+static memcpy_t __attribute__((noinline))
+get_memcpy (void)
+{
+ return memcpy;
+}
+
+static memcpy_t __attribute__((noinline))
+get_mempcpy (void)
+{
+ return mempcpy;
+}
+
+/* element_region & decl_region. */
+
+void test1 (void)
+{
+ memcpy_t fn = get_memcpy ();
+
+ int32_t buf[4] = {0};
+ fn (&buf[2], buf, 2 * sizeof(int32_t));
+}
+
+void test2 (void)
+{
+ memcpy_t fn = get_memcpy ();
+
+ int32_t buf[4] = {0};
+ fn (&buf[1], buf, 2 * sizeof(int32_t)); /* { dg-line test2 } */
+
+ /* { dg-warning "calling 'memcpy' with overlapping buffers results in undefined behavior" "warning" { target *-*-* } test2 } */
+ /* { dg-message "copying 8 bytes from '&buf' to '&buf\\\[1\\\]' overlaps by 4 bytes" "note" { target *-*-* } test2 } */
+ /* { dg-message "use 'memmove' instead of 'memcpy' with overlapping buffers" "note" { target *-*-* } test2 } */
+}
+
+/* element_region & element_region. */
+
+void test3 (void)
+{
+ memcpy_t fn = get_memcpy ();
+
+ int32_t buf[4] = {0};
+ fn (&buf[2], &buf[0], 2 * sizeof(int32_t));
+}
+
+void test4 (void)
+{
+ memcpy_t fn = get_memcpy ();
+
+ int32_t buf[4] = {0};
+ fn (&buf[1], &buf[0], 2 * sizeof(int32_t)); /* { dg-line test4 } */
+
+ /* { dg-warning "calling 'memcpy' with overlapping buffers results in undefined behavior" "warning" { target *-*-* } test4 } */
+ /* { dg-message "copying 8 bytes from '&buf\\\[0\\\]' to '&buf\\\[1\\\]' overlaps by 4 bytes" "note" { target *-*-* } test4 } */
+ /* { dg-message "use 'memmove' instead of 'memcpy' with overlapping buffers" "note" { target *-*-* } test4 } */
+}
+
+/* offset_region & decl_region. */
+
+void test5 (void)
+{
+ memcpy_t fn = get_mempcpy ();
+
+ int32_t buf[4] = {0};
+ fn (buf + 2, buf, 2 * sizeof(int32_t));
+}
+
+void test6 (void)
+{
+ memcpy_t fn = get_memcpy ();
+
+ int32_t buf[4] = {0};
+ fn (buf + 1, buf, 2 * sizeof(int32_t)); /* { dg-line test6 } */
+
+ /* { dg-warning "calling 'memcpy' with overlapping buffers results in undefined behavior" "warning" { target *-*-* } test6 } */
+ /* { dg-message "copying 8 bytes from '&buf' to '&buf \\\+ 4' overlaps by 4 bytes" "note" { target *-*-* } test6 } */
+ /* { dg-message "use 'memmove' instead of 'memcpy' with overlapping buffers" "note" { target *-*-* } test6 } */
+}
+
+/* offset_region & offset_region. */
+
+void test7 (void)
+{
+ memcpy_t fn = get_memcpy ();
+
+ int32_t buf[5] = {0};
+ fn (buf + 3, buf + 1, 2 * sizeof(int32_t));
+}
+
+void test8 (void)
+{
+ memcpy_t fn = get_mempcpy ();
+
+ int32_t buf[4] = {0};
+ fn (buf + 3, buf + 2, 2 * sizeof(int32_t)); /* { dg-line test8 } */
+
+ /* { dg-warning "calling 'mempcpy' with overlapping buffers results in undefined behavior" "warning" { target *-*-* } test8 } */
+ /* { dg-message "copying 8 bytes from '&buf \\\+ 8' to '&buf \\\+ 12' overlaps by 4 bytes" "note" { target *-*-* } test8 } */
+ /* { dg-message "use 'memmove' instead of 'mempcpy' with overlapping buffers" "note" { target *-*-* } test8 } */
+}
+
+/* element_region & heap_allocated_region. */
+
+void test9 (void)
+{
+ memcpy_t fn = get_memcpy ();
+
+ int32_t *buf = malloc (4 * sizeof (int32_t));
+ if (!buf)
+ return;
+ fn (&buf[2], buf, 2 * sizeof(int32_t));
+ free (buf);
+}
+
+void test10 (void)
+{
+ memcpy_t fn = get_memcpy ();
+
+ int32_t *buf = malloc (4 * sizeof (int32_t));
+ if (!buf)
+ return;
+ fn (&buf[1], buf, 2 * sizeof(int32_t)); /* { dg-line test10 } */
+ free (buf);
+
+ /* { dg-warning "calling 'memcpy' with overlapping buffers results in undefined behavior" "warning" { target *-*-* } test10 } */
+ /* { dg-message "copying 8 bytes from 'buf' to 'buf \\\+ 4' overlaps by 4 bytes" "note" { target *-*-* } test10 } */
+ /* { dg-message "use 'memmove' instead of 'memcpy' with overlapping buffers" "note" { target *-*-* } test10 } */
+}
+
+/* offset_region & heap_allocated_region. */
+
+void test11 (void)
+{
+ memcpy_t fn = get_mempcpy ();
+
+ int32_t *buf = malloc (4 * sizeof (int32_t));
+ if (!buf)
+ return;
+ fn (buf + 2, buf, 2 * sizeof(int32_t));
+ free (buf);
+}
+
+void test12 (void)
+{
+ memcpy_t fn = get_memcpy ();
+
+ int32_t *buf = malloc (4 * sizeof (int32_t));
+ if (!buf)
+ return;
+ fn (buf + 1, buf, 2 * sizeof(int32_t)); /* { dg-line test12 } */
+ free (buf);
+
+ /* { dg-warning "calling 'memcpy' with overlapping buffers results in undefined behavior" "warning" { target *-*-* } test12 } */
+ /* { dg-message "copying 8 bytes from 'buf' to 'buf \\\+ 4' overlaps by 4 bytes" "note" { target *-*-* } test12 } */
+ /* { dg-message "use 'memmove' instead of 'memcpy' with overlapping buffers" "note" { target *-*-* } test12 } */
+}
+
+/* aliased region. */
+
+void test13 (void)
+{
+ memcpy_t fn = get_memcpy ();
+
+ int32_t buf[4] = {0};
+ void *view = buf;
+ fn (view, buf, 2 * sizeof(int32_t)); /* { dg-line test13 } */
+
+ /* { dg-warning "calling 'memcpy' with overlapping buffers results in undefined behavior" "warning" { target *-*-* } test13 } */
+ /* { dg-message "copying 8 bytes from '&buf' to 'view' overlaps by 8 bytes" "note" { target *-*-* } test13 } */
+ /* { dg-message "use 'memmove' instead of 'memcpy' with overlapping buffers" "note" { target *-*-* } test13 } */
+}
+
+void test14 (void)
+{
+ memcpy_t fn = get_memcpy ();
+
+ int32_t buf[4] = {0};
+ void *view = buf;
+ fn (view + 2 * sizeof(int32_t), buf, 2 * sizeof(int32_t));
+}
+
+void test15 (void)
+{
+ memcpy_t fn = get_memcpy ();
+
+ int32_t buf[4] = {0};
+ void *view = buf;
+ fn (view + sizeof(int32_t), buf, 2 * sizeof(int32_t)); /* { dg-line test15 } */
+
+ /* { dg-warning "calling 'memcpy' with overlapping buffers results in undefined behavior" "warning" { target *-*-* } test15 } */
+ /* { dg-message "copying 8 bytes from '&buf' to 'view \\\+ 4' overlaps by 4 bytes" "note" { target *-*-* } test15 } */
+ /* { dg-message "use 'memmove' instead of 'memcpy' with overlapping buffers" "note" { target *-*-* } test15 } */
+}
+
+void test16 (void)
+{
+ memcpy_t fn = get_mempcpy ();
+
+ int16_t buf[2];
+ int32_t *view = (int32_t *) buf;
+ fn (view, buf, sizeof (int32_t)); /* { dg-line test16 } */
+
+ /* { dg-warning "calling 'mempcpy' with overlapping buffers results in undefined behavior" "warning" { target *-*-* } test16 } */
+ /* { dg-message "copying 4 bytes from '&buf' to 'view' overlaps by 4 bytes" "note" { target *-*-* } test16 } */
+ /* { dg-message "use 'memmove' instead of 'mempcpy' with overlapping buffers" "note" { target *-*-* } test16 } */
+}
+
+void test17 (void)
+{
+ memcpy_t fn = get_memcpy ();
+
+ int16_t buf[4];
+ int32_t *view = (int32_t *) buf;
+ fn (&view[1], buf, sizeof (int32_t));
+}
+
+/* field_region. */
+
+struct my_struct {
+ int32_t i[4];
+};
+
+void test18 (void)
+{
+ memcpy_t fn = get_memcpy ();
+
+ struct my_struct s;
+ for (int i = 0; i < 4; i++)
+ s.i[i] = i;
+ fn (s.i + 2, s.i, 2 * sizeof (int32_t));
+}
+
+void test19 (void)
+{
+ memcpy_t fn = get_memcpy ();
+
+ struct my_struct s;
+ for (int i = 0; i < 4; i++)
+ s.i[i] = i;
+ fn (s.i + 1, s.i, 2 * sizeof (int32_t)); /* { dg-line test19 } */
+
+ /* { dg-warning "calling 'memcpy' with overlapping buffers results in undefined behavior" "warning" { target *-*-* } test19 } */
+ /* { dg-message "copying 8 bytes from '&s.i' to '&s.i \\\+ 4' overlaps by 4 bytes" "note" { target *-*-* } test19 } */
+ /* { dg-message "use 'memmove' instead of 'memcpy' with overlapping buffers" "note" { target *-*-* } test19 } */
+}
+
+void test20 (int cond)
+{
+ memcpy_t fn = get_memcpy ();
+
+ int32_t buf[4];
+ int32_t n;
+ if (cond)
+ n = 2 * sizeof (int32_t);
+ else
+ n = 3 * sizeof (int32_t);
+ fn (buf + 2, buf, n); /* { dg-line test20 } */
+
+ /* { dg-warning "calling 'memcpy' with overlapping buffers results in undefined behavior" "warning" { target *-*-* } test20 } */
+ /* { dg-message "copying 12 bytes from '&buf' to '&buf \\\+ 8' overlaps by 4 bytes" "note" { target *-*-* } test20 } */
+ /* { dg-message "use 'memmove' instead of 'memcpy' with overlapping buffers" "note" { target *-*-* } test20 } */
+}
+
+struct nested_struct {
+ struct my_struct *ptr;
+};
+
+void test21 (void)
+{
+ memcpy_t fn = get_memcpy ();
+
+ struct my_struct first;
+ memset (first.i, 0, sizeof (first.i));
+ struct my_struct second;
+ memset (second.i, 0, sizeof (second.i));
+
+ struct my_struct arr[2];
+ arr[0] = first;
+ arr[1] = second;
+ struct nested_struct ns;
+ ns.ptr = arr;
+
+ fn (&ns.ptr[0], &ns.ptr[1], 3 * sizeof (int32_t));
+ fn (ns.ptr, ((char *) ns.ptr) + 2 * sizeof (int32_t), 2 * sizeof (int32_t));
+ fn (ns.ptr, ((char *) ns.ptr) + 2 * sizeof (int32_t), 3 * sizeof (int32_t)); /* { dg-line test21a } */
+ fn (ns.ptr[0].i, ns.ptr[1].i, 3 * sizeof (int32_t));
+ fn (ns.ptr[0].i, ns.ptr[0].i + 2, 2 * sizeof (int32_t));
+ fn (ns.ptr[0].i, ns.ptr[0].i + 2, 3 * sizeof (int32_t)); /* { dg-line test21b } */
+
+ /* { dg-warning "calling 'memcpy' with overlapping buffers results in undefined behavior" "warning" { target *-*-* } test21a } */
+ /* { dg-message "copying 12 bytes from" "note" { target *-*-* } test21a } */
+ /* { dg-message "use 'memmove' instead of 'memcpy' with overlapping buffers" "note" { target *-*-* } test21a } */
+ /* { dg-warning "calling 'memcpy' with overlapping buffers results in undefined behavior" "warning" { target *-*-* } test21b } */
+ /* { dg-message "copying 12 bytes from" "note" { target *-*-* } test21b } */
+ /* { dg-message "use 'memmove' instead of 'memcpy' with overlapping buffers" "note" { target *-*-* } test21b } */
+}
+
+/* 35 bits aka more than 4 bytes. */
+struct bit_struct {
+ unsigned int a : 7;
+ unsigned int b : 7;
+ unsigned int c : 7;
+ unsigned int d : 7;
+ unsigned int e : 7;
+};
+
+void test22 (void)
+{
+ memcpy_t fn = get_memcpy ();
+ struct bit_struct bs;
+ memset (&bs, 0, sizeof (bs));
+
+ char *ptr = (char *) &bs;
+ fn (ptr, ptr + 2, 2);
+ fn (ptr + 2, ptr, 3); /* { dg-line test22 } */
+
+ /* { dg-warning "calling 'memcpy' with overlapping buffers results in undefined behavior" "warning" { target *-*-* } test22 } */
+ /* { dg-message "use 'memmove' instead of 'memcpy' with overlapping buffers" "note" { target *-*-* } test22 } */
+ /* { dg-message "copying 3 bytes from 'ptr' to 'ptr \\\+ 2' overlaps by 1 byte\\n" "note" { target *-*-* } test22 } */
+}
+
+/* Union. */
+
+union my_union {
+ int16_t s;
+ int32_t m;
+};
+
+void test23 (void)
+{
+ memcpy_t fn = get_memcpy ();
+
+ union my_union u;
+ void *ptr = (void *) &u;
+ fn (ptr + 2, &u, 2);
+}
+
+void test24 (void)
+{
+ memcpy_t fn = get_memcpy ();
+
+ union my_union u;
+ void *ptr = (void *) &u;
+ fn (ptr + 1, &u, 2); /* { dg-line test24 } */
+
+ /* { dg-warning "calling 'memcpy' with overlapping buffers results in undefined behavior" "warning" { target *-*-* } test24 } */
+ /* { dg-message "use 'memmove' instead of 'memcpy' with overlapping buffers" "note" { target *-*-* } test24 } */
+ /* { dg-message "copying 2 bytes from '&u' to 'ptr \\\+ 1' overlaps by 1 byte\\n" "note" { target *-*-* } test24 } */
+}
+
+/* Test fallback. */
+
+void test25 (int n)
+{
+ memcpy_t fn = get_memcpy ();
+
+ int32_t buf[4];
+ fn (buf, buf, 2 * n); /* { dg-line test25 } */
+
+ /* { dg-warning "calling 'memcpy' with overlapping buffers results in undefined behavior" "warning" { target *-*-* } test25 } */
+ /* { dg-message "copying n \\\* 2 bytes from '&buf' to '&buf' overlaps by n \\\* 2 bytes" "note" { target *-*-* } test25 } */
+ /* { dg-message "use 'memmove' instead of 'memcpy' with overlapping buffers" "note" { target *-*-* } test25 } */
+}
+
+void test26 (int n)
+{
+ memcpy_t fn = get_memcpy ();
+
+ int32_t buf[4];
+ void *alias = buf;
+ fn (alias, buf, 2 * n); /* { dg-line test26 } */
+
+ /* { dg-warning "calling 'memcpy' with overlapping buffers results in undefined behavior" "warning" { target *-*-* } test26 } */
+ /* { dg-message "copying n \\\* 2 bytes from '&buf' to 'alias' overlaps by n \\\* 2 bytes" "note" { target *-*-* } test26 } */
+ /* { dg-message "use 'memmove' instead of 'memcpy' with overlapping buffers" "note" { target *-*-* } test26 } */
+}
+
+void test27 (int n)
+{
+ memcpy_t fn = get_memcpy ();
+
+ int buf[4];
+ int *alias = buf;
+ fn (alias + 1, buf, 2 * n);
+}
+
+/* Interprocedural. */
+
+void __attribute__((noinline)) memcpy_wrapper (void *dst, void *src, size_t count)
+{
+ memcpy_t fn = get_memcpy ();
+
+ fn (dst, src, count); /* { dg-line test28 } */
+
+ /* { dg-warning "calling 'memcpy' with overlapping buffers results in undefined behavior" "warning" { target *-*-* } test28 } */
+ /* { dg-message "copying 12 bytes from 'src' to 'dst' overlaps by 4 bytes" "note" { target *-*-* } test28 } */
+ /* { dg-message "use 'memmove' instead of 'memcpy' with overlapping buffers" "note" { target *-*-* } test28 } */
+}
+
+void test28 (void)
+{
+ int32_t buf[5];
+ memcpy_wrapper (buf + 2, buf, 3 * sizeof (int32_t));
+}