diff options
author | Simon Marchi <simon.marchi@polymtl.ca> | 2021-11-08 16:06:07 -0500 |
---|---|---|
committer | Simon Marchi <simon.marchi@polymtl.ca> | 2021-12-03 16:37:36 -0500 |
commit | 4bce7cdaf481901edbc5ee47d953ea7e8efb56ca (patch) | |
tree | f085df5b718a9438b5d5174b5e3c9be15e2cea7c /gdb/unittests | |
parent | 7509b82979550970342a4494d727b3fb06bffd65 (diff) | |
download | gdb-4bce7cdaf481901edbc5ee47d953ea7e8efb56ca.zip gdb-4bce7cdaf481901edbc5ee47d953ea7e8efb56ca.tar.gz gdb-4bce7cdaf481901edbc5ee47d953ea7e8efb56ca.tar.bz2 |
gdbsupport: add array_view copy function
An assertion was recently added to array_view::operator[] to ensure we
don't do out of bounds accesses. However, when the array_view is copied
to or from using memcpy, it bypasses that safety.
To address this, add a `copy` free function that copies data from an
array view to another, ensuring that the destination and source array
views have the same size. When copying to or from parts of an
array_view, we are expected to use gdb::array_view::slice, which does
its own bounds check. With all that, any copy operation that goes out
of bounds should be caught by an assertion at runtime.
copy is implemented using std::copy and std::copy_backward, which, at
least on libstdc++, appears to pick memmove when copying trivial data.
So in the end there shouldn't be much difference vs using a bare memcpy,
as we do right now. When copying non-trivial data, std::copy and
std::copy_backward assigns each element in a loop.
To properly support overlapping ranges, we must use std::copy or
std::copy_backward, depending on whether the destination is before the
source or vice-versa. std::copy and std::copy_backward don't support
copying exactly overlapping ranges (where the source range is equal to
the destination range). But in this case, no copy is needed anyway, so
we do nothing.
The order of parameters of the new copy function is based on std::copy
and std::copy_backward, where the source comes before the destination.
Change a few randomly selected spots to use the new function, to show
how it can be used.
Add a test for the new function, testing both with arrays of a trivial
type (int) and of a non-trivial type (foo). Test non-overlapping
ranges as well as three kinds of overlapping ranges: source before dest,
dest before source, and dest == source.
Change-Id: Ibeaca04e0028410fd44ce82f72e60058d6230a03
Diffstat (limited to 'gdb/unittests')
-rw-r--r-- | gdb/unittests/array-view-selftests.c | 104 |
1 files changed, 104 insertions, 0 deletions
diff --git a/gdb/unittests/array-view-selftests.c b/gdb/unittests/array-view-selftests.c index 9df48db..998bd3a 100644 --- a/gdb/unittests/array-view-selftests.c +++ b/gdb/unittests/array-view-selftests.c @@ -546,6 +546,108 @@ run_tests () } } +template <typename T> +void +run_copy_test () +{ + /* Test non-overlapping copy. */ + { + const std::vector<T> src_v = {1, 2, 3, 4}; + std::vector<T> dest_v (4, -1); + + SELF_CHECK (dest_v != src_v); + copy (gdb::array_view<const T> (src_v), gdb::array_view<T> (dest_v)); + SELF_CHECK (dest_v == src_v); + } + + /* Test overlapping copy, where the source is before the destination. */ + { + std::vector<T> vec = {1, 2, 3, 4, 5, 6, 7, 8}; + gdb::array_view<T> v = vec; + + copy (v.slice (1, 4), + v.slice (2, 4)); + + std::vector<T> expected = {1, 2, 2, 3, 4, 5, 7, 8}; + SELF_CHECK (vec == expected); + } + + /* Test overlapping copy, where the source is after the destination. */ + { + std::vector<T> vec = {1, 2, 3, 4, 5, 6, 7, 8}; + gdb::array_view<T> v = vec; + + copy (v.slice (2, 4), + v.slice (1, 4)); + + std::vector<T> expected = {1, 3, 4, 5, 6, 6, 7, 8}; + SELF_CHECK (vec == expected); + } + + /* Test overlapping copy, where the source is the same as the destination. */ + { + std::vector<T> vec = {1, 2, 3, 4, 5, 6, 7, 8}; + gdb::array_view<T> v = vec; + + copy (v.slice (2, 4), + v.slice (2, 4)); + + std::vector<T> expected = {1, 2, 3, 4, 5, 6, 7, 8}; + SELF_CHECK (vec == expected); + } +} + +/* Class with a non-trivial copy assignment operator, used to test the + array_view copy function. */ +struct foo +{ + /* Can be implicitly constructed from an int, such that we can use the same + templated test function to test against array_view<int> and + array_view<foo>. */ + foo (int n) + : n (n) + {} + + /* Needed to avoid -Wdeprecated-copy-with-user-provided-copy error with + Clang. */ + foo (const foo &other) = default; + + void operator= (const foo &other) + { + this->n = other.n; + this->n_assign_op_called++; + } + + bool operator==(const foo &other) const + { + return this->n == other.n; + } + + int n; + + /* Number of times the assignment operator has been called. */ + static int n_assign_op_called; +}; + +int foo::n_assign_op_called = 0; + +/* Test the array_view copy free function. */ + +static void +run_copy_tests () +{ + /* Test with a trivial type. */ + run_copy_test<int> (); + + /* Test with a non-trivial type. */ + foo::n_assign_op_called = 0; + run_copy_test<foo> (); + + /* Make sure that for the non-trivial type foo, the assignment operator was + called an amount of times that makes sense. */ + SELF_CHECK (foo::n_assign_op_called == 12); +} + } /* namespace array_view_tests */ } /* namespace selftests */ @@ -555,4 +657,6 @@ _initialize_array_view_selftests () { selftests::register_test ("array_view", selftests::array_view_tests::run_tests); + selftests::register_test ("array_view-copy", + selftests::array_view_tests::run_copy_tests); } |