blob: 8841af64eff965be8eaa079719a64da0ddbc220a [file]
//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------
#include "RuntimeLibraryPch.h"
namespace Js
{
bool ArrayBufferBase::Is(Var value)
{
return ArrayBuffer::Is(value) || SharedArrayBuffer::Is(value);
}
ArrayBufferBase* ArrayBufferBase::FromVar(Var value)
{
Assert(ArrayBufferBase::Is(value));
return static_cast<ArrayBuffer *> (value);
}
ArrayBuffer* ArrayBuffer::NewFromDetachedState(DetachedStateBase* state, JavascriptLibrary *library)
{
ArrayBufferDetachedStateBase* arrayBufferState = (ArrayBufferDetachedStateBase *)state;
ArrayBuffer *toReturn = nullptr;
switch (arrayBufferState->allocationType)
{
case ArrayBufferAllocationType::CoTask:
toReturn = library->CreateProjectionArraybuffer(arrayBufferState->buffer, arrayBufferState->bufferLength);
break;
case ArrayBufferAllocationType::Heap:
case ArrayBufferAllocationType::MemAlloc:
toReturn = library->CreateArrayBuffer(arrayBufferState->buffer, arrayBufferState->bufferLength);
break;
default:
AssertMsg(false, "Unknown allocationType of ArrayBufferDetachedStateBase ");
}
return toReturn;
}
void ArrayBuffer::DetachBufferFromParent(ArrayBufferParent* parent)
{
if (parent == nullptr)
{
return;
}
switch (JavascriptOperators::GetTypeId(parent))
{
case TypeIds_Int8Array:
if (Int8VirtualArray::Is(parent))
{
if (VirtualTableInfo<Int8VirtualArray>::HasVirtualTable(parent))
{
VirtualTableInfo<Int8Array>::SetVirtualTable(parent);
}
else
{
Assert(VirtualTableInfo<CrossSiteObject<Int8VirtualArray>>::HasVirtualTable(parent));
VirtualTableInfo<CrossSiteObject<Int8Array>>::SetVirtualTable(parent);
}
}
TypedArrayBase::FromVar(parent)->ClearLengthAndBufferOnDetach();
break;
case TypeIds_Uint8Array:
if (Uint8VirtualArray::Is(parent))
{
if (VirtualTableInfo<Uint8VirtualArray>::HasVirtualTable(parent))
{
VirtualTableInfo<Uint8Array>::SetVirtualTable(parent);
}
else
{
Assert(VirtualTableInfo<CrossSiteObject<Uint8VirtualArray>>::HasVirtualTable(parent));
VirtualTableInfo<CrossSiteObject<Uint8Array>>::SetVirtualTable(parent);
}
}
TypedArrayBase::FromVar(parent)->ClearLengthAndBufferOnDetach();
break;
case TypeIds_Uint8ClampedArray:
if (Uint8ClampedVirtualArray::Is(parent))
{
if (VirtualTableInfo<Uint8ClampedVirtualArray>::HasVirtualTable(parent))
{
VirtualTableInfo<Uint8ClampedArray>::SetVirtualTable(parent);
}
else
{
Assert(VirtualTableInfo<CrossSiteObject<Uint8ClampedVirtualArray>>::HasVirtualTable(parent));
VirtualTableInfo<CrossSiteObject<Uint8ClampedArray>>::SetVirtualTable(parent);
}
}
TypedArrayBase::FromVar(parent)->ClearLengthAndBufferOnDetach();
break;
case TypeIds_Int16Array:
if (Int16VirtualArray::Is(parent))
{
if (VirtualTableInfo<Int16VirtualArray>::HasVirtualTable(parent))
{
VirtualTableInfo<Int16Array>::SetVirtualTable(parent);
}
else
{
Assert(VirtualTableInfo<CrossSiteObject<Int16VirtualArray>>::HasVirtualTable(parent));
VirtualTableInfo<CrossSiteObject<Int16Array>>::SetVirtualTable(parent);
}
}
TypedArrayBase::FromVar(parent)->ClearLengthAndBufferOnDetach();
break;
case TypeIds_Uint16Array:
if (Uint16VirtualArray::Is(parent))
{
if (VirtualTableInfo<Uint16VirtualArray>::HasVirtualTable(parent))
{
VirtualTableInfo<Uint16Array>::SetVirtualTable(parent);
}
else
{
Assert(VirtualTableInfo<CrossSiteObject<Uint16VirtualArray>>::HasVirtualTable(parent));
VirtualTableInfo<CrossSiteObject<Uint16Array>>::SetVirtualTable(parent);
}
}
TypedArrayBase::FromVar(parent)->ClearLengthAndBufferOnDetach();
break;
case TypeIds_Int32Array:
if (Int32VirtualArray::Is(parent))
{
if (VirtualTableInfo<Int32VirtualArray>::HasVirtualTable(parent))
{
VirtualTableInfo<Int32Array>::SetVirtualTable(parent);
}
else
{
Assert(VirtualTableInfo<CrossSiteObject<Int32VirtualArray>>::HasVirtualTable(parent));
VirtualTableInfo<CrossSiteObject<Int32Array>>::SetVirtualTable(parent);
}
}
TypedArrayBase::FromVar(parent)->ClearLengthAndBufferOnDetach();
break;
case TypeIds_Uint32Array:
if (Uint32VirtualArray::Is(parent))
{
if (VirtualTableInfo<Uint32VirtualArray>::HasVirtualTable(parent))
{
VirtualTableInfo<Uint32Array>::SetVirtualTable(parent);
}
else
{
Assert(VirtualTableInfo<CrossSiteObject<Uint32VirtualArray>>::HasVirtualTable(parent));
VirtualTableInfo<CrossSiteObject<Uint32Array>>::SetVirtualTable(parent);
}
}
TypedArrayBase::FromVar(parent)->ClearLengthAndBufferOnDetach();
break;
case TypeIds_Float32Array:
if (Float32VirtualArray::Is(parent))
{
if (VirtualTableInfo<Float32VirtualArray>::HasVirtualTable(parent))
{
VirtualTableInfo<Float32Array>::SetVirtualTable(parent);
}
else
{
Assert(VirtualTableInfo<CrossSiteObject<Float32VirtualArray>>::HasVirtualTable(parent));
VirtualTableInfo<CrossSiteObject<Float32Array>>::SetVirtualTable(parent);
}
}
TypedArrayBase::FromVar(parent)->ClearLengthAndBufferOnDetach();
break;
case TypeIds_Float64Array:
if (Float64VirtualArray::Is(parent))
{
if (VirtualTableInfo<Float64VirtualArray>::HasVirtualTable(parent))
{
VirtualTableInfo<Float64Array>::SetVirtualTable(parent);
}
else
{
Assert(VirtualTableInfo<CrossSiteObject<Float64VirtualArray>>::HasVirtualTable(parent));
VirtualTableInfo<CrossSiteObject<Float64Array>>::SetVirtualTable(parent);
}
}
TypedArrayBase::FromVar(parent)->ClearLengthAndBufferOnDetach();
break;
case TypeIds_Int64Array:
case TypeIds_Uint64Array:
case TypeIds_CharArray:
case TypeIds_BoolArray:
TypedArrayBase::FromVar(parent)->ClearLengthAndBufferOnDetach();
break;
case TypeIds_DataView:
DataView::FromVar(parent)->ClearLengthAndBufferOnDetach();
break;
default:
AssertMsg(false, "We need an explicit case for any parent of ArrayBuffer.");
break;
}
}
ArrayBufferDetachedStateBase* ArrayBuffer::DetachAndGetState()
{
Assert(!this->isDetached);
AutoPtr<ArrayBufferDetachedStateBase> arrayBufferState(this->CreateDetachedState(this->buffer, this->bufferLength));
this->buffer = nullptr;
this->bufferLength = 0;
this->isDetached = true;
if (this->primaryParent != nullptr && this->primaryParent->Get() == nullptr)
{
this->primaryParent = nullptr;
}
if (this->primaryParent != nullptr)
{
this->DetachBufferFromParent(this->primaryParent->Get());
}
if (this->otherParents != nullptr)
{
this->otherParents->Map([&](RecyclerWeakReference<ArrayBufferParent>* item)
{
this->DetachBufferFromParent(item->Get());
});
}
return arrayBufferState.Detach();
}
void ArrayBuffer::AddParent(ArrayBufferParent* parent)
{
if (this->primaryParent == nullptr || this->primaryParent->Get() == nullptr)
{
this->primaryParent = this->GetRecycler()->CreateWeakReferenceHandle(parent);
}
else
{
if (this->otherParents == nullptr)
{
this->otherParents = RecyclerNew(this->GetRecycler(), OtherParents, this->GetRecycler());
}
if (this->otherParents->increasedCount >= ParentsCleanupThreshold)
{
auto iter = this->otherParents->GetEditingIterator();
while (iter.Next())
{
if (iter.Data()->Get() == nullptr)
{
iter.RemoveCurrent();
}
}
this->otherParents->increasedCount = 0;
}
this->otherParents->PrependNode(this->GetRecycler()->CreateWeakReferenceHandle(parent));
this->otherParents->increasedCount++;
}
}
uint32 ArrayBuffer::ToIndex(Var value, int32 errorCode, ScriptContext *scriptContext, uint32 MaxAllowedLength, bool checkSameValueZero)
{
if (JavascriptOperators::IsUndefined(value))
{
return 0;
}
if (TaggedInt::Is(value))
{
int64 index = TaggedInt::ToInt64(value);
if (index < 0 || index >(int64)MaxAllowedLength)
{
JavascriptError::ThrowRangeError(scriptContext, errorCode);
}
return (uint32)index;
}
// Slower path
double d = JavascriptConversion::ToInteger(value, scriptContext);
if (d < 0.0 || d >(double)MaxAllowedLength)
{
JavascriptError::ThrowRangeError(scriptContext, errorCode);
}
if (checkSameValueZero)
{
Var integerIndex = JavascriptNumber::ToVarNoCheck(d, scriptContext);
Var index = JavascriptNumber::ToVar(JavascriptConversion::ToLength(integerIndex, scriptContext), scriptContext);
if (!JavascriptConversion::SameValueZero(integerIndex, index))
{
JavascriptError::ThrowRangeError(scriptContext, errorCode);
}
}
return (uint32)d;
}
Var ArrayBuffer::NewInstance(RecyclableObject* function, CallInfo callInfo, ...)
{
PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
ARGUMENTS(args, callInfo);
ScriptContext* scriptContext = function->GetScriptContext();
AssertMsg(args.Info.Count > 0, "Should always have implicit 'this'");
Var newTarget = callInfo.Flags & CallFlags_NewTarget ? args.Values[args.Info.Count] : args[0];
bool isCtorSuperCall = (callInfo.Flags & CallFlags_New) && newTarget != nullptr && !JavascriptOperators::IsUndefined(newTarget);
Assert(isCtorSuperCall || !(callInfo.Flags & CallFlags_New) || args[0] == nullptr);
if (!(callInfo.Flags & CallFlags_New) || (newTarget && JavascriptOperators::IsUndefinedObject(newTarget)))
{
JavascriptError::ThrowTypeError(scriptContext, JSERR_ClassConstructorCannotBeCalledWithoutNew, _u("ArrayBuffer"));
}
uint32 byteLength = 0;
if (args.Info.Count > 1)
{
byteLength = ToIndex(args[1], JSERR_ArrayLengthConstructIncorrect, scriptContext, MaxArrayBufferLength);
}
RecyclableObject* newArr = scriptContext->GetLibrary()->CreateArrayBuffer(byteLength);
Assert(ArrayBuffer::Is(newArr));
if (byteLength > 0 && !ArrayBuffer::FromVar(newArr)->GetByteLength())
{
JavascriptError::ThrowRangeError(scriptContext, JSERR_FunctionArgument_Invalid);
}
#if ENABLE_DEBUG_CONFIG_OPTIONS
if (Js::Configuration::Global.flags.IsEnabled(Js::autoProxyFlag))
{
newArr = Js::JavascriptProxy::AutoProxyWrapper(newArr);
}
#endif
return isCtorSuperCall ?
JavascriptOperators::OrdinaryCreateFromConstructor(RecyclableObject::FromVar(newTarget), newArr, nullptr, scriptContext) :
newArr;
}
// ArrayBuffer.prototype.byteLength as described in ES6 draft #20 section 24.1.4.1
Var ArrayBuffer::EntryGetterByteLength(RecyclableObject* function, CallInfo callInfo, ...)
{
PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
ARGUMENTS(args, callInfo);
ScriptContext* scriptContext = function->GetScriptContext();
Assert(!(callInfo.Flags & CallFlags_New));
if (args.Info.Count == 0 || !ArrayBuffer::Is(args[0]))
{
JavascriptError::ThrowTypeError(scriptContext, JSERR_NeedArrayBufferObject);
}
ArrayBuffer* arrayBuffer = ArrayBuffer::FromVar(args[0]);
if (arrayBuffer->IsDetached())
{
return JavascriptNumber::ToVar(0, scriptContext);
}
return JavascriptNumber::ToVar(arrayBuffer->GetByteLength(), scriptContext);
}
// ArrayBuffer.isView as described in ES6 draft #20 section 24.1.3.1
Var ArrayBuffer::EntryIsView(RecyclableObject* function, CallInfo callInfo, ...)
{
PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
ARGUMENTS(args, callInfo);
Assert(!(callInfo.Flags & CallFlags_New));
AssertMsg(args.Info.Count > 0, "Should always have implicit 'this'");
JavascriptLibrary* library = function->GetScriptContext()->GetLibrary();
Var arg = library->GetUndefined();
if (args.Info.Count > 1)
{
arg = args[1];
}
// Only DataView or any TypedArray objects have [[ViewedArrayBuffer]] internal slots
if (DataView::Is(arg) || TypedArrayBase::Is(arg))
{
return library->GetTrue();
}
return library->GetFalse();
}
// ArrayBuffer.transfer as described in Luke Wagner's proposal: https://gist.github.com/lukewagner/2735af7eea411e18cf20
Var ArrayBuffer::EntryTransfer(RecyclableObject* function, CallInfo callInfo, ...)
{
ScriptContext* scriptContext = function->GetScriptContext();
PROBE_STACK(scriptContext, Js::Constants::MinStackDefault);
ARGUMENTS(args, callInfo);
Assert(!(callInfo.Flags & CallFlags_New));
CHAKRATEL_LANGSTATS_INC_BUILTINCOUNT(ArrayBuffer_Constructor_transfer);
if (args.Info.Count < 2 || !ArrayBuffer::Is(args[1]))
{
JavascriptError::ThrowTypeError(scriptContext, JSERR_NeedArrayBufferObject);
}
ArrayBuffer* arrayBuffer = ArrayBuffer::FromVar(args[1]);
if (arrayBuffer->IsDetached())
{
JavascriptError::ThrowTypeError(scriptContext, JSERR_DetachedTypedArray, _u("ArrayBuffer.transfer"));
}
uint32 newBufferLength = arrayBuffer->bufferLength;
if (args.Info.Count >= 3)
{
newBufferLength = ToIndex(args[2], JSERR_ArrayLengthConstructIncorrect, scriptContext, MaxArrayBufferLength);
// ToIndex above can call user script (valueOf) which can detach the buffer
if (arrayBuffer->IsDetached())
{
JavascriptError::ThrowTypeError(scriptContext, JSERR_DetachedTypedArray, _u("ArrayBuffer.transfer"));
}
}
return arrayBuffer->TransferInternal(newBufferLength);
}
// ArrayBuffer.prototype.slice as described in ES6 draft #19 section 24.1.4.3.
Var ArrayBuffer::EntrySlice(RecyclableObject* function, CallInfo callInfo, ...)
{
ScriptContext* scriptContext = function->GetScriptContext();
PROBE_STACK(scriptContext, Js::Constants::MinStackDefault);
ARGUMENTS(args, callInfo);
AssertMsg(args.Info.Count > 0, "Should always have implicit 'this'");
Assert(!(callInfo.Flags & CallFlags_New));
if (!ArrayBuffer::Is(args[0]))
{
JavascriptError::ThrowTypeError(scriptContext, JSERR_NeedArrayBufferObject);
}
JavascriptLibrary* library = scriptContext->GetLibrary();
ArrayBuffer* arrayBuffer = ArrayBuffer::FromVar(args[0]);
if (arrayBuffer->IsDetached()) // 24.1.4.3: 5. If IsDetachedBuffer(O) is true, then throw a TypeError exception.
{
JavascriptError::ThrowTypeError(scriptContext, JSERR_DetachedTypedArray, _u("ArrayBuffer.prototype.slice"));
}
int64 len = arrayBuffer->bufferLength;
int64 start = 0, end = 0;
int64 newLen;
// If no start or end arguments, use the entire length
if (args.Info.Count < 2)
{
newLen = len;
}
else
{
start = JavascriptArray::GetIndexFromVar(args[1], len, scriptContext);
// If no end argument, use length as the end
if (args.Info.Count < 3 || args[2] == library->GetUndefined())
{
end = len;
}
else
{
end = JavascriptArray::GetIndexFromVar(args[2], len, scriptContext);
}
newLen = end > start ? end - start : 0;
}
// We can't have allocated an ArrayBuffer with byteLength > MaxArrayBufferLength.
// start and end are clamped to valid indices, so the new length also cannot exceed MaxArrayBufferLength.
// Therefore, should be safe to cast down newLen to uint32.
// TODO: If we ever support allocating ArrayBuffer with byteLength > MaxArrayBufferLength we may need to review this math.
Assert(newLen < MaxArrayBufferLength);
uint32 byteLength = static_cast<uint32>(newLen);
ArrayBuffer* newBuffer = nullptr;
if (scriptContext->GetConfig()->IsES6SpeciesEnabled())
{
Var constructorVar = JavascriptOperators::SpeciesConstructor(arrayBuffer, scriptContext->GetLibrary()->GetArrayBufferConstructor(), scriptContext);
JavascriptFunction* constructor = JavascriptFunction::FromVar(constructorVar);
Js::Var constructorArgs[] = {constructor, JavascriptNumber::ToVar(byteLength, scriptContext)};
Js::CallInfo constructorCallInfo(Js::CallFlags_New, _countof(constructorArgs));
Js::Var newVar = JavascriptOperators::NewScObject(constructor, Js::Arguments(constructorCallInfo, constructorArgs), scriptContext);
if (!ArrayBuffer::Is(newVar)) // 24.1.4.3: 19.If new does not have an [[ArrayBufferData]] internal slot throw a TypeError exception.
{
JavascriptError::ThrowTypeError(scriptContext, JSERR_NeedArrayBufferObject);
}
newBuffer = ArrayBuffer::FromVar(newVar);
if (newBuffer->IsDetached()) // 24.1.4.3: 21. If IsDetachedBuffer(new) is true, then throw a TypeError exception.
{
JavascriptError::ThrowTypeError(scriptContext, JSERR_DetachedTypedArray, _u("ArrayBuffer.prototype.slice"));
}
if (newBuffer == arrayBuffer) // 24.1.4.3: 22. If SameValue(new, O) is true, then throw a TypeError exception.
{
JavascriptError::ThrowTypeError(scriptContext, JSERR_NeedArrayBufferObject);
}
if (newBuffer->bufferLength < byteLength) // 24.1.4.3: 23.If the value of new's [[ArrayBufferByteLength]] internal slot < newLen, then throw a TypeError exception.
{
JavascriptError::ThrowTypeError(scriptContext, JSERR_ArgumentOutOfRange, _u("ArrayBuffer.prototype.slice"));
}
}
else
{
newBuffer = library->CreateArrayBuffer(byteLength);
}
Assert(newBuffer);
Assert(newBuffer->bufferLength >= byteLength);
if (arrayBuffer->IsDetached()) // 24.1.4.3: 24. NOTE: Side-effects of the above steps may have detached O. 25. If IsDetachedBuffer(O) is true, then throw a TypeError exception.
{
JavascriptError::ThrowTypeError(scriptContext, JSERR_DetachedTypedArray, _u("ArrayBuffer.prototype.slice"));
}
// Don't bother doing memcpy if we aren't copying any elements
if (byteLength > 0)
{
AssertMsg(arrayBuffer->buffer != nullptr, "buffer must not be null when we copy from it");
js_memcpy_s(newBuffer->buffer, byteLength, arrayBuffer->buffer + start, byteLength);
}
return newBuffer;
}
Var ArrayBuffer::EntryGetterSymbolSpecies(RecyclableObject* function, CallInfo callInfo, ...)
{
ARGUMENTS(args, callInfo);
Assert(args.Info.Count > 0);
return args[0];
}
ArrayBuffer* ArrayBuffer::FromVar(Var aValue)
{
AssertMsg(Is(aValue), "var must be an ArrayBuffer");
return static_cast<ArrayBuffer *>(RecyclableObject::FromVar(aValue));
}
bool ArrayBuffer::Is(Var aValue)
{
return JavascriptOperators::GetTypeId(aValue) == TypeIds_ArrayBuffer;
}
template <class Allocator>
ArrayBuffer::ArrayBuffer(uint32 length, DynamicType * type, Allocator allocator) :
ArrayBufferBase(type)
{
buffer = nullptr;
bufferLength = 0;
if (length > MaxArrayBufferLength)
{
JavascriptError::ThrowTypeError(GetScriptContext(), JSERR_FunctionArgument_Invalid);
}
else if (length > 0)
{
Recycler* recycler = GetType()->GetLibrary()->GetRecycler();
if (recycler->ReportExternalMemoryAllocation(length))
{
buffer = (BYTE*)allocator(length);
if (buffer == nullptr)
{
recycler->ReportExternalMemoryFree(length);
}
}
if (buffer == nullptr)
{
recycler->CollectNow<CollectOnTypedArrayAllocation>();
if (recycler->ReportExternalMemoryAllocation(length))
{
buffer = (BYTE*)allocator(length);
if (buffer == nullptr)
{
recycler->ReportExternalMemoryFailure(length);
}
}
else
{
JavascriptError::ThrowOutOfMemoryError(GetScriptContext());
}
}
if (buffer != nullptr)
{
bufferLength = length;
ZeroMemory(buffer, bufferLength);
}
}
}
ArrayBuffer::ArrayBuffer(byte* buffer, uint32 length, DynamicType * type) :
buffer(buffer), bufferLength(length), ArrayBufferBase(type)
{
if (length > MaxArrayBufferLength)
{
JavascriptError::ThrowTypeError(GetScriptContext(), JSERR_FunctionArgument_Invalid);
}
}
BOOL ArrayBuffer::GetDiagTypeString(StringBuilder<ArenaAllocator>* stringBuilder, ScriptContext* requestContext)
{
stringBuilder->AppendCppLiteral(_u("Object, (ArrayBuffer)"));
return TRUE;
}
BOOL ArrayBuffer::GetDiagValueString(StringBuilder<ArenaAllocator>* stringBuilder, ScriptContext* requestContext)
{
stringBuilder->AppendCppLiteral(_u("[object ArrayBuffer]"));
return TRUE;
}
#if ENABLE_TTD
void ArrayBufferParent::MarkVisitKindSpecificPtrs(TTD::SnapshotExtractor* extractor)
{
extractor->MarkVisitVar(this->arrayBuffer);
}
void ArrayBufferParent::ProcessCorePaths()
{
this->GetScriptContext()->TTDWellKnownInfo->EnqueueNewPathVarAsNeeded(this, this->arrayBuffer, _u("!buffer"));
}
#endif
JavascriptArrayBuffer::JavascriptArrayBuffer(uint32 length, DynamicType * type) :
ArrayBuffer(length, type, IsValidVirtualBufferLength(length) ? AsmJsVirtualAllocator : malloc)
{
}
JavascriptArrayBuffer::JavascriptArrayBuffer(byte* buffer, uint32 length, DynamicType * type) :
ArrayBuffer(buffer, length, type)
{
}
JavascriptArrayBuffer::JavascriptArrayBuffer(DynamicType * type) : ArrayBuffer(0, type, malloc)
{
}
JavascriptArrayBuffer* JavascriptArrayBuffer::Create(uint32 length, DynamicType * type)
{
Recycler* recycler = type->GetScriptContext()->GetRecycler();
JavascriptArrayBuffer* result = RecyclerNewFinalized(recycler, JavascriptArrayBuffer, length, type);
Assert(result);
recycler->AddExternalMemoryUsage(length);
return result;
}
JavascriptArrayBuffer* JavascriptArrayBuffer::Create(byte* buffer, uint32 length, DynamicType * type)
{
Recycler* recycler = type->GetScriptContext()->GetRecycler();
JavascriptArrayBuffer* result = RecyclerNewFinalized(recycler, JavascriptArrayBuffer, buffer, length, type);
Assert(result);
recycler->AddExternalMemoryUsage(length);
return result;
}
ArrayBufferDetachedStateBase* JavascriptArrayBuffer::CreateDetachedState(BYTE* buffer, uint32 bufferLength)
{
#if ENABLE_FAST_ARRAYBUFFER
if (IsValidVirtualBufferLength(bufferLength))
{
return HeapNew(ArrayBufferDetachedState<FreeFn>, buffer, bufferLength, FreeMemAlloc, ArrayBufferAllocationType::MemAlloc);
}
else
{
return HeapNew(ArrayBufferDetachedState<FreeFn>, buffer, bufferLength, free, ArrayBufferAllocationType::Heap);
}
#else
return HeapNew(ArrayBufferDetachedState<FreeFn>, buffer, bufferLength, free, ArrayBufferAllocationType::Heap);
#endif
}
bool JavascriptArrayBuffer::IsValidAsmJsBufferLengthAlgo(uint length, bool forceCheck)
{
/*
1. length >= 2^16
2. length is power of 2 or (length > 2^24 and length is multiple of 2^24)
3. length is a multiple of 4K
*/
const bool isLongEnough = length >= 0x10000;
const bool isPow2 = ::Math::IsPow2(length);
// No need to check for length > 2^24, because it already has to be non zero length
const bool isMultipleOf2e24 = (length & 0xFFFFFF) == 0;
const bool isPageSizeMultiple = (length % AutoSystemInfo::PageSize) == 0;
return (
#ifndef ENABLE_FAST_ARRAYBUFFER
forceCheck &&
#endif
isLongEnough &&
(isPow2 || isMultipleOf2e24) &&
isPageSizeMultiple
);
}
bool JavascriptArrayBuffer::IsValidAsmJsBufferLength(uint length, bool forceCheck)
{
return IsValidAsmJsBufferLengthAlgo(length, forceCheck);
}
bool JavascriptArrayBuffer::IsValidVirtualBufferLength(uint length) const
{
#if ENABLE_FAST_ARRAYBUFFER
return !PHASE_OFF1(Js::TypedArrayVirtualPhase) && IsValidAsmJsBufferLengthAlgo(length, true);
#else
return false;
#endif
}
void JavascriptArrayBuffer::Finalize(bool isShutdown)
{
// In debugger scenario, ScriptAuthor can create scriptContext and delete scriptContext
// explicitly. So for the builtin, while javascriptLibrary is still alive fine, the
// matching scriptContext might have been deleted and the javascriptLibrary->scriptContext
// field reset (but javascriptLibrary is still alive).
// Use the recycler field off library instead of scriptcontext to avoid av.
// Recycler may not be available at Dispose. We need to
// free the memory and report that it has been freed at the same
// time. Otherwise, AllocationPolicyManager is unable to provide correct feedback
#if ENABLE_FAST_ARRAYBUFFER
//AsmJS Virtual Free
if (buffer && IsValidVirtualBufferLength(this->bufferLength))
{
FreeMemAlloc(buffer);
}
else
{
free(buffer);
}
#else
free(buffer);
#endif
Recycler* recycler = GetType()->GetLibrary()->GetRecycler();
recycler->ReportExternalMemoryFree(bufferLength);
buffer = nullptr;
bufferLength = 0;
}
void JavascriptArrayBuffer::Dispose(bool isShutdown)
{
/* See JavascriptArrayBuffer::Finalize */
}
// Copy memory from src to dst, truncate if dst smaller, zero extra memory
// if dst larger
static void MemCpyZero(__bcount(dstSize) BYTE* dst, size_t dstSize,
__in_bcount(count) const BYTE* src, size_t count)
{
js_memcpy_s(dst, dstSize, src, min(dstSize, count));
if (dstSize > count)
{
ZeroMemory(dst + count, dstSize - count);
}
}
// Same as realloc but zero newly allocated portion if newSize > oldSize
static BYTE* ReallocZero(BYTE* ptr, size_t oldSize, size_t newSize)
{
BYTE* ptrNew = (BYTE*)realloc(ptr, newSize);
if (ptrNew && newSize > oldSize)
{
ZeroMemory(ptrNew + oldSize, newSize - oldSize);
}
return ptrNew;
}
ArrayBuffer * JavascriptArrayBuffer::TransferInternal(uint32 newBufferLength)
{
ArrayBuffer* newArrayBuffer;
Recycler* recycler = this->GetRecycler();
if (this->bufferLength > 0)
{
ReportDifferentialAllocation(newBufferLength);
}
if (newBufferLength == 0 || this->bufferLength == 0)
{
newArrayBuffer = GetLibrary()->CreateArrayBuffer(newBufferLength);
if (newBufferLength > 0 && !newArrayBuffer->GetByteLength())
{
JavascriptError::ThrowOutOfMemoryError(GetScriptContext());
}
}
else
{
BYTE * newBuffer = nullptr;
if (IsValidVirtualBufferLength(this->bufferLength))
{
if (IsValidVirtualBufferLength(newBufferLength))
{
// we are transferring between an optimized buffer using a length that can be optimized
if (newBufferLength < this->bufferLength)
{
#pragma prefast(suppress:6250, "Calling 'VirtualFree' without the MEM_RELEASE flag might free memory but not address descriptors (VADs).")
VirtualFree(this->buffer + newBufferLength, this->bufferLength - newBufferLength, MEM_DECOMMIT);
}
else if (newBufferLength > this->bufferLength)
{
LPVOID newMem = VirtualAlloc(this->buffer + this->bufferLength, newBufferLength - this->bufferLength, MEM_COMMIT, PAGE_READWRITE);
if (!newMem)
{
recycler->ReportExternalMemoryFailure(newBufferLength - this->bufferLength);
JavascriptError::ThrowOutOfMemoryError(GetScriptContext());
}
}
newBuffer = this->buffer;
}
else
{
// we are transferring from an optimized buffer, but the new length isn't compatible, so start over and copy to new memory
newBuffer = (BYTE*)malloc(newBufferLength);
if (!newBuffer)
{
recycler->ReportExternalMemoryFailure(newBufferLength - this->bufferLength);
JavascriptError::ThrowOutOfMemoryError(GetScriptContext());
}
MemCpyZero(newBuffer, newBufferLength, this->buffer, this->bufferLength);
}
}
else
{
if (IsValidVirtualBufferLength(newBufferLength))
{
// we are transferring from an unoptimized buffer, but new length can be optimized, so move to that
newBuffer = (BYTE*)AsmJsVirtualAllocator(newBufferLength);
if (!newBuffer)
{
recycler->ReportExternalMemoryFailure(newBufferLength - this->bufferLength);
JavascriptError::ThrowOutOfMemoryError(GetScriptContext());
}
MemCpyZero(newBuffer, newBufferLength, this->buffer, this->bufferLength);
}
else if (newBufferLength != this->bufferLength)
{
// both sides will just be regular ArrayBuffer, so realloc
newBuffer = ReallocZero(this->buffer, this->bufferLength, newBufferLength);
if (!newBuffer)
{
recycler->ReportExternalMemoryFailure(newBufferLength - this->bufferLength);
JavascriptError::ThrowOutOfMemoryError(GetScriptContext());
}
}
else
{
newBuffer = this->buffer;
}
}
newArrayBuffer = GetLibrary()->CreateArrayBuffer(newBuffer, newBufferLength);
}
AutoDiscardPTR<Js::ArrayBufferDetachedStateBase> state(DetachAndGetState());
state->MarkAsClaimed();
return newArrayBuffer;
}
template<typename Func>
void Js::JavascriptArrayBuffer::ReportDifferentialAllocation(uint32 newBufferLength, Func reportFailureFn)
{
Recycler* recycler = this->GetRecycler();
// Report differential external memory allocation.
// If current bufferLength == 0, new ArrayBuffer creation records the allocation
// so no need to do it here.
if (newBufferLength != this->bufferLength)
{
// Expanding buffer
if (newBufferLength > this->bufferLength)
{
if (!recycler->ReportExternalMemoryAllocation(newBufferLength - this->bufferLength))
{
recycler->CollectNow<CollectOnTypedArrayAllocation>();
if (!recycler->ReportExternalMemoryAllocation(newBufferLength - this->bufferLength))
{
reportFailureFn();
}
}
}
// Contracting buffer
else
{
recycler->ReportExternalMemoryFree(this->bufferLength - newBufferLength);
}
}
}
void JavascriptArrayBuffer::ReportDifferentialAllocation(uint32 newBufferLength)
{
ScriptContext* scriptContext = GetScriptContext();
ReportDifferentialAllocation(newBufferLength, [scriptContext] {
JavascriptError::ThrowOutOfMemoryError(scriptContext);
});
}
#if ENABLE_TTD
TTD::NSSnapObjects::SnapObjectType JavascriptArrayBuffer::GetSnapTag_TTD() const
{
return TTD::NSSnapObjects::SnapObjectType::SnapArrayBufferObject;
}
void JavascriptArrayBuffer::ExtractSnapObjectDataInto(TTD::NSSnapObjects::SnapObject* objData, TTD::SlabAllocator& alloc)
{
TTD::NSSnapObjects::SnapArrayBufferInfo* sabi = alloc.SlabAllocateStruct<TTD::NSSnapObjects::SnapArrayBufferInfo>();
sabi->Length = this->GetByteLength();
if (sabi->Length == 0)
{
sabi->Buff = nullptr;
}
else
{
sabi->Buff = alloc.SlabAllocateArray<byte>(sabi->Length);
memcpy(sabi->Buff, this->GetBuffer(), sabi->Length);
}
TTD::NSSnapObjects::StdExtractSetKindSpecificInfo<TTD::NSSnapObjects::SnapArrayBufferInfo*, TTD::NSSnapObjects::SnapObjectType::SnapArrayBufferObject>(objData, sabi);
}
#endif
template<typename Allocator>
Js::WebAssemblyArrayBuffer::WebAssemblyArrayBuffer(uint32 length, DynamicType * type, Allocator allocator):
JavascriptArrayBuffer(length, type, allocator)
{
#ifndef ENABLE_FAST_ARRAYBUFFER
CompileAssert(UNREACHED);
#endif
Assert(allocator == WasmVirtualAllocator);
// Make sure we always have a buffer even if the length is 0
if (buffer == nullptr && length == 0)
{
// We want to allocate an empty buffer using virtual memory
buffer = (BYTE*)allocator(0);
}
if (buffer == nullptr)
{
JavascriptError::ThrowOutOfMemoryError(GetScriptContext());
}
}
// Treat as a normal JavascriptArrayBuffer
WebAssemblyArrayBuffer::WebAssemblyArrayBuffer(uint32 length, DynamicType * type) :
JavascriptArrayBuffer(length, type, malloc)
{
}
WebAssemblyArrayBuffer::WebAssemblyArrayBuffer(byte* buffer, uint32 length, DynamicType * type):
JavascriptArrayBuffer(buffer, length, type)
{
#if ENABLE_FAST_ARRAYBUFFER
if (CONFIG_FLAG(WasmFastArray) && buffer == nullptr)
{
JavascriptError::ThrowOutOfMemoryError(GetScriptContext());
}
#endif
}
WebAssemblyArrayBuffer* WebAssemblyArrayBuffer::Create(byte* buffer, uint32 length, DynamicType * type)
{
Recycler* recycler = type->GetScriptContext()->GetRecycler();
WebAssemblyArrayBuffer* result;
if (buffer)
{
result = RecyclerNewFinalized(recycler, WebAssemblyArrayBuffer, buffer, length, type);
}
else
{
#if ENABLE_FAST_ARRAYBUFFER
if (CONFIG_FLAG(WasmFastArray))
{
result = RecyclerNewFinalized(recycler, WebAssemblyArrayBuffer, length, type, WasmVirtualAllocator);
}
else
#endif
{
result = RecyclerNewFinalized(recycler, WebAssemblyArrayBuffer, length, type);
}
}
Assert(result);
recycler->AddExternalMemoryUsage(length);
return result;
}
bool WebAssemblyArrayBuffer::IsValidVirtualBufferLength(uint length) const
{
#if ENABLE_FAST_ARRAYBUFFER
return CONFIG_FLAG(WasmFastArray);
#else
return false;
#endif
}
WebAssemblyArrayBuffer* WebAssemblyArrayBuffer::GrowMemory(uint32 newBufferLength)
{
if (newBufferLength < this->bufferLength)
{
Assert(UNREACHED);
JavascriptError::ThrowTypeError(GetScriptContext(), WASMERR_BufferGrowOnly);
}
uint32 growSize = newBufferLength - this->bufferLength;
bool failedReport = false;
const auto reportFailedFn = [&failedReport] { failedReport = true; };
WebAssemblyArrayBuffer* newArrayBuffer = nullptr;
#if ENABLE_FAST_ARRAYBUFFER
if (CONFIG_FLAG(WasmFastArray))
{
AssertOrFailFast(this->buffer);
ReportDifferentialAllocation(newBufferLength, reportFailedFn);
if (failedReport)
{
return nullptr;
}
if (growSize > 0)
{
LPVOID newMem = VirtualAlloc(this->buffer + this->bufferLength, growSize, MEM_COMMIT, PAGE_READWRITE);
if (!newMem)
{
Recycler* recycler = this->GetRecycler();
recycler->ReportExternalMemoryFailure(newBufferLength);
return nullptr;
}
}
newArrayBuffer = GetLibrary()->CreateWebAssemblyArrayBuffer(this->buffer, newBufferLength);
}
else
#endif
if (this->GetByteLength() == 0)
{
if (growSize > 0)
{
newArrayBuffer = GetLibrary()->CreateWebAssemblyArrayBuffer(newBufferLength);
}
else
{
newArrayBuffer = GetLibrary()->CreateWebAssemblyArrayBuffer(this->buffer, 0);
}
}
else
{
ReportDifferentialAllocation(newBufferLength, reportFailedFn);
if (failedReport)
{
return nullptr;
}
byte* newBuffer = ReallocZero(this->buffer, this->bufferLength, newBufferLength);
if (!newBuffer)
{
this->GetRecycler()->ReportExternalMemoryFailure(newBufferLength - this->bufferLength);
return nullptr;
}
newArrayBuffer = GetLibrary()->CreateWebAssemblyArrayBuffer(newBuffer, newBufferLength);
}
if (!newArrayBuffer || newArrayBuffer->GetByteLength() != newBufferLength)
{
return nullptr;
}
AutoDiscardPTR<Js::ArrayBufferDetachedStateBase> state(DetachAndGetState());
state->MarkAsClaimed();
return newArrayBuffer;
}
ArrayBuffer * WebAssemblyArrayBuffer::TransferInternal(uint32 newBufferLength)
{
JavascriptError::ThrowTypeError(GetScriptContext(), WASMERR_CantDetach);
}
ProjectionArrayBuffer::ProjectionArrayBuffer(uint32 length, DynamicType * type) :
ArrayBuffer(length, type, CoTaskMemAlloc)
{
}
ProjectionArrayBuffer::ProjectionArrayBuffer(byte* buffer, uint32 length, DynamicType * type) :
ArrayBuffer(buffer, length, type)
{
}
ProjectionArrayBuffer* ProjectionArrayBuffer::Create(uint32 length, DynamicType * type)
{
Recycler* recycler = type->GetScriptContext()->GetRecycler();
recycler->AddExternalMemoryUsage(length);
return RecyclerNewFinalized(recycler, ProjectionArrayBuffer, length, type);
}
ProjectionArrayBuffer* ProjectionArrayBuffer::Create(byte* buffer, uint32 length, DynamicType * type)
{
Recycler* recycler = type->GetScriptContext()->GetRecycler();
// This is user passed [in] buffer, user should AddExternalMemoryUsage before calling jscript, but
// I don't see we ask everyone to do this. Let's add the memory pressure here as well.
recycler->AddExternalMemoryUsage(length);
return RecyclerNewFinalized(recycler, ProjectionArrayBuffer, buffer, length, type);
}
void ProjectionArrayBuffer::Dispose(bool isShutdown)
{
CoTaskMemFree(buffer);
}
ArrayBuffer * ProjectionArrayBuffer::TransferInternal(uint32 newBufferLength)
{
ArrayBuffer* newArrayBuffer;
if (newBufferLength == 0 || this->bufferLength == 0)
{
newArrayBuffer = GetLibrary()->CreateProjectionArraybuffer(newBufferLength);
}
else
{
BYTE * newBuffer = (BYTE*)CoTaskMemRealloc(this->buffer, newBufferLength);
if (!newBuffer)
{
JavascriptError::ThrowOutOfMemoryError(GetScriptContext());
}
newArrayBuffer = GetLibrary()->CreateProjectionArraybuffer(newBuffer, newBufferLength);
}
AutoDiscardPTR<Js::ArrayBufferDetachedStateBase> state(DetachAndGetState());
state->MarkAsClaimed();
return newArrayBuffer;
}
ExternalArrayBuffer::ExternalArrayBuffer(byte *buffer, uint32 length, DynamicType *type)
: ArrayBuffer(buffer, length, type)
{
}
#if ENABLE_TTD
TTD::NSSnapObjects::SnapObjectType ExternalArrayBuffer::GetSnapTag_TTD() const
{
//We re-map ExternalArrayBuffers to regular buffers since the 'real' host will be gone when we replay
return TTD::NSSnapObjects::SnapObjectType::SnapArrayBufferObject;
}
void ExternalArrayBuffer::ExtractSnapObjectDataInto(TTD::NSSnapObjects::SnapObject* objData, TTD::SlabAllocator& alloc)
{
TTD::NSSnapObjects::SnapArrayBufferInfo* sabi = alloc.SlabAllocateStruct<TTD::NSSnapObjects::SnapArrayBufferInfo>();
sabi->Length = this->GetByteLength();
if(sabi->Length == 0)
{
sabi->Buff = nullptr;
}
else
{
sabi->Buff = alloc.SlabAllocateArray<byte>(sabi->Length);
memcpy(sabi->Buff, this->GetBuffer(), sabi->Length);
}
TTD::NSSnapObjects::StdExtractSetKindSpecificInfo<TTD::NSSnapObjects::SnapArrayBufferInfo*, TTD::NSSnapObjects::SnapObjectType::SnapArrayBufferObject>(objData, sabi);
}
#endif
}