Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions include/EASTL/algorithm.h
Original file line number Diff line number Diff line change
Expand Up @@ -1257,6 +1257,9 @@ namespace eastl
template <bool isMove>
struct move_and_copy_backward_helper<eastl::random_access_iterator_tag, isMove, true>
{
// GCC 10 to 15 report null pointers from vector::insert into an empty vector, on the path for ranges
// that fit in the capacity, which a non-empty range can't take there.
EA_DISABLE_GCC_WARNING(-Wnonnull)
template <typename T>
static T* move_or_copy_backward(const T* first, const T* last, T* resultEnd)
{
Expand All @@ -1267,6 +1270,7 @@ namespace eastl
else
return resultEnd;
}
EA_RESTORE_GCC_WARNING()
};

template <bool isMove, typename BidirectionalIterator1, typename BidirectionalIterator2>
Expand Down
5 changes: 5 additions & 0 deletions include/EASTL/bitset.h
Original file line number Diff line number Diff line change
Expand Up @@ -973,6 +973,9 @@ namespace eastl
}


// GCC 11 reports out of bounds subscripts and writes at -O3; bitset::set only calls this with i < N.
EA_DISABLE_GCC_WARNING(-Warray-bounds)
EA_DISABLE_GCC_WARNING(-Wstringop-overflow)
template <size_t NW, typename WordType>
inline void BitsetBase<NW, WordType>::set(size_type i, bool value)
{
Expand All @@ -981,6 +984,8 @@ namespace eastl
else
mWord[i >> kBitsPerWordShift] &= ~(static_cast<word_type>(1) << (i & kBitsPerWordMask));
}
EA_RESTORE_GCC_WARNING()
EA_RESTORE_GCC_WARNING()


template <size_t NW, typename WordType>
Expand Down
9 changes: 7 additions & 2 deletions include/EASTL/bonus/tuple_vector.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,15 @@ struct TupleVecLeaf;
template <typename Indices, typename... Ts>
struct TupleVecIter;

// Always false, but dependent on I, so that a static_assert using it only fires on instantiation.
template <eastl_size_t I>
struct tuplevec_index_out_of_range : public false_type {};

// tuplevec_element helper to be able to isolate a type given an index
template <eastl_size_t I>
struct tuplevec_element<I>
{
static_assert(I != I, "tuplevec_element index out of range");
static_assert(tuplevec_index_out_of_range<I>::value, "tuplevec_element index out of range");
};

template <typename T, typename... Ts>
Expand Down Expand Up @@ -313,7 +317,8 @@ void swallow(Ts&&...) { }

inline bool variadicAnd(bool cond) { return cond; }

inline bool variadicAnd(bool cond, bool conds...) { return cond && variadicAnd(conds); }
template <typename... Bools>
inline bool variadicAnd(bool cond, Bools... conds) { return cond && variadicAnd(conds...); }

// Helper struct to check for strict compatibility between two iterators, whilst still allowing for
// conversion between TupleVecImpl<Ts...>::iterator and TupleVecImpl<Ts...>::const_iterator.
Expand Down
24 changes: 12 additions & 12 deletions include/EASTL/chrono.h
Original file line number Diff line number Diff line change
Expand Up @@ -731,27 +731,27 @@ namespace chrono
///////////////////////////////////////////////////////////////////////////////
// integer chrono literals
///////////////////////////////////////////////////////////////////////////////
EA_CONSTEXPR chrono::hours operator"" h(unsigned long long h) { return chrono::hours(h); }
EA_CONSTEXPR chrono::minutes operator"" min(unsigned long long m) { return chrono::minutes(m); }
EA_CONSTEXPR chrono::seconds operator"" s(unsigned long long s) { return chrono::seconds(s); }
EA_CONSTEXPR chrono::milliseconds operator"" ms(unsigned long long ms) { return chrono::milliseconds(ms); }
EA_CONSTEXPR chrono::microseconds operator"" us(unsigned long long us) { return chrono::microseconds(us); }
EA_CONSTEXPR chrono::nanoseconds operator"" ns(unsigned long long ns) { return chrono::nanoseconds(ns); }
EA_CONSTEXPR chrono::hours operator""h(unsigned long long h) { return chrono::hours(h); }
EA_CONSTEXPR chrono::minutes operator""min(unsigned long long m) { return chrono::minutes(m); }
EA_CONSTEXPR chrono::seconds operator""s(unsigned long long s) { return chrono::seconds(s); }
EA_CONSTEXPR chrono::milliseconds operator""ms(unsigned long long ms) { return chrono::milliseconds(ms); }
EA_CONSTEXPR chrono::microseconds operator""us(unsigned long long us) { return chrono::microseconds(us); }
EA_CONSTEXPR chrono::nanoseconds operator""ns(unsigned long long ns) { return chrono::nanoseconds(ns); }

