/* -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 4 -*- */ /* vi: set ts=4 sw=4 expandtab: (add to ~/.vimrc: set modeline modelines=5) */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "avmplus.h" #include "zlib.h" // Bugzilla 729336 followup: fix project files to add lzma to search path #include "../other-licenses/lzma/LzmaLib.h" #ifndef UINT32_T_MAX #define UINT32_T_MAX (uint32_t(0xFFFFFFFFUL)) #endif // Compiler and architecture dependent byte swapping functions. // // Define HAVE_BYTESWAP32 and/or HAVE_BYTESWAP16 on platforms where there are // instructions or fast idioms for byte swapping backed by compiler intrinsics. // // This is not quite a porting API, because we do not abstract byteSwap16() // and byteSwap32() generally: if fast byte swapping is not available then // it's probably faster to use generic in-line byte load/store code. Hence // the code is all here and not in a platform file. // // Survey of architectures: // // On x86 there is BSWAP (32-bit) and XCHG (16-bit). // On PPC there is supposedly a three-operation sequence that byte swaps 32-bit // data [Warren, Hacker's Delight]. // On MIPS a two-operation sequence of WSBH and ROTR can byte swap a 32-bit word, // WSBH can byte swap a halfword by itself. // On SH4 a combination of SWAP.B (twice) and SWAP.H should byte swap a 32-bit word, // while SWAP.B by itself should byte swap a 16-bit halfword. // On ARM there are the REV (32-bit) and REV16 (16-bit) instructions since ARMv6. // To my knowledge SPARC has no byte swap instructions. // // Survey of compilers: // // MSVC starting with Visual Studio 2005 have _byteswap_ulong, _byteswap_ushort, _byteswap_uint64. // GCC starting with 4.3 have __builtin_bswap32 and __builtin_bswap64, must include #if defined VMCFG_IA32 || defined VMCFG_AMD64 || defined VMCFG_PPC || defined VMCFG_MIPS || defined VMCFG_SH4 || defined VMCFG_ARM #if defined _MSC_VER #define HAVE_BYTESWAP32 #define HAVE_BYTESWAP16 REALLY_INLINE uint32_t byteSwap32(uint32_t x) { return _byteswap_ulong(x); } REALLY_INLINE uint16_t byteSwap16(uint16_t x) { return _byteswap_ushort(x); } #endif // _MSC_VER #if defined __GNUC__ && (__GNUC__ == 4 && __GNUC_MINOR__ >= 3 || __GNUC__ > 4) #include #define HAVE_BYTESWAP32 REALLY_INLINE uint32_t byteSwap32(uint32_t x) { return __builtin_bswap32(x); } #endif // __GNUC__ #endif // Various architectures with byteswap idioms backed by intrinsics // MAX_BYTEARRAY_STORE_LENGTH is the largest value that can be taken on by m_length, // strictly less than 2^32. See constraint in ByteArray::Grower::ReallocBackingStore(). // // MAX_BYTEARRAY_SHORT_ACCESS_LENGTH is a value no smaller than 4095 and no greater // than 2^32-1-MAX_BYTEARRAY_STORE_LENGTH. // // The purpose of these two limits is to allow relatively efficient bounds checking // on common ByteArray accesses, see comments on ByteArray::requestBytesForShortRead() // and ByteArray::requestBytesForShortWrite() for more information. #define MAX_BYTEARRAY_STORE_LENGTH (MMgc::GCHeap::kMaxObjectSize - MMgc::GCHeap::kBlockSize*2) #define MAX_BYTEARRAY_SHORT_ACCESS_LENGTH (MMgc::GCHeap::kBlockSize*2 - 1) namespace avmplus { template REALLY_INLINE T min(T a, T b) { return (a < b) ? a : b; } // Rounds a up to nearest integral multiple of b. // requires: T is integral type. // returns r, where r is least integral multiple of b such that r >= a. // // Bugzilla 699176: lift to shared header or specialize to uint64_t. template REALLY_INLINE T roundUpTo(T a, T b) { return ((a + (b - 1)) / b) * b; } /** * check for overlap in the address ranges [dst,dst+count] and [src,src+count]. * Motivating use case: checking whether it's safe to call memcpy or whether * (the potentially slower) memmove must be used instead. * NB. memcpy is allowed to copy backwards, in big gulps, etc. No overlap is acceptable. * I (matz) wonder if the extra checking done by this method washes out the benefit of calling memcopy? * Prompted by (google)fuzzing bug 3303673. */ inline bool non_overlapping_memcpy_args(const void * dst, const void *src, const uint32_t count) { //replaces: if ((uintptr_t(dst) - uintptr_t(src)) >= uintptr_t(count)) //which was one sided and hence wrong. //pointer difference is naturally long on x86_64, but 32 bit on ia32, // do calculation carefully in pointer sized ints. uintptr_t p_src = uintptr_t(src); uintptr_t p_dst = uintptr_t(dst); //compare the pointers unsigned. //do the subtraction such that result is positive and can't overflow anything. //if the difference is > uint32_t, it's okay, compare to count will be right. if ( p_src >= p_dst ){ return (p_src - p_dst) >= count; }else{ return (p_dst - p_src) >= count; } } // When we might be reading or writing to ourself, use this function // apparently SunPro compiler doesn't like combining REALLY_INLINE with static functions in CPP files /*static*/ REALLY_INLINE void move_or_copy(void* dst, const void* src, uint32_t count) { if ( non_overlapping_memcpy_args(dst,src,count) ) { VMPI_memcpy(dst, src, count); //not overlapping, so can safely use memcpy } else { VMPI_memmove(dst, src, count); //overlap. have to use memmove. } } // // ByteArray // ByteArray::ByteArray(Toplevel* toplevel) : DataIOBase() , DataInput() , DataOutput() , m_toplevel(toplevel) , m_gc(toplevel->core()->GetGC()) , m_subscribers(m_gc, 0) , m_copyOnWriteOwner(NULL) , m_position(0) , m_buffer(mmfx_new(Buffer())) , m_isShareable(false) , m_isLinkWrapper(false) { static_assert(uint64_t(MAX_BYTEARRAY_STORE_LENGTH) < 0x100000000ULL, "Constraint on MAX_BYTEARRAY_STORE_LENGTH"); static_assert(MAX_BYTEARRAY_SHORT_ACCESS_LENGTH >= 4095, "Constraint on MAX_BYTEARRAY_SHORT_ACCESS_LENGTH"); static_assert(uint64_t(MAX_BYTEARRAY_STORE_LENGTH) + uint64_t(MAX_BYTEARRAY_SHORT_ACCESS_LENGTH) < 0x100000000ULL, "Constraints on internal ByteArray constants"); AvmAssert(m_gc != NULL); m_buffer->array = NULL; m_buffer->capacity = 0; m_buffer->length = 0; } ByteArray::ByteArray(Toplevel* toplevel, const ByteArray& other) : DataIOBase() , DataInput() , DataOutput() , m_toplevel(toplevel) , m_gc(toplevel->core()->GetGC()) , m_subscribers(m_gc, 0) , m_copyOnWriteOwner(NULL) , m_position(0) , m_buffer(other.m_isShareable ? other.m_buffer : mmfx_new(Buffer) ) , m_isShareable(other.m_isShareable) , m_isLinkWrapper(false) { AvmAssert(m_gc != NULL); if (m_isShareable) { return; } m_buffer->capacity = other.m_buffer->capacity; m_buffer->length = other.m_buffer->length; // only allocate a new array if the other bytearray has data in it if (other.m_buffer->array) { m_buffer->array = mmfx_new_array_opt(uint8_t, m_buffer->capacity, MMgc::kCanFailAndZero); if (!m_buffer->array) ThrowMemoryError(); TellGcNewBufferMemory(m_buffer->array, m_buffer->capacity); VMPI_memcpy(m_buffer->array, other.m_buffer->array, m_buffer->length); } else { m_buffer->array = NULL; } } ByteArray::ByteArray(Toplevel* toplevel, ByteArray::Buffer* source, bool shareable) : DataIOBase() , DataInput() , DataOutput() , m_toplevel(toplevel) , m_gc(toplevel->core()->GetGC()) , m_subscribers(m_gc, 0) , m_copyOnWriteOwner(NULL) , m_position(0) , m_buffer(source) , m_isShareable(shareable) , m_isLinkWrapper(false) { // Note that this constructor is only used when receiving a ByteArray from another worker if (!m_isShareable) { // If we made a copy of the ByteArray (i.e. it's not sharable), then we should account for the memory that we're about to receive. // (until now, it's been sitting in the message queue, not assigned to any GC) TellGcNewBufferMemory(m_buffer->array, m_buffer->capacity); } } /* virtual */ void ByteArray::Buffer::destroy() { if (array) { AvmAssert(capacity > 0); // FIXME: the lack of the following appears to be accidentally correct, because // for normal uses array will be NULL in this->destroy() // and the Isolate use doesn't signal dependent allocation //m_gc->SignalDependentDeallocation(capacity, MMgc::typeByteArray); mmfx_delete_array(array); } mmfx_delete(this); } /* virtual */ ByteArray::Buffer::~Buffer() { } ByteArray::~ByteArray() { #if defined(VMCFG_TELEMETRY_SAMPLER) && defined(DEBUGGER) if (m_gc->GetAttachedSampler()) { ((IMemorySampler *)m_gc->GetAttachedSampler())->recordDeallocation(this, 0); } #endif if (!m_isShareable) { // no: this can reallocate memory, which is bad to do in a dtor // m_subscribers.clear(); // if this is the only ByteArray referencing this buffer // then it is okay to clear it. if (m_buffer->RefCount() == 1) { UnprotectedClear(); } } else { m_copyOnWriteOwner = NULL; // rely on refcounting, i.e., m_buffer->destroy() } m_buffer = NULL; } void ByteArray::UnprotectedClear() { if (m_buffer->array && !IsCopyOnWrite()) { AvmAssert(m_buffer->capacity > 0); // Note that TellGcXXX always expects capacity, not (logical) length. TellGcDeleteBufferMemory(m_buffer->array, m_buffer->capacity); mmfx_delete_array(m_buffer->array); } m_buffer->array = NULL; m_buffer->capacity = 0; m_buffer->length = 0; #if defined(VMCFG_TELEMETRY_SAMPLER) && defined(DEBUGGER) if (m_gc->GetAttachedSampler()) { ((IMemorySampler *)m_gc->GetAttachedSampler())->recordObjectReallocation(this); } #endif m_copyOnWriteOwner = NULL; } void ByteArray::SetCopyOnWriteOwner(MMgc::GCObject* owner) { if (owner != NULL && m_gc->IsPointerToGCPage(this)) { WB(m_gc, m_gc->FindBeginningFast(this), &m_copyOnWriteOwner, owner); } else { m_copyOnWriteOwner = owner; } } void ByteArray::SetCopyOnWriteData(MMgc::GCObject* owner, const uint8_t* data, uint32_t length) { Clear(); m_buffer->array = const_cast(data); m_buffer->capacity = length; m_buffer->length = length; // we must have a non-null value for m_copyOnWriteOwner, as we // use it as an implicit boolean as well, so if none is provided, // cheat and use m_gc->emptyWeakRef if (owner == NULL) owner = (MMgc::GCObject*)m_gc->emptyWeakRef; SetCopyOnWriteOwner(owner); } REALLY_INLINE bool ByteArray::Grower::RequestWillReallocBackingStore() const { return m_minimumCapacity > m_owner->m_buffer->capacity || m_owner->IsCopyOnWrite(); } REALLY_INLINE bool ByteArray::Grower::RequestExceedsMemoryAvailable() const { return m_minimumCapacity > (MMgc::GCHeap::kMaxObjectSize - MMgc::GCHeap::kBlockSize*2); } void FASTCALL ByteArray::Grower::EnsureWritableCapacity() { if (RequestExceedsMemoryAvailable()) m_owner->ThrowMemoryError(); if (RequestWillReallocBackingStore()) { uint32_t newCapacity = m_owner->m_buffer->capacity << 1; if (newCapacity < m_minimumCapacity) newCapacity = m_minimumCapacity; if (newCapacity < kGrowthIncr) newCapacity = kGrowthIncr; ReallocBackingStore(newCapacity); } } void FASTCALL ByteArray::Grower::ReallocBackingStore(uint32_t newCapacity) { // The extra check on maximum size is necessary because // mmfx_new_array_opt doesn't return NULL but instead Aborts // when the size approaches 2^32. We want to be consistent // across the entire uint32_t range and throw a memory error // instead. The subtraction of two blocks has to do with how // FixedMalloc::LargeAlloc does this check. // // The length limitation is however useful for other purposes. // Observe that the length limit is duplicated above in the // definition of MAX_BYTEARRAY_STORE_LENGTH and that there is // code below in ByteArray::requestBytesForShortRead() and // ByteArray::requestBytesForShortWrite() that depends on the // limit on m_length, see comments there and also comments on // MAX_BYTEARRAY_STORE_LENGTH. static_assert(MAX_BYTEARRAY_STORE_LENGTH == (MMgc::GCHeap::kMaxObjectSize - MMgc::GCHeap::kBlockSize*2), "Constraint on maximum ByteArray storage size"); if (newCapacity > (MMgc::GCHeap::kMaxObjectSize - MMgc::GCHeap::kBlockSize*2)) m_owner->ThrowMemoryError(); // Bugzilla 699176, 763519: EnsureWritableCapacity passes a // newCapacity that is strictly larger than our existing one, // but Grower::SetLengthCommon, when called from length // setter, can pass a newCapacity that is smaller (by design). // Therefore, eagerly return here only when the old and new // capacities are equal (or if the owner is copy-on-write). if (!(newCapacity != m_owner->m_buffer->capacity || m_owner->IsCopyOnWrite())) { return; } if (newCapacity < m_minimumCapacity) newCapacity = m_minimumCapacity; if (newCapacity < kGrowthIncr) newCapacity = kGrowthIncr; m_oldArray = m_owner->m_buffer->array; m_oldLength = m_owner->m_buffer->length; m_oldCapacity = m_owner->m_buffer->capacity; uint8_t* newArray = mmfx_new_array_opt(uint8_t, newCapacity, MMgc::kCanFail); if (!newArray) m_owner->ThrowMemoryError(); // Note that TellGcXXX always expects capacity, not (logical) length. m_owner->TellGcNewBufferMemory(newArray, newCapacity); if (m_oldArray) { VMPI_memcpy(newArray, m_oldArray, min(newCapacity, m_oldLength)); if (newCapacity > m_oldLength) VMPI_memset(newArray+m_oldLength, 0, newCapacity-m_oldLength); } else { VMPI_memset(newArray, 0, newCapacity); } m_owner->m_buffer->array = newArray; m_owner->m_buffer->capacity = newCapacity; if (m_owner->m_copyOnWriteOwner != NULL) { m_owner->m_copyOnWriteOwner = NULL; // Set this to NULL so we don't attempt to delete it in our dtor. m_oldArray = NULL; } #if defined(VMCFG_TELEMETRY_SAMPLER) && defined(DEBUGGER) if (m_owner->m_gc->GetAttachedSampler()) { ((IMemorySampler *)m_owner->m_gc->GetAttachedSampler())->recordObjectReallocation(m_owner); } #endif } /* Why the "Grower" class? (1) It provides a clean way to defer discarding the old buffer until the end of the calling function; this matters in the case of Write(), as it's legal to call Write() on your own buffer, and so if growth occurs, you must not discard the old buffer until copying takes place. (2) It avoid redundant calls to NotifySubscribers(); previously we'd call once when a reallocation occurred, then again when the length field changed. (3) It streamlines copy-on-write handling; formerly we either did redundant CopyOnWrite alloc-and-copy followed by a Grow alloc-and-copy, or an if-else clause with redundant alloc-and-copy. Of course, the dtor will be skipped (and thus notify and deletes skipped) if a longjmp over it, but the old code was subject to the same defects, so this is not a new liability. */ ByteArray::Grower::~Grower() { if (m_oldArray != m_owner->m_buffer->array || m_oldLength != m_owner->m_buffer->length) { m_owner->NotifySubscribers(); } // m_oldArray could be NULL if we grew a copy-on-write ByteArray. if (m_oldArray != NULL && m_oldArray != m_owner->m_buffer->array) { // Note that TellGcXXX always expects capacity, not (logical) length. m_owner->TellGcDeleteBufferMemory(m_oldArray, m_oldCapacity); mmfx_delete_array(m_oldArray); } } // // The following code is added to support sharing byte array data between // two Isolates. Domain memory is affected by changes to shared byte arrays // and must be notified within a safepoint when a backing store has been // changed. Every attempt was made to avoid a safepoint when not // needed. // // Assumptions: // - Acquiring a safepoint is more expensive than 2-3 function calls and // several if statements. // - A byte array is only considered shared when its buffer has more than // a single reference to it and the only way to get an additional reference // is to share a byte array between isolates, using setSharedProperty or // a similar mechanism. // // Invariants: // - all safepoint operations must happen using a SafepointTask object // - a safepoint cannot be recursively entered // - a safepoint cannot be long jumped out of; any exception must be // caught and not allowed to escape the safepoint. // - if the current operation is not in a safepoint no operations can be // in a safepoint task. // - only operations that change the size of a byte array's backing // store may require a safepoint // - only backing stores that are shared between isolates may require // a safepoint. // // // This is the base class for all ByteArray tasks that *may* need to run // in a safepoint. the exec method makes the determination of requiring // safepoint based on if the associated byte array is local or shared. // class ByteArrayTask: public vmbase::SafepointTask { public: ByteArrayTask(ByteArray* ba) : m_byteArray(ba) , m_core(ba->m_toplevel->core()) , m_exception(NULL) {} void exec() { Isolate* isolate = m_core->getIsolate(); isolate->getAggregate()->runSafepointTaskHoldingIsolateMapLock(this); if (m_exception) { m_core->throwException(m_exception); } } protected: ByteArray* m_byteArray; AvmCore* m_core; Exception* m_exception; }; // // ensures that we change the length of the byte array within a safepoint // when required. if the new length will not cause a resize or if the // byte array isn't shared then no safepoint // class ByteArraySetLengthTask: public ByteArrayTask { public: ByteArraySetLengthTask(ByteArray* ba, uint32_t newLength, bool calledFromLengthSetter) : ByteArrayTask(ba) , m_newLength(newLength) , m_calledFromLengthSetter(calledFromLengthSetter) { } void run() { // safepoints cannot survive exceptions TRY(m_core, kCatchAction_Rethrow) { m_byteArray->UnprotectedSetLengthCommon(m_newLength, m_calledFromLengthSetter); } CATCH(Exception* e) { m_exception = e; } END_CATCH; END_TRY; } private: uint32_t m_newLength; bool m_calledFromLengthSetter; }; // // this task ensures that the compare and swap operation is atomic // if the backing store will be updated when the length is set this // task ensures for shared byte arrays that it will be performed // in a safepoint. // class ByteArrayCompareAndSwapLengthTask: public ByteArrayTask { public: ByteArrayCompareAndSwapLengthTask(ByteArray* ba, uint32_t expectedLength, uint32_t newLength) : ByteArrayTask(ba) , result(0) , m_expectedLength(expectedLength) , m_newLength(newLength) { } void run() { // safepoints cannot survive exceptions TRY(m_core, kCatchAction_Rethrow) { // we are in a safepoint or no other threads can possibly // be referencing this byte array result = m_byteArray->UnprotectedAtomicCompareAndSwapLength(m_expectedLength, m_newLength); } CATCH(Exception* e) { m_exception = e; } END_CATCH; END_TRY; } uint32_t result; private: uint32_t m_expectedLength; uint32_t m_newLength; }; // // this task ensures that the clear operation will be performed // in a safepoint. // class ByteArrayClearTask: public ByteArrayTask { public: ByteArrayClearTask(ByteArray* ba) : ByteArrayTask(ba) { } void run() { // safepoints cannot survive exceptions TRY(m_core, kCatchAction_Rethrow) { m_byteArray->UnprotectedClear(); } CATCH(Exception* e) { m_exception = e; } END_CATCH; END_TRY; } }; // // this task is used for lzma compression operations on // shared byte arrays *only*. // it allows the very expensive lzma compression algorithm // to run on a temporary buffer outside of a safepoint // and then to swap the temp/scratch buffer in a safepoint // class ByteArraySwapBufferTask: public ByteArrayTask { public: ByteArraySwapBufferTask(ByteArray* ba, ByteArray::Buffer* dest) : ByteArrayTask(ba) , m_destination(dest) {} void run() { ByteArray::Buffer* src = m_byteArray->getUnderlyingBuffer(); AvmAssert((src != NULL) && (m_destination != NULL)); m_destination->array = src->array; m_destination->capacity = src->capacity; m_destination->length = src->length; // we don't want to swap the sources array // value we just want to set it to null // as the compression method will notify // the GC and delete it after the safepoint // has completed src->array = NULL; m_byteArray->m_buffer = m_destination; // notify all interested parties ByteArray::UpdateSubscribers(); } private: FixedHeapRef m_destination; }; void ByteArray::Clear() { if (m_subscribers.length() > 0) { AvmAssert(false); // shouldn't get here? m_toplevel->throwRangeError(kInvalidRangeError); } if (IsShared()) { ByteArrayClearTask task(this); task.run(); } else { UnprotectedClear(); } } uint8_t* FASTCALL ByteArray::GetWritableBuffer() { // setlength is always called before using this // we are asserting that to remain the case return m_buffer->array; } uint8_t& ByteArray::operator[](uint32_t index) { if (index >= m_buffer->length) { // Suppose index=2^32-1. Then: // // - on 32-bit systems m_array[index] will reference the byte preceding // the array in memory, which should not be read or written, and // // - on 64-bit systems m_array[index] will reference the byte at offset // 2^31-1 from the start of m_array, most likely crashing the program // on access because it's not allocated. // // In both it sets the length to 0, which is not expected. // // The choice of MemoryError here is not arbitrary: this is the error // we would get by trying to grow the array to accomodate 2^32 bytes, // and indeed if index == 2^32-2 then SetLength will throw that error // because the max array size is 2^32-8K, see EnsureWritableCapacity(). // // Observe that ByteArrayObject::setUintProperty() does not range check. // Hence we check here and thereby cover all other callers too. // // (ByteArrayObject::getUintProperty() calls uint8_t operator[], not // uint8_t& operator[]. The former range checks properly.) if (index == UINT32_T_MAX) ThrowMemoryError(); SetLength(index + 1); } return m_buffer->array[index]; } // Set the length to x+y, but check for overflow. // // MemoryError is the error thrown by SetLength(index), or really by // ByteArray::Grower::ReallocBackingStore(), if we try to create a // buffer larger than the buffer limit, so it's the most consistent // error to throw here. // // We don't know anything about x and y so 64-bit math is the most // expedient solution. SetLength() is not hot in any case, it's always // guarded by some other check. void FASTCALL ByteArray::SetLength(uint32_t x, uint32_t y) { uint64_t sum = uint64_t(x) + uint64_t(y); if (sum >= 0x100000000ULL) ThrowMemoryError(); SetLength(uint32_t(sum)); } // Bugzilla 699176: This threshold controls whether we switch to // large-size handling. The chosen increment (24 MB) was // suggested by Scott Petersen based on experiments with policy in // Alchemy; the chosen threshold (also 24 MB) was assumed to be an // appropriate value, but it would be worthwhile to check that // assumption when time permits. static const uint32_t kHugeGrowthThreshold = 24*1024*1024; static const uint32_t kHugeGrowthIncr = 24*1024*1024; void ByteArray::SetLengthCommon(uint32_t newLength, bool calledFromLengthSetter) { if (IsShared()) { ByteArraySetLengthTask task(this, newLength, calledFromLengthSetter); task.exec(); } else { UnprotectedSetLengthCommon(newLength, calledFromLengthSetter); } } void ByteArray::UnprotectedSetLengthCommon(uint32_t newLength, bool calledFromLengthSetter) { if (m_subscribers.length() > 0 && newLength < DomainEnv::GLOBAL_MEMORY_MIN_SIZE) { m_toplevel->throwRangeError(kInvalidRangeError); } Grower grower(this, newLength); grower.SetLengthCommon(newLength, calledFromLengthSetter); } void ByteArray::Grower::SetLengthCommon(uint32_t newLength, bool calledFromLengthSetter) { if (!calledFromLengthSetter || (newLength < kHugeGrowthThreshold && m_owner->m_buffer->length < kHugeGrowthThreshold)) { if (newLength > m_owner->m_buffer->capacity) { EnsureWritableCapacity(); } } else { // Bugzilla 699176: Setting the length is different than other // expanding/contracting operations; it represents the client // saying exactly how much (or how little) storage they // anticipate using. // // Thus, when we are (or will be) at a large size: // 1. Call ReallocBackingStore() directly and unconditionally, // rather than conditionally via EnsureWritableCapacity(). // 2. Round capacity up to multiple of kHugeGrowthIncr to // avoid some pathoglogical cases. // (overflow paranoia; see notes above SetLength(uint32_t, uint32_t). uint64_t newCapRoundedUp = roundUpTo(uint64_t(newLength), uint64_t(kHugeGrowthIncr)); uint32_t newCap = ((newCapRoundedUp <= MAX_BYTEARRAY_STORE_LENGTH) ? uint32_t(newCapRoundedUp) : newLength); AvmAssert(newCap >= newLength); if (newCap != m_owner->m_buffer->capacity) { ReallocBackingStore(newCap); } } m_owner->m_buffer->length = newLength; if (m_owner->m_position > newLength) { m_owner->m_position = newLength; } // notify all subscribers to this byte array data if it is being // shared between workers ByteArray::UpdateSubscribers(); } void FASTCALL ByteArray::SetLength(uint32_t newLength) { SetLengthCommon(newLength, false); } void FASTCALL ByteArray::SetLengthFromAS3(uint32_t newLength) { SetLengthCommon(newLength, true); } void ByteArray::Read(void* buffer, uint32_t count) { CheckEOF(count); move_or_copy(buffer, m_buffer->array + m_position, count); m_position += count; } /*static*/ void ByteArray::UpdateSubscribers() { // anytime the length of the bytearray is updated notify all workers // regardless of if the backing store is reallocated or not. if (vmbase::SafepointRecord::hasCurrent()) { if (vmbase::SafepointRecord::current()->manager()->inSafepointTask()) { AvmCore::getActiveCore()->getIsolate()->getAggregate()->reloadGlobalMemories(); } } } void ByteArray::Write(const void* buffer, uint32_t count) { // Though m_length is limited to 2^32-8K by EnsureWritableCapacity() above, m_position is // not similarly limited - it can be set beyond length and can be as large as 2^32-1. // No callers to Write() check that m_position + count does not overflow, so we // check that here. There's a question of whether MemoryError or RangeError is the "best" // error to throw; ReadByteArray throws RangeError in the similar circumstance but as // EnsureWritableCapacity() always throws MemoryError, we choose that here. if (count > UINT32_T_MAX - m_position) // Do not rearrange, guards against 64-bit overflow ThrowMemoryError(); uint32_t writeEnd = m_position + count; Grower grower(this, writeEnd); grower.EnsureWritableCapacity(); move_or_copy(m_buffer->array + m_position, buffer, count); m_position += count; if (m_buffer->length < m_position) m_buffer->length = m_position; } void ByteArray::EnsureCapacity(uint32_t capacity) { Grower grower(this, capacity); grower.EnsureWritableCapacity(); } NO_INLINE void ByteArray::EnsureCapacityNoInline(uint32_t capacity) { this->EnsureCapacity(capacity); } bool ByteArray::EnsureCapacityOrFail(uint32_t newCap, enum CatchAction catch_action, Exception **exn_recv) { // Argh, even when refactored like so, Android NDK r6b // compiler (in darwin-x86/arm-linux-androideabi/bin/g++, aka // GCC 4.4.3, at -O3 optimization level) continues to issue // warnings (promoted to errors by -Werror) of the form: // // error: argument 'this' might be clobbered by 'longjmp' or 'vfork' // error: argument 'newCap' might be clobbered by 'longjmp' or 'vfork' // // A quick survey hints that this is a GNU compiler bug. The // main work-arounds Felix has identified are to either: // * put 'this' and 'newCap' into volatile locals, or // * keep the call inside the TRY body from being inlined. TRY(m_toplevel->core(), catch_action) { EnsureCapacityNoInline(newCap); return true; } CATCH(Exception *exn) { *exn_recv = exn; return false; } END_CATCH END_TRY } void ByteArray::NotifySubscribers() { for (uint32_t i = 0, n = m_subscribers.length(); i < n; ++i) { AvmAssert(m_buffer->length >= DomainEnv::GLOBAL_MEMORY_MIN_SIZE); DomainEnv* subscriber = m_subscribers.get(i); if (subscriber) { subscriber->notifyGlobalMemoryChanged(m_buffer->array, m_buffer->length); } else { // Domain went away? remove link m_subscribers.removeAt(i); --i; } } } bool ByteArray::addSubscriber(DomainEnv* subscriber) { if (m_buffer->length >= DomainEnv::GLOBAL_MEMORY_MIN_SIZE) { removeSubscriber(subscriber); m_subscribers.add(subscriber); // notify the new "subscriber" of the current state of the world subscriber->notifyGlobalMemoryChanged(m_buffer->array, m_buffer->length); return true; } return false; } bool ByteArray::removeSubscriber(DomainEnv* subscriber) { for (uint32_t i = 0, n = m_subscribers.length(); i < n; ++i) { if (m_subscribers.get(i) == subscriber) { m_subscribers.removeAt(i); return true; } } return false; } #ifdef DEBUGGER uint64_t ByteArray::bytesUsed() const { // If m_copyOnWrite is set, then we don't own the buffer, so the profiler // should not attribute it to us. return IsCopyOnWrite() ? 0 : m_buffer ? m_buffer->capacity : 0; } #endif void ByteArray::TellGcNewBufferMemory(const uint8_t* buf, uint32_t numberOfBytes) { if (buf && numberOfBytes > 0) { m_gc->SignalDependentAllocation(numberOfBytes, MMgc::typeByteArray); } } void ByteArray::TellGcDeleteBufferMemory(const uint8_t* buf, uint32_t numberOfBytes) { // Note that we can't rely on using m_toplevel->core()->GetGC(); // order of destruction is unspecified, and if this is called at destruction // time, Toplevel might have been destroyed before us. So keep a separate GC*. if (buf && numberOfBytes > 0) { m_gc->SignalDependentDeallocation(numberOfBytes, MMgc::typeByteArray); } } void ByteArray::Compress(CompressionAlgorithm algorithm) { switch (algorithm) { case k_lzma: CompressViaLzma(); break; case k_zlib: default: CompressViaZlibVariant(algorithm); break; } #if defined(VMCFG_TELEMETRY_SAMPLER) && defined(DEBUGGER) if (m_gc->GetAttachedSampler()) { ((IMemorySampler *)m_gc->GetAttachedSampler())->recordObjectReallocation(this); } #endif } // lzma-compressed data format: // 5 bytes: LZMA properties // 8 bytes: uncompressed size k (little-endian) // k bytes: payload (the compressed data) static const uint32_t lzmaHeaderSize = LZMA_PROPS_SIZE + 8; struct lzma_compressed { uint8_t lzmaProps[LZMA_PROPS_SIZE]; // our max byte array length is 4 bytes but size in lzma // header is 8 bytes; we reflect that here by splitting the // two (and leaving the high bits at zero) uint8_t unpackedSize[4]; uint8_t unpackedSizeHighBits[4]; // (not uint32_t; that injects padding) uint8_t compressedPayload[1]; // payload is variable sized. }; REALLY_INLINE uint32_t umax(uint32_t a, uint32_t b) { return a > b ? a : b; } void ByteArray::CompressViaLzma() { // Snarf the data and give ourself some empty data // (remember, existing data might be copy-on-write so don't dance on it) uint8_t* origData = m_buffer->array; uint32_t origLen = m_buffer->length; uint32_t origCap = m_buffer->capacity; uint32_t origPos = m_position; MMgc::GCObject* origCopyOnWriteOwner = m_copyOnWriteOwner; if (!origLen) // empty buffer should give empty result return; // we need to create a new scratch buffer that we will // swap with previous one when the compression operation is // completed, so that we don't disturb any other workers // that are referencing this one. // This is done to avoid a long safepoint task as all other // workers must be halted during a safepoint. const bool cShared = IsShared(); // ByteArray's sharedness is immutable implicitly for the duration of this op since this worker is doing this op and cannot share it and it is not already shared (e.g. placed in a MessaegChannel). FixedHeapRef origBuffer = m_buffer; if (cShared) { m_buffer = mmfx_new(Buffer()); } m_buffer->array = NULL; m_buffer->length = 0; m_buffer->capacity = 0; m_position = 0; m_copyOnWriteOwner = NULL; // Unlike zlib, lzma does not provide a method for computing // any upper bound on "compressed" size. So we guess, and // retry if the guess was too aggressive. The retry is only // needed if the data was incompressible; we compress anyhow // for compatibility with the lzma v1 format (the v1 headers // do not have a way to flag an uncompressed payload). uint32_t newCap = umax(origCap, lzmaHeaderSize); size_t lzmaPropsSize = LZMA_PROPS_SIZE; int retcode; struct lzma_compressed *destOverlay; size_t destLen; retry_compress: Exception *exn; bool ensured = EnsureCapacityOrFail(newCap, kCatchAction_Rethrow, &exn); if (!ensured) { // clean up when the EnsureCapacity call fails. if (cShared) { m_buffer = origBuffer; } m_buffer->array = origData; m_buffer->length = origLen; m_buffer->capacity = origCap; m_position = origPos; SetCopyOnWriteOwner(origCopyOnWriteOwner); m_toplevel->core()->throwException(exn); } destOverlay = (struct lzma_compressed*) m_buffer->array; destLen = m_buffer->capacity - lzmaHeaderSize; // if this bytearray's data is being shared then we have to // snap shot it before we run any compression algorithm otherwise // the data could be changed in a way that may cause an exploit uint8_t* dataSnapshot = origData; if (cShared) { dataSnapshot = mmfx_new_array(uint8_t, origLen); VMPI_memcpy(dataSnapshot, origData, origLen); } retcode = LzmaCompress(destOverlay->compressedPayload, &destLen, dataSnapshot, origLen, destOverlay->lzmaProps, &lzmaPropsSize, 9, // -1 would yield default level (5), 1<<20, // 0 would yield default dictSize (1<<24) -1, // default lc (3), -1, // default lp (0), -1, // default pb (2), -1, // default fb (32), 1); // -1 would yield default numThreads (2) if (cShared) { mmfx_delete_array(dataSnapshot); } switch (retcode) { case SZ_OK: if (destLen > (m_buffer->capacity - lzmaHeaderSize)) { AvmAssertMsg(false, "LZMA broke its contract."); // Belt-and-suspenders: If control gets here, // something is terribly wrong, and LZMA is lying to // us. Rather than risk establishing a bogus structure, // fail as if lzma returned an error code. goto error_cases; } destOverlay->unpackedSize[0] = (uint8_t)((origLen) & 0xFF); destOverlay->unpackedSize[1] = (uint8_t)((origLen >> 8) & 0xFF); destOverlay->unpackedSize[2] = (uint8_t)((origLen >> 16) & 0xFF); destOverlay->unpackedSize[3] = (uint8_t)((origLen >> 24) & 0xFF); AvmAssert(destOverlay->unpackedSizeHighBits[0] == 0 && destOverlay->unpackedSizeHighBits[1] == 0 && destOverlay->unpackedSizeHighBits[2] == 0 && destOverlay->unpackedSizeHighBits[3] == 0); m_buffer->length = uint32_t(lzmaHeaderSize + destLen); break; case SZ_ERROR_OUTPUT_EOF: // Our guessed target length was not conservative enough. // Since this is a compression algorithm, go with linear // growth on failure (rather than e.g. exponential). newCap += origCap; goto retry_compress; case SZ_ERROR_MEM: case SZ_ERROR_PARAM: case SZ_ERROR_THREAD: default: error_cases: if (cShared) { m_buffer = origBuffer; } // On other failures, just give up. // Even though we set length to 0 (effectively clearing // the state), we set array back to origData so that its // memory will be properly managed. m_buffer->array = origData; m_buffer->length = 0; m_buffer->capacity = origCap; break; } // Analogous to zlib, maintain policy that Compress() sets // position == length (while Uncompress() sets position == 0). m_position = m_buffer->length; if (cShared) { ByteArraySwapBufferTask task(this, origBuffer); task.exec(); } if (origData && origData != m_buffer->array && origCopyOnWriteOwner == NULL) { // Note that TellGcXXX always expects capacity, not (logical) length. TellGcDeleteBufferMemory(origData, origCap); mmfx_delete_array(origData); } } void ByteArray::CompressViaZlibVariant(CompressionAlgorithm algorithm) { // Snarf the data and give ourself some empty data // (remember, existing data might be copy-on-write so don't dance on it) uint8_t* origData = m_buffer->array; uint32_t origLen = m_buffer->length; uint32_t origCap = m_buffer->capacity; uint32_t origPos = m_position; MMgc::GCObject* origCopyOnWriteOwner = m_copyOnWriteOwner; if (!origLen) // empty buffer should give empty result return; const bool cShared = IsShared(); // ByteArray's sharedness is immutable implicitly for the duration of this op since this worker is doing this op and cannot share it and it is not already shared (e.g. placed in a MessaegChannel). // if this bytearray's data is being shared then we have to // snap shot it before we run any compression algorithm otherwise // the data could be changed in a way that may cause an exploit uint8_t* dataSnapshot = origData; FixedHeapRef origBuffer = m_buffer; if (cShared) { dataSnapshot = mmfx_new_array(uint8_t, origLen); VMPI_memcpy(dataSnapshot, origData, origLen); m_buffer = mmfx_new(Buffer()); } m_buffer->array = NULL; m_buffer->length = 0; m_buffer->capacity = 0; m_position = 0; m_copyOnWriteOwner = NULL; int error = Z_OK; // Use zlib to compress the data. This next block is essentially the // implementation of the compress2() method, but modified to pass a // negative window value (-15) to deflateInit2() for k_deflate mode // in order to obtain deflate-only compression (no ZLib headers). const int MAX_WINDOW_RAW_DEFLATE = -15; const int DEFAULT_MEMORY_USE = 8; z_stream stream; VMPI_memset(&stream, 0, sizeof(stream)); error = deflateInit2(&stream, Z_BEST_COMPRESSION, Z_DEFLATED, algorithm == k_zlib ? MAX_WBITS : MAX_WINDOW_RAW_DEFLATE, DEFAULT_MEMORY_USE, Z_DEFAULT_STRATEGY); AvmAssert(error == Z_OK); uint32_t newCap = deflateBound(&stream, origLen); Exception *exn; bool ensured = EnsureCapacityOrFail(newCap, kCatchAction_Rethrow, &exn); if (!ensured) { // clean up when the EnsureCapacity call fails. if (cShared) { m_buffer = origBuffer; mmfx_delete_array(dataSnapshot); } else { m_buffer->array = origData; m_buffer->length = origLen; m_buffer->capacity = origCap; m_position = origPos; SetCopyOnWriteOwner(origCopyOnWriteOwner); } m_toplevel->core()->throwException(exn); } stream.next_in = dataSnapshot; stream.avail_in = origLen; stream.next_out = m_buffer->array; stream.avail_out = m_buffer->capacity; error = deflate(&stream, Z_FINISH); AvmAssert(error == Z_STREAM_END); m_buffer->length = stream.total_out; AvmAssert(m_buffer->length <= m_buffer->capacity); // Note that Compress() has always ended with position == length, // but Uncompress() has always ended with position == 0. // Weird, but we must maintain it. m_position = m_buffer->length; deflateEnd(&stream); if (cShared) { mmfx_delete_array(dataSnapshot); ByteArraySwapBufferTask task(this, origBuffer); task.exec(); } // Note: the Compress() method has never reported an error for corrupted data, // so we won't start now. (Doing so would probably require a version check, // to avoid breaking content that relies on misbehavior.) if (origData && origData != m_buffer->array && origCopyOnWriteOwner == NULL) { // Note that TellGcXXX always expects capacity, not (logical) length. TellGcDeleteBufferMemory(origData, origCap); mmfx_delete_array(origData); } } void ByteArray::Uncompress(CompressionAlgorithm algorithm) { switch (algorithm) { case k_lzma: UncompressViaLzma(); break; case k_zlib: default: UncompressViaZlibVariant(algorithm); break; } #if defined(VMCFG_TELEMETRY_SAMPLER) && defined(DEBUGGER) if (m_gc->GetAttachedSampler()) { ((IMemorySampler *)m_gc->GetAttachedSampler())->recordObjectReallocation(this); } #endif } void ByteArray::UncompressViaLzma() { // Snarf the data and give ourself some empty data // (remember, existing data might be copy-on-write so don't dance on it) uint8_t* origData = m_buffer->array; uint32_t origCap = m_buffer->capacity; uint32_t origLen = m_buffer->length; uint32_t origPos = m_position; MMgc::GCObject* origCopyOnWriteOwner = m_copyOnWriteOwner; if (!origLen) // empty buffer should give empty result return; if (!m_buffer->array || m_buffer->length < lzmaHeaderSize) return; // if this bytearray's data is being shared then we have to // snap shot it before we run any compression algorithm otherwise // the data could be changed in a way that may cause an exploit uint8_t* dataSnapshot = origData; const bool cShared = IsShared(); // ByteArray's sharedness is immutable implicitly for the duration of this op since this worker is doing this op and cannot share it and it is not already shared (e.g. placed in a MessaegChannel). if (cShared) { dataSnapshot = mmfx_new_array(uint8_t, origLen); VMPI_memcpy(dataSnapshot, origData, origLen); } struct lzma_compressed *srcOverlay; srcOverlay = (struct lzma_compressed*)dataSnapshot; uint32_t unpackedLen; unpackedLen = (uint32_t)srcOverlay->unpackedSize[0]; unpackedLen += (uint32_t)srcOverlay->unpackedSize[1] << 8; unpackedLen += (uint32_t)srcOverlay->unpackedSize[2] << 16; unpackedLen += (uint32_t)srcOverlay->unpackedSize[3] << 24; // check that size is reasonable before modifying internal structure. if (srcOverlay->unpackedSizeHighBits[0] != 0 || srcOverlay->unpackedSizeHighBits[1] != 0 || srcOverlay->unpackedSizeHighBits[2] != 0 || srcOverlay->unpackedSizeHighBits[3] != 0) { if (cShared) { mmfx_delete_array(dataSnapshot); } // We can't allocate a byte array of such large size. ThrowMemoryError(); } size_t srcLen = (origLen - lzmaHeaderSize); FixedHeapRef origBuffer = m_buffer; if (cShared) { m_buffer = mmfx_new(Buffer()); } m_buffer->array = NULL; m_buffer->length = 0; m_buffer->capacity = 0; m_position = 0; m_copyOnWriteOwner = NULL; // Since we rely on unpackedLen being correct, we do not need // to loop with different trial lengths; either it works on // first try, or it will always fail. Exception *exn; bool ensured = EnsureCapacityOrFail(unpackedLen, kCatchAction_Rethrow, &exn); if (!ensured) { // clean up when the EnsureCapacity call fails. if (cShared) { m_buffer = origBuffer; mmfx_delete_array(dataSnapshot); } // (keep in sync with state restoration in error_cases: labelled below) m_buffer->array = origData; m_buffer->length = origLen; m_buffer->capacity = origCap; m_position = origPos; SetCopyOnWriteOwner(origCopyOnWriteOwner); origBuffer = NULL; // release ref before throwing m_toplevel->core()->throwException(exn); } int retcode = SZ_OK; size_t destLen = unpackedLen; retcode = LzmaUncompress(m_buffer->array, &destLen, srcOverlay->compressedPayload, &srcLen, srcOverlay->lzmaProps, LZMA_PROPS_SIZE); if (cShared) { mmfx_delete_array(dataSnapshot); } switch (retcode) { case SZ_OK: // - OK if (destLen != unpackedLen) { // Belt-and-suspenders: If control gets here, // something is terribly wrong, and either LZMA is // lying, or the lzma header in source byte array got // garbled. Rather than risk establishing a bogus // structure, fail as if lzma returned an error code. goto error_cases; } m_buffer->length = uint32_t(destLen); if (cShared) { ByteArraySwapBufferTask task(this, origBuffer); task.exec(); } if (origData && origData != m_buffer->array && origCopyOnWriteOwner == NULL) { // Note that TellGcXXX always expects capacity, not (logical) length. TellGcDeleteBufferMemory(origData, origCap); mmfx_delete_array(origData); } break; case SZ_ERROR_DATA: // - Data error case SZ_ERROR_MEM: // - Memory allocation arror case SZ_ERROR_UNSUPPORTED: // - Unsupported properties case SZ_ERROR_INPUT_EOF: // - it needs more bytes in input buffer (src) default: error_cases: // In error cases: if (cShared) { m_buffer = origBuffer; } // 1) free the new buffer TellGcDeleteBufferMemory(m_buffer->array, m_buffer->capacity); mmfx_delete_array(m_buffer->array); // 2) put the original data back. // (keep in sync with state restoration above) m_buffer->array = origData; m_buffer->length = origLen; m_buffer->capacity = origCap; m_position = origPos; SetCopyOnWriteOwner(origCopyOnWriteOwner); origBuffer = NULL; // release ref before throwing toplevel()->throwIOError(kCompressedDataError); break; } } void ByteArray::UncompressViaZlibVariant(CompressionAlgorithm algorithm) { // Snarf the data and give ourself some empty data // (remember, existing data might be copy-on-write so don't dance on it) uint8_t* origData = m_buffer->array; uint32_t origCap = m_buffer->capacity; uint32_t origLen = m_buffer->length; uint32_t origPos = m_position; MMgc::GCObject* origCopyOnWriteOwner = m_copyOnWriteOwner; if (!origLen) // empty buffer should give empty result return; const bool cShared = IsShared(); // ByteArray's sharedness is immutable implicitly for the duration of this op since this worker is doing this op and cannot share it and it is not already shared (e.g. placed in a MessaegChannel). FixedHeapRef origBuffer = m_buffer; m_buffer->array = NULL; m_buffer->length = 0; m_buffer->capacity = 0; m_position = 0; m_copyOnWriteOwner = NULL; int error = Z_OK; uint8_t* scratch = NULL; uint8_t* dataSnapshot = NULL; TRY(m_toplevel->core(), kCatchAction_Rethrow) { // if this bytearray's data is being shared then we have to // snap shot it before we run any compression algorithm otherwise // the data could be changed in a way that may cause an exploit dataSnapshot = origData; if (cShared) { m_buffer = mmfx_new(Buffer()); dataSnapshot = mmfx_new_array(uint8_t, origLen); VMPI_memcpy(dataSnapshot, origData, origLen); } // we know that the uncompressed data will be at least as // large as the compressed data, so let's start there, // rather than at zero. EnsureCapacity(origCap); const uint32_t kScratchSize = 8192; scratch = mmfx_new_array(uint8_t, kScratchSize); z_stream stream; VMPI_memset(&stream, 0, sizeof(stream)); error = inflateInit2(&stream, algorithm == k_zlib ? 15 : -15); AvmAssert(error == Z_OK); stream.next_in = dataSnapshot; stream.avail_in = origLen; while (error == Z_OK) { stream.next_out = scratch; stream.avail_out = kScratchSize; error = inflate(&stream, Z_NO_FLUSH); Write(scratch, kScratchSize - stream.avail_out); } inflateEnd(&stream); mmfx_delete_array(scratch); if (cShared) { mmfx_delete_array(dataSnapshot); } } CATCH(Exception* e) { mmfx_delete_array(scratch); if (cShared) { mmfx_delete_array(dataSnapshot); } m_toplevel->core()->throwException(e); } END_CATCH; END_TRY; if (error == Z_STREAM_END) { if (cShared) { ByteArraySwapBufferTask task(this, origBuffer); task.exec(); } // everything is cool if (origData && origData != m_buffer->array && origCopyOnWriteOwner == NULL) { // Note that TellGcXXX always expects capacity, not (logical) length. TellGcDeleteBufferMemory(origData, origCap); mmfx_delete_array(origData); } // Note that Compress() has always ended with position == length, // but Uncompress() has always ended with position == 0. // Weird, but we must maintain it. m_position = 0; } else { // When we error: // 1) free the new buffer TellGcDeleteBufferMemory(m_buffer->array, m_buffer->capacity); mmfx_delete_array(m_buffer->array); if (cShared) { m_buffer = origBuffer; } // 2) put the original data back. m_buffer->array = origData; m_buffer->length = origLen; m_buffer->capacity = origCap; m_position = origPos; SetCopyOnWriteOwner(origCopyOnWriteOwner); origBuffer = NULL; // release ref before throwing toplevel()->throwIOError(kCompressedDataError); } } // For requestBytesForShortRead() there is no limit on m_position, but m_length // is limited to MAX_BYTEARRAY_STORE_LENGTH, which is well below 2^32. We limit // nbytes to MAX_BYTEARRAY_SHORT_ACCESS_LENGTH, which is less than // 2^32-MAX_BYTEARRAY_STORE_LENGTH but at least 4095. Callers that might have a // larger value for nbytes should not use this API. The purpose of all these limits // is to make the range check tractably small for inlining in jitted code without // limiting the ByteArray size unreasonably. (Requiring m_length < 2^31 would lead // to further optimizations but that seems unreasonably short.) // // Observe that ByteArray::Grower::ReallocBackingStore() implements the appropriate // limit on m_capacity, and that is the only code that allocates memory for ByteArray. // Everywhere else we ensure m_length <= m_capacity. REALLY_INLINE uint8_t* ByteArray::requestBytesForShortRead(uint32_t nbytes) { AvmAssert(m_buffer->length <= m_buffer->capacity); AvmAssert(m_buffer->capacity <= MAX_BYTEARRAY_STORE_LENGTH); AvmAssert(nbytes > 0 && nbytes <= MAX_BYTEARRAY_SHORT_ACCESS_LENGTH); // m_position + nbytes does not overflow uint32_t in the second disjunct because: // // m_position < m_length <= m_capacity <= MAX_BYTEARRAY_STORE_LENGTH (by the first disjunct + global invariants) // nbytes <= MAX_BYTEARRAY_SHORT_ACCESS_LENGTH (global invariant) // MAX_BYTEARRAY_STORE_LENGTH + MAX_BYTEARRAY_SHORT_ACCESS_LENGTH < 2^32 (global invariant) if (m_position >= m_buffer->length || m_position + nbytes > m_buffer->length) ThrowEOFError(); // Does not return uint8_t *b = m_buffer->array + m_position; m_position += nbytes; return b; } // Same argument as for requestBytesForShortRead(), above. REALLY_INLINE uint8_t* ByteArray::requestBytesForShortWrite(uint32_t nbytes) { AvmAssert(nbytes > 0 && nbytes <= MAX_BYTEARRAY_SHORT_ACCESS_LENGTH); // m_position + nbytes does not overflow uint32_t in the second disjunct because: // // m_position < m_length <= m_capacity <= MAX_BYTEARRAY_STORE_LENGTH (by the first disjunct + global invariants) // nbytes <= MAX_BYTEARRAY_SHORT_ACCESS_LENGTH (global invariant) // MAX_BYTEARRAY_STORE_LENGTH + MAX_BYTEARRAY_SHORT_ACCESS_LENGTH < 2^32 (global invariant) if (m_position >= m_buffer->length || m_position + nbytes > m_buffer->length) SetLength(m_position, nbytes); // The addition would *not* be safe against overflow here AvmAssert(m_buffer->length <= m_buffer->capacity); AvmAssert(m_buffer->capacity <= MAX_BYTEARRAY_STORE_LENGTH); AvmAssert(m_buffer->length >= nbytes && m_position <= m_buffer->length - nbytes); uint8_t *b = m_buffer->array + m_position; m_position += nbytes; return b; } int32_t ByteArray::CAS(uint32_t index, int32_t expected, int32_t next) { if (m_buffer->length == 0) { m_toplevel->throwRangeError(kInvalidRangeError); } const size_t minLength = sizeof(int32_t); if ((m_buffer->length < minLength) || (index > (m_buffer->length - minLength))) { m_toplevel->throwRangeError(kInvalidRangeError); } // require word alignment if (index % sizeof(expected) != 0) { m_toplevel->throwRangeError(kInvalidRangeError); } uint8_t* wordptr = &m_buffer->array[index]; return vmbase::AtomicOps::compareAndSwap32WithBarrierPrev(expected, next, (int32_t*)wordptr); } bool ByteArray::isShareable () const { return m_isShareable; } bool ByteArray::setShareable (bool value) { if (m_isShareable == value) return false; // no change. if (m_isShareable == true && value == false) return false; // FIXME can't 'un-share' yet. No change m_isShareable = value; return true; // changed. } // // ByteArrayObject // ByteArrayObject::ByteArrayObject(VTable* ivtable, ScriptObject* delegate) : ScriptObject(ivtable, delegate) #ifdef DEBUGGER , m_flushBeforeConstructionComplete(false) #endif , m_byteArray(toplevel()) { c.set(&m_byteArray, sizeof(ByteArray)); ByteArrayClass* cls = toplevel()->byteArrayClass(); m_byteArray.SetObjectEncoding((ObjectEncoding)cls->get_defaultObjectEncoding()); toplevel()->byteArrayCreated(this); #ifdef DEBUGGER if (m_flushBeforeConstructionComplete) { registerDependentObjects(getSampler()); getSampler()->recordObjectReallocation(this); } #endif } ByteArrayObject::ByteArrayObject(VTable* ivtable, ScriptObject* delegate, const ByteArray& source) : ScriptObject(ivtable, delegate) #ifdef DEBUGGER , m_flushBeforeConstructionComplete(false) #endif , m_byteArray(toplevel(), source) { c.set(&m_byteArray, sizeof(ByteArray)); toplevel()->byteArrayCreated(this); #ifdef DEBUGGER if (m_flushBeforeConstructionComplete) { registerDependentObjects(getSampler()); getSampler()->recordObjectReallocation(this); } #endif } ByteArrayObject::ByteArrayObject(VTable* ivtable, ScriptObject* delegate, ByteArray::Buffer* source) : ScriptObject(ivtable, delegate) #ifdef DEBUGGER , m_flushBeforeConstructionComplete(false) #endif , m_byteArray(toplevel(), source, false) { c.set(&m_byteArray, sizeof(ByteArray)); toplevel()->byteArrayCreated(this); #ifdef DEBUGGER if (m_flushBeforeConstructionComplete) { registerDependentObjects(getSampler()); getSampler()->recordObjectReallocation(this); } #endif } // Inspection of the object code (GCC 4.2) shows that the forced inlining of GetLength // and operator[] lead to optimal code here: no redundant range check is being executed // even if there is a redundant check in operator[]. Atom ByteArrayObject::getUintProperty(uint32_t i) const { if (i < m_byteArray.GetLength()) { intptr_t const b = m_byteArray[i]; return atomFromIntptrValue(b); } else { return undefinedAtom; } } bool ByteArrayObject::hasUintProperty(uint32_t i) const { return i < m_byteArray.GetLength(); } void ByteArrayObject::setUintProperty(uint32_t i, Atom value) { m_byteArray[i] = uint8_t(AvmCore::integer(value)); } Atom ByteArrayObject::getAtomProperty(Atom name) const { uint32_t index; if (AvmCore::getIndexFromAtom(name, &index)) { return getUintProperty(index); } return ScriptObject::getAtomProperty(name); } void ByteArrayObject::setAtomProperty(Atom name, Atom value) { uint32_t index; if (AvmCore::getIndexFromAtom(name, &index)) { setUintProperty(index, value); } else { ScriptObject::setAtomProperty(name, value); } } bool ByteArrayObject::hasAtomProperty(Atom name) const { if (core()->currentBugCompatibility()->bugzilla558863) { uint32_t index; if (AvmCore::getIndexFromAtom(name, &index)) { return index < m_byteArray.GetLength(); } return ScriptObject::hasAtomProperty(name); } else { return ScriptObject::hasAtomProperty(name) || getAtomProperty(name) != undefinedAtom; } } Atom ByteArrayObject::getMultinameProperty(const Multiname* name) const { uint32_t index; if (name->isValidDynamicName() && name->getName()->parseIndex(index)) { return getUintProperty(index); } return ScriptObject::getMultinameProperty(name); } void ByteArrayObject::setMultinameProperty(const Multiname* name, Atom value) { uint32_t index; if (name->isValidDynamicName() && name->getName()->parseIndex(index)) { setUintProperty(index, value); } else { ScriptObject::setMultinameProperty(name, value); } } bool ByteArrayObject::hasMultinameProperty(const Multiname* name) const { uint32_t index; if (name->isValidDynamicName() && name->getName()->parseIndex(index)) { return index < m_byteArray.GetLength(); } return ScriptObject::hasMultinameProperty(name); } String* ByteArrayObject::_toString() { uint32_t len = m_byteArray.GetLength(); const uint8_t* c = m_byteArray.GetReadableBuffer(); Toplevel* toplevel = this->toplevel(); AvmCore* core = toplevel->core(); if (len >= 3) { // UTF8 BOM if ((c[0] == 0xef) && (c[1] == 0xbb) && (c[2] == 0xbf)) { return core->newStringUTF8((const char*)c + 3, len - 3); } else if ((c[0] == 0xfe) && (c[1] == 0xff)) { //UTF-16 big endian c += 2; len = (len - 2) >> 1; return core->newStringEndianUTF16(/*littleEndian*/false, (const wchar*)c, len); } else if ((c[0] == 0xff) && (c[1] == 0xfe)) { //UTF-16 little endian c += 2; len = (len - 2) >> 1; return core->newStringEndianUTF16(/*littleEndian*/true, (const wchar*)c, len); } } uint8_t* buffer = NULL; if (c && len > 0 && c[len-1] != 0) { buffer = mmfx_new_array(uint8_t, len+1); VMPI_memcpy(buffer, c, len); buffer[len] = 0; } String* result = toplevel->tryFromSystemCodepage(buffer ? buffer : c); if (buffer) mmfx_delete_array(buffer); if (result != NULL) return result; // Use newStringUTF8() with "strict" explicitly set to false to mimick old, // buggy behavior, where malformed UTF-8 sequences are stored as single characters. return core->newStringUTF8((const char*)c, len, false); } Atom ByteArrayObject::readObject() { return m_byteArray.ReadObject(); } void ByteArrayObject::writeObject(Atom value) { m_byteArray.WriteObject(value); } int ByteArrayObject::readByte() { return (int8_t)*m_byteArray.requestBytesForShortRead(1); } int ByteArrayObject::readUnsignedByte() { return *m_byteArray.requestBytesForShortRead(1); } REALLY_INLINE uint32_t ByteArrayObject::read16() { uint8_t *b = m_byteArray.requestBytesForShortRead(2); if (m_byteArray.GetEndian() == m_byteArray.GetNativeEndian()) { // GetNativeEndian expands to a constant #if defined VMCFG_UNALIGNED_INT_ACCESS return *(uint16_t*)b; #elif defined VMCFG_BIG_ENDIAN return ((uint32_t)b[0] << 8) | (uint32_t)b[1]; // read big-endian #else return ((uint32_t)b[1] << 8) | (uint32_t)b[0]; // read little-endian #endif } else { #if defined VMCFG_UNALIGNED_INT_ACCESS && defined HAVE_BYTESWAP16 return byteSwap16(*(uint16_t*)b); #elif defined VMCFG_BIG_ENDIAN return ((uint32_t)b[1] << 8) | (uint32_t)b[0]; // read little-endian #else return ((uint32_t)b[0] << 8) | (uint32_t)b[1]; // read big-endian #endif } } int ByteArrayObject::readShort() { return (int)(int16_t)read16(); } int ByteArrayObject::readUnsignedShort() { return (int)read16(); } REALLY_INLINE uint32_t ByteArrayObject::read32() { uint8_t* b = m_byteArray.requestBytesForShortRead(4); if (m_byteArray.GetEndian() == m_byteArray.GetNativeEndian()) // GetNativeEndian expands to a constant { #if defined VMCFG_UNALIGNED_INT_ACCESS return *(uint32_t*)b; #elif defined VMCFG_BIG_ENDIAN return ((uint32_t)b[0] << 24) | ((uint32_t)b[1] << 16) | ((uint32_t)b[2] << 8) | (uint32_t)b[3]; // read big-endian #else return ((uint32_t)b[3] << 24) | ((uint32_t)b[2] << 16) | ((uint32_t)b[1] << 8) | (uint32_t)b[0]; // read little-endian #endif } else { #if defined VMCFG_UNALIGNED_INT_ACCESS && defined HAVE_BYTESWAP32 return byteSwap32(*(uint32_t*)b); #elif defined VMCFG_BIG_ENDIAN return ((uint32_t)b[3] << 24) | ((uint32_t)b[2] << 16) | ((uint32_t)b[1] << 8) | (uint32_t)b[0]; // read little-endian #else return ((uint32_t)b[0] << 24) | ((uint32_t)b[1] << 16) | ((uint32_t)b[2] << 8) | (uint32_t)b[3]; // read big-endian #endif } } int ByteArrayObject::readInt() { return (int)read32(); } uint32_t ByteArrayObject::readUnsignedInt() { return read32(); } // Some observations from x86 testing of float and double reading: // // - It pays to make use of VMCFG_UNALIGNED_FP_ACCESS. // - It pays to assemble an int32 in a register and then store it in // a 32-bit field of the union, that is then read (either alone for // float or as part of a pair for double), rather than shuffling // bytes individually into a byte array in the union. // Bugzilla 569691/685441: Do not try to be clever here by loading from // '*(uint32_t*)b' into 'u.word', even if VMCFG_UNALIGNED_INT_ACCESS and // on native endianness - gcc may emit code that loads directly // to the ARM VFP register, and that requires VMCFG_UNALIGNED_FP_ACCESS. REALLY_INLINE float ByteArrayObject::readFloatHelper(uint8_t* b) { union { uint32_t word; float fval; } u; if (m_byteArray.GetEndian() == m_byteArray.GetNativeEndian()) // GetNativeEndian expands to a constant { #if defined VMCFG_UNALIGNED_FP_ACCESS return *(float*)b; #elif defined VMCFG_BIG_ENDIAN u.word = ((uint32_t)b[0] << 24) | ((uint32_t)b[1] << 16) | ((uint32_t)b[2] << 8) | (uint32_t)b[3]; // read big-endian return u.fval; #else u.word = ((uint32_t)b[3] << 24) | ((uint32_t)b[2] << 16) | ((uint32_t)b[1] << 8) | (uint32_t)b[0]; // read little-endian return u.fval; #endif } else { #if defined VMCFG_UNALIGNED_INT_ACCESS && defined HAVE_BYTESWAP32 u.word = byteSwap32(*(uint32_t*)b); #elif defined VMCFG_BIG_ENDIAN u.word = ((uint32_t)b[3] << 24) | ((uint32_t)b[2] << 16) | ((uint32_t)b[1] << 8) | (uint32_t)b[0]; // read little-endian #else u.word = ((uint32_t)b[0] << 24) | ((uint32_t)b[1] << 16) | ((uint32_t)b[2] << 8) | (uint32_t)b[3]; // read big-endian #endif return u.fval; } } double ByteArrayObject::readFloat() { return readFloatHelper(m_byteArray.requestBytesForShortRead(4)); } #ifdef VMCFG_FLOAT // There will be four endianness checks here but one hopes the compiler will // common them after inlining. void ByteArrayObject::readFloat4(float4_t* retval) { uint8_t *b = m_byteArray.requestBytesForShortRead(16); float x = readFloatHelper(b); float y = readFloatHelper(b+4); float z = readFloatHelper(b+8); float w = readFloatHelper(b+12); float4_t v = { x, y, z, w }; *retval = v; } #endif // Bugzilla 569691/685441: Do not try to be clever here by loading from // '*(uint32_t*)b' into 'u.word[i]', even if VMCFG_UNALIGNED_INT_ACCESS and // on native endianness - gcc may emit code that loads directly // to the ARM VFP register, and that requires VMCFG_UNALIGNED_FP_ACCESS. double ByteArrayObject::readDouble() { // Handle reversed word order for doubles #if defined VMCFG_DOUBLE_MSW_FIRST const int first = 1; const int second = 0; #else const int first = 0; const int second = 1; #endif union { uint32_t words[2]; double dval; } u; uint8_t *b = m_byteArray.requestBytesForShortRead(8); if (m_byteArray.GetEndian() == m_byteArray.GetNativeEndian()) // GetNativeEndian expands to a constant { #if defined VMCFG_UNALIGNED_FP_ACCESS return *(double*)b; #elif defined VMCFG_BIG_ENDIAN u.words[first] = ((uint32_t)b[0] << 24) | ((uint32_t)b[1] << 16) | ((uint32_t)b[2] << 8) | (uint32_t)b[3]; // read u.words[second] = ((uint32_t)b[4] << 24) | ((uint32_t)b[5] << 16) | ((uint32_t)b[6] << 8) | (uint32_t)b[7]; // big-endian return u.dval; #else u.words[first] = ((uint32_t)b[3] << 24) | ((uint32_t)b[2] << 16) | ((uint32_t)b[1] << 8) | (uint32_t)b[0]; // read u.words[second] = ((uint32_t)b[7] << 24) | ((uint32_t)b[6] << 16) | ((uint32_t)b[5] << 8) | (uint32_t)b[4]; // little-endian return u.dval; #endif } else { #if defined VMCFG_UNALIGNED_INT_ACCESS && defined HAVE_BYTESWAP32 u.words[first] = byteSwap32(*(uint32_t*)(b+4)); u.words[second] = byteSwap32(*(uint32_t*)b); #elif defined VMCFG_BIG_ENDIAN u.words[first] = ((uint32_t)b[7] << 24) | ((uint32_t)b[6] << 16) | ((uint32_t)b[5] << 8) | (uint32_t)b[4]; // read u.words[second] = ((uint32_t)b[3] << 24) | ((uint32_t)b[2] << 16) | ((uint32_t)b[1] << 8) | (uint32_t)b[0]; // little-endian #else u.words[first] = ((uint32_t)b[4] << 24) | ((uint32_t)b[5] << 16) | ((uint32_t)b[6] << 8) | (uint32_t)b[7]; // read u.words[second] = ((uint32_t)b[0] << 24) | ((uint32_t)b[1] << 16) | ((uint32_t)b[2] << 8) | (uint32_t)b[3]; // big-endian #endif return u.dval; } } bool ByteArrayObject::readBoolean() { return (*m_byteArray.requestBytesForShortRead(1)) != 0; } void ByteArrayObject::writeBoolean(bool value) { m_byteArray.requestBytesForShortWrite(1)[0] = (value ? 1 : 0); } void ByteArrayObject::writeByte(int value) { m_byteArray.requestBytesForShortWrite(1)[0] = (uint8_t)value; } void ByteArrayObject::writeShort(int value) { uint8_t *b = m_byteArray.requestBytesForShortWrite(2); if (m_byteArray.GetEndian() == m_byteArray.GetNativeEndian()) // GetNativeEndian expands to a constant { #if defined VMCFG_UNALIGNED_INT_ACCESS *(int16_t*)b = (int16_t)value; #elif defined VMCFG_BIG_ENDIAN b[0] = (uint8_t)(value >> 8); // write b[1] = (uint8_t)value; // big-endian #else b[1] = (uint8_t)(value >> 8); // write b[0] = (uint8_t)value; // little-endian #endif } else { #if defined VMCFG_UNALIGNED_INT_ACCESS && defined HAVE_BYTESWAP16 *(int16_t*)b = byteSwap16((uint16_t)value); #elif defined VMCFG_BIG_ENDIAN b[1] = (uint8_t)(value >> 8); // write b[0] = (uint8_t)value; // little-endian #else b[0] = (uint8_t)(value >> 8); // write b[1] = (uint8_t)value; // big-endian #endif } } REALLY_INLINE void ByteArrayObject::write32(uint32_t value) { uint8_t *b = m_byteArray.requestBytesForShortWrite(4); if (m_byteArray.GetEndian() == m_byteArray.GetNativeEndian()) // GetNativeEndian expands to a constant { #if defined VMCFG_UNALIGNED_INT_ACCESS *(uint32_t*)b = value; #elif defined VMCFG_BIG_ENDIAN b[0] = (uint8_t)(value >> 24); // write b[1] = (uint8_t)(value >> 16); // big b[2] = (uint8_t)(value >> 8); // endian b[3] = (uint8_t)value; #else b[3] = (uint8_t)(value >> 24); // write b[2] = (uint8_t)(value >> 16); // little b[1] = (uint8_t)(value >> 8); // endian b[0] = (uint8_t)value; #endif } else { #if defined VMCFG_UNALIGNED_INT_ACCESS && defined HAVE_BYTESWAP32 *(uint32_t*)b = byteSwap32(value); #elif defined VMCFG_BIG_ENDIAN b[3] = (uint8_t)(value >> 24); // write b[2] = (uint8_t)(value >> 16); // little b[1] = (uint8_t)(value >> 8); // endian b[0] = (uint8_t)value; #else b[0] = (uint8_t)(value >> 24); // write b[1] = (uint8_t)(value >> 16); // big b[2] = (uint8_t)(value >> 8); // endian b[3] = (uint8_t)value; #endif } } void ByteArrayObject::writeInt(int value) { write32((uint32_t)value); } void ByteArrayObject::writeUnsignedInt(uint32_t value) { write32(value); } // Bugzilla 569691/685441: Do not try to be clever here by storing from // 'u.word' into '*(uint32_t*)b', even if both VMCFG_UNALIGNED_INT_ACCESS and // on native endianness - gcc will emit code that stores directly // from the ARM VFP register, and that requires VMCFG_UNALIGNED_FP_ACCESS. REALLY_INLINE void ByteArrayObject::writeFloatHelper(float value, uint8_t* b) { union { uint32_t word; float fval; } u; if (m_byteArray.GetEndian() == m_byteArray.GetNativeEndian()) // GetNativeEndian expands to a constant { #if defined VMCFG_UNALIGNED_FP_ACCESS *(float*)b = value; #elif defined VMCFG_BIG_ENDIAN u.fval = value; uint32_t w = u.word; b[0] = (uint8_t)(w >> 24); // write b[1] = (uint8_t)(w >> 16); // big b[2] = (uint8_t)(w >> 8); // endian b[3] = (uint8_t)w; #else u.fval = value; uint32_t w = u.word; b[3] = (uint8_t)(w >> 24); // write b[2] = (uint8_t)(w >> 16); // little b[1] = (uint8_t)(w >> 8); // endian b[0] = (uint8_t)w; #endif } else { u.fval = value; #if defined VMCFG_UNALIGNED_INT_ACCESS && defined HAVE_BYTESWAP32 *(uint32_t*)b = byteSwap32(u.word); #elif defined VMCFG_BIG_ENDIAN uint32_t w = u.word; b[3] = (uint8_t)(w >> 24); // write b[2] = (uint8_t)(w >> 16); // little b[1] = (uint8_t)(w >> 8); // endian b[0] = (uint8_t)w; #else uint32_t w = u.word; b[0] = (uint8_t)(w >> 24); // write b[1] = (uint8_t)(w >> 16); // big b[2] = (uint8_t)(w >> 8); // endian b[3] = (uint8_t)w; #endif } } void ByteArrayObject::writeFloat(double value) { writeFloatHelper((float)value, m_byteArray.requestBytesForShortWrite(4)); } #ifdef VMCFG_FLOAT // There will be four endianness checks here but one hopes the compiler will // common them after inlining. void ByteArrayObject::writeFloat4(const float4_t& v) { uint8_t* b = m_byteArray.requestBytesForShortWrite(16); writeFloatHelper(f4_x(v), b); writeFloatHelper(f4_y(v), b+4); writeFloatHelper(f4_z(v), b+8); writeFloatHelper(f4_w(v), b+12); } #endif // Bugzilla 569691/685441: Do not try to be clever here by storing from // 'u.word[i]' into '*(uint32_t*)b', even if both VMCFG_UNALIGNED_INT_ACCESS and // on native endianness - gcc will emit code that stores directly // from the ARM VFP register, and that requires VMCFG_UNALIGNED_FP_ACCESS. void ByteArrayObject::writeDouble(double value) { // Handle reversed word order for doubles #if defined VMCFG_DOUBLE_MSW_FIRST const int first = 1; const int second = 0; #else const int first = 0; const int second = 1; #endif union { uint32_t words[2]; double dval; } u; uint8_t *b = m_byteArray.requestBytesForShortWrite(8); if (m_byteArray.GetEndian() == m_byteArray.GetNativeEndian()) // GetNativeEndian expands to a constant { #if defined VMCFG_UNALIGNED_FP_ACCESS *(double*)b = value; #elif defined VMCFG_BIG_ENDIAN uint32_t w; u.dval = value; w = u.words[first]; b[0] = (uint8_t)(w >> 24); // write b[1] = (uint8_t)(w >> 16); // big b[2] = (uint8_t)(w >> 8); // endian b[3] = (uint8_t)w; w = u.words[second]; b[4] = (uint8_t)(w >> 24); // write b[5] = (uint8_t)(w >> 16); // big b[6] = (uint8_t)(w >> 8); // endian b[7] = (uint8_t)w; #else uint32_t w; u.dval = value; w = u.words[first]; b[3] = (uint8_t)(w >> 24); // write b[2] = (uint8_t)(w >> 16); // little b[1] = (uint8_t)(w >> 8); // endian b[0] = (uint8_t)w; w = u.words[second]; b[7] = (uint8_t)(w >> 24); // write b[6] = (uint8_t)(w >> 16); // little b[5] = (uint8_t)(w >> 8); // endian b[4] = (uint8_t)w; #endif } else { u.dval = value; #if defined VMCFG_UNALIGNED_INT_ACCESS && defined HAVE_BYTESWAP32 *(uint32_t*)b = byteSwap32(u.words[second]); // write *(uint32_t*)(b+4) = byteSwap32(u.words[first]); // opposite endianness #elif defined VMCFG_BIG_ENDIAN uint32_t w; u.dval = value; w = u.words[first]; b[7] = (uint8_t)(w >> 24); // write b[6] = (uint8_t)(w >> 16); // little b[5] = (uint8_t)(w >> 8); // endian b[4] = (uint8_t)w; w = u.words[second]; b[3] = (uint8_t)(w >> 24); // write b[2] = (uint8_t)(w >> 16); // little b[1] = (uint8_t)(w >> 8); // endian b[0] = (uint8_t)w; #else uint32_t w; u.dval = value; w = u.words[second]; b[0] = (uint8_t)(w >> 24); // write b[1] = (uint8_t)(w >> 16); // big b[2] = (uint8_t)(w >> 8); // endian b[3] = (uint8_t)w; w = u.words[first]; b[4] = (uint8_t)(w >> 24); // write b[5] = (uint8_t)(w >> 16); // big b[6] = (uint8_t)(w >> 8); // endian b[7] = (uint8_t)w; #endif } } ByteArray::CompressionAlgorithm ByteArrayObject::algorithmToEnum(String* algorithm) { Toplevel* toplevel = this->toplevel(); toplevel->checkNull(algorithm, "algorithm"); if (algorithm->equalsLatin1("zlib")) { return ByteArray::k_zlib; } if (algorithm->equalsLatin1("deflate")) { return ByteArray::k_deflate; } if( algorithm->equalsLatin1("lzma")) { return ByteArray::k_lzma; } else { // Unknown format toplevel->throwIOError(kCompressedDataError); return ByteArray::k_zlib; // not reached, pacify compiler } } void ByteArrayObject::_compress(String* algorithm) { m_byteArray.Compress(algorithmToEnum(algorithm)); } void ByteArrayObject::_uncompress(String* algorithm) { m_byteArray.Uncompress(algorithmToEnum(algorithm)); } void ByteArrayObject::writeBytes(ByteArrayObject *bytes, uint32_t offset, uint32_t length) { toplevel()->checkNull(bytes, "bytes"); if (length == 0) { length = bytes->get_length() - offset; } m_byteArray.WriteByteArray(bytes->GetByteArray(), offset, length); } void ByteArrayObject::readBytes(ByteArrayObject *bytes, uint32_t offset, uint32_t length) { toplevel()->checkNull(bytes, "bytes"); if (length == 0) { length = m_byteArray.Available(); } m_byteArray.ReadByteArray(bytes->GetByteArray(), offset, length); } String* ByteArrayObject::readMultiByte(uint32_t length, String* charSet) { toplevel()->checkNull(charSet, "charSet"); return m_byteArray.ReadMultiByte(length, charSet); } String* ByteArrayObject::readUTF() { return readUTFBytes((uint32_t)readUnsignedShort()); } String* ByteArrayObject::readUTFBytes(uint32_t length) { if (m_byteArray.Available() < length) toplevel()->throwEOFError(kEOFError); const uint8_t* p = (const uint8_t*)m_byteArray.GetReadableBuffer() + m_byteArray.GetPosition(); uint32_t countBytesConsumed = length; // Skip UTF8 BOM (it is consumed from input, but not part of output). if (length >= 3 && p[0] == 0xEFU && p[1] == 0xBBU && p[2] == 0xBFU) { p += 3; length -= 3; } // Bugzilla 687341: we must stop at NUL, so here we need to scan for a NUL // in the portion we're considering. With a fix to that bug we won't need // to do so. #if 1 const uint8_t* limit = p + length; const uint8_t* q = p; while (q < limit && *q != 0) q++; #else const uint8_t* q = p + length; #endif String *result = toplevel()->core()->newStringUTF8((const char*)p, int(q-p)); // The position is always updated as if the entire string had been consumed, // even if there was a NUL that made us stop early. m_byteArray.SetPosition(m_byteArray.GetPosition()+countBytesConsumed); return result; } void ByteArrayObject::writeMultiByte(String* value, String* charSet) { toplevel()->checkNull(value, "value"); toplevel()->checkNull(charSet, "charSet"); m_byteArray.WriteMultiByte(value, charSet); } void ByteArrayObject::writeUTF(String* value) { toplevel()->checkNull(value, "value"); m_byteArray.WriteUTF(value); } void ByteArrayObject::writeUTFBytes(String* value) { toplevel()->checkNull(value, "value"); m_byteArray.WriteUTFBytes(value); } uint32_t ByteArrayObject::get_objectEncoding() { return m_byteArray.GetObjectEncoding(); } void ByteArrayObject::set_objectEncoding(uint32_t objectEncoding) { if ((objectEncoding == kAMF3)||(objectEncoding == kAMF0)) { m_byteArray.SetObjectEncoding(ObjectEncoding(objectEncoding)); } else { toplevel()->throwArgumentError(kInvalidEnumError, "objectEncoding"); } } Stringp ByteArrayObject::get_endian() { return (m_byteArray.GetEndian() == kBigEndian) ? core()->kbigEndian : core()->klittleEndian; } void ByteArrayObject::set_endian(Stringp type) { Toplevel* toplevel = this->toplevel(); AvmCore* core = toplevel->core(); toplevel->checkNull(type, "endian"); type = core->internString(type); if (type == core->kbigEndian) { m_byteArray.SetEndian(kBigEndian); } else if (type == core->klittleEndian) { m_byteArray.SetEndian(kLittleEndian); } else { toplevel->throwArgumentError(kInvalidEnumError, "type"); } } void ByteArrayObject::clear() { m_byteArray.Clear(); m_byteArray.SetPosition(0); } int32_t ByteArrayObject::atomicCompareAndSwapIntAt(int32_t byteIndex , int32_t expectedValue, int32_t newValue ) { return m_byteArray.CAS(byteIndex, expectedValue, newValue); } int32_t ByteArrayObject::atomicCompareAndSwapLength(int32_t expectedLength, int32_t newLength) { if (m_byteArray.IsShared()) { ByteArrayCompareAndSwapLengthTask task(&m_byteArray, expectedLength, newLength); task.exec(); return task.result; } else { return m_byteArray.UnprotectedAtomicCompareAndSwapLength(expectedLength, newLength); } } int32_t ByteArray::UnprotectedAtomicCompareAndSwapLength(int32_t expectedLength, int32_t newLength) { int32_t result = GetLength(); if (expectedLength == result) { const bool CalledFromAS3Setter = true; Grower grower(this, newLength); grower.SetLengthCommon(newLength, CalledFromAS3Setter); } return result; } void ByteArrayObject::set_shareable(bool val) { m_byteArray.setShareable(val); } bool ByteArrayObject::get_shareable() { return m_byteArray.isShareable(); } ChannelItem* ByteArrayObject::makeChannelItem() { class ByteArrayChannelItem: public ChannelItem { public: ByteArrayChannelItem(ByteArray::Buffer* value, bool shareable) { m_value = value; m_isShareable = shareable; } void intern(ByteArrayObject* ba) const { Toplevel* tl = ba->toplevel(); tl->internObject(m_value, ba); } Atom getAtom(Toplevel* toplevel) const { ByteArrayObject* baObject = toplevel->getInternedObject(m_value).staticCast(); if (baObject == NULL) { ByteArrayClass* baClass = toplevel->byteArrayClass(); ByteArray ba(toplevel, m_value, m_isShareable); baObject = new (toplevel->gc(), MMgc::kExact) ByteArrayObject(baClass->ivtable(), baClass->prototypePtr(), ba); toplevel->internObject(m_value, baObject); } return baObject->toAtom(); } private: bool m_isShareable; FixedHeapRef m_value; }; ByteArrayChannelItem* item = NULL; ByteArray::Buffer* buffer = GetByteArray().getUnderlyingBuffer(); const bool cIsShareable = GetByteArray().isShareable(); if (!cIsShareable) { ByteArray::Buffer* copy = mmfx_new(ByteArray::Buffer); copy->capacity = buffer->capacity; copy->length = buffer->length; if (buffer->array) { copy->array = mmfx_new_array_opt(uint8_t, buffer->capacity, MMgc::kCanFailAndZero); if (copy->array) { VMPI_memcpy(copy->array, buffer->array, buffer->length); } } else { copy->array = NULL; } item = mmfx_new(ByteArrayChannelItem(copy, cIsShareable)); } else { item = mmfx_new(ByteArrayChannelItem(buffer, cIsShareable)); } item->intern(this); return item; } #ifdef DEBUGGER uint64_t ByteArrayObject::bytesUsed() const { if (!c.v) { return 0; } uint64_t size = ScriptObject::bytesUsed(); size += m_byteArray.bytesUsed(); return size; } void ByteArrayObject::registerDependentObjects(IMemorySampler* memSampler) { if (!c.v) { m_flushBeforeConstructionComplete = true; return; } memSampler->addDependentObject(this, &m_byteArray); ScriptObject::registerDependentObjects(memSampler); } #endif // // ByteArrayClass // ByteArrayClass::ByteArrayClass(VTable *vtable) : ClassClosure(vtable) { setPrototypePtr(toplevel()->objectClass->construct()); set_defaultObjectEncoding(kEncodeDefault); } GCRef ByteArrayClass::constructByteArray() { return constructObject(); } }