Hi, below is the patch for the buffer overlap checker in the analyzer. It contains a working buffer overlap warning as well as most of the code needed to also warn on the general case aka when arguments alias with an argument passed to restrict-qualified parameter.
The current C standard draft states that aliases to restrict-qualified parameters are defined behavior if neither the alias nor the restrict-qualified parameter is written. We've not yet come to a conclusion how to handle this and thus, the function doing the general case check is not used. A possible call site has a TODO stating that and the current limitations are documented in invoke.texi. - Tim This patch adds a new checker to complain about overlapping buffers on calls to memcpy and mempcpy. Regression-tested on Linux x86_64 and tested as usual on coreutils, curl, httpd and openssh. 2022-08-21 Tim Lange <m...@tim-lange.me> gcc/analyzer/ChangeLog: PR analyzer/105898 * analyzer.opt: Add Wanalyzer-restrict. * region-model-impl-calls.cc (region_model::impl_call_memcpy): Add call to region_model::check_region_overlap. (region_model::impl_call_mempcpy): New function. * region-model.cc (class restrict_alias): Concrete diagnostic to complain about the disregard of the restrict qualifier. (class region_overlap): Concrete diagnostic to complain about overlapping buffers. (region_model::check_region_aliases): New function. (region_model::check_region_overlap): New function. (region_model::on_call_pre): Add call to region_model::impl_call_mempcpy. * region-model.h (class region_model): Add check_region_aliases and check_region_overlap. * region.cc (region::unwrap_cast): New helper function. * region.h: Add region::unwrap_cast. * svalue.cc (svalue::unwrap_cast): New helper function. * svalue.h: Add svalue::unwrap_cast. gcc/ChangeLog: PR analyzer/105898 * doc/invoke.texi: Add Wanalyzer-restrict. gcc/testsuite/ChangeLog: PR analyzer/105898 * gcc.dg/analyzer/restrict-1.c: New test. --- gcc/analyzer/analyzer.opt | 4 + gcc/analyzer/region-model-impl-calls.cc | 25 +- gcc/analyzer/region-model.cc | 284 ++++++++++++++ gcc/analyzer/region-model.h | 8 + gcc/analyzer/region.cc | 11 + gcc/analyzer/region.h | 1 + gcc/analyzer/svalue.cc | 11 + gcc/analyzer/svalue.h | 1 + gcc/doc/invoke.texi | 17 + gcc/testsuite/gcc.dg/analyzer/restrict-1.c | 413 +++++++++++++++++++++ 10 files changed, 774 insertions(+), 1 deletion(-) create mode 100644 gcc/testsuite/gcc.dg/analyzer/restrict-1.c diff --git a/gcc/analyzer/analyzer.opt b/gcc/analyzer/analyzer.opt index 437ea92e130..ae5ebdb0d41 100644 --- a/gcc/analyzer/analyzer.opt +++ b/gcc/analyzer/analyzer.opt @@ -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. diff --git a/gcc/analyzer/region-model-impl-calls.cc b/gcc/analyzer/region-model-impl-calls.cc index 8eebd122d42..bc8e343643a 100644 --- a/gcc/analyzer/region-model-impl-calls.cc +++ b/gcc/analyzer/region-model-impl-calls.cc @@ -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 diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc index ec29be259b5..0c9d8833ccf 100644 --- a/gcc/analyzer/region-model.cc +++ b/gcc/analyzer/region-model.cc @@ -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; diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h index 7ce832f6ce4..0a6fd431876 100644 --- a/gcc/analyzer/region-model.h +++ b/gcc/analyzer/region-model.h @@ -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, diff --git a/gcc/analyzer/region.cc b/gcc/analyzer/region.cc index f4aba6b9c88..6408ce5bbf2 100644 --- a/gcc/analyzer/region.cc +++ b/gcc/analyzer/region.cc @@ -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. */ diff --git a/gcc/analyzer/region.h b/gcc/analyzer/region.h index d37584b7285..82d655e619e 100644 --- a/gcc/analyzer/region.h +++ b/gcc/analyzer/region.h @@ -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; diff --git a/gcc/analyzer/svalue.cc b/gcc/analyzer/svalue.cc index f5a5f1c9697..6e3461fd2be 100644 --- a/gcc/analyzer/svalue.cc +++ b/gcc/analyzer/svalue.cc @@ -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. */ diff --git a/gcc/analyzer/svalue.h b/gcc/analyzer/svalue.h index f4cab0d4134..92e6917f360 100644 --- a/gcc/analyzer/svalue.h +++ b/gcc/analyzer/svalue.h @@ -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, diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index f65d351a5fc..d88ede653c0 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -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 diff --git a/gcc/testsuite/gcc.dg/analyzer/restrict-1.c b/gcc/testsuite/gcc.dg/analyzer/restrict-1.c new file mode 100644 index 00000000000..8d6732ce602 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/restrict-1.c @@ -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)); +} -- 2.37.2