///////////////////////////////////////////////////////////////////////////////
// float chrono literals
///////////////////////////////////////////////////////////////////////////////
EA_CONSTEXPR chrono::duration<long double, ratio<3600, 1>> operator"" h(long double h)
EA_CONSTEXPR chrono::duration<long double, ratio<3600, 1>> operator""h(long double h)
{ return chrono::duration<long double, ratio<3600, 1>>(h); }
EA_CONSTEXPR chrono::duration<long double, ratio<60, 1>> operator"" min(long double m)
EA_CONSTEXPR chrono::duration<long double, ratio<60, 1>> operator""min(long double m)
{ return chrono::duration<long double, ratio<60, 1>>(m); }
EA_CONSTEXPR chrono::duration<long double> operator"" s(long double s)
EA_CONSTEXPR chrono::duration<long double> operator""s(long double s)
{ return chrono::duration<long double>(s); }
EA_CONSTEXPR chrono::duration<float, milli> operator"" ms(long double ms)
EA_CONSTEXPR chrono::duration<float, milli> operator""ms(long double ms)
{ return chrono::duration<long double, milli>(ms); }
EA_CONSTEXPR chrono::duration<float, micro> operator"" us(long double us)
EA_CONSTEXPR chrono::duration<float, micro> operator""us(long double us)
{ return chrono::duration<long double, micro>(us); }
EA_CONSTEXPR chrono::duration<float, nano> operator"" ns(long double ns)
EA_CONSTEXPR chrono::duration<float, nano> operator""ns(long double ns)
{ return chrono::duration<long double, nano>(ns); }

} // namespace chrono_literals
Expand Down
80 changes: 56 additions & 24 deletions include/EASTL/internal/char_traits.h
Original file line number Diff line number Diff line change
Expand Up @@ -97,121 +97,137 @@ namespace eastl
bool DecodePart(const wchar_t*& pSrc, const wchar_t* pSrcEnd, char8_t*& pDest, char8_t* pDestEnd);
#endif

namespace details
{
// Calls the DecodePart overload for SrcT2 and DestT2 on the same memory. The pointers are converted by value
// and written back, because accessing them through references to another pointer type breaks strict aliasing.
template <typename SrcT2, typename DestT2, typename SrcT, typename DestT>
inline bool DecodePartAs(const SrcT*& pSrc, const SrcT* pSrcEnd, DestT*& pDest, DestT* pDestEnd)
{
const SrcT2* pSrc2 = reinterpret_cast<const SrcT2*>(pSrc);
DestT2* pDest2 = reinterpret_cast<DestT2*>(pDest);
const bool result = DecodePart(pSrc2, reinterpret_cast<const SrcT2*>(pSrcEnd), pDest2, reinterpret_cast<DestT2*>(pDestEnd));
pSrc = reinterpret_cast<const SrcT*>(pSrc2);
pDest = reinterpret_cast<DestT*>(pDest2);
return result;
}
}


#if EA_WCHAR_UNIQUE
inline bool DecodePart(const wchar_t*& pSrc, const wchar_t* pSrcEnd, wchar_t*& pDest, wchar_t* pDestEnd)
{
return DecodePart(reinterpret_cast<const char*&>(pSrc), reinterpret_cast<const char*>(pSrcEnd), reinterpret_cast<char*&>(pDest), reinterpret_cast<char*&>(pDestEnd));
return details::DecodePartAs<char, char>(pSrc, pSrcEnd, pDest, pDestEnd);
}

inline bool DecodePart(const wchar_t*& pSrc, const wchar_t* pSrcEnd, char*& pDest, char* pDestEnd)
{
#if (EA_WCHAR_SIZE == 2)
return DecodePart(reinterpret_cast<const char16_t*&>(pSrc), reinterpret_cast<const char16_t*>(pSrcEnd), pDest, pDestEnd);
return details::DecodePartAs<char16_t, char>(pSrc, pSrcEnd, pDest, pDestEnd);
#elif (EA_WCHAR_SIZE == 4)
return DecodePart(reinterpret_cast<const char32_t*&>(pSrc), reinterpret_cast<const char32_t*>(pSrcEnd), pDest, pDestEnd);
return details::DecodePartAs<char32_t, char>(pSrc, pSrcEnd, pDest, pDestEnd);
#endif
}

inline bool DecodePart(const wchar_t*& pSrc, const wchar_t* pSrcEnd, char16_t*& pDest, char16_t* pDestEnd)
{
#if (EA_WCHAR_SIZE == 2)
return DecodePart(reinterpret_cast<const char16_t*&>(pSrc), reinterpret_cast<const char16_t*>(pSrcEnd), pDest, pDestEnd);
return details::DecodePartAs<char16_t, char16_t>(pSrc, pSrcEnd, pDest, pDestEnd);
#elif (EA_WCHAR_SIZE == 4)
return DecodePart(reinterpret_cast<const char32_t*&>(pSrc), reinterpret_cast<const char32_t*>(pSrcEnd), pDest, pDestEnd);
return details::DecodePartAs<char32_t, char16_t>(pSrc, pSrcEnd, pDest, pDestEnd);
#endif
}

inline bool DecodePart(const wchar_t*& pSrc, const wchar_t* pSrcEnd, char32_t*& pDest, char32_t* pDestEnd)
{
#if (EA_WCHAR_SIZE == 2)
return DecodePart(reinterpret_cast<const char16_t*&>(pSrc), reinterpret_cast<const char16_t*>(pSrcEnd), pDest, pDestEnd);
return details::DecodePartAs<char16_t, char32_t>(pSrc, pSrcEnd, pDest, pDestEnd);
#elif (EA_WCHAR_SIZE == 4)
return DecodePart(reinterpret_cast<const char32_t*&>(pSrc), reinterpret_cast<const char32_t*>(pSrcEnd), pDest, pDestEnd);
return details::DecodePartAs<char32_t, char32_t>(pSrc, pSrcEnd, pDest, pDestEnd);
#endif
}

inline bool DecodePart(const char*& pSrc, const char* pSrcEnd, wchar_t*& pDest, wchar_t* pDestEnd)
{
#if (EA_WCHAR_SIZE == 2)
return DecodePart(pSrc, pSrcEnd, reinterpret_cast<char16_t*&>(pDest), reinterpret_cast<char16_t*>(pDestEnd));
return details::DecodePartAs<char, char16_t>(pSrc, pSrcEnd, pDest, pDestEnd);
#elif (EA_WCHAR_SIZE == 4)
return DecodePart(pSrc, pSrcEnd, reinterpret_cast<char32_t*&>(pDest), reinterpret_cast<char32_t*>(pDestEnd));
return details::DecodePartAs<char, char32_t>(pSrc, pSrcEnd, pDest, pDestEnd);
#endif
}

inline bool DecodePart(const char16_t*& pSrc, const char16_t* pSrcEnd, wchar_t*& pDest, wchar_t* pDestEnd)
{
#if (EA_WCHAR_SIZE == 2)
return DecodePart(pSrc, pSrcEnd, reinterpret_cast<char16_t*&>(pDest), reinterpret_cast<char16_t*>(pDestEnd));
return details::DecodePartAs<char16_t, char16_t>(pSrc, pSrcEnd, pDest, pDestEnd);
#elif (EA_WCHAR_SIZE == 4)
return DecodePart(pSrc, pSrcEnd, reinterpret_cast<char32_t*&>(pDest), reinterpret_cast<char32_t*>(pDestEnd));
return details::DecodePartAs<char16_t, char32_t>(pSrc, pSrcEnd, pDest, pDestEnd);
#endif
}

inline bool DecodePart(const char32_t*& pSrc, const char32_t* pSrcEnd, wchar_t*& pDest, wchar_t* pDestEnd)
{
#if (EA_WCHAR_SIZE == 2)
return DecodePart(pSrc, pSrcEnd, reinterpret_cast<char16_t*&>(pDest), reinterpret_cast<char16_t*>(pDestEnd));
return details::DecodePartAs<char32_t, char16_t>(pSrc, pSrcEnd, pDest, pDestEnd);
#elif (EA_WCHAR_SIZE == 4)
return DecodePart(pSrc, pSrcEnd, reinterpret_cast<char32_t*&>(pDest), reinterpret_cast<char32_t*>(pDestEnd));
return details::DecodePartAs<char32_t, char32_t>(pSrc, pSrcEnd, pDest, pDestEnd);
#endif
}
#endif

#if EA_CHAR8_UNIQUE
inline bool DecodePart(const char8_t*& pSrc, const char8_t* pSrcEnd, char8_t*& pDest, char8_t* pDestEnd)
{
return DecodePart(reinterpret_cast<const char*&>(pSrc), reinterpret_cast<const char*>(pSrcEnd), reinterpret_cast<char*&>(pDest), reinterpret_cast<char*&>(pDestEnd));
return details::DecodePartAs<char, char>(pSrc, pSrcEnd, pDest, pDestEnd);
}

inline bool DecodePart(const char8_t*& pSrc, const char8_t* pSrcEnd, char*& pDest, char* pDestEnd)
{
return DecodePart(reinterpret_cast<const char*&>(pSrc), reinterpret_cast<const char*>(pSrcEnd), pDest, pDestEnd);
return details::DecodePartAs<char, char>(pSrc, pSrcEnd, pDest, pDestEnd);
}

inline bool DecodePart(const char8_t*& pSrc, const char8_t* pSrcEnd, char16_t*& pDest, char16_t* pDestEnd)
{
return DecodePart(reinterpret_cast<const char*&>(pSrc), reinterpret_cast<const char*>(pSrcEnd), pDest, pDestEnd);
return details::DecodePartAs<char, char16_t>(pSrc, pSrcEnd, pDest, pDestEnd);
}

inline bool DecodePart(const char8_t*& pSrc, const char8_t* pSrcEnd, char32_t*& pDest, char32_t* pDestEnd)
{
return DecodePart(reinterpret_cast<const char*&>(pSrc), reinterpret_cast<const char*>(pSrcEnd), pDest, pDestEnd);
return details::DecodePartAs<char, char32_t>(pSrc, pSrcEnd, pDest, pDestEnd);
}

inline bool DecodePart(const char*& pSrc, const char* pSrcEnd, char8_t*& pDest, char8_t* pDestEnd)
{
return DecodePart(pSrc, pSrcEnd, reinterpret_cast<char*&>(pDest), reinterpret_cast<char*&>(pDestEnd));
return details::DecodePartAs<char, char>(pSrc, pSrcEnd, pDest, pDestEnd);
}

inline bool DecodePart(const char16_t*& pSrc, const char16_t* pSrcEnd, char8_t*& pDest, char8_t* pDestEnd)
{
return DecodePart(pSrc, pSrcEnd, reinterpret_cast<char*&>(pDest), reinterpret_cast<char*&>(pDestEnd));
return details::DecodePartAs<char16_t, char>(pSrc, pSrcEnd, pDest, pDestEnd);
}

inline bool DecodePart(const char32_t*& pSrc, const char32_t* pSrcEnd, char8_t*& pDest, char8_t* pDestEnd)
{
return DecodePart(pSrc, pSrcEnd, reinterpret_cast<char*&>(pDest), reinterpret_cast<char*&>(pDestEnd));
return details::DecodePartAs<char32_t, char>(pSrc, pSrcEnd, pDest, pDestEnd);
}
#endif

#if EA_CHAR8_UNIQUE && EA_WCHAR_UNIQUE
inline bool DecodePart(const char8_t*& pSrc, const char8_t* pSrcEnd, wchar_t*& pDest, wchar_t* pDestEnd)
{
#if (EA_WCHAR_SIZE == 2)
return DecodePart(pSrc, pSrcEnd, reinterpret_cast<char16_t*&>(pDest), reinterpret_cast<char16_t*>(pDestEnd));
return details::DecodePartAs<char8_t, char16_t>(pSrc, pSrcEnd, pDest, pDestEnd);
#elif (EA_WCHAR_SIZE == 4)
return DecodePart(pSrc, pSrcEnd, reinterpret_cast<char32_t*&>(pDest), reinterpret_cast<char32_t*>(pDestEnd));
return details::DecodePartAs<char8_t, char32_t>(pSrc, pSrcEnd, pDest, pDestEnd);
#endif
}

inline bool DecodePart(const wchar_t*& pSrc, const wchar_t* pSrcEnd, char8_t*& pDest, char8_t* pDestEnd)
{
#if (EA_WCHAR_SIZE == 2)
return DecodePart(reinterpret_cast<const char16_t*&>(pSrc), reinterpret_cast<const char16_t*>(pSrcEnd), reinterpret_cast<char*&>(pDest), reinterpret_cast<char*>(pDestEnd));
return details::DecodePartAs<char16_t, char>(pSrc, pSrcEnd, pDest, pDestEnd);
#elif (EA_WCHAR_SIZE == 4)
return DecodePart(reinterpret_cast<const char32_t*&>(pSrc), reinterpret_cast<const char32_t*>(pSrcEnd), reinterpret_cast<char*&>(pDest), reinterpret_cast<char*>(pDestEnd));
return details::DecodePartAs<char32_t, char>(pSrc, pSrcEnd, pDest, pDestEnd);
#endif
}
#endif
Expand Down Expand Up @@ -249,7 +265,15 @@ namespace eastl

#if defined(EA_COMPILER_CPP17_ENABLED)
// All main compilers offer a constexpr __builtin_memcmp as soon as C++17 was available.
// GCC 10 to 14 report bounds larger than one of the strings, on paths where the callers (for example
// string_view's operator==) have already ruled them out.
#if defined(EA_COMPILER_GNUC) && (EA_COMPILER_VERSION >= 11000)
EA_DISABLE_GCC_WARNING(-Wstringop-overread)
#else
EA_DISABLE_GCC_WARNING(-Wstringop-overflow)
#endif
constexpr int Compare(const char* p1, const char* p2, size_t n) { return __builtin_memcmp(p1, p2, n); }
EA_RESTORE_GCC_WARNING()

#if !defined(EA_COMPILER_GNUC)
// GCC doesn't offer __builtin_wmemcmp.
Expand Down Expand Up @@ -337,12 +361,20 @@ namespace eastl
#endif

// If either pDestination or pSource is an invalid or null pointer, the behavior is undefined, even if (pSourceEnd - pSource) is zero.
// GCC 12 to 15 report reads past short string literals, on string reallocation paths that need a longer
// source; GCC can't tell, because CharStrlen doesn't use __builtin_strlen with GCC.
#if defined(EA_COMPILER_GNUC) && (EA_COMPILER_VERSION >= 11000)
EA_DISABLE_GCC_WARNING(-Wstringop-overread)
#endif
template <typename T>
inline T* CharStringUninitializedCopy(const T* pSource, const T* pSourceEnd, T* pDestination)
{
memmove(pDestination, pSource, (size_t)(pSourceEnd - pSource) * sizeof(T));
return pDestination + (pSourceEnd - pSource);
}
#if defined(EA_COMPILER_GNUC) && (EA_COMPILER_VERSION >= 11000)
EA_RESTORE_GCC_WARNING()
#endif


// CharTypeStringFindEnd
Expand Down
9 changes: 7 additions & 2 deletions include/EASTL/internal/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -676,11 +676,16 @@ namespace eastl
// EASTL_CRASH
//
// Executes an invalid memory write, which should result in an exception
// on most platforms.
// on most platforms. With GCC and clang, it then traps, so the compiler
// knows that EASTL_CRASH() doesn't return.
//
///////////////////////////////////////////////////////////////////////////////

#define EASTL_CRASH() *((volatile int*)0) = 0xDEADC0DE;
#if defined(__GNUC__) || defined(__clang__)
#define EASTL_CRASH() do { *((volatile int*)0) = 0xDEADC0DE; __builtin_trap(); } while (0)
#else
#define EASTL_CRASH() *((volatile int*)0) = 0xDEADC0DE;
#endif


///////////////////////////////////////////////////////////////////////////////
Expand Down
4 changes: 4 additions & 0 deletions include/EASTL/internal/copy_help.h
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,9 @@ namespace eastl
template <bool isMove>
struct move_and_copy_helper<eastl::random_access_iterator_tag, isMove, true>
{
// GCC 10 to 15 report a null destination from vector::insert into an empty vector, on the path for
// ranges that fit in the capacity, which a non-empty range can't take there.
EA_DISABLE_GCC_WARNING(-Wnonnull)
template <typename T>
static T* move_or_copy(const T* first, const T* last, T* result)
{
Expand All @@ -117,6 +120,7 @@ namespace eastl
// We could use memcpy here if there's no range overlap, but memcpy is rarely much faster than memmove.
return (T*)memmove(result, first, (size_t)((uintptr_t)last - (uintptr_t)first)) + (last - first);
}
EA_RESTORE_GCC_WARNING()
};


Expand Down
3 changes: 3 additions & 0 deletions include/EASTL/internal/fixed_pool.h
Original file line number Diff line number Diff line change
Expand Up @@ -1401,11 +1401,14 @@ namespace eastl
return mOverflowAllocator.allocate(n, alignment, offset, flags);
}

// GCC 11 to 15 report that the overflow allocator may free the fixed buffer, which the test rules out.
EA_DISABLE_GCC_WARNING(-Wfree-nonheap-object)
void deallocate(void* p, size_t n)
{
if(p != mpPoolBegin)
mOverflowAllocator.deallocate(p, n); // Can't do this to our own allocation.
}
EA_RESTORE_GCC_WARNING()

const char* get_name() const
{
Expand Down
12 changes: 8 additions & 4 deletions include/EASTL/internal/red_black_tree.h
Original file line number Diff line number Diff line change
Expand Up @@ -1405,7 +1405,8 @@ namespace eastl
// and set is to return a pair and not just an iterator.

node_type* pNodeNew = DoCreateNode(eastl::forward<Args>(args)...); // Note that pNodeNew->mpLeft, mpRight, mpParent, will be uninitialized.
const key_type& key = extract_key{}(pNodeNew->mValue);
extract_key extractKey;
const key_type& key = extractKey(pNodeNew->mValue);

bool canInsert;
rbtree_node_base* pPosition = DoGetKeyInsertionPositionUniqueKeys(canInsert, key);
Expand All @@ -1431,7 +1432,8 @@ namespace eastl
// key, and make DoInsertValueImpl take that node as an argument. That way there is no value created on the stack.

node_type* const pNodeNew = DoCreateNode(eastl::forward<Args>(args)...); // Note that pNodeNew->mpLeft, mpRight, mpParent, will be uninitialized.
const key_type& key = extract_key{}(pNodeNew->mValue);
extract_key extractKey;
const key_type& key = extractKey(pNodeNew->mValue);

rbtree_node_base* pPosition = DoGetKeyInsertionPositionNonuniqueKeys(key);

Expand Down Expand Up @@ -1611,7 +1613,8 @@ namespace eastl
// a forced insertion position for the value when possible.

node_type* pNodeNew = DoCreateNode(eastl::forward<Args>(args)...); // Note that pNodeNew->mpLeft, mpRight, mpParent, will be uninitialized.
const key_type& key(extract_key{}(pNodeNew->mValue));
extract_key extractKey;
const key_type& key(extractKey(pNodeNew->mValue));

bool bForceToLeft;
rbtree_node_base* pPosition = DoGetKeyInsertionPositionUniqueKeysHint(position, bForceToLeft, key);
Expand Down Expand Up @@ -1645,7 +1648,8 @@ namespace eastl
// a forced insertion position for the value when possible.

node_type* pNodeNew = DoCreateNode(eastl::forward<Args>(args)...); // Note that pNodeNew->mpLeft, mpRight, mpParent, will be uninitialized.
const key_type& key(extract_key{}(pNodeNew->mValue));
extract_key extractKey;
const key_type& key(extractKey(pNodeNew->mValue));

bool bForceToLeft;
rbtree_node_base* pPosition = DoGetKeyInsertionPositionNonuniqueKeysHint(position, bForceToLeft, key);
Expand Down
Loading
Loading