mirror of
https://sourceware.org/git/binutils-gdb.git
synced 2024-12-21 04:42:53 +08:00
e92f2b5eef
While reading the interface of gdb::array_view, I realized that the constructor that builds an array_view on top of a contiguous container (such as std::vector, std::array or even gdb::array_view) can be missused. Lets consider the following code sample: struct Parent { Parent (int a): a { a } {} int a; }; std::ostream &operator<< (std::ostream& os, const Parent & p) { os << "Parent {a=" << p.a << "}"; return os; } struct Child : public Parent { Child (int a, int b): Parent { a }, b { b } {} int b; }; std::ostream &operator<< (std::ostream& os, const Child & p) { os << "Child {a=" << p.a << ", b=" << p.b << "}"; return os; } template <typename T> void print (const gdb::array_view<const T> &p) { std::for_each (p.begin (), p.end (), [](const T &p) { std::cout << p << '\n'; }); } Then with the current interface nothinng prevents this usage of array_view to be done: const std::array<Child, 3> elts = { Child {1, 2}, Child {3, 4}, Child {5, 6} }; print_all<Parent> (elts); This compiles fine and produces the following output: Parent {a=1} Parent {a=2} Parent {a=3} which is obviously wrong. There is nowhere in memory a Parent-like object for which the A member is 2 and this call to print_all<Parent> shold not compile at all (calling print_all<Child> is however fine). This comes down to the fact that a Child* is convertible into a Parent*, and that an array view is constructed to a pointer to the first element and a size. The valid type pointed to that can be used with this constructor are restricted using SFINAE, which requires that a pointer to a member into the underlying container can be converted into a pointer the array_view's data type. This patch proposes to change the constraints on the gdb::array_view ctor which accepts a container now requires that the (decayed) type of the elements in the container match the (decayed) type of the array_view being constructed. Applying this change required minimum adjustment in GDB codebase, which are also included in this patch. Tested by rebuilding.
281 lines
8.6 KiB
C++
281 lines
8.6 KiB
C++
/* Copyright (C) 2017-2021 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<DecayedConvertible
|
||
<typename std::remove_pointer
|
||
<decltype (std::declval<Container> ().data ())
|
||
>::type>>,
|
||
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
|
||
{
|
||
#if defined(_GLIBCXX_DEBUG)
|
||
gdb_assert (index < m_size);
|
||
#endif
|
||
return m_array[index];
|
||
}
|
||
constexpr const_reference operator[] (size_t index) const noexcept
|
||
{
|
||
#if defined(_GLIBCXX_DEBUG)
|
||
gdb_assert (index < m_size);
|
||
#endif
|
||
return m_array[index];
|
||
}
|
||
|
||
constexpr size_type size () const noexcept { return m_size; }
|
||
constexpr bool empty () const noexcept { return m_size == 0; }
|
||
|
||
/* Slice an array view. */
|
||
|
||
/* Return a new array view over SIZE elements starting at START. */
|
||
constexpr array_view<T> slice (size_type start, size_type size) const noexcept
|
||
{
|
||
#if defined(_GLIBCXX_DEBUG)
|
||
gdb_assert (start + size <= m_size);
|
||
#endif
|
||
return {m_array + start, size};
|
||
}
|
||
|
||
/* Return a new array view over all the elements after START,
|
||
inclusive. */
|
||
constexpr array_view<T> slice (size_type start) const noexcept
|
||
{
|
||
#if defined(_GLIBCXX_DEBUG)
|
||
gdb_assert (start <= m_size);
|
||
#endif
|
||
return {m_array + start, size () - start};
|
||
}
|
||
|
||
private:
|
||
T *m_array;
|
||
size_type m_size;
|
||
};
|
||
|
||
/* Compare LHS and RHS for (deep) equality. That is, whether LHS and
|
||
RHS have the same sizes, and whether each pair of elements of LHS
|
||
and RHS at the same position compares equal. */
|
||
|
||
template <typename T>
|
||
bool
|
||
operator== (const gdb::array_view<T> &lhs, const gdb::array_view<T> &rhs)
|
||
{
|
||
if (lhs.size () != rhs.size ())
|
||
return false;
|
||
|
||
for (size_t i = 0; i < lhs.size (); i++)
|
||
if (!(lhs[i] == rhs[i]))
|
||
return false;
|
||
|
||
return true;
|
||
}
|
||
|
||
/* Compare two array_views for inequality. */
|
||
|
||
template <typename T>
|
||
bool
|
||
operator!= (const gdb::array_view<T> &lhs, const gdb::array_view<T> &rhs)
|
||
{
|
||
return !(lhs == rhs);
|
||
}
|
||
|
||
/* Create an array view from a pointer to an array and an element
|
||
count.
|
||
|
||
This is useful as alternative to constructing an array_view using
|
||
brace initialization when the size variable you have handy is of
|
||
signed type, since otherwise without an explicit cast the code
|
||
would be ill-formed.
|
||
|
||
For example, with:
|
||
|
||
extern void foo (int, int, gdb::array_view<value *>);
|
||
|
||
value *args[2];
|
||
int nargs;
|
||
foo (1, 2, {values, nargs});
|
||
|
||
You'd get:
|
||
|
||
source.c:10: error: narrowing conversion of ‘nargs’ from ‘int’ to
|
||
‘size_t {aka long unsigned int}’ inside { } [-Werror=narrowing]
|
||
|
||
You could fix it by writing the somewhat distracting explicit cast:
|
||
|
||
foo (1, 2, {values, (size_t) nargs});
|
||
|
||
Or by instantiating an array_view explicitly:
|
||
|
||
foo (1, 2, gdb::array_view<value *>(values, nargs));
|
||
|
||
Or, better, using make_array_view, which has the advantage of
|
||
inferring the arrav_view element's type:
|
||
|
||
foo (1, 2, gdb::make_array_view (values, nargs));
|
||
*/
|
||
|
||
template<typename U>
|
||
constexpr inline array_view<U>
|
||
make_array_view (U *array, size_t size) noexcept
|
||
{
|
||
return {array, size};
|
||
}
|
||
|
||
} /* namespace gdb */
|
||
|
||
#endif
|