diff --git a/include/exiv2/slice.hpp b/include/exiv2/slice.hpp
new file mode 100644
index 00000000..c26d058a
--- /dev/null
+++ b/include/exiv2/slice.hpp
@@ -0,0 +1,726 @@
+// ********************************************************* -*- C++ -*-
+/*
+ * Copyright (C) 2004-2018 Exiv2 maintainers
+ *
+ * This program is part of the Exiv2 distribution.
+ *
+ * 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 2
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA.
+ */
+/*!
+ @file slice.hpp
+ @brief Simple implementation of slices (=views) for STL containers and C-arrays
+ @author Dan Čermák (D4N)
+ dan.cermak@cgc-instruments.com
+ @date 30-March-18, D4N: created
+ */
+
+#ifndef EXIV2_INCLUDE_SLICE_HPP
+#define EXIV2_INCLUDE_SLICE_HPP
+
+#include
+#include
+#include
+#include
+
+namespace Exiv2
+{
+ namespace Internal
+ {
+ // TODO: remove these custom implementations once we have C++11
+ template
+ struct remove_const
+ {
+ typedef T type;
+ };
+
+ template
+ struct remove_const
+ {
+ typedef T type;
+ };
+
+ template
+ struct remove_volatile
+ {
+ typedef T type;
+ };
+ template
+ struct remove_volatile
+ {
+ typedef T type;
+ };
+ template
+ struct remove_cv
+ {
+ typedef typename remove_const::type>::type type;
+ };
+
+ template
+ struct remove_pointer
+ {
+ typedef T type;
+ };
+
+ template
+ struct remove_pointer
+ {
+ typedef T type;
+ };
+
+ template
+ struct remove_pointer
+ {
+ typedef T type;
+ };
+
+ /*!
+ * Common base class of all slice implementations.
+ *
+ * Implements only the most basic functions, which do not require any
+ * knowledge about the stored data.
+ */
+ struct SliceBase
+ {
+ inline SliceBase(size_t begin, size_t end) : begin_(begin), end_(end)
+ {
+ if (begin >= end) {
+ throw std::out_of_range("Begin must be smaller than end");
+ }
+ }
+
+ /*!
+ * Return the number of elements in the slice.
+ */
+ inline size_t size() const throw()
+ {
+ // cannot underflow, as we know that begin < end
+ return end_ - begin_;
+ }
+
+ protected:
+ /*!
+ * Throw an exception when index is too large.
+ *
+ * @throw std::out_of_range when `index` will access an element
+ * outside of the slice
+ */
+ inline void rangeCheck(size_t index) const
+ {
+ if (index >= size()) {
+ throw std::out_of_range("Index outside of the slice");
+ }
+ }
+
+ /*!
+ * lower and upper bounds of the slice with respect to the
+ * container/array stored in storage_
+ */
+ const size_t begin_, end_;
+ };
+
+ /*!
+ * @brief This class provides the public-facing const-qualified methods
+ * of a slice.
+ *
+ * The public methods are implemented in a generic fashion using a
+ * storage_type. This type contains the actual reference to the data to
+ * which the slice points and provides the following methods:
+ *
+ * - (const) value_type& unsafeAt(size_t index) (const)
+ * Return the value at the given index of the underlying container,
+ * without promising to perform a range check and without any
+ * knowledge of the slices' size
+ *
+ * - const_iterator/iterator unsafeGetIteratorAt(size_t index) (const)
+ * Return a (constant) iterator at the given index of the underlying
+ * container. Again, no range checks are promised.
+ *
+ * - Constructor(data_type& data, size_t begin, size_t end)
+ * Can use `begin` & `end` to perform range checks on `data`, but
+ * should not store both values. Must not take ownership of `data`!
+ *
+ * - Must save data as a public member named `data_`.
+ *
+ * - Must provide appropriate typedefs for iterator, const_iterator and
+ * value_type
+ */
+ template class storage_type, typename data_type>
+ struct ConstSliceBase : SliceBase
+ {
+ typedef typename storage_type::iterator iterator;
+ typedef typename storage_type::const_iterator const_iterator;
+ typedef typename storage_type::value_type value_type;
+
+ /*!
+ * Default contructor, requires begin to be smaller than end,
+ * otherwise an exception is thrown. Also forwards all parameters to
+ * the constructor of storage_
+ */
+ ConstSliceBase(data_type& data, size_t begin, size_t end)
+ : SliceBase(begin, end), storage_(data, begin, end)
+ {
+ }
+
+ /*!
+ * Obtain a constant reference to the element with the specified
+ * index in the slice.
+ *
+ * @throw std::out_of_range when index is out of bounds of the slice
+ */
+ const value_type& at(size_t index) const
+ {
+ rangeCheck(index);
+ // we know: begin_ < end <= size() <= SIZE_T_MAX
+ // and: index < end - begin
+ // thus: index + begin < end <= SIZE_T_MAX
+ // => no overflow is possible
+ return storage_.unsafeAt(begin_ + index);
+ }
+
+ /*!
+ * Obtain a constant iterator to the first element in the slice.
+ */
+ const_iterator cbegin() const throw()
+ {
+ return storage_.unsafeGetIteratorAt(begin_);
+ }
+
+ /*!
+ * Obtain a constant iterator to the first beyond the slice.
+ */
+ const_iterator cend() const throw()
+ {
+ return storage_.unsafeGetIteratorAt(end_);
+ }
+
+ /*!
+ * Create a constant sub-slice with the given bounds (with respect
+ * to the current slice).
+ *
+ * @tparam slice_type Type of the slice that this function shall
+ * return. Provide it with the type of the class that derives from
+ * mutable_slice_base.
+ */
+ template
+ slice_type subSlice(size_t begin, size_t end) const
+ {
+ this->rangeCheck(begin);
+ // end == size() is a legal value, since end is the first
+ // element beyond the slice
+ // end == 0 is not a legal value (subtraction will underflow and
+ // throw an exception)
+ this->rangeCheck(end - 1);
+ // additions are safe, begin and end are smaller than size()
+ const size_t new_begin = begin + this->begin_;
+ const size_t new_end = this->begin_ + end;
+ if (new_end > this->end_) {
+ throw std::out_of_range("Invalid input parameters to slice");
+ }
+ return slice_type(storage_.data_, new_begin, new_end);
+ }
+
+ protected:
+ /*!
+ * Stores a reference to the actual data.
+ */
+ storage_type storage_;
+ };
+
+ /*!
+ * This class provides all public-facing non-const-qualified methods of
+ * slices. It only re-implements the const-qualified versions as
+ * non-const.
+ */
+ template class storage_type, typename data_type>
+ struct MutableSliceBase : public ConstSliceBase
+ {
+ typedef typename ConstSliceBase::iterator iterator;
+ typedef typename ConstSliceBase::const_iterator const_iterator;
+ typedef typename ConstSliceBase::value_type value_type;
+
+ /*!
+ * Forwards everything to the constructor of const_slice_base
+ *
+ * @todo use using once we have C++11
+ */
+ MutableSliceBase(data_type& data, size_t begin, size_t end)
+ : ConstSliceBase(data, begin, end)
+ {
+ }
+
+ /*!
+ * Obtain a reference to the element with the specified index in the
+ * slice.
+ *
+ * @throw std::out_of_range when index is out of bounds of the slice
+ */
+ value_type& at(size_t index)
+ {
+ this->rangeCheck(index);
+ return this->storage_.unsafeAt(this->begin_ + index);
+ }
+
+ const value_type& at(size_t index) const
+ {
+ // TODO: use using base_type::at once we have C++11
+ return base_type::at(index);
+ }
+
+ /*!
+ * Obtain an iterator to the first element in the slice.
+ */
+ iterator begin() throw()
+ {
+ return this->storage_.unsafeGetIteratorAt(this->begin_);
+ }
+
+ /*!
+ * Obtain an iterator to the first element beyond the slice.
+ */
+ iterator end() throw()
+ {
+ return this->storage_.unsafeGetIteratorAt(this->end_);
+ }
+
+ protected:
+ /*!
+ * Explicitly convert this instance into a base-class of the
+ * appropriate constant version of this slice.
+ *
+ * This function is required to properly implement the `subSlice()
+ * const` function for mutable slices. The problem here is, that a
+ * slice and a slice actually don't share the same base
+ * class `ConstSliceBase`. Instead `slice`
+ * inherits from `ConstSliceBase` and `slice` inherits from `ConstSliceBase`.
+ *
+ * Now, `slice` can call the `subSlice() const` method from its
+ * base class, but that will return a mutable `slice`! Instead we
+ * use this function to convert the ``slice` into the parent of
+ * the appropriate `slice` and call its `subSlice() const`,
+ * which returns the correct type.
+ */
+ ConstSliceBase to_const_base() const throw()
+ {
+ return ConstSliceBase(this->storage_.data_, this->begin_, this->end_);
+ }
+
+ typedef ConstSliceBase base_type;
+
+ /*!
+ * Create a mutable sub-slice with the given bounds (with respect to
+ * the current slice).
+ *
+ * @tparam slice_type Type of the slice that this function shall
+ * return. Provide it with the type of the class that derives from
+ * mutable_slice_base.
+ */
+ template
+ slice_type subSlice(size_t begin, size_t end)
+ {
+ this->rangeCheck(begin);
+ // end == size() is a legal value, since end is the first
+ // element beyond the slice
+ // end == 0 is not a legal value (subtraction will underflow and
+ // throw an exception)
+ this->rangeCheck(end - 1);
+
+ // additions are safe, begin & end are smaller than size()
+ const size_t new_begin = begin + this->begin_;
+ const size_t new_end = this->begin_ + end;
+ if (new_end > this->end_) {
+ throw std::out_of_range("Invalid input parameters to slice");
+ }
+ return slice_type(this->storage_.data_, new_begin, new_end);
+ }
+ };
+
+ /*!
+ * Implementation of the storage concept for STL-containers.
+ *
+ * @tparam container Type of the STL-container.
+ */
+ template
+ struct ContainerStorage
+ {
+ typedef typename container::iterator iterator;
+
+ typedef typename container::const_iterator const_iterator;
+
+ typedef typename Internal::remove_cv::type value_type;
+
+ /*!
+ * @throw std::out_of_range when end is larger than the container's
+ * size.
+ */
+ ContainerStorage(container& data, size_t /* begin*/, size_t end) : data_(data)
+ {
+ if (end > data.size()) {
+ throw std::out_of_range("Invalid input parameters to slice");
+ }
+ }
+
+ /*!
+ * Obtain a constant reference to the element with the given `index`
+ * in the container.
+ *
+ * @throw whatever container::at() throws
+ */
+ const value_type& unsafeAt(size_t index) const
+ {
+ return data_.at(index);
+ }
+
+ value_type& unsafeAt(size_t index)
+ {
+ return data_.at(index);
+ }
+
+ /*!
+ * Obtain an iterator at the position of the element with the given
+ * index in the container.
+ *
+ * @throw whatever container::begin() and std::advance() throw
+ */
+ iterator unsafeGetIteratorAt(size_t index)
+ {
+ // we are screwed if the container got changed => try to catch it
+ assert(index <= data_.size());
+
+ iterator it = data_.begin();
+ std::advance(it, index);
+ return it;
+ }
+
+ const_iterator unsafeGetIteratorAt(size_t index) const
+ {
+ assert(index <= data_.size());
+
+ const_iterator it = data_.begin();
+ std::advance(it, index);
+ return it;
+ }
+
+ container& data_;
+ };
+
+ /*!
+ * @brief Implementation of the storage concept for slices of C arrays.
+ *
+ * @tparam storage_type Type as which the C-array should be stored. Use
+ * this parameter to save constant arrays as `const` and mutable ones as
+ * non-`const`.
+ */
+ template
+ struct PtrSliceStorage
+ {
+ typedef typename remove_cv::type>::type value_type;
+ typedef value_type* iterator;
+ typedef const value_type* const_iterator;
+
+ /*!
+ * Stores ptr and checks that it is not `NULL`. The slice's bounds
+ * are ignored, as we do not know the array's length.
+ *
+ * @throw std::invalid_argument when ptr is `NULL`
+ */
+ PtrSliceStorage(storage_type ptr, size_t /*begin*/, size_t /*end*/) : data_(ptr)
+ {
+ // TODO: change this to nullptr once we use C++11
+ if (ptr == NULL) {
+ throw std::invalid_argument("Null pointer passed to slice constructor");
+ }
+ }
+
+ /*!
+ * Obtain a reference to the element with the given `index` in the
+ * array.
+ *
+ * @throw nothing
+ */
+ value_type& unsafeAt(size_t index) throw()
+ {
+ return data_[index];
+ }
+
+ const value_type& unsafeAt(size_t index) const throw()
+ {
+ return data_[index];
+ }
+
+ /*!
+ * Obtain an iterator (=pointer) at the position of the element with
+ * the given index in the container.
+ *
+ * @throw nothing
+ */
+ iterator unsafeGetIteratorAt(size_t index) throw()
+ {
+ return data_ + index;
+ }
+
+ const_iterator unsafeGetIteratorAt(size_t index) const throw()
+ {
+ return data_ + index;
+ }
+
+ storage_type data_;
+ };
+
+ } // namespace Internal
+
+ /*!
+ * @brief Slice (= view) for STL containers.
+ *
+ * This is a very simple implementation of slices (i.e. views of sub-arrays)
+ * for STL containers that support O(1) element access and random access
+ * iterators (like std::vector, std::array and std::string).
+ *
+ * A slice represents the semi-open interval [begin, end) and provides a
+ * (mutable) view, it does however not own the data! It can be used to
+ * conveniently pass parts of containers into functions without having to use
+ * iterators or offsets.
+ *
+ * In contrast to C++20's std::span it is impossible to read beyond the
+ * container's bounds and unchecked access is not-possible (by design).
+ *
+ * Example usage:
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp}
+ * std::vector vec = {0, 1, 2, 3, 4};
+ * slice > one_two(vec, 1, 3);
+ * assert(one_two.size() == 2);
+ * assert(one_two.at(0) == 1 && one_two.at(1) == 2);
+ * // mutate the contents:
+ * one_two.at(0) *= 2;
+ * one_two.at(1) *= 3;
+ * assert(one_two.at(0) == 2 && one_two.at(1) == 6);
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * Slices also offer access via iterators of the same type as the underlying
+ * container, so that they can be used in a comparable fashion:
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp}
+ * std::vector vec = {0, 1, 2, 3, 4};
+ * slice> three_four(vec, 3, 5);
+ * assert(*three_four.begin() == 3 && *three_four.end() == 4);
+ * // this prints:
+ * // 3
+ * // 4
+ * for (const auto & elem : three_four) {
+ * std::cout << elem << std::endl;
+ * }
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * @tparam container A STL container type, like vector or array. Must support
+ * array-like access via the `at()` method.
+ */
+ template
+ struct Slice : public Internal::MutableSliceBase
+ {
+ typedef typename container::iterator iterator;
+
+ typedef typename container::const_iterator const_iterator;
+
+ typedef typename Internal::remove_cv::type value_type;
+
+ /*!
+ * @brief Construct a slice of the container `cont` starting at `begin`
+ * (including) and ending before `end`.
+ *
+ * @param[in] cont Reference to the container
+ * @param[in] begin First element of the slice.
+ * @param[in] end First element beyond the slice.
+ *
+ * @throws std::out_of_range For invalid slice bounds: when end is not
+ * larger than begin or when the slice's bounds are larger than the
+ * container's size.
+ *
+ * Please note that due to the requirement that `end` must be larger
+ * than `begin` (they cannot be equal) it is impossible to construct a
+ * slice with zero length.
+ */
+ Slice(container& cont, size_t begin, size_t end)
+ : Internal::MutableSliceBase(cont, begin, end)
+ {
+ }
+
+ /*!
+ * Construct a sub-slice of this slice with the given bounds. The bounds
+ * are evaluated with respect to the current slice.
+ *
+ * @param[in] begin First element in the new slice.
+ * @param[in] end First element beyond the new slice.
+ *
+ * @throw std::out_of_range when begin or end are invalid
+ */
+ Slice subSlice(size_t begin, size_t end)
+ {
+ return Internal::MutableSliceBase::template subSlice(begin,
+ end);
+ }
+
+ /*!
+ * Constructs a new constant subSlice. Behaves otherwise exactly like
+ * the non-const version.
+ */
+ Slice subSlice(size_t begin, size_t end) const
+ {
+ return this->to_const_base().template subSlice >(begin, end);
+ }
+ };
+
+ /*!
+ * @brief Specialization of slices for constant containers.
+ */
+ template
+ struct Slice : public Internal::ConstSliceBase
+ {
+ typedef typename container::iterator iterator;
+
+ typedef typename container::const_iterator const_iterator;
+
+ typedef typename Internal::remove_cv::type value_type;
+
+ Slice(const container& cont, size_t begin, size_t end)
+ : Internal::ConstSliceBase(cont, begin, end)
+ {
+ }
+
+ Slice subSlice(size_t begin, size_t end) const
+ {
+ return Internal::ConstSliceBase::template subSlice >(begin, end);
+ }
+ };
+
+ /*!
+ * Specialization of slices for constant C-arrays.
+ *
+ * These have exactly the same interface as the slices for STL-containers,
+ * with the *crucial* exception, that the slice's constructor *cannot* make
+ * a proper bounds check! It can only verify that you didn't accidentally
+ * swap begin and end!
+ */
+ template
+ struct Slice : public Internal::ConstSliceBase
+ {
+ /*!
+ * Constructor.
+ *
+ * @param[in] ptr C-array of which a slice should be constructed. Must
+ * not be a null pointer.
+ * @param[in] begin Index of the first element in the slice.
+ * @param[in] end Index of the first element that is no longer in the
+ * slice.
+ *
+ * Please note that the constructor has no way how to verify that
+ * `begin` and `end` are not out of bounds of the provided array!
+ */
+ Slice(const T* ptr, size_t begin, size_t end)
+ : Internal::ConstSliceBase(ptr, begin, end)
+ {
+ // TODO: use using in C++11
+ }
+
+ Slice subSlice(size_t begin, size_t end) const
+ {
+ return Internal::ConstSliceBase::template subSlice >(
+ begin, end);
+ }
+ };
+
+ /*!
+ * Specialization of slices for (mutable) C-arrays.
+ */
+ template
+ struct Slice : public Internal::MutableSliceBase
+ {
+ Slice(T* ptr, size_t begin, size_t end)
+ : Internal::MutableSliceBase(ptr, begin, end)
+ {
+ // TODO: use using in C++11
+ }
+
+ Slice subSlice(size_t begin, size_t end)
+ {
+ return Internal::MutableSliceBase::template subSlice >(begin, end);
+ }
+
+ Slice subSlice(size_t begin, size_t end) const
+ {
+ return this->to_const_base().template subSlice >(begin, end);
+ }
+ };
+
+ /*!
+ * @brief Return a new slice with the given bounds.
+ *
+ * Convenience wrapper around the slice's constructor for automatic template
+ * parameter deduction.
+ */
+ template
+ inline Slice makeSlice(T& cont, size_t begin, size_t end)
+ {
+ return Slice(cont, begin, end);
+ }
+
+ /*!
+ * Overload of makeSlice for slices of C-arrays.
+ */
+ template
+ inline Slice makeSlice(T* ptr, size_t begin, size_t end)
+ {
+ return Slice(ptr, begin, end);
+ }
+
+ /*!
+ * @brief Return a new slice spanning the whole container.
+ */
+ template
+ inline Slice makeSlice(container& cont)
+ {
+ return Slice(cont, 0, cont.size());
+ }
+
+ /*!
+ * @brief Return a new slice spanning from begin until the end of the
+ * container.
+ */
+ template
+ inline Slice makeSliceFrom(container& cont, size_t begin)
+ {
+ return Slice(cont, begin, cont.size());
+ }
+
+ /*!
+ * @brief Return a new slice spanning until `end`.
+ */
+ template
+ inline Slice makeSliceUntil(container& cont, size_t end)
+ {
+ return Slice(cont, 0, end);
+ }
+
+ /*!
+ * Overload of makeSliceUntil for pointer based slices.
+ */
+ template
+ inline Slice makeSliceUntil(T* ptr, size_t end)
+ {
+ return Slice(ptr, 0, end);
+ }
+
+} // namespace Exiv2
+
+#endif /* EXIV2_INCLUDE_SLICE_HPP */
diff --git a/include/exiv2/types.hpp b/include/exiv2/types.hpp
index 0a902f0f..13cc8295 100644
--- a/include/exiv2/types.hpp
+++ b/include/exiv2/types.hpp
@@ -31,6 +31,7 @@
// included header files
#include "config.h"
+#include "slice.hpp"
#include "exiv2lib_export.h"
// + standard includes
@@ -274,6 +275,21 @@ namespace Exiv2 {
long size_;
}; // class DataBuf
+ /*!
+ * @brief Create a new Slice from a DataBuf given the bounds.
+ *
+ * @param[in] begin, end Bounds of the new Slice. `begin` must be smaller
+ * than `end` and both must not be larger than LONG_MAX.
+ * @param[in] buf The DataBuf from which' data the Slice will be
+ * constructed
+ *
+ * @throw std::invalid_argument when `end` is larger than `LONG_MAX` or
+ * anything that the constructor of @ref Slice throws
+ */
+ EXIV2API Slice makeSlice(DataBuf& buf, size_t begin, size_t end);
+
+ //! Overload of makeSlice for `const DataBuf`, returning an immutable Slice
+ EXIV2API Slice makeSlice(const DataBuf& buf, size_t begin, size_t end);
// *****************************************************************************
// free functions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index a7993898..9f1afbc6 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -82,6 +82,7 @@ set( LIBEXIV2_HDR
../include/exiv2/riffvideo.hpp
../include/exiv2/rw2image.hpp
../include/exiv2/rwlock.hpp
+ ../include/exiv2/slice.hpp
../include/exiv2/tags.hpp
../include/exiv2/tgaimage.hpp
../include/exiv2/tiffimage.hpp
diff --git a/src/types.cpp b/src/types.cpp
index dc059c11..bc029903 100644
--- a/src/types.cpp
+++ b/src/types.cpp
@@ -29,6 +29,7 @@
#include "i18n.h" // for _exvGettext
#include "unused.h"
#include "safe_op.hpp"
+#include "enforce.hpp"
// + standard includes
#ifdef EXV_UNICODE_PATH
@@ -200,6 +201,24 @@ namespace Exiv2 {
// *************************************************************************
// free functions
+ static void checkDataBufBounds(const DataBuf& buf, size_t end) {
+ enforce(end <= static_cast(std::numeric_limits::max()),
+ "end of slice too large to be compared with DataBuf bounds.");
+ enforce(static_cast(end) <= buf.size_, "Invalid slice bounds specified");
+ }
+
+ Slice makeSlice(DataBuf& buf, size_t begin, size_t end)
+ {
+ checkDataBufBounds(buf, end);
+ return Slice(buf.pData_, begin, end);
+ }
+
+ Slice makeSlice(const DataBuf& buf, size_t begin, size_t end)
+ {
+ checkDataBufBounds(buf, end);
+ return Slice(buf.pData_, begin, end);
+ }
+
std::ostream& operator<<(std::ostream& os, const Rational& r)
{
return os << r.first << "/" << r.second;
diff --git a/unitTests/CMakeLists.txt b/unitTests/CMakeLists.txt
index 2c9bc05c..5c8096c6 100644
--- a/unitTests/CMakeLists.txt
+++ b/unitTests/CMakeLists.txt
@@ -10,6 +10,7 @@ add_executable(unit_tests mainTestRunner.cpp
test_TimeValue.cpp
test_cr2header_int.cpp
test_helper_functions.cpp
+ test_slice.cpp
$
)
diff --git a/unitTests/test_slice.cpp b/unitTests/test_slice.cpp
new file mode 100644
index 00000000..4459ca6a
--- /dev/null
+++ b/unitTests/test_slice.cpp
@@ -0,0 +1,432 @@
+#include
+
+#include "slice.hpp"
+#include "types.hpp"
+
+#include "gtestwrapper.h"
+
+using namespace Exiv2;
+
+template
+class slice;
+
+/*!
+ * This namespace contains the helper-function get_test_data. It is intented
+ * to be used for test with the slice fixture: it returns the appropriate
+ * data to the constructor of slice. For (const) T==std::vector it returns the
+ * fixtures meber vec_, for (const) T==int* it returns vec_.data()
+ *
+ * Due to C++98's limitations, this requires a separate traits class, that
+ * specifies the return type *and* a specialization of get_test_data for each
+ * case (maybe some can be reduced with SFINAE, but that ain't improving
+ * readability either).
+ *
+ * Unfortunately, C++11 will probably only make the return_type_traits go away,
+ * but not the template specializations of get_test_data (for that we need
+ * C++17, so see you in 2025).
+ */
+namespace cpp_98_boilerplate
+{
+ template
+ struct return_type_traits
+ {
+ typedef T type;
+ };
+
+ template
+ struct return_type_traits >
+ {
+ typedef typename std::vector& type;
+ };
+
+ template
+ struct return_type_traits >
+ {
+ typedef const typename std::vector& type;
+ };
+
+ template
+ typename return_type_traits::type get_test_data(slice& st);
+
+} // namespace cpp_98_boilerplate
+
+/*!
+ * Fixture for slice testing. Has one public vector of ints with size vec_size
+ * that is filled with the numbers from 0 to vec_size - 1.
+ *
+ * The vector vec_ is used to construct slices either from a std::vector, or
+ * from raw C-arrays. Which type is used, is set by the template parameter
+ * T. Thus we guarantee, that the interface is completely independent of the
+ * underlying datatype.
+ *
+ * @tparam T Type that is used to construct a slice for testing.
+ */
+template
+class slice : public ::testing::Test
+{
+public:
+ static const size_t vec_size = 10;
+
+ virtual void SetUp()
+ {
+ vec_.reserve(vec_size);
+ for (unsigned int i = 0; i < vec_size; ++i) {
+ vec_.push_back(i);
+ }
+ }
+
+ Slice getTestSlice(size_t begin = 1, size_t end = vec_size - 1)
+ {
+ return Slice(cpp_98_boilerplate::get_test_data(*this), begin, end);
+ }
+
+ // TODO: once we have C++11: use initializer list
+ std::vector vec_;
+};
+
+// specializations of get_test_data are provided here, since they must have the
+// full definition of slice available
+namespace cpp_98_boilerplate
+{
+ template <>
+ int* get_test_data(slice& st)
+ {
+ return st.vec_.data();
+ }
+
+ template <>
+ const int* get_test_data(slice& st)
+ {
+ return st.vec_.data();
+ }
+
+ template <>
+ std::vector& get_test_data >(slice >& st)
+ {
+ return st.vec_;
+ }
+
+ template <>
+ const std::vector& get_test_data >(slice >& st)
+ {
+ return st.vec_;
+ }
+} // namespace cpp_98_boilerplate
+
+/*!
+ * Fixture to run test for mutable slices.
+ *
+ * It adds nothing new, it is just a separate class, so that we can run
+ * different tests on it.
+ */
+template
+class mutableSlice : public slice
+{
+};
+
+TYPED_TEST_CASE_P(slice);
+TYPED_TEST_CASE_P(mutableSlice);
+
+TYPED_TEST_P(slice, atAccess)
+{
+ // typedef Slice slice_t;
+ // const size_t begin = 1;
+ // const size_t end = this->vec_.size() - 1;
+ Slice sl = this->getTestSlice();
+
+ ASSERT_EQ(this->vec_.size() - 2, sl.size());
+
+ for (unsigned int i = 0; i < sl.size(); ++i) {
+ ASSERT_EQ(this->vec_.at(i + 1), sl.at(i));
+ }
+}
+
+// TODO C++11: test range based for loop
+TYPED_TEST_P(slice, iteratorAccess)
+{
+ Slice sl = this->getTestSlice();
+
+ std::vector::const_iterator vec_it = this->vec_.begin() + 1;
+ for (typename Slice::const_iterator it = sl.cbegin(); it < sl.cend(); ++it, ++vec_it) {
+ ASSERT_EQ(*it, *vec_it);
+ }
+
+ ASSERT_THROW(sl.at(sl.size()), std::out_of_range);
+}
+
+TYPED_TEST_P(slice, constructionFailsFromInvalidRange)
+{
+ // start > end
+ ASSERT_THROW(this->getTestSlice(2, 1), std::out_of_range);
+}
+
+TYPED_TEST_P(slice, constructionFailsWithZeroLength)
+{
+ ASSERT_THROW(this->getTestSlice(1, 1), std::out_of_range);
+}
+
+/*!
+ * Test the construction of subSlices and their behavior.
+ */
+TYPED_TEST_P(slice, subSliceSuccessfulConstruction)
+{
+ typedef Slice slice_t;
+
+ // 0 1 2 3 4 5 6 7 8 9
+ // | | center_vals
+ // | | middle
+ slice_t center_vals = this->getTestSlice(3, 7);
+ ASSERT_EQ(center_vals.size(), static_cast(4));
+ ASSERT_NO_THROW(center_vals.subSlice(1, 3));
+
+ ASSERT_NO_THROW(center_vals.subSlice(1, center_vals.size()));
+}
+
+TYPED_TEST_P(slice, subSliceFunctions)
+{
+ Slice middle = this->getTestSlice(3, 7).subSlice(1, 3);
+
+ ASSERT_EQ(middle.size(), static_cast(2));
+ ASSERT_EQ(middle.at(1), static_cast::value_type>(5));
+}
+
+TYPED_TEST_P(slice, subSliceFailedConstruction)
+{
+ // 0 1 2 3 4 5 6 7 8 9
+ // | | middle
+ Slice middle = this->getTestSlice(4, 6);
+
+ ASSERT_THROW(middle.subSlice(1, 5), std::out_of_range);
+ ASSERT_THROW(middle.subSlice(2, 1), std::out_of_range);
+ ASSERT_THROW(middle.subSlice(2, 2), std::out_of_range);
+}
+
+/*! try to cause integer overflows in a sub-optimal implementation */
+TYPED_TEST_P(slice, subSliceConstructionOverflowResistance)
+{
+ Slice center_vals = this->getTestSlice(3, 7);
+
+ ASSERT_THROW(center_vals.subSlice(std::numeric_limits::max() - 2, 3), std::out_of_range);
+ ASSERT_THROW(center_vals.subSlice(2, std::numeric_limits::max() - 1), std::out_of_range);
+}
+
+/*!
+ * This function's purpose is only to check whether we can pass all slices by
+ * constant reference.
+ */
+template
+void checkConstSliceValueAt(const Slice& sl, typename Slice::value_type value, size_t index)
+{
+ ASSERT_EQ(sl.at(index), value);
+}
+
+/*!
+ * Check that the contents of the slice are ascending via an iterator based for
+ * loop.
+ */
+template
+void checkConstSliceIterator(const Slice& sl, typename Slice::value_type first_value)
+{
+ for (typename Slice::const_iterator it = sl.cbegin(); it < sl.cend(); ++it) {
+ ASSERT_EQ(*it, first_value++);
+ }
+}
+
+template
+void checkSubSlice(const Slice& sl)
+{
+ ASSERT_EQ(sl.at(1), sl.subSlice(1, sl.size()).at(0));
+}
+
+/*!
+ * Test that all slices can be also passed as const references and still work
+ */
+TYPED_TEST_P(slice, constMethodsPreserveConst)
+{
+ typedef Slice slice_t;
+
+ // 0 1 2 3 4 5 6 7 8 9
+ // | | center_vals
+ slice_t center_vals = this->getTestSlice(3, 7);
+
+ // check at() const works
+ checkConstSliceValueAt(center_vals, 4, 1);
+
+ checkConstSliceIterator(center_vals, 3);
+
+ checkSubSlice(center_vals);
+}
+
+/*!
+ * Test the non-const iterators
+ */
+TYPED_TEST_P(mutableSlice, iterators)
+{
+ typedef Slice slice_t;
+ slice_t sl = this->getTestSlice();
+
+ ASSERT_EQ(*sl.begin(), static_cast(1));
+ ASSERT_EQ(*sl.end(), static_cast(this->vec_size - 1));
+
+ for (typename slice_t::iterator it = sl.begin(); it < sl.end(); ++it) {
+ *it = 2 * (*it);
+ }
+
+ ASSERT_EQ(this->vec_.at(0), 0);
+ for (size_t j = 1; j < this->vec_size - 1; ++j) {
+ ASSERT_EQ(this->vec_.at(j), static_cast(2 * j));
+ ASSERT_EQ(this->vec_.at(j), sl.at(j - 1));
+ }
+ ASSERT_EQ(this->vec_.at(this->vec_size - 1), static_cast(this->vec_size - 1));
+}
+
+/*!
+ * Test the non-const version of at()
+ */
+TYPED_TEST_P(mutableSlice, at)
+{
+ typedef Slice slice_t;
+ slice_t sl = this->getTestSlice(2, 4);
+
+ sl.at(0) = 6;
+ sl.at(1) = 12;
+
+ ASSERT_EQ(this->vec_.at(2), 6);
+ ASSERT_EQ(this->vec_.at(3), 12);
+ for (size_t j = 0; j < this->vec_size - 1; ++j) {
+ if (j == 2 || j == 3) {
+ continue;
+ }
+ ASSERT_EQ(this->vec_.at(j), static_cast(j));
+ }
+}
+
+TEST(pointerSlice, failedConstructionFromNullpointer)
+{
+ ASSERT_THROW(Slice(NULL, 1, 2), std::invalid_argument);
+}
+
+/*!
+ * Test the construction of an invalid slices from a container (so that a proper
+ * range check can be conducted)
+ */
+TEST(containerSlice, failedConstructionFromContainer)
+{
+ std::vector tmp(10);
+ // slice end too large
+ ASSERT_THROW(Slice >(tmp, 1, tmp.size() + 1), std::out_of_range);
+}
+
+/*!
+ * Test all functions from the makeSlice* family.
+ */
+TEST(containerSlice, makeSlice)
+{
+ std::string str = "this is a sentence";
+
+ Slice is = makeSlice(str, 5, 7);
+ ASSERT_TRUE(std::equal(is.begin(), is.end(), "is"));
+
+ Slice sl_this = makeSliceUntil(str, 4);
+ ASSERT_TRUE(std::equal(sl_this.begin(), sl_this.end(), "this"));
+
+ Slice sl_sentence = makeSliceFrom(str, 10);
+ ASSERT_TRUE(std::equal(sl_sentence.begin(), sl_sentence.end(), "sentence"));
+
+ Slice sl_full = makeSlice(str);
+ ASSERT_TRUE(std::equal(sl_full.begin(), sl_full.end(), str.c_str()));
+}
+
+struct stringSlice : public ::testing::Test
+{
+ std::string sentence;
+
+ virtual void SetUp()
+ {
+ sentence = "this is a sentence";
+ }
+};
+
+TEST_F(stringSlice, at)
+{
+ const Slice is_a = makeSlice(static_cast(this->sentence), 5, 10);
+
+ ASSERT_EQ(is_a.at(0), 'i');
+ ASSERT_EQ(is_a.at(4), ' ');
+}
+
+TEST_F(stringSlice, atFailure)
+{
+ const Slice is_a = makeSlice(static_cast(this->sentence), 5, 10);
+ ASSERT_THROW(is_a.at(5), std::out_of_range);
+}
+
+TEST_F(stringSlice, size)
+{
+ const Slice is_a = makeSlice(static_cast(this->sentence), 5, 10);
+ ASSERT_EQ(is_a.size(), static_cast(5));
+}
+
+TEST_F(stringSlice, mutateString)
+{
+ Slice is_a_mutable = makeSlice(this->sentence, 5, 10);
+
+ for (Slice::iterator it = is_a_mutable.begin(); it < is_a_mutable.end(); ++it) {
+ *it = ' ';
+ }
+
+ ASSERT_STREQ(this->sentence.c_str(), "this sentence");
+}
+
+template
+struct dataBufSlice : public ::testing::Test
+{
+ static byte data[4]; // = {0xde, 0xad, 0xbe, 0xef};
+ DataBuf buf;
+
+ virtual void SetUp()
+ {
+ buf = DataBuf(data, sizeof(data));
+ }
+};
+
+template
+byte dataBufSlice::data[4] = {0xde, 0xad, 0xbe, 0xef};
+
+TYPED_TEST_CASE_P(dataBufSlice);
+
+TYPED_TEST_P(dataBufSlice, successfulConstruction)
+{
+ // just check that makeSlice appears to work
+ ASSERT_EQ(makeSlice(static_cast(this->buf), 1, 3).size(), static_cast(2));
+}
+
+TYPED_TEST_P(dataBufSlice, failedConstruction)
+{
+ // check that we get an exception when end is larger than LONG_MAX
+ ASSERT_THROW(
+ makeSlice(static_cast(this->buf), 1, static_cast(std::numeric_limits::max()) + 1),
+ std::invalid_argument);
+
+ // check that we get an exception when end is larger than the DataBuf
+ ASSERT_THROW(makeSlice(static_cast(this->buf), 1, 5), std::out_of_range);
+}
+
+//
+// GTest boilerplate to get the tests running for all the different types
+//
+REGISTER_TYPED_TEST_CASE_P(slice, atAccess, iteratorAccess, constructionFailsFromInvalidRange,
+ constructionFailsWithZeroLength, subSliceSuccessfulConstruction, subSliceFunctions,
+ subSliceFailedConstruction, subSliceConstructionOverflowResistance,
+ constMethodsPreserveConst);
+
+typedef ::testing::Types, std::vector, int*, const int*> test_types_t;
+INSTANTIATE_TYPED_TEST_CASE_P(, slice, test_types_t);
+
+REGISTER_TYPED_TEST_CASE_P(mutableSlice, iterators, at);
+typedef ::testing::Types, int*> mut_test_types_t;
+INSTANTIATE_TYPED_TEST_CASE_P(, mutableSlice, mut_test_types_t);
+
+REGISTER_TYPED_TEST_CASE_P(dataBufSlice, successfulConstruction, failedConstruction);
+typedef ::testing::Types data_buf_types_t;
+INSTANTIATE_TYPED_TEST_CASE_P(, dataBufSlice, data_buf_types_t);