diff options
-rw-r--r-- | gdb/ChangeLog | 8 | ||||
-rw-r--r-- | gdb/Makefile.in | 2 | ||||
-rw-r--r-- | gdb/common/array-view.h | 179 | ||||
-rw-r--r-- | gdb/unittests/array-view-selftests.c | 494 |
4 files changed, 683 insertions, 0 deletions
diff --git a/gdb/ChangeLog b/gdb/ChangeLog index 2d12899..f0e9797 100644 --- a/gdb/ChangeLog +++ b/gdb/ChangeLog @@ -1,5 +1,13 @@ 2017-09-04 Pedro Alves <palves@redhat.com> + * Makefile.in (SUBDIR_UNITTESTS_SRCS): Add + unittests/array-view-selftests.c. + (SUBDIR_UNITTESTS_OBS): Add array-view-selftests.o. + * common/array-view.h: New file. + * unittests/array-view-selftests.c: New file. + +2017-09-04 Pedro Alves <palves@redhat.com> + * cli/cli-cmds.c (edit_command): Pass message to ambiguous_line_spec. (list_command): Pass message to ambiguous_line_spec. Say diff --git a/gdb/Makefile.in b/gdb/Makefile.in index 85de646..d9094fd 100644 --- a/gdb/Makefile.in +++ b/gdb/Makefile.in @@ -526,6 +526,7 @@ SUBDIR_PYTHON_LDFLAGS = SUBDIR_PYTHON_CFLAGS = SUBDIR_UNITTESTS_SRCS = \ + unittests/array-view-selftests.c \ unittests/environ-selftests.c \ unittests/function-view-selftests.c \ unittests/offset-type-selftests.c \ @@ -534,6 +535,7 @@ SUBDIR_UNITTESTS_SRCS = \ unittests/scoped_restore-selftests.c SUBDIR_UNITTESTS_OBS = \ + array-view-selftests.o \ environ-selftests.o \ function-view-selftests.o \ offset-type-selftests.o \ diff --git a/gdb/common/array-view.h b/gdb/common/array-view.h new file mode 100644 index 0000000..85d189c --- /dev/null +++ b/gdb/common/array-view.h @@ -0,0 +1,179 @@ +/* Copyright (C) 2017 Free Software Foundation, Inc. + + This file is part of GDB. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#ifndef COMMON_ARRAY_VIEW_H +#define COMMON_ARRAY_VIEW_H + +#include "traits.h" +#include <type_traits> + +/* An array_view is an abstraction that provides a non-owning view + over a sequence of contiguous objects. + + A way to put it is that array_view is to std::vector (and + std::array and built-in arrays with rank==1) like std::string_view + is to std::string. + + The main intent of array_view is to use it as function input + parameter type, making it possible to pass in any sequence of + contiguous objects, irrespective of whether the objects live on the + stack or heap and what actual container owns them. Implicit + construction from the element type is supported too, making it easy + to call functions that expect an array of elements when you only + have one element (usually on the stack). For example: + + struct A { .... }; + void function (gdb::array_view<A> as); + + std::vector<A> std_vec = ...; + std::array<A, N> std_array = ...; + A array[] = {...}; + A elem; + + function (std_vec); + function (std_array); + function (array); + function (elem); + + Views can be either mutable or const. A const view is simply + created by specifying a const T as array_view template parameter, + in which case operator[] of non-const array_view objects ends up + returning const references. Making the array_view itself const is + analogous to making a pointer itself be const. I.e., disables + re-seating the view/pointer. + + Since array_view objects are small (pointer plus size), and + designed to be trivially copyable, they should generally be passed + around by value. + + You can find unit tests covering the whole API in + unittests/array-view-selftests.c. */ + +namespace gdb { + +template <typename T> +class array_view +{ + /* True iff decayed T is the same as decayed U. E.g., we want to + say that 'T&' is the same as 'const T'. */ + template <typename U> + using IsDecayedT = typename std::is_same<typename std::decay<T>::type, + typename std::decay<U>::type>; + + /* True iff decayed T is the same as decayed U, and 'U *' is + implicitly convertible to 'T *'. This is a requirement for + several methods. */ + template <typename U> + using DecayedConvertible = gdb::And<IsDecayedT<U>, + std::is_convertible<U *, T *>>; + +public: + using value_type = T; + using reference = T &; + using const_reference = const T &; + using size_type = size_t; + + /* Default construction creates an empty view. */ + constexpr array_view () noexcept + : m_array (nullptr), m_size (0) + {} + + /* Create an array view over a single object of the type of an + array_view element. The created view as size==1. This is + templated on U to allow constructing a array_view<const T> over a + (non-const) T. The "convertible" requirement makes sure that you + can't create an array_view<T> over a const T. */ + template<typename U, + typename = Requires<DecayedConvertible<U>>> + constexpr array_view (U &elem) noexcept + : m_array (&elem), m_size (1) + {} + + /* Same as above, for rvalue references. */ + template<typename U, + typename = Requires<DecayedConvertible<U>>> + constexpr array_view (U &&elem) noexcept + : m_array (&elem), m_size (1) + {} + + /* Create an array view from a pointer to an array and an element + count. */ + template<typename U, + typename = Requires<DecayedConvertible<U>>> + constexpr array_view (U *array, size_t size) noexcept + : m_array (array), m_size (size) + {} + + /* Create an array view from a range. This is templated on both U + an V to allow passing in a mix of 'const T *' and 'T *'. */ + template<typename U, typename V, + typename = Requires<DecayedConvertible<U>>, + typename = Requires<DecayedConvertible<V>>> + constexpr array_view (U *begin, V *end) noexcept + : m_array (begin), m_size (end - begin) + {} + + /* Create an array view from an array. */ + template<typename U, size_t Size, + typename = Requires<DecayedConvertible<U>>> + constexpr array_view (U (&array)[Size]) noexcept + : m_array (array), m_size (Size) + {} + + /* Create an array view from a contiguous container. E.g., + std::vector and std::array. */ + template<typename Container, + typename = Requires<gdb::Not<IsDecayedT<Container>>>, + typename + = Requires<std::is_convertible + <decltype (std::declval<Container> ().data ()), + T *>>, + typename + = Requires<std::is_convertible + <decltype (std::declval<Container> ().size ()), + size_type>>> + constexpr array_view (Container &&c) noexcept + : m_array (c.data ()), m_size (c.size ()) + {} + + /* Observer methods. Some of these can't be constexpr until we + require C++14. */ + /*constexpr14*/ T *data () noexcept { return m_array; } + constexpr const T *data () const noexcept { return m_array; } + + /*constexpr14*/ T *begin () noexcept { return m_array; } + constexpr const T *begin () const noexcept { return m_array; } + + /*constexpr14*/ T *end () noexcept { return m_array + m_size; } + constexpr const T *end () const noexcept { return m_array + m_size; } + + /*constexpr14*/ reference operator[] (size_t index) noexcept + { return m_array[index]; } + constexpr const_reference operator[] (size_t index) const noexcept + { return m_array[index]; } + + constexpr size_type size () const noexcept { return m_size; } + constexpr bool empty () const noexcept { return m_size == 0; } + +private: + T *m_array; + size_type m_size; +}; + +} /* namespace gdb */ + +#endif diff --git a/gdb/unittests/array-view-selftests.c b/gdb/unittests/array-view-selftests.c new file mode 100644 index 0000000..c4c95bc --- /dev/null +++ b/gdb/unittests/array-view-selftests.c @@ -0,0 +1,494 @@ +/* Self tests for array_view for GDB, the GNU debugger. + + Copyright (C) 2017 Free Software Foundation, Inc. + + This file is part of GDB. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#include "defs.h" +#include "selftest.h" +#include "common/array-view.h" + +namespace selftests { +namespace array_view_tests { + +/* Triviality checks. */ +#define CHECK_TRAIT(TRAIT) \ + static_assert (std::TRAIT<gdb::array_view<gdb_byte>>::value, "") + +#if HAVE_IS_TRIVIALLY_COPYABLE + +CHECK_TRAIT (is_trivially_copyable); +CHECK_TRAIT (is_trivially_move_assignable); +CHECK_TRAIT (is_trivially_move_constructible); +CHECK_TRAIT (is_trivially_destructible); + +#endif + +#undef CHECK_TRAIT + +/* Wrapper around std::is_convertible to make the code using it a bit + shorter. (With C++14 we'd use a variable template instead.) */ + +template<typename From, typename To> +static constexpr bool +is_convertible () +{ + return std::is_convertible<From, To>::value; +} + +/* Check for implicit conversion to immutable and mutable views. */ + +static constexpr bool +check_convertible () +{ + using T = gdb_byte; + using gdb::array_view; + + return (true + /* immutable array_view */ + && is_convertible<const T (&) [1], array_view<const T>> () + && is_convertible<T (&) [1], array_view<const T>> () + && is_convertible<const T, array_view<const T>> () + && is_convertible<T, array_view<const T>> () + + /* mutable array_view */ + && is_convertible<T (&) [1], array_view<T>> () + && !is_convertible<const T (&) [1], array_view<T>> () + && is_convertible<T, array_view<T>> () + && !is_convertible<const T, array_view<T>> () + + /* While float is implicitly convertible to gdb_byte, we + don't want implicit float->array_view<gdb_byte> + conversion. */ + && !is_convertible<float, array_view<const T>> () + && !is_convertible<float, array_view<T>> ()); +} + +static_assert (check_convertible (), ""); + +namespace no_slicing +{ +struct A { int i; }; +struct B : A { int j; }; +struct C : A { int l; }; + +/* Check that there's no array->view conversion for arrays of derived + types or subclasses. */ +static constexpr bool +check () +{ + using gdb::array_view; + + return (true + + /* array->view */ + + && is_convertible <A (&)[1], array_view<A>> () + && !is_convertible <B (&)[1], array_view<A>> () + && !is_convertible <C (&)[1], array_view<A>> () + + && !is_convertible <A (&)[1], array_view<B>> () + && is_convertible <B (&)[1], array_view<B>> () + && !is_convertible <C (&)[1], array_view<B>> () + + /* elem->view */ + + && is_convertible <A, array_view<A>> () + && !is_convertible <B, array_view<A>> () + && !is_convertible <C, array_view<A>> () + + && !is_convertible <A, array_view<B>> () + && is_convertible <B, array_view<B>> () + && !is_convertible <C, array_view<B>> ()); +} + +} /* namespace no_slicing */ + +static_assert (no_slicing::check (), ""); + +/* Check that array_view implicitly converts from std::vector. */ + +static constexpr bool +check_convertible_from_std_vector () +{ + using gdb::array_view; + using T = gdb_byte; + + /* Note there's no such thing as std::vector<const T>. */ + + return (true + && is_convertible <std::vector<T>, array_view<T>> () + && is_convertible <std::vector<T>, array_view<const T>> ()); +} + +static_assert (check_convertible_from_std_vector (), ""); + +/* Check that array_view implicitly converts from std::array. */ + +static constexpr bool +check_convertible_from_std_array () +{ + using gdb::array_view; + using T = gdb_byte; + + /* Note: a non-const T view can't refer to a const T array. */ + + return (true + && is_convertible <std::array<T, 1>, array_view<T>> () + && is_convertible <std::array<T, 1>, array_view<const T>> () + && !is_convertible <std::array<const T, 1>, array_view<T>> () + && is_convertible <std::array<const T, 1>, array_view<const T>> ()); +} + +static_assert (check_convertible_from_std_array (), ""); + +/* Check that VIEW views C (a container like std::vector/std::array) + correctly. */ + +template<typename View, typename Container> +static bool +check_container_view (const View &view, const Container &c) +{ + if (view.empty ()) + return false; + if (view.size () != c.size ()) + return false; + if (view.data () != c.data ()) + return false; + for (size_t i = 0; i < c.size (); i++) + { + if (&view[i] != &c[i]) + return false; + if (view[i] != c[i]) + return false; + } + return true; +} + +/* Check that VIEW views E (an object of the type of a view element) + correctly. */ + +template<typename View, typename Elem> +static bool +check_elem_view (const View &view, const Elem &e) +{ + if (view.empty ()) + return false; + if (view.size () != 1) + return false; + if (view.data () != &e) + return false; + if (&view[0] != &e) + return false; + if (view[0] != e) + return false; + return true; +} + +/* Check for operator[]. The first overload is taken iff + 'view<T>()[0] = T()' is a valid expression. */ + +template<typename View, + typename = decltype (std::declval<View> ()[0] + = std::declval<typename View::value_type> ())> +static bool +check_op_subscript (const View &view) +{ + return true; +} + +/* This overload is taken iff 'view<T>()[0] = T()' is not a valid + expression. */ + +static bool +check_op_subscript (...) +{ + return false; +} + +/* Check construction with pointer + size. This is a template in + order to test both gdb_byte and const gdb_byte. */ + +template<typename T> +static void +check_ptr_size_ctor () +{ + T data[] = {0x11, 0x22, 0x33, 0x44}; + + gdb::array_view<T> view (data + 1, 2); + + SELF_CHECK (!view.empty ()); + SELF_CHECK (view.size () == 2); + SELF_CHECK (view.data () == &data[1]); + SELF_CHECK (view[0] == data[1]); + SELF_CHECK (view[1] == data[2]); + + gdb::array_view<const T> cview (data + 1, 2); + SELF_CHECK (!cview.empty ()); + SELF_CHECK (cview.size () == 2); + SELF_CHECK (cview.data () == &data[1]); + SELF_CHECK (cview[0] == data[1]); + SELF_CHECK (cview[1] == data[2]); +} + +/* Asserts std::is_constructible. */ + +template<typename T, typename... Args> +static constexpr bool +require_not_constructible () +{ + static_assert (!std::is_constructible<T, Args...>::value, ""); + + /* constexpr functions can't return void in C++11 (N3444). */ + return true; +}; + +/* Check the array_view<T>(PTR, SIZE) ctor, when T is a pointer. */ + +void +check_ptr_size_ctor2 () +{ + struct A {}; + A an_a; + + A *array[] = { &an_a }; + const A * const carray[] = { &an_a }; + + gdb::array_view<A *> v1 = {array, ARRAY_SIZE (array)}; + gdb::array_view<A *> v2 = {array, (char) ARRAY_SIZE (array)}; + gdb::array_view<A * const> v3 = {array, ARRAY_SIZE (array)}; + gdb::array_view<const A * const> cv1 = {carray, ARRAY_SIZE (carray)}; + + require_not_constructible<gdb::array_view<A *>, decltype (carray), size_t> (); + + SELF_CHECK (v1[0] == array[0]); + SELF_CHECK (v2[0] == array[0]); + SELF_CHECK (v3[0] == array[0]); + + SELF_CHECK (!v1.empty ()); + SELF_CHECK (v1.size () == 1); + SELF_CHECK (v1.data () == &array[0]); + + SELF_CHECK (cv1[0] == carray[0]); + + SELF_CHECK (!cv1.empty ()); + SELF_CHECK (cv1.size () == 1); + SELF_CHECK (cv1.data () == &carray[0]); +} + +/* Check construction with a pair of pointers. This is a template in + order to test both gdb_byte and const gdb_byte. */ + +template<typename T> +static void +check_ptr_ptr_ctor () +{ + T data[] = {0x11, 0x22, 0x33, 0x44}; + + gdb::array_view<T> view (data + 1, data + 3); + + SELF_CHECK (!view.empty ()); + SELF_CHECK (view.size () == 2); + SELF_CHECK (view.data () == &data[1]); + SELF_CHECK (view[0] == data[1]); + SELF_CHECK (view[1] == data[2]); + + gdb_byte array[] = {0x11, 0x22, 0x33, 0x44}; + const gdb_byte *p1 = array; + gdb_byte *p2 = array + ARRAY_SIZE (array); + gdb::array_view<const gdb_byte> view2 (p1, p2); +} + +/* Check construction with a pair of pointers of mixed constness. */ + +static void +check_ptr_ptr_mixed_cv () +{ + gdb_byte array[] = {0x11, 0x22, 0x33, 0x44}; + const gdb_byte *cp = array; + gdb_byte *p = array; + gdb::array_view<const gdb_byte> view1 (cp, p); + gdb::array_view<const gdb_byte> view2 (p, cp); + SELF_CHECK (view1.empty ()); + SELF_CHECK (view2.empty ()); +} + +/* Check range-for support (i.e., begin()/end()). This is a template + in order to test both gdb_byte and const gdb_byte. */ + +template<typename T> +static void +check_range_for () +{ + T data[] = {1, 2, 3, 4}; + gdb::array_view<T> view (data); + + typename std::decay<T>::type sum = 0; + for (auto &elem : view) + sum += elem; + SELF_CHECK (sum == 1 + 2 + 3 + 4); +} + +/* Entry point. */ + +static void +run_tests () +{ + /* Empty views. */ + { + constexpr gdb::array_view<gdb_byte> view1; + constexpr gdb::array_view<const gdb_byte> view2; + + static_assert (view1.empty (), ""); + static_assert (view1.data () == nullptr, ""); + static_assert (view1.size () == 0, ""); + static_assert (view2.empty (), ""); + static_assert (view2.size () == 0, ""); + static_assert (view2.data () == nullptr, ""); + } + + std::vector<gdb_byte> vec = {0x11, 0x22, 0x33, 0x44 }; + std::array<gdb_byte, 4> array = {{0x11, 0x22, 0x33, 0x44}}; + + /* Various tests of views over std::vector. */ + { + gdb::array_view<gdb_byte> view = vec; + SELF_CHECK (check_container_view (view, vec)); + gdb::array_view<const gdb_byte> cview = vec; + SELF_CHECK (check_container_view (cview, vec)); + } + + /* Likewise, over std::array. */ + { + gdb::array_view<gdb_byte> view = array; + SELF_CHECK (check_container_view (view, array)); + gdb::array_view<gdb_byte> cview = array; + SELF_CHECK (check_container_view (cview, array)); + } + + /* op=(std::vector/std::array/elem) */ + { + gdb::array_view<gdb_byte> view; + + view = vec; + SELF_CHECK (check_container_view (view, vec)); + view = std::move (vec); + SELF_CHECK (check_container_view (view, vec)); + + view = array; + SELF_CHECK (check_container_view (view, array)); + view = std::move (array); + SELF_CHECK (check_container_view (view, array)); + + gdb_byte elem = 0; + view = elem; + SELF_CHECK (check_elem_view (view, elem)); + view = std::move (elem); + SELF_CHECK (check_elem_view (view, elem)); + } + + /* Test copy/move ctor and mutable->immutable conversion. */ + { + gdb_byte data[] = {0x11, 0x22, 0x33, 0x44}; + gdb::array_view<gdb_byte> view1 = data; + gdb::array_view<gdb_byte> view2 = view1; + gdb::array_view<gdb_byte> view3 = std::move (view1); + gdb::array_view<const gdb_byte> cview1 = data; + gdb::array_view<const gdb_byte> cview2 = cview1; + gdb::array_view<const gdb_byte> cview3 = std::move (cview1); + SELF_CHECK (view1[0] == data[0]); + SELF_CHECK (view2[0] == data[0]); + SELF_CHECK (view3[0] == data[0]); + SELF_CHECK (cview1[0] == data[0]); + SELF_CHECK (cview2[0] == data[0]); + SELF_CHECK (cview3[0] == data[0]); + } + + /* Same, but op=(view). */ + { + gdb_byte data[] = {0x55, 0x66, 0x77, 0x88}; + gdb::array_view<gdb_byte> view1; + gdb::array_view<gdb_byte> view2; + gdb::array_view<gdb_byte> view3; + gdb::array_view<const gdb_byte> cview1; + gdb::array_view<const gdb_byte> cview2; + gdb::array_view<const gdb_byte> cview3; + + view1 = data; + view2 = view1; + view3 = std::move (view1); + cview1 = data; + cview2 = cview1; + cview3 = std::move (cview1); + SELF_CHECK (view1[0] == data[0]); + SELF_CHECK (view2[0] == data[0]); + SELF_CHECK (view3[0] == data[0]); + SELF_CHECK (cview1[0] == data[0]); + SELF_CHECK (cview2[0] == data[0]); + SELF_CHECK (cview3[0] == data[0]); + } + + /* op[] */ + { + std::vector<gdb_byte> vec = {0x11, 0x22}; + gdb::array_view<gdb_byte> view = vec; + gdb::array_view<const gdb_byte> cview = vec; + + /* Check that op[] on a non-const view of non-const T returns a + mutable reference. */ + view[0] = 0x33; + SELF_CHECK (vec[0] == 0x33); + + /* OTOH, check that assigning through op[] on a view of const T + wouldn't compile. */ + SELF_CHECK (!check_op_subscript (cview)); + /* For completeness. */ + SELF_CHECK (check_op_subscript (view)); + } + + check_ptr_size_ctor<const gdb_byte> (); + check_ptr_size_ctor<gdb_byte> (); + check_ptr_size_ctor2 (); + check_ptr_ptr_ctor<const gdb_byte> (); + check_ptr_ptr_ctor<gdb_byte> (); + check_ptr_ptr_mixed_cv (); + + check_range_for<gdb_byte> (); + check_range_for<const gdb_byte> (); + + /* Check that the right ctor overloads are taken when the element is + a container. */ + { + using Vec = std::vector<gdb_byte>; + Vec vecs[3]; + + gdb::array_view<Vec> view_array = vecs; + SELF_CHECK (view_array.size () == 3); + + Vec elem; + gdb::array_view<Vec> view_elem = elem; + SELF_CHECK (view_elem.size () == 1); + } +} + +} /* namespace array_view_tests */ +} /* namespace selftests */ + +void +_initialize_array_view_selftests () +{ + selftests::register_test (selftests::array_view_tests::run_tests); +} |