diff options
-rw-r--r-- | common/safe-bool.h | 65 | ||||
-rw-r--r-- | common/scummsys.h | 11 | ||||
-rw-r--r-- | common/span.h | 995 | ||||
-rw-r--r-- | common/type-traits.h | 34 | ||||
-rw-r--r-- | test/common/span.h | 789 |
5 files changed, 1894 insertions, 0 deletions
diff --git a/common/safe-bool.h b/common/safe-bool.h new file mode 100644 index 0000000000..7cbe29931c --- /dev/null +++ b/common/safe-bool.h @@ -0,0 +1,65 @@ +/** + * Boost Software License - Version 1.0 - August 17th, 2003 + * + * Permission is hereby granted, free of charge, to any person or organization + * obtaining a copy of the software and accompanying documentation covered by + * this license (the "Software") to use, reproduce, display, distribute, + * execute, and transmit the Software, and to prepare derivative works of the + * Software, and to permit third-parties to whom the Software is furnished to + * do so, all subject to the following: + * + * The copyright notices in the Software and this entire statement, including + * the above license grant, this restriction and the following disclaimer, + * must be included in all copies of the Software, in whole or in part, and + * all derivative works of the Software, unless such copies or derivative + * works are solely in the form of machine-executable object code generated by + * a source language processor. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT + * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE + * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef COMMON_SAFE_BOOL_H +#define COMMON_SAFE_BOOL_H + +namespace Common { + namespace impl { + template <typename T> + struct no_base {}; + + template <typename T> + struct safe_bool_impl { + typedef T *TP; // workaround to make parsing easier + TP stub; + typedef TP safe_bool_impl::*type; + }; + } + + /** + * Prevents `operator bool` from implicitly converting to other types. + */ + template <typename DerivedT, typename BaseT = impl::no_base<DerivedT> > + struct SafeBool : BaseT { + private: + typedef impl::safe_bool_impl<DerivedT> impl_t; + typedef typename impl_t::type bool_type; + + public: + operator bool_type() const { + return static_cast<const DerivedT *>(this)->operator_bool() ? + &impl_t::stub : 0; + } + + operator bool_type() { + return static_cast<DerivedT *>(this)->operator_bool() ? + &impl_t::stub : 0; + } + }; +} // End of namespace Common + +#endif diff --git a/common/scummsys.h b/common/scummsys.h index 1845f218bd..6fd9be81c8 100644 --- a/common/scummsys.h +++ b/common/scummsys.h @@ -151,6 +151,17 @@ #if !defined(__SYMBIAN32__) #include <new> #endif + + /** + * Generates a compile-time assertion. + * + * @param expression An expression that can be evaluated at compile time. + * @param message An underscore-delimited message to be presented at compile + * time if the expression evaluates to false. + */ + #define STATIC_ASSERT(expression, message) \ + extern int STATIC_ASSERT_##message[(expression) ? 1 : -1]; \ + (void)(STATIC_ASSERT_##message); #endif // The following math constants are usually defined by the system math.h header, but diff --git a/common/span.h b/common/span.h new file mode 100644 index 0000000000..db6110a91c --- /dev/null +++ b/common/span.h @@ -0,0 +1,995 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source 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, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef COMMON_SPAN_H +#define COMMON_SPAN_H + +#include "common/file.h" +#include "common/memstream.h" +#include "common/safe-bool.h" +#include "common/scummsys.h" +#include "common/type-traits.h" + +namespace Common { + +#define COMMON_SPAN_TYPEDEFS \ + typedef typename super_type::value_type value_type; \ + typedef typename super_type::difference_type difference_type; \ + typedef typename super_type::index_type index_type; \ + typedef typename super_type::size_type size_type; \ + typedef typename super_type::const_iterator const_iterator; \ + typedef typename super_type::iterator iterator; \ + typedef typename super_type::pointer pointer; \ + typedef typename super_type::const_pointer const_pointer; \ + typedef typename super_type::reference reference; \ + typedef typename super_type::const_reference const_reference; + +enum { + kSpanMaxSize = 0xFFFFFFFF, + kSpanKeepOffset = 0xFFFFFFFF +}; + +#pragma mark - +#pragma mark SpanValidationMode + +enum SpanValidationMode { + kValidateRead, + kValidateWrite, + kValidateSeek +}; + +namespace SpanInternal { +#pragma mark - +#pragma mark SpanIterator + /** + * Bounds-checked iteration over a span of memory. + */ + template <typename Span, bool IsConst> + class SpanIterator { + typedef typename Span::value_type span_value_type; + typedef typename Conditional<IsConst, const Span, Span>::type span_type; + + public: + typedef typename Span::difference_type difference_type; + typedef typename RemoveConst<span_value_type>::type value_type; + typedef typename Conditional<IsConst, const span_value_type, span_value_type>::type *pointer; + typedef typename Conditional<IsConst, const span_value_type, span_value_type>::type &reference; + + inline SpanIterator() : _span(nullptr), _index(0) {} + + inline SpanIterator(span_type *const span, const difference_type index) : + _span(span), + _index(index) { + if (span != nullptr) { + span->validate(index, 0, kValidateSeek); + } + } + + inline SpanIterator(const SpanIterator &other) : + _span(other._span), + _index(other._index) {} + + inline SpanIterator &operator=(const SpanIterator &other) { + _span = other._span; + _index = other._index; + return *this; + } + +#pragma mark - +#pragma mark SpanIterator - Dereferencing operations + + public: + inline reference operator*() const { + // validation is in Span::operator[] + return (*_span)[_index]; + } + + inline pointer operator->() const { + return &operator*(); + } + + inline reference operator[](const difference_type index) const { + // validation is in SpanIterator::operator+= + return *(*this + index); + } + +#pragma mark - +#pragma mark SpanIterator - Arithmetic operations + + public: + inline SpanIterator &operator+=(const difference_type delta) { + assert(_span != nullptr); + _span->validate(_index, delta, kValidateSeek); + _index += delta; + return *this; + } + + inline SpanIterator &operator-=(const difference_type delta) { + return operator+=(-delta); + } + + inline SpanIterator &operator++() { + return operator+=(1); + } + + inline SpanIterator operator++(int) { + SpanIterator old(*this); + operator+=(1); + return old; + } + + inline SpanIterator &operator--() { + return operator+=(-1); + } + + inline SpanIterator operator--(int) { + SpanIterator old(*this); + operator+=(-1); + return old; + } + + inline SpanIterator operator+(const difference_type delta) const { + SpanIterator it(*this); + return it += delta; + } + + inline SpanIterator operator-(const difference_type delta) const { + return operator+(-delta); + } + + inline difference_type operator-(const SpanIterator &other) const { + assert(_span == other._span); + return _index - other._index; + } + +#pragma mark - +#pragma mark SpanIterator - Comparison operations + + public: + inline bool operator==(const SpanIterator& other) const { + return _span == other._span && _index == other._index; + } + + inline bool operator!=(const SpanIterator& other) const { + return !operator==(other); + } + + inline bool operator<(const SpanIterator& other) const { + assert(_span == other._span); + return _index < other._index; + } + + inline bool operator<=(const SpanIterator& other) const { + return !other.operator<(*this); + } + + inline bool operator>(const SpanIterator& other) const { + return other.operator<(*this); + } + + inline bool operator>=(const SpanIterator& other) const { + return !operator<(other); + } + +#pragma mark - +#pragma mark SpanIterator - Data access convenience functions + + public: + inline int8 getInt8() const { + return _span->getInt8At(_index); + } + + inline uint8 getUint8() const { + return _span->getUint8At(_index); + } + + inline int16 getInt16BE() const { + return _span->getInt16BEAt(_index); + } + + inline int16 getInt16LE() const { + return _span->getInt16LEAt(_index); + } + + inline uint16 getUint16BE() const { + return _span->getUint16BEAt(_index); + } + + inline uint16 getUint16LE() const { + return _span->getUint16LEAt(_index); + } + + inline uint32 getUint24LE() const { + return _span->getUint24LEAt(_index); + } + + inline uint32 getUint32() const { + return _span->getUint32At(_index); + } + + inline int32 getInt32BE() const { + return _span->getInt32BEAt(_index); + } + + inline int32 getInt32LE() const { + return _span->getInt32LEAt(_index); + } + + inline uint32 getUint32BE() const { + return _span->getUint32BEAt(_index); + } + + inline uint32 getUint32LE() const { + return _span->getUint32LEAt(_index); + } + + protected: + span_type *_span; + difference_type _index; + }; +} // End of namespace SpanInternal + +#pragma mark - +#pragma mark SpanBase + +template <typename ValueType, template <typename> class Derived> +class SpanBase : public SafeBool<Derived<ValueType> > { + typedef Derived<ValueType> derived_type; + typedef typename AddConst<derived_type>::type const_derived_type; + typedef typename RemoveConst<derived_type>::type mutable_derived_type; + +#if !defined(__GNUC__) || GCC_ATLEAST(3, 0) + template <typename T, bool U> friend class SpanInternal::SpanIterator; + template <typename T, template <typename> class U> friend class SpanBase; + template <typename T, typename U> friend struct SafeBool; +#endif +#ifdef CXXTEST_RUNNING + friend class ::SpanTestSuite; +#endif + +public: + typedef ValueType value_type; + typedef ptrdiff_t difference_type; + typedef size_t index_type; + typedef size_t size_type; + typedef SpanInternal::SpanIterator<derived_type, true> const_iterator; + typedef SpanInternal::SpanIterator<derived_type, false> iterator; + typedef value_type *pointer; + typedef const value_type *const_pointer; + typedef value_type &reference; + typedef const value_type &const_reference; + + inline size_type byteSize() const { return impl().size() * sizeof(value_type); } + +#if !defined(__GNUC__) || GCC_ATLEAST(3, 0) +protected: +#endif + inline SpanBase() {} + inline SpanBase(const SpanBase &) {} + inline SpanBase &operator=(const SpanBase &) { return this->impl(); } + inline ~SpanBase() {} + + inline const_derived_type &impl() const { return static_cast<const_derived_type &>(*this); } + inline mutable_derived_type &impl() { return static_cast<mutable_derived_type &>(*this); } + +#pragma mark - +#pragma mark SpanBase - Interface + +#if !defined(__GNUC__) || GCC_ATLEAST(3, 0) +protected: +#endif + inline void clear(); + + inline size_type size() const; + + inline const_iterator cbegin() const; + inline const_iterator cend() const; + inline const_iterator begin() const; + inline const_iterator end() const; + inline iterator begin(); + inline iterator end(); + + inline pointer data() const; + +#pragma mark - +#pragma mark SpanBase - Data access functions + +public: + inline const_reference operator[](const index_type index) const { + impl().validate(index, sizeof(value_type)); + return impl().data()[index]; + } + + inline reference operator[](const index_type index) { + impl().validate(index, sizeof(value_type)); + return impl().data()[index]; + } + + inline int8 getInt8At(const index_type index) const { + STATIC_ASSERT(sizeof(value_type) == sizeof(uint8), int8_can_only_be_read_from_byte_or_char_spans); + return (int8)getUint8At(index); + } + + inline uint8 getUint8At(const index_type index) const { + STATIC_ASSERT(sizeof(value_type) == sizeof(uint8), uint8_can_only_be_read_from_byte_or_char_spans); + impl().validate(index, sizeof(uint8)); + return (uint8)impl().data()[index]; + } + + inline int16 getInt16BEAt(const index_type index) const { + STATIC_ASSERT(sizeof(value_type) <= sizeof(uint16), int16_can_only_be_read_from_int16_or_smaller_spans); + return (int16)impl().getUint16BEAt(index); + } + + inline int16 getInt16LEAt(const index_type index) const { + STATIC_ASSERT(sizeof(value_type) <= sizeof(uint16), int16_can_only_be_read_from_int16_or_smaller_spans); + return (int16)impl().getUint16LEAt(index); + } + + inline uint16 getUint16BEAt(const index_type index) const { + STATIC_ASSERT(sizeof(value_type) <= sizeof(uint16), uint16_can_only_be_read_from_int16_or_smaller_spans); + impl().validate(index, sizeof(uint16)); + return READ_BE_UINT16(impl().data() + index); + } + + inline uint16 getUint16LEAt(const index_type index) const { + STATIC_ASSERT(sizeof(value_type) <= sizeof(uint16), uint16_can_only_be_read_from_int16_or_smaller_spans); + impl().validate(index, sizeof(uint16)); + return READ_LE_UINT16(impl().data() + index); + } + + inline uint32 getUint24LEAt(const index_type index) const { + STATIC_ASSERT(sizeof(value_type) <= 3, uint24_can_only_be_read_from_int24_or_smaller_spans); + impl().validate(index, 3); + return READ_LE_UINT24(impl().data() + index); + } + + inline uint32 getUint32At(const index_type index) const { + STATIC_ASSERT(sizeof(value_type) <= sizeof(uint32), uint32_can_only_be_read_from_int32_or_smaller_spans); + impl().validate(index, sizeof(uint32)); + return READ_UINT32(impl().data() + index); + } + + inline int32 getInt32BEAt(const index_type index) const { + STATIC_ASSERT(sizeof(value_type) <= sizeof(uint32), int32_can_only_be_read_from_int32_or_smaller_spans); + return (int32)impl().getUint32BEAt(index); + } + + inline int32 getInt32LEAt(const index_type index) const { + STATIC_ASSERT(sizeof(value_type) <= sizeof(uint32), int32_can_only_be_read_from_int32_or_smaller_spans); + return (int32)impl().getUint32LEAt(index); + } + + inline uint32 getUint32BEAt(const index_type index) const { + STATIC_ASSERT(sizeof(value_type) <= sizeof(uint32), uint32_can_only_be_read_from_int32_or_smaller_spans); + impl().validate(index, sizeof(uint32)); + return READ_BE_UINT32(impl().data() + index); + } + + inline uint32 getUint32LEAt(const index_type index) const { + STATIC_ASSERT(sizeof(value_type) <= sizeof(uint32), uint32_can_only_be_read_from_int32_or_smaller_spans); + impl().validate(index, sizeof(uint32)); + return READ_LE_UINT32(impl().data() + index); + } + + inline String getStringAt(const index_type index, size_type numEntries = kSpanMaxSize) const { + STATIC_ASSERT(sizeof(value_type) == sizeof(char), strings_can_only_be_read_from_byte_or_char_spans); + const char *string = (const char *)impl().data(); + + if (numEntries == kSpanMaxSize) { + numEntries = strnlen(string, impl().size() - index); + } + + impl().validate(index, numEntries); + return String(string + index, numEntries); + } + + /** + * Returns a raw pointer to memory after validating the given index and + * size. Use this only in performance-critical code, like processing pixel + * data in a loop, where validating each read independently would introduce + * unnecessary overhead. + */ + inline const_pointer getUnsafeDataAt(const index_type index, size_type numEntries = kSpanMaxSize) const { + if (numEntries == kSpanMaxSize) { + numEntries = impl().size() - index; + } + + impl().validate(index, numEntries * sizeof(value_type)); + return impl().data() + index; + } + + inline pointer getUnsafeDataAt(const index_type index, size_type numEntries = kSpanMaxSize) { + if (numEntries == kSpanMaxSize) { + numEntries = impl().size() - index; + } + + impl().validate(index, numEntries * sizeof(value_type)); + return impl().data() + index; + } + + inline MemoryReadStream toStream(const index_type index = 0, size_type numEntries = kSpanMaxSize) const { + if (numEntries == kSpanMaxSize) { + numEntries = impl().size(); + } + + impl().validate(index, numEntries * sizeof(value_type)); + return MemoryReadStream(impl().data() + index, numEntries * sizeof(value_type), DisposeAfterUse::NO); + } + +#pragma mark - +#pragma mark SpanBase - Operators + +public: + template <typename Other> + inline bool operator==(const Other &other) const { + return impl().data() == other.impl().data() && impl().size() == other.impl().size(); + } + + template <typename Other> + inline bool operator!=(const Other &other) const { + return !operator==(other); + } + + template <typename Other> + inline difference_type operator-(const Other &other) const { + return impl().data() - other.impl().data(); + } + + template <typename Other> + inline bool operator<(const Other &other) const { + return impl().data() < other.impl().data(); + } + + template <typename Other> + inline bool operator<=(const Other &other) const { + return !other.operator<(*this); + } + + template <typename Other> + inline bool operator>(const Other &other) const { + return other.operator<(*this); + } + + template <typename Other> + inline bool operator>=(const Other &other) const { + return !operator<(other); + } + +#if !defined(__GNUC__) || GCC_ATLEAST(3, 0) +protected: +#endif + inline bool operator_bool() const { return impl().data() != nullptr; } + +#pragma mark - +#pragma mark SpanBase - Copying + +public: + /** + * Copies data from this span to a raw pointer. To only copy a portion of + * the span, call subspan first. + */ + inline void unsafeCopyDataTo(void *target) const { + memcpy(target, impl().data(), impl().byteSize()); + } + + /** + * Copies the data from this span to the given target span. To only copy a + * portion of the span, call subspan first. + */ + template <typename Other> + inline void copyDataTo(Other &target) const { + assert((impl().byteSize() % sizeof(typename Other::value_type)) == 0); + target.impl().validate(0, impl().byteSize(), kValidateWrite); + memcpy(target.impl().data(), impl().data(), impl().byteSize()); + } + +#pragma mark - +#pragma mark SpanBase - Validation + +#if !defined(__GNUC__) || GCC_ATLEAST(3, 0) +protected: +#endif + /** + * @returns true if bounds are invalid. + */ + inline bool checkInvalidBounds(const index_type index, const difference_type deltaInBytes) const { + // There is a potential that large bogus values may cause arithmetic + // overflow, so the individual operands are checked separately first. + // Values that are not allowed to be negative are treated as unsigned to + // reduce the number of necessary comparisons + const size_t maxByteOffset = index * (signed)sizeof(value_type) + deltaInBytes; + return index > impl().size() || deltaInBytes > (difference_type)impl().byteSize() || maxByteOffset > impl().byteSize(); + } + + inline void validate(const index_type index, const difference_type deltaInBytes, const SpanValidationMode mode = kValidateRead) const { + /* LCOV_EXCL_START */ + if (impl().checkInvalidBounds(index, deltaInBytes)) { + error("%s", impl().getValidationMessage(index, deltaInBytes, mode).c_str()); + } + /* LCOV_EXCL_STOP */ + } +}; + +#pragma mark - +#pragma mark SpanImpl + +template <typename ValueType, template <typename> class Derived> +class SpanImpl : public SpanBase<ValueType, Derived> { + typedef SpanBase<ValueType, Derived> super_type; + typedef typename AddConst<Derived<ValueType> >::type const_derived_type; + typedef typename RemoveConst<Derived<ValueType> >::type mutable_derived_type; + +#if !defined(__GNUC__) || GCC_ATLEAST(3, 0) + template <typename T, template <typename> class U> friend class SpanImpl; +#endif +#ifdef CXXTEST_RUNNING + friend class ::SpanTestSuite; +#endif + +public: + COMMON_SPAN_TYPEDEFS + + inline SpanImpl() : super_type(), _data(nullptr), _size(0) {} + + inline SpanImpl(const pointer data_, const size_type size_) : + super_type(), + _data(data_), + _size(size_) {} + + template <typename Other> + inline SpanImpl(const Other &other) : + super_type(), + _data(other.data()), + _size(other.size()) {} + + inline void clear() { + _data = nullptr; + _size = 0; + } + + inline size_type size() const { return _size; } + inline pointer data() const { return _data; } + + inline const_iterator cbegin() const { return const_iterator(&this->impl(), 0); } + inline const_iterator cend() const { return const_iterator(&this->impl(), size()); } + inline const_iterator begin() const { return const_iterator(&this->impl(), 0); } + inline const_iterator end() const { return const_iterator(&this->impl(), size()); } + inline iterator begin() { return iterator(&this->impl(), 0); } + inline iterator end() { return iterator(&this->impl(), size()); } + + const String name() const { return String::format("%p", static_cast<const void *>(data())); } + + String getValidationMessage(const index_type index, const difference_type deltaInBytes, const SpanValidationMode mode) const { + const char *modeName; + switch (mode) { + case kValidateRead: + modeName = "reading"; + break; + case kValidateWrite: + modeName = "writing"; + break; + case kValidateSeek: + modeName = "seeking"; + break; + } + + return String::format("Access violation %s %s: %ld + %ld > %ld", + modeName, + this->impl().name().c_str(), + index, + deltaInBytes / sizeof(value_type), + size()); + } + +#pragma mark - +#pragma mark SpanImpl - Subspan + +public: + template <typename NewValueType> + inline const Derived<NewValueType> subspan(const index_type index, size_type numEntries = kSpanMaxSize) const { + Derived<NewValueType> span; + populateSubspan(span, index, numEntries); + return span; + } + + template <typename NewValueType> + inline Derived<NewValueType> subspan(const index_type index, size_type numEntries = kSpanMaxSize) { + Derived<NewValueType> span; + populateSubspan(span, index, numEntries); + return span; + } + + inline const_derived_type subspan(const index_type index, const size_type numEntries = kSpanMaxSize) const { + return subspan<value_type>(index, numEntries); + } + + inline mutable_derived_type subspan(const index_type index, const size_type numEntries = kSpanMaxSize) { + return subspan<value_type>(index, numEntries); + } + +#if !defined(__GNUC__) || GCC_ATLEAST(3, 0) +protected: +#endif + template <typename NewValueType> + void populateSubspan(Derived<NewValueType> &span, const index_type index, size_type numEntries) const { + if (numEntries == kSpanMaxSize) { + numEntries = CLIP<size_type>(size() - index, 0, size()); + } + + assert(numEntries * sizeof(value_type) % sizeof(NewValueType) == 0); + this->validate(index, numEntries * sizeof(value_type), kValidateSeek); + + span._data = (NewValueType *)const_cast<mutable_value_type *>(_data + index); + span._size = numEntries * sizeof(value_type) / sizeof(NewValueType); + } + +#pragma mark - +#pragma mark SpanImpl - Allocation + +private: + typedef typename RemoveConst<value_type>::type mutable_value_type; + typedef Derived<mutable_value_type> mutable_value_derived_type; + +public: + mutable_value_derived_type &allocate(const size_type numEntries) { + assert(_data == nullptr); + assert(numEntries != kSpanMaxSize); + _data = new mutable_value_type[numEntries]; + _size = numEntries; + return (mutable_value_derived_type &)const_cast<Derived<value_type> &>(this->impl()); + } + + template <typename Other> + mutable_value_derived_type &allocateFromSpan(const Other &other) { + assert(_data == nullptr); + assert(sizeof(value_type) == sizeof(typename Other::value_type)); + _data = new mutable_value_type[other.size()]; + _size = other.size(); + copy(other.begin(), other.end(), const_cast<mutable_value_type *>(_data)); + return (mutable_value_derived_type &)const_cast<Derived<value_type> &>(this->impl()); + } + + mutable_value_derived_type &allocateFromStream(SeekableReadStream &stream, size_type numEntries = kSpanMaxSize) { + if (numEntries == kSpanMaxSize) { + numEntries = (stream.size() - stream.pos()) / sizeof(value_type); + } + + const uint32 bytesRequested = numEntries * sizeof(value_type); + assert(stream.pos() + bytesRequested <= (uint)stream.size()); + allocate(numEntries); + const uint32 bytesRead = stream.read((void *)const_cast<mutable_value_type *>(_data), bytesRequested); + assert(bytesRead == bytesRequested); + return (mutable_value_derived_type &)const_cast<Derived<value_type> &>(this->impl()); + } + + value_type *_data; + size_type _size; +}; + +#pragma mark - +#pragma mark Span + +template <typename ValueType> +class Span : public SpanImpl<ValueType, Span> { + typedef SpanImpl<ValueType, ::Common::Span> super_type; + typedef typename AddConst<Span<ValueType> >::type const_derived_type; + typedef typename RemoveConst<Span<ValueType> >::type mutable_derived_type; +#if !defined(__GNUC__) || GCC_ATLEAST(3, 0) + template <typename T> friend class Span; +#endif + +public: + COMMON_SPAN_TYPEDEFS + + inline Span() : super_type() {} + + inline Span(const pointer data_, const size_type size_) : super_type(data_, size_) {} + + // Allows unrelated sibling classes like NamedSpan to assign to superclass + // siblings like Span + template <typename Other> + inline Span(const Other &other) : super_type(other) {} +}; + +#pragma mark - +#pragma mark NamedSpanImpl + +template <typename ValueType, template <typename> class Derived> +class NamedSpanImpl : public SpanImpl<ValueType, Derived> { + typedef SpanImpl<ValueType, Derived> super_type; + typedef typename AddConst<Derived<ValueType> >::type const_derived_type; + typedef typename RemoveConst<Derived<ValueType> >::type mutable_derived_type; + +#if !defined(__GNUC__) || GCC_ATLEAST(3, 0) + template <typename T, template <typename> class U> friend class NamedSpanImpl; +#endif +#ifdef CXXTEST_RUNNING + friend class ::SpanTestSuite; +#endif + +public: + COMMON_SPAN_TYPEDEFS + + inline NamedSpanImpl() : super_type(), _name(), _sourceByteOffset(0) {} + + inline NamedSpanImpl(const pointer data_, + const size_type size_, + const String &name_ = String(), + const size_type sourceByteOffset_ = 0) : + super_type(data_, size_), + _name(name_), + _sourceByteOffset(sourceByteOffset_) {} + + template <typename Other> + inline NamedSpanImpl(const Other &other) : + super_type(other), + _name(other.name()), + _sourceByteOffset(other.sourceByteOffset()) {} + + inline void clear() { + super_type::clear(); + _name.clear(); + _sourceByteOffset = 0; + } + + const String &name() const { return _name; } + String &name() { return _name; } + + const size_type &sourceByteOffset() const { return _sourceByteOffset; } + size_type &sourceByteOffset() { return _sourceByteOffset; } + +private: + String _name; + size_type _sourceByteOffset; + +#pragma mark - +#pragma mark NamedSpanImpl - Subspan + +public: + template <typename NewValueType> + inline const Derived<NewValueType> subspan(const index_type index, const size_type numEntries = kSpanMaxSize, const String &name_ = String(), const size_type sourceByteOffset_ = kSpanKeepOffset) const { + Derived<NewValueType> span; + populateSubspan(span, index, numEntries, name_, sourceByteOffset_); + return span; + } + + template <typename NewValueType> + inline Derived<NewValueType> subspan(const index_type index, const size_type numEntries = kSpanMaxSize, const String &name_ = String(), const size_type sourceByteOffset_ = kSpanKeepOffset) { + Derived<NewValueType> span; + populateSubspan(span, index, numEntries, name_, sourceByteOffset_); + return span; + } + + inline const_derived_type subspan(const index_type index, const size_type numEntries = kSpanMaxSize, const String &name_ = String(), const size_type sourceByteOffset_ = kSpanKeepOffset) const { + return subspan<value_type>(index, numEntries, name_, sourceByteOffset_); + } + + inline mutable_derived_type subspan(const index_type index, const size_type numEntries = kSpanMaxSize, const String &name_ = String(), const size_type sourceByteOffset_ = kSpanKeepOffset) { + return subspan<value_type>(index, numEntries, name_, sourceByteOffset_); + } + +#if !defined(__GNUC__) || GCC_ATLEAST(3, 0) +protected: +#endif + template <typename NewValueType> + void populateSubspan(Derived<NewValueType> &span, const index_type index, size_type numEntries, const String &name_, const size_type sourceByteOffset_ = kSpanKeepOffset) const { + super_type::template populateSubspan<NewValueType>(span, index, numEntries); + + if (name_.empty()) { + span._name = _name; + } else { + span._name = name_; + } + + if (sourceByteOffset_ == kSpanKeepOffset) { + span._sourceByteOffset = _sourceByteOffset + index * sizeof(value_type); + } else { + span._sourceByteOffset = sourceByteOffset_; + } + } + +#pragma mark - +#pragma mark NamedSpanImpl - Validation + +public: + String getValidationMessage(const index_type index, const difference_type deltaInBytes, const SpanValidationMode mode) const { + const index_type indexInBytes = index * sizeof(value_type); + const size_type maxSizeInBytes = this->impl().byteSize(); + + return super_type::getValidationMessage(index, deltaInBytes, mode) + + String::format(" (abs: %ld + %ld > %ld)", + this->impl().sourceByteOffset() + indexInBytes, + deltaInBytes, + this->impl().sourceByteOffset() + maxSizeInBytes); + } + +#pragma mark - +#pragma mark NamedSpanImpl - Allocation + +private: + typedef typename RemoveConst<value_type>::type mutable_value_type; + typedef Derived<mutable_value_type> mutable_value_derived_type; + +public: + mutable_value_derived_type &allocate(const size_type numEntries, const String &name_ = String()) { + super_type::allocate(numEntries); + _name = name_; + _sourceByteOffset = 0; + return (mutable_value_derived_type &)const_cast<Derived<value_type> &>(this->impl()); + } + + template <typename OtherValueType> + mutable_value_derived_type &allocateFromSpan(const NamedSpanImpl<OtherValueType, Derived> &other) { + super_type::allocateFromSpan(other); + _name = other.name(); + _sourceByteOffset = other.sourceByteOffset(); + return (mutable_value_derived_type &)const_cast<Derived<value_type> &>(this->impl()); + } + + template <typename OtherValueType, template <typename> class OtherDerived> + mutable_value_derived_type &allocateFromSpan(const SpanImpl<OtherValueType, OtherDerived> &other) { + super_type::allocateFromSpan(other); + return (mutable_value_derived_type &)const_cast<Derived<value_type> &>(this->impl()); + } + + mutable_value_derived_type &allocateFromStream(SeekableReadStream &stream, size_type numEntries = kSpanMaxSize, const String &name_ = String()) { + super_type::allocateFromStream(stream, numEntries); + _name = name_; + _sourceByteOffset = 0; + return (mutable_value_derived_type &)const_cast<Derived<value_type> &>(this->impl()); + } + + mutable_value_derived_type &allocateFromStream(File &file, const size_type numEntries = kSpanMaxSize) { + return allocateFromStream(file, numEntries, file.getName()); + } +}; + +#pragma mark - +#pragma mark NamedSpan + +template <typename ValueType> +class NamedSpan : public NamedSpanImpl<ValueType, NamedSpan> { + typedef NamedSpanImpl<ValueType, ::Common::NamedSpan> super_type; + +#if !defined(__GNUC__) || GCC_ATLEAST(3, 0) + template <typename T> friend class NamedSpan; +#endif + +public: + COMMON_SPAN_TYPEDEFS + + inline NamedSpan() : super_type() {} + + inline NamedSpan(const pointer data_, + const size_type size_, + const String &name_ = String(), + const size_type sourceByteOffset_ = 0) : + super_type(data_, size_, name_, sourceByteOffset_) {} + + template <typename Other> + inline NamedSpan(const Other &other) : super_type(other) {} +}; + +#pragma mark - +#pragma mark SpanOwner + +/** + * Similar to ScopedPtr, but allows holding and disposing pointers inside Spans + * without requiring an additional pointer to data, and with copyability. + */ +template <typename OwnedSpan> +class SpanOwner : public SafeBool<SpanOwner<OwnedSpan> > { + typedef typename OwnedSpan::value_type value_type; + typedef typename OwnedSpan::size_type size_type; + typedef typename OwnedSpan::index_type index_type; + typedef typename OwnedSpan::pointer pointer; + typedef typename OwnedSpan::reference reference; + typedef typename OwnedSpan::const_reference const_reference; + +#if !defined(__GNUC__) || GCC_ATLEAST(3, 0) + template <typename T, typename U> friend struct SafeBool; +#endif + +public: + inline SpanOwner() : _span() {} + + inline SpanOwner(const OwnedSpan &span) : _span(span) {} + + /** + * Creates a new owned copy of the memory from the other SpanOwner. + */ + inline SpanOwner(const SpanOwner &other) { + // Allocating memory when copy-constructing from an unallocated owner + // will break the new owner by making it appear allocated even though + // it doesn't (and shouldn't) contain data + if (!other) { + SpanOwner(); + return; + } + + _span.allocateFromSpan(other._span); + } + + /** + * Transfers ownership of the Span from the other owner to this owner. + * If this owner already holds another Span, the old Span will be destroyed. + */ + inline SpanOwner &operator=(SpanOwner &other) { + if (this == &other) { + return *this; + } + + if (_span.data()) { + delete[] const_cast<typename RemoveConst<value_type>::type *>(_span.data()); + } + _span = other._span; + other.release(); + return *this; + } + + inline ~SpanOwner() { + delete[] const_cast<typename RemoveConst<value_type>::type *>(_span.data()); + } + + /** + * Releases the memory owned by this SpanOwner to the caller. + */ + inline pointer release() { + pointer data = _span.data(); + _span.clear(); + return data; + } + + /** + * Destroys the memory owned by this owner. + */ + inline void clear() { + delete[] const_cast<typename RemoveConst<value_type>::type *>(_span.data()); + _span.clear(); + } + +#if !defined(__GNUC__) || GCC_ATLEAST(3, 0) +protected: +#endif + inline bool operator_bool() const { return _span; } + +private: + OwnedSpan _span; + +#pragma mark - +#pragma mark SpanOwner - Data access + +public: + inline const OwnedSpan &operator*() const { return _span; } + inline OwnedSpan &operator*() { return _span; } + + inline const OwnedSpan *operator->() const { return &_span; } + inline OwnedSpan *operator->() { return &_span; } + + inline const_reference operator[](const index_type index) const { return _span[index]; } + inline reference operator[](const index_type index) { return _span[index]; } +}; + +} // End of namespace Common + +#endif diff --git a/common/type-traits.h b/common/type-traits.h new file mode 100644 index 0000000000..4b17317374 --- /dev/null +++ b/common/type-traits.h @@ -0,0 +1,34 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source 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, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef COMMON_TYPE_TRAITS_H +#define COMMON_TYPE_TRAITS_H + +namespace Common { + template <bool b, class T, class U> struct Conditional { typedef T type; }; + template <class T, class U> struct Conditional<false, T, U> { typedef U type; }; + template <typename T> struct RemoveConst { typedef T type; }; + template <typename T> struct RemoveConst<const T> { typedef T type; }; + template <typename T> struct AddConst { typedef const T type; }; +} // End of namespace Common + +#endif diff --git a/test/common/span.h b/test/common/span.h new file mode 100644 index 0000000000..d73a2e2266 --- /dev/null +++ b/test/common/span.h @@ -0,0 +1,789 @@ +#include <cxxtest/TestSuite.h> + +class SpanTestSuite; + +#include "common/span.h" +#include "common/str.h" + +class SpanTestSuite : public CxxTest::TestSuite { + struct Foo { + int a; + }; + + template <typename ValueType, template <typename> class Derived> + class SiblingSpanImpl : public Common::SpanImpl<ValueType, Derived> { + typedef Common::SpanImpl<ValueType, Derived> super_type; + public: + COMMON_SPAN_TYPEDEFS + SiblingSpanImpl() : super_type() {} + SiblingSpanImpl(pointer data_, size_type size_) : super_type(data_, size_) {} + }; + + template <typename ValueType> + class SiblingSpan : public SiblingSpanImpl<ValueType, SiblingSpan> { + typedef SiblingSpanImpl<ValueType, ::SpanTestSuite::SiblingSpan> super_type; + public: + COMMON_SPAN_TYPEDEFS + SiblingSpan() : super_type() {} + SiblingSpan(pointer data_, size_type size_) : super_type(data_, size_) {} + }; + + template <typename ValueType, template <typename> class Derived> + class SubSpanImpl : public Common::NamedSpanImpl<ValueType, Derived> { + typedef Common::NamedSpanImpl<ValueType, Derived> super_type; + public: + COMMON_SPAN_TYPEDEFS + SubSpanImpl() : super_type() {} + SubSpanImpl(pointer data_, + size_type size_, + const Common::String &name_ = Common::String(), + const size_type sourceByteOffset_ = 0) : + super_type(data_, size_, name_, sourceByteOffset_) {} + + template <typename Other> + SubSpanImpl(const Other &other) : super_type(other) {} + }; + + template <typename ValueType> + class SubSpan : public SubSpanImpl<ValueType, SubSpan> { + typedef SubSpanImpl<ValueType, ::SpanTestSuite::SubSpan> super_type; + public: + COMMON_SPAN_TYPEDEFS + SubSpan() : super_type() {} + SubSpan(pointer data_, + size_type size_, + const Common::String &name_ = Common::String(), + const size_type sourceByteOffset_ = 0) : + super_type(data_, size_, name_, sourceByteOffset_) {} + + template <typename Other> + SubSpan(const Other &other) : super_type(other) {} + }; + +public: + void test_sibling_span() { + byte data[] = { 'h', 'e', 'l', 'l', 'o' }; + SiblingSpan<byte> ss(data, sizeof(data)); + Common::Span<byte> superInstance = ss; + TS_ASSERT_EQUALS(ss.data(), data); + TS_ASSERT_EQUALS(superInstance.data(), data); + } + + void test_sub_span() { + byte data[] = { 'h', 'e', 'l', 'l', 'o' }; + SubSpan<byte> ss(data, sizeof(data), "custom subspan"); + Common::NamedSpan<byte> namedSuper = ss; + Common::Span<byte> unnamedSuper = ss; + TS_ASSERT(ss.name() == "custom subspan"); + TS_ASSERT(namedSuper.name() == ss.name()); + TS_ASSERT(unnamedSuper.name() == Common::String::format("%p", (void *)data)); + } + + void test_span_iterator_const() { + byte data[] = { 'h', 'e', 'l', 'l', 'o' }; + const Common::Span<byte> span(data, sizeof(data)); + + Common::Span<byte>::const_iterator it = span.cbegin(); + + Common::Span<byte>::const_iterator sameIt(it); + + TS_ASSERT_EQUALS(sameIt, it); + + uint i; + for (i = 0; it != span.cend(); ++i, ++it) { + TS_ASSERT_EQUALS(*it, data[i]); + TS_ASSERT_LESS_THAN(i, sizeof(data)); + } + TS_ASSERT_EQUALS(i, sizeof(data)); + + it = span.cend() - 1; + for (i = sizeof(data) - 1; it != span.cbegin(); --i, --it) { + TS_ASSERT_EQUALS(data[i], *it); + } + TS_ASSERT_EQUALS(i, 0U); + + it = span.cbegin(); + + it += 4; + TS_ASSERT_EQUALS(data[4], *it); + + it -= 4; + TS_ASSERT_EQUALS(data[0], *it); + + TS_ASSERT_EQUALS(data[0], *it++); + + TS_ASSERT_EQUALS(data[1], *it--); + + TS_ASSERT_EQUALS(span.cend() - span.cbegin(), 5); + + TS_ASSERT_EQUALS(*(span.cbegin() + 4), data[4]); + + TS_ASSERT_EQUALS(*(span.cend() - 4), data[1]); + + TS_ASSERT(span.cbegin() < span.cend()); + + TS_ASSERT(span.cbegin() <= span.cend()); + TS_ASSERT(span.cbegin() <= span.cbegin()); + + TS_ASSERT(span.cend() > span.cbegin()); + + TS_ASSERT(span.cend() >= span.cbegin()); + TS_ASSERT(span.cend() >= span.cend()); + } + + void test_span_iterator() { + byte data[] = { 'h', 'e', 'l', 'l', 'o' }; + Common::Span<byte> span(data, sizeof(data)); + + // empty iterator should default construct OK + Common::Span<byte>::iterator defaultIt; + + Common::Span<byte>::iterator it = span.begin(); + + Common::Span<byte>::iterator sameIt(it); + + TS_ASSERT_EQUALS(sameIt, it); + + uint i; + for (i = 0; it != span.end(); ++i, ++it) { + TS_ASSERT_EQUALS(*it, data[i]); + TS_ASSERT_LESS_THAN(i, sizeof(data)); + } + TS_ASSERT_EQUALS(i, sizeof(data)); + + it = span.end() - 1; + for (i = sizeof(data) - 1; it != span.begin(); --i, --it) { + TS_ASSERT_EQUALS(data[i], *it); + } + TS_ASSERT_EQUALS(i, 0U); + + it = span.begin(); + + it += 4; + TS_ASSERT_EQUALS(data[4], *it); + + it -= 4; + TS_ASSERT_EQUALS(data[0], *it); + + TS_ASSERT_EQUALS(data[0], *it++); + + TS_ASSERT_EQUALS(data[1], *it--); + + TS_ASSERT_EQUALS(span.end() - span.begin(), 5); + + TS_ASSERT_EQUALS(*(span.begin() + 4), data[4]); + + TS_ASSERT_EQUALS(*(span.end() - 4), data[1]); + + TS_ASSERT(span.begin() < span.end()); + + TS_ASSERT(span.begin() <= span.end()); + TS_ASSERT(span.begin() <= span.begin()); + + TS_ASSERT(span.end() > span.begin()); + + TS_ASSERT(span.end() >= span.begin()); + TS_ASSERT(span.end() >= span.end()); + + it = span.begin(); + for (i = 0; it != span.end(); ++i, ++it) { + *it = 'a' + i; + } + + it = span.begin(); + for (i = 0; it != span.end(); ++i, ++it) { + TS_ASSERT_EQUALS(*it, 'a' + i); + TS_ASSERT_EQUALS(data[i], 'a' + i); + } + } + + void test_span_iterator_integers() { + const byte data[] = { 0xFF, 1, 2, 3, 2, 1, 0xFF }; + Common::Span<const byte> span(data, sizeof(data)); + Common::Span<const byte>::const_iterator it = span.cbegin(); + + TS_ASSERT_EQUALS(it.getInt8(), -1); + TS_ASSERT_EQUALS(it.getUint8(), 255); + TS_ASSERT_EQUALS(it.getInt16BE(), -255); + TS_ASSERT_EQUALS(it.getUint16BE(), 65281U); + TS_ASSERT_EQUALS((it + 5).getInt16LE(), -255); + TS_ASSERT_EQUALS((it + 5).getUint16LE(), 65281U); + TS_ASSERT_EQUALS(it.getUint24LE(), 131583U); +#ifdef SCUMM_LITTLE_ENDIAN + TS_ASSERT_EQUALS((it + 3).getUint32(), 4278256131U); +#elif SCUMM_BIG_ENDIAN + TS_ASSERT_EQUALS(it.getUint32(), 4278256131U); +#else +#error No endianness detected +#endif + TS_ASSERT_EQUALS(it.getInt32BE(), -16711165); + TS_ASSERT_EQUALS(it.getUint32BE(), 4278256131U); + TS_ASSERT_EQUALS((it + 3).getInt32LE(), -16711165); + TS_ASSERT_EQUALS((it + 3).getUint32LE(), 4278256131U); + } + + void test_span_iterator_ptr() { + Foo foo[2]; + foo[0].a = 1; + foo[1].a = 2; + + const Common::Span<Foo> span(foo, 2); + Common::Span<Foo>::const_iterator it = span.cbegin(); + TS_ASSERT_EQUALS(it->a, 1); + ++it; + TS_ASSERT_EQUALS(it->a, 2); + + TS_ASSERT_EQUALS(it[0].a, 2); + TS_ASSERT_EQUALS(it[-1].a, 1); + --it; + TS_ASSERT_EQUALS(it[1].a, 2); + } + + void test_span_owner() { + Common::SpanOwner<Common::Span<byte> > owner; + owner->allocate(3); + owner[0] = 'a'; + owner[1] = 'b'; + owner[2] = 'c'; + for (int i = 0; i < 3; ++i) { + TS_ASSERT_EQUALS(owner->getUint8At(i), 'a' + i); + TS_ASSERT_EQUALS((*owner)[i], 'a' + i); + } + + { + Common::SpanOwner<Common::NamedSpan<byte> > owner2; + TS_ASSERT(owner2->data() == nullptr); + owner2->allocateFromSpan(*owner); + TS_ASSERT(owner2->data() != nullptr); + TS_ASSERT_DIFFERS(owner->data(), owner2->data()); + + for (int i = 0; i < 3; ++i) { + TS_ASSERT_EQUALS(owner2->getUint8At(i), 'a' + i); + TS_ASSERT_EQUALS((*owner2)[i], 'a' + i); + } + + TS_ASSERT_EQUALS((bool)owner2, true); + owner2.release(); + TS_ASSERT_EQUALS((bool)owner2, false); + } + + { + Common::SpanOwner<Common::Span<byte> > owner2; + TS_ASSERT_EQUALS((bool)owner, true); + void *dataPtr = owner->data(); + owner2 = owner; + TS_ASSERT_EQUALS((bool)owner, false); + TS_ASSERT(owner->data() == nullptr); + TS_ASSERT_EQUALS(owner2->data(), dataPtr); + + // tests destruction of held pointer by reassignment + owner2 = owner; + + // tests nullipotence of assignment to self + dataPtr = owner2->data(); + owner2 = owner2; + TS_ASSERT(owner2->data() == dataPtr); + } + + { + char *data = new char[6]; + Common::strlcpy(data, "hello", 6); + const Common::SpanOwner<Common::Span<const char> > constOwner(Common::Span<const char>(data, 6)); + TS_ASSERT_EQUALS((*constOwner)[0], 'h'); + TS_ASSERT_EQUALS(constOwner->getUint8At(1), 'e'); + TS_ASSERT_EQUALS(constOwner[2], 'l'); + } + + { + TS_ASSERT_EQUALS((bool)owner, false); + Common::SpanOwner<Common::Span<byte> > owner2(owner); + TS_ASSERT_EQUALS((bool)owner2, false); + } + + { + owner->allocate(1); + TS_ASSERT_EQUALS((bool)owner, true); + Common::SpanOwner<Common::Span<byte> > owner2(owner); + TS_ASSERT_EQUALS((bool)owner2, true); + TS_ASSERT_DIFFERS(owner->data(), owner2->data()); + } + + { + TS_ASSERT_EQUALS((bool)owner, true); + void *dataPtr = owner->data(); + TS_ASSERT_EQUALS(owner.release(), dataPtr); + TS_ASSERT_EQUALS((bool)owner, false); + } + } + + void test_span_owner_named_span() { + Common::SpanOwner<Common::NamedSpan<byte> > owner; + owner->allocate(3, "foo"); + owner[0] = 'a'; + owner[1] = 'b'; + owner[2] = 'c'; + for (int i = 0; i < 3; ++i) { + TS_ASSERT_EQUALS(owner->getUint8At(i), 'a' + i); + TS_ASSERT_EQUALS((*owner)[i], 'a' + i); + } + TS_ASSERT(owner->name() == "foo"); + + { + Common::SpanOwner<Common::NamedSpan<byte> > owner2; + TS_ASSERT(owner2->data() == nullptr); + owner2->allocateFromSpan(*owner); + TS_ASSERT(owner2->data() != nullptr); + TS_ASSERT_DIFFERS(owner->data(), owner2->data()); + TS_ASSERT(owner2->name() == "foo"); + + for (int i = 0; i < 3; ++i) { + TS_ASSERT_EQUALS(owner2->getUint8At(i), 'a' + i); + TS_ASSERT_EQUALS((*owner2)[i], 'a' + i); + } + + TS_ASSERT_EQUALS((bool)owner2, true); + owner2.release(); + TS_ASSERT_EQUALS((bool)owner2, false); + } + + { + Common::SpanOwner<Common::NamedSpan<byte> > owner2; + TS_ASSERT_EQUALS((bool)owner, true); + void *dataPtr = owner->data(); + owner2 = owner; + TS_ASSERT_EQUALS((bool)owner, false); + TS_ASSERT(owner->data() == nullptr); + TS_ASSERT_EQUALS(owner2->data(), dataPtr); + + // tests destruction of held pointer by reassignment + owner2 = owner; + } + + { + char *data = new char[6]; + Common::strlcpy(data, "hello", 6); + const Common::SpanOwner<Common::NamedSpan<const char> > constOwner(Common::NamedSpan<const char>(data, 6)); + TS_ASSERT_EQUALS((*constOwner)[0], 'h'); + TS_ASSERT_EQUALS(constOwner->getUint8At(1), 'e'); + TS_ASSERT_EQUALS(constOwner[2], 'l'); + } + + { + TS_ASSERT_EQUALS((bool)owner, false); + Common::SpanOwner<Common::NamedSpan<byte> > owner2(owner); + TS_ASSERT_EQUALS((bool)owner2, false); + } + + { + owner->allocate(1); + TS_ASSERT_EQUALS((bool)owner, true); + Common::SpanOwner<Common::NamedSpan<byte> > owner2(owner); + TS_ASSERT_EQUALS((bool)owner2, true); + TS_ASSERT_DIFFERS(owner->data(), owner2->data()); + } + + { + TS_ASSERT_EQUALS((bool)owner, true); + void *dataPtr = owner->data(); + TS_ASSERT_EQUALS(owner.release(), dataPtr); + TS_ASSERT_EQUALS((bool)owner, false); + } + } + + void test_span_allocate_from_stream() { + byte data[] = "hello"; + Common::MemoryReadStream stream(data, sizeof(data)); + Common::SpanOwner<Common::Span<byte> > owner; + owner->allocateFromStream(stream, 2); + TS_ASSERT(owner->data() != data); + TS_ASSERT_EQUALS(owner->size(), 2U); + TS_ASSERT_EQUALS(owner[0], 'h'); + TS_ASSERT_EQUALS(owner[1], 'e'); + owner.clear(); + TS_ASSERT(owner->data() == nullptr); + stream.seek(0, SEEK_SET); + + owner->allocateFromStream(stream); + TS_ASSERT(owner->data() != data); + TS_ASSERT_EQUALS(owner->size(), sizeof(data)); + TS_ASSERT_EQUALS(owner[0], 'h'); + TS_ASSERT_EQUALS(owner[1], 'e'); + TS_ASSERT_EQUALS(owner[2], 'l'); + TS_ASSERT_EQUALS(owner[3], 'l'); + TS_ASSERT_EQUALS(owner[4], 'o'); + + Common::SpanOwner<Common::NamedSpan<const byte> > owner2; + stream.seek(0, SEEK_SET); + owner2->allocateFromStream(stream, Common::kSpanMaxSize, "streamname"); + TS_ASSERT(owner2->data() != data); + TS_ASSERT_EQUALS(owner2->size(), sizeof(data)); + TS_ASSERT_EQUALS(owner2[0], 'h'); + TS_ASSERT_EQUALS(owner2[1], 'e'); + TS_ASSERT_EQUALS(owner2[2], 'l'); + TS_ASSERT_EQUALS(owner2[3], 'l'); + TS_ASSERT_EQUALS(owner2[4], 'o'); + TS_ASSERT_EQUALS(owner2->name(), "streamname"); + } + + void test_span_byte() { + { + byte data[] = { 'h', 'e', 'l', 'l', 'o' }; + Common::Span<byte> span(data, sizeof(data)); + + TS_ASSERT_EQUALS(span.size(), sizeof(data)); + TS_ASSERT_EQUALS(span.byteSize(), sizeof(data)); + + Common::Span<byte> other(span); + TS_ASSERT_EQUALS(span, other); + other.clear(); + TS_ASSERT(span != other); + + TS_ASSERT_EQUALS(span[0], 'h'); + TS_ASSERT_EQUALS(span[1], 'e'); + span[1] = 'o'; + TS_ASSERT_EQUALS(span[1], 'o'); + + TS_ASSERT((bool)span); + span.clear(); + TS_ASSERT(!(bool)span); + } + + { + byte data[] = { 'h', 'e', 'l', 'l', 'o' }; + const Common::Span<const byte> span(data, sizeof(data)); + + TS_ASSERT_EQUALS(span.size(), sizeof(data)); + TS_ASSERT_EQUALS(span.byteSize(), sizeof(data)); + + const Common::Span<const byte> other(span); + TS_ASSERT_EQUALS(span, other); + + TS_ASSERT_EQUALS(span[0], 'h'); + TS_ASSERT_EQUALS(span[1], 'e'); + } + } + + void test_span_integers() { + const byte data[] = { 0xFF, 1, 2, 3, 2, 1, 0xFF }; + Common::Span<const byte> span(data, sizeof(data)); + + TS_ASSERT_EQUALS(span[0], 255); + TS_ASSERT_EQUALS(span.getInt8At(0), -1); + TS_ASSERT_EQUALS(span.getUint8At(0), 255U); + TS_ASSERT_EQUALS(span.getInt16BEAt(0), -255); + TS_ASSERT_EQUALS(span.getUint16BEAt(0), 65281U); + TS_ASSERT_EQUALS(span.getInt16LEAt(5), -255); + TS_ASSERT_EQUALS(span.getUint16LEAt(5), 65281U); + TS_ASSERT_EQUALS(span.getUint24LEAt(0), 131583U); + TS_ASSERT_EQUALS(span.getInt32BEAt(0), -16711165); + TS_ASSERT_EQUALS(span.getUint32BEAt(0), 4278256131U); + TS_ASSERT_EQUALS(span.getInt32LEAt(3), -16711165); + TS_ASSERT_EQUALS(span.getUint32LEAt(3), 4278256131U); + +#ifdef SCUMM_LITTLE_ENDIAN + TS_ASSERT_EQUALS(span.getUint32At(3), 4278256131U); +#elif SCUMM_BIG_ENDIAN + TS_ASSERT_EQUALS(span.getUint32At(0), 4278256131U); +#else +#error No endianness detected +#endif + } + + void test_span_string() { + char data[] = "hello"; + Common::Span<char> span(data, sizeof(data)); + TS_ASSERT_EQUALS(span[sizeof(data) - 1], '\0'); + + TS_ASSERT(span.getStringAt(0) == data); + TS_ASSERT(span.getStringAt(0, 2) == "he"); + span[3] = '\0'; + TS_ASSERT(span.getStringAt(0) == "hel"); + } + + void test_span_unsafe_data() { + char data[] = "hello"; + Common::Span<char> span(data, sizeof(data)); + + char *ptr = span.getUnsafeDataAt(0, 6); + TS_ASSERT_EQUALS(ptr, data); + ptr = span.getUnsafeDataAt(0); + TS_ASSERT_EQUALS(ptr, data); + + const Common::Span<const char> span2(data, sizeof(data)); + const char *ptr2 = span2.getUnsafeDataAt(0, 6); + TS_ASSERT_EQUALS(ptr2, data); + ptr2 = span2.getUnsafeDataAt(0); + TS_ASSERT_EQUALS(ptr2, data); + } + + void test_span_subspan() { + { + byte data[] = { 1, 2, 3, 4, 5, 6 }; + Common::Span<byte> span(data, sizeof(data)); + + TS_ASSERT_EQUALS(span.subspan(0).size(), sizeof(data) - 0); + TS_ASSERT_EQUALS(span.subspan(2).size(), sizeof(data) - 2); + TS_ASSERT_EQUALS(span.subspan(2, 2).size(), 2U); + TS_ASSERT_EQUALS(span.subspan<uint16>(0).size(), sizeof(data) / 2); + TS_ASSERT_EQUALS(span.subspan<uint16>(0).byteSize(), sizeof(data)); + TS_ASSERT_EQUALS(span.subspan<uint16>(0, 2).size(), 1U); + TS_ASSERT_EQUALS(span.subspan<uint16>(0, 2).byteSize(), 2U); + +#ifdef SCUMM_LITTLE_ENDIAN + TS_ASSERT_EQUALS(span.subspan<uint16>(0)[1], 4 << 8 | 3); +#elif SCUMM_BIG_ENDIAN + TS_ASSERT_EQUALS(span.subspan<uint16>(0)[1], 3 << 8 | 4); +#else +#error No endianness detected +#endif + + Common::Span<uint16> shortSpan = span.subspan<uint16>(0); + TS_ASSERT_EQUALS(shortSpan.byteSize(), span.byteSize()); + TS_ASSERT(shortSpan.size() != span.size()); + shortSpan[1] = 0xFFFF; + Common::Span<byte> byteSpan = shortSpan.subspan<byte>(1); + TS_ASSERT_EQUALS(byteSpan.size(), sizeof(data) - 1 * sizeof(uint16)); + TS_ASSERT_EQUALS(byteSpan[0], 0xFF); + TS_ASSERT_EQUALS(byteSpan[1], 0xFF); + } + + { + byte data[] = { 1, 2, 3, 4, 5, 6 }; + const Common::Span<const byte> span(data, sizeof(data)); + + TS_ASSERT_EQUALS(span.subspan(0).size(), sizeof(data) - 0); + TS_ASSERT_EQUALS(span.subspan(2).size(), sizeof(data) - 2); + TS_ASSERT_EQUALS(span.subspan(2, 2).size(), 2U); + TS_ASSERT_EQUALS(span.subspan<uint16>(0).size(), sizeof(data) / 2); + TS_ASSERT_EQUALS(span.subspan<uint16>(0).byteSize(), sizeof(data)); + TS_ASSERT_EQUALS(span.subspan<uint16>(0, 2).size(), 1U); + TS_ASSERT_EQUALS(span.subspan<uint16>(0, 2).byteSize(), 2U); + +#ifdef SCUMM_LITTLE_ENDIAN + TS_ASSERT_EQUALS(span.subspan<uint16>(0)[1], 4 << 8 | 3); +#elif SCUMM_BIG_ENDIAN + TS_ASSERT_EQUALS(span.subspan<uint16>(0)[1], 3 << 8 | 4); +#else +#error No endianness detected +#endif + + const Common::Span<uint16> shortSpan = span.subspan<uint16>(0); + TS_ASSERT_EQUALS(shortSpan.byteSize(), span.byteSize()); + TS_ASSERT(shortSpan.size() != span.size()); + Common::Span<byte> byteSpan = shortSpan.subspan<byte>(1); + TS_ASSERT_EQUALS(byteSpan.size(), sizeof(data) - 1 * sizeof(uint16)); + TS_ASSERT_EQUALS(byteSpan[0], 3); + TS_ASSERT_EQUALS(byteSpan[1], 4); + } + } + + void test_span_to_stream() { + const byte data[] = { 0, 1, 2, 3 }; + Common::Span<const byte> span(data, sizeof(data)); + + { + Common::MemoryReadStream stream(span.toStream(1, 2)); + byte out; + TS_ASSERT_EQUALS(stream.read(&out, 1), 1U); + TS_ASSERT_EQUALS(out, 1); + TS_ASSERT_EQUALS(stream.read(&out, 1), 1U); + TS_ASSERT_EQUALS(out, 2); + TS_ASSERT_EQUALS(stream.read(&out, 1), 0U); + } + + { + Common::MemoryReadStream stream = span.toStream(); + byte out; + TS_ASSERT_EQUALS(stream.read(&out, 1), 1U); + TS_ASSERT_EQUALS(out, 0); + TS_ASSERT_EQUALS(stream.read(&out, 1), 1U); + TS_ASSERT_EQUALS(out, 1); + TS_ASSERT_EQUALS(stream.read(&out, 1), 1U); + TS_ASSERT_EQUALS(out, 2); + TS_ASSERT_EQUALS(stream.read(&out, 1), 1U); + TS_ASSERT_EQUALS(out, 3); + TS_ASSERT_EQUALS(stream.read(&out, 1), 0U); + } + } + + void test_span_copying() { + const byte data[] = { 0, 1, 2, 3, 4, 5 }; + Common::Span<const byte> span(data, sizeof(data)); + + byte targetData[sizeof(data)] = {}; + Common::Span<byte> target(targetData, sizeof(targetData)); + span.copyDataTo(target); + for (uint i = 0; i < sizeof(data); ++i) { + TS_ASSERT_EQUALS(target[i], i); + } + + byte out[sizeof(data)]; + span.unsafeCopyDataTo(out); + for (uint i = 0; i < sizeof(data); ++i) { + TS_ASSERT_EQUALS(out[i], i); + } + } + + void test_span_validation() { + byte data[6]; + Common::Span<byte> span(data, sizeof(data)); + TS_ASSERT(!span.checkInvalidBounds(0, 0)); + TS_ASSERT(!span.checkInvalidBounds(0, 6)); + TS_ASSERT(!span.checkInvalidBounds(2, 4)); + TS_ASSERT(!span.checkInvalidBounds(4, 2)); + TS_ASSERT(!span.checkInvalidBounds(6, 0)); + TS_ASSERT(!span.checkInvalidBounds(2, -2)); + TS_ASSERT(span.checkInvalidBounds(-2, 2)); // negative index disallowed + TS_ASSERT(span.checkInvalidBounds(6, 1)); // combined positive overflow (+7) + TS_ASSERT(span.checkInvalidBounds(2, -4)); // negative overflow (-2) + TS_ASSERT(span.checkInvalidBounds(0, 10)); // delta positive overflow + + const ptrdiff_t big = 1L << (8 * sizeof(ptrdiff_t) - 1); + TS_ASSERT(span.checkInvalidBounds(big, 0)); + TS_ASSERT(span.checkInvalidBounds(0, big)); + TS_ASSERT(span.checkInvalidBounds(big, big)); + } + + void test_span_validation_message() { + byte data[1]; + Common::Span<byte> span(data, sizeof(data)); + + Common::String source = span.name(); + Common::String actual; + Common::String expected; + + actual = span.getValidationMessage(12, 34, Common::kValidateRead); + expected = Common::String::format("Access violation reading %s: 12 + 34 > 1", source.c_str()); + TS_ASSERT_EQUALS(actual, expected); + + actual = span.getValidationMessage(23, 45, Common::kValidateWrite); + expected = Common::String::format("Access violation writing %s: 23 + 45 > 1", source.c_str()); + TS_ASSERT_EQUALS(actual, expected); + + actual = span.getValidationMessage(-34, -56, Common::kValidateSeek); + expected = Common::String::format("Access violation seeking %s: -34 + -56 > 1", source.c_str()); + TS_ASSERT_EQUALS(actual, expected); + } + + void test_span_comparators() { + byte data[2]; + Common::Span<const byte> span0(data, sizeof(data)); + Common::Span<const byte> span1(data, sizeof(data)); + Common::Span<const byte> span2(data, sizeof(data) - 1); + Common::Span<const byte> span3(data + 1, sizeof(data) - 1); + Common::Span<const byte> span4(data + 2, sizeof(data) - 2); + + TS_ASSERT(span0 == span1); + TS_ASSERT(span0 != span2); + TS_ASSERT(span0 <= span1); + TS_ASSERT(span0 <= span3); + TS_ASSERT(span0 < span3); + TS_ASSERT(span3 < span4); + TS_ASSERT(span4 > span3); + TS_ASSERT(span3 > span0); + TS_ASSERT(span4 >= span4); + TS_ASSERT(span0 >= span1); + + TS_ASSERT_EQUALS(span1 - span0, 0); + TS_ASSERT_EQUALS(span3 - span0, 1); + TS_ASSERT_EQUALS(span4 - span0, 2); + TS_ASSERT_EQUALS(span0 - span1, 0); + TS_ASSERT_EQUALS(span0 - span3, -1); + TS_ASSERT_EQUALS(span0 - span4, -2); + } + + void test_named_span() { + byte data[6] = { 0, 1, 2, 3, 4, 5 }; + Common::NamedSpan<byte> span(data, sizeof(data), "foo.data"); + TS_ASSERT_EQUALS(span.name(), "foo.data"); + + Common::String actual; + Common::String expected; + + actual = span.getValidationMessage(12, 34, Common::kValidateRead); + expected = "Access violation reading foo.data: 12 + 34 > 6 (abs: 12 + 34 > 6)"; + TS_ASSERT_EQUALS(actual, expected); + + { + Common::NamedSpan<byte> subspan = span.subspan(2); + + expected = "Access violation reading foo.data: 23 + 45 > 4 (abs: 25 + 45 > 6)"; + actual = subspan.getValidationMessage(23, 45, Common::kValidateRead); + TS_ASSERT_EQUALS(actual, expected); + } + + { + Common::NamedSpan<byte> subspan = span.subspan(2, Common::kSpanMaxSize, "new.data"); + expected = "Access violation reading new.data: -34 + -56 > 4 (abs: -32 + -56 > 6)"; + actual = subspan.getValidationMessage(-34, -56, Common::kValidateRead); + TS_ASSERT_EQUALS(actual, expected); + } + + { + Common::NamedSpan<byte> subspan = span.subspan(2, Common::kSpanMaxSize, "new.data", 0); + expected = "Access violation reading new.data: -34 + -56 > 4 (abs: -34 + -56 > 4)"; + actual = subspan.getValidationMessage(-34, -56, Common::kValidateRead); + TS_ASSERT_EQUALS(actual, expected); + } + + Common::NamedSpan<byte> span2; + span = span2 = span; + TS_ASSERT_EQUALS(span2, span); + TS_ASSERT(span2.name() == span.name()); + TS_ASSERT(span2.sourceByteOffset() == span.sourceByteOffset()); + + Common::Span<byte> superclassInstance; + superclassInstance = span; + TS_ASSERT_EQUALS(span, superclassInstance); + + Common::Span<byte> subclassInstance(superclassInstance); + TS_ASSERT_EQUALS(subclassInstance, superclassInstance); + + const Common::NamedSpan<const byte> constSpan(span); + + { + Common::NamedSpan<const byte> subspan = constSpan.subspan(2); + + expected = "Access violation reading foo.data: 23 + 45 > 4 (abs: 25 + 45 > 6)"; + actual = subspan.getValidationMessage(23, 45, Common::kValidateRead); + TS_ASSERT_EQUALS(actual, expected); + TS_ASSERT_EQUALS(subspan.sourceByteOffset(), 2U); + } + + { + Common::NamedSpan<const byte> subspan = constSpan.subspan(2, Common::kSpanMaxSize, "new.data"); + expected = "Access violation reading new.data: -34 + -56 > 4 (abs: -32 + -56 > 6)"; + actual = subspan.getValidationMessage(-34, -56, Common::kValidateRead); + TS_ASSERT_EQUALS(actual, expected); + } + + { + Common::NamedSpan<const byte> subspan = constSpan.subspan(2, Common::kSpanMaxSize, "new.data", 0); + expected = "Access violation reading new.data: -34 + -56 > 4 (abs: -34 + -56 > 4)"; + actual = subspan.getValidationMessage(-34, -56, Common::kValidateRead); + TS_ASSERT_EQUALS(actual, expected); + } + + { + Common::NamedSpan<const byte> subspan = constSpan.subspan(2, Common::kSpanMaxSize, "new.data", 0); + subspan.sourceByteOffset() = 2; + expected = "Access violation reading new.data: -34 + -56 > 4 (abs: -32 + -56 > 6)"; + actual = subspan.getValidationMessage(-34, -56, Common::kValidateRead); + TS_ASSERT_EQUALS(actual, expected); + } + + { + Common::MemoryReadStream *stream = new Common::MemoryReadStream(data, sizeof(data)); + Common::File file; + file.open(stream, "test.txt"); + Common::SpanOwner<Common::NamedSpan<const byte> > fileOwner; + fileOwner->allocateFromStream(file); + TS_ASSERT_EQUALS(fileOwner->size(), (uint)file.size()); + file.close(); + TS_ASSERT(fileOwner->name() == "test.txt"); + for (uint i = 0; i < fileOwner->size(); ++i) { + TS_ASSERT_EQUALS(fileOwner->getInt8At(i), data[i]); + } + } + } +}; |