blob: fd1e7a9fe108106c1c2fb4358bcc77a6da60ff8a [file] [log] [blame]
//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------
// Implements ArrayBuffer according to Khronos spec.
//----------------------------------------------------------------------------
#pragma once
namespace Js
{
class ArrayBufferParent;
class ArrayBuffer : public DynamicObject
{
private:
static PropertyId const specialPropertyIds[];
public:
// we need to install cross-site thunk on the nested array buffer when marshaling
// typed array.
DEFINE_VTABLE_CTOR_ABSTRACT(ArrayBuffer, DynamicObject);
virtual void MarshalToScriptContext(Js::ScriptContext * scriptContext) = 0;
#define MAX_ASMJS_ARRAYBUFFER_LENGTH 0x100000000 //4GB
private:
bool GetPropertyBuiltIns(PropertyId propertyId, Var* value, BOOL* result);
void ClearParentsLength(ArrayBufferParent* parent);
static uint32 GetByteLengthFromVar(ScriptContext* scriptContext, Var length);
public:
template <typename FreeFN>
class ArrayBufferDetachedState : public ArrayBufferDetachedStateBase
{
public:
FreeFN* freeFunction;
ArrayBufferDetachedState(BYTE* buffer, uint32 bufferLength, FreeFN* freeFunction, ArrayBufferAllocationType allocationType)
: ArrayBufferDetachedStateBase(TypeIds_ArrayBuffer, buffer, bufferLength, allocationType),
freeFunction(freeFunction)
{}
virtual void ClearSelfOnly() override
{
HeapDelete(this);
}
virtual void DiscardState() override
{
if (this->buffer != nullptr)
{
freeFunction(this->buffer);
this->buffer = nullptr;
}
this->bufferLength = 0;
}
virtual void Discard() override
{
ClearSelfOnly();
}
};
template <typename Allocator>
ArrayBuffer(__declspec(guard(overflow)) uint32 length, DynamicType * type, Allocator allocator);
ArrayBuffer(byte* buffer, __declspec(guard(overflow)) uint32 length, DynamicType * type);
class EntryInfo
{
public:
static FunctionInfo NewInstance;
static FunctionInfo Slice;
static FunctionInfo IsView;
static FunctionInfo GetterByteLength;
static FunctionInfo GetterSymbolSpecies;
static FunctionInfo Transfer;
};
static Var NewInstance(RecyclableObject* function, CallInfo callInfo, ...);
static Var EntrySlice(RecyclableObject* function, CallInfo callInfo, ...);
static Var EntryIsView(RecyclableObject* function, CallInfo callInfo, ...);
static Var EntryGetterByteLength(RecyclableObject* function, CallInfo callInfo, ...);
static Var EntryGetterSymbolSpecies(RecyclableObject* function, CallInfo callInfo, ...);
static Var EntryTransfer(RecyclableObject* function, CallInfo callInfo, ...);
static bool Is(Var aValue);
static ArrayBuffer* NewFromDetachedState(DetachedStateBase* state, JavascriptLibrary *library);
static ArrayBuffer* FromVar(Var aValue);
virtual BOOL HasProperty(Js::PropertyId propertyId) override;
virtual BOOL GetProperty(Js::Var originalInstance, Js::PropertyId propertyId, Js::Var* value, Js::PropertyValueInfo* info, Js::ScriptContext* requestContext) override;
virtual BOOL GetProperty(Js::Var originalInstance, Js::JavascriptString* propertyNameString, Js::Var* value, Js::PropertyValueInfo* info, Js::ScriptContext* requestContext) override;
virtual BOOL GetPropertyReference(Js::Var originalInstance, Js::PropertyId propertyId, Js::Var* value, Js::PropertyValueInfo* info, Js::ScriptContext* requestContext) override;
virtual BOOL SetProperty(Js::PropertyId propertyId, Js::Var value, Js::PropertyOperationFlags flags, Js::PropertyValueInfo* info) override;
virtual BOOL SetProperty(Js::JavascriptString* propertyNameString, Js::Var value, Js::PropertyOperationFlags flags, Js::PropertyValueInfo* info) override;
virtual BOOL DeleteProperty(Js::PropertyId propertyId, Js::PropertyOperationFlags flags) override;
virtual BOOL IsEnumerable(PropertyId propertyId) override;
virtual BOOL IsConfigurable(PropertyId propertyId) override;
virtual BOOL IsWritable(PropertyId propertyId) override;
virtual BOOL SetEnumerable(PropertyId propertyId, BOOL value) override;
virtual BOOL SetWritable(PropertyId propertyId, BOOL value) override;
virtual BOOL SetConfigurable(PropertyId propertyId, BOOL value) override;
virtual BOOL SetAttributes(PropertyId propertyId, PropertyAttributes attributes) override;
virtual BOOL SetAccessors(PropertyId propertyId, Var getter, Var setter, PropertyOperationFlags flags) override;
virtual BOOL GetSpecialPropertyName(uint32 index, Var *propertyName, ScriptContext * requestContext) override;
virtual uint GetSpecialPropertyCount() const override;
virtual PropertyId const * GetSpecialPropertyIds() const override;
virtual BOOL InitProperty(Js::PropertyId propertyId, Js::Var value, PropertyOperationFlags flags = PropertyOperation_None, Js::PropertyValueInfo* info = NULL) override;
virtual BOOL SetPropertyWithAttributes(PropertyId propertyId, Var value, PropertyAttributes attributes, PropertyValueInfo* info, PropertyOperationFlags flags = PropertyOperation_None , SideEffects possibleSideEffects = SideEffects_Any) override;
virtual BOOL GetDiagTypeString(StringBuilder<ArenaAllocator>* stringBuilder, ScriptContext* requestContext) override;
virtual BOOL GetDiagValueString(StringBuilder<ArenaAllocator>* stringBuilder, ScriptContext* requestContext) override;
virtual ArrayBufferDetachedStateBase* DetachAndGetState();
bool IsDetached() { return this->isDetached; }
void SetIsAsmJsBuffer(){ mIsAsmJsBuffer = true; }
uint32 GetByteLength() const { return bufferLength; }
BYTE* GetBuffer() const { return buffer; }
static int GetByteLengthOffset() { return offsetof(ArrayBuffer, bufferLength); }
static int GetIsDetachedOffset() { return offsetof(ArrayBuffer, isDetached); }
static int GetBufferOffset() { return offsetof(ArrayBuffer, buffer); }
void AddParent(ArrayBufferParent* parent);
void RemoveParent(ArrayBufferParent* parent);
#if _WIN64
//maximum 2G -1 for amd64
static const uint32 MaxArrayBufferLength = 0x7FFFFFFF;
#else
// maximum 1G to avoid arithmetic overflow.
static const uint32 MaxArrayBufferLength = 1 << 30;
#endif
virtual bool IsValidAsmJsBufferLength(uint length, bool forceCheck = false) { return false; }
virtual bool IsValidVirtualBufferLength(uint length) { return false; }
protected:
typedef void __cdecl FreeFn(void* ptr);
virtual ArrayBufferDetachedStateBase* CreateDetachedState(BYTE* buffer, __declspec(guard(overflow)) uint32 bufferLength) = 0;
virtual ArrayBuffer * TransferInternal(__declspec(guard(overflow)) uint32 newBufferLength) = 0;
inline BOOL IsBuiltinProperty(PropertyId);
static uint32 GetIndexFromVar(Js::Var arg, uint32 length, ScriptContext* scriptContext);
//In most cases, the ArrayBuffer will only have one parent
RecyclerWeakReference<ArrayBufferParent>* primaryParent;
JsUtil::List<RecyclerWeakReference<ArrayBufferParent>*>* otherParents;
BYTE *buffer; // Points to a heap allocated RGBA buffer, can be null
uint32 bufferLength; // Number of bytes allocated
// When an ArrayBuffer is detached, the TypedArray and DataView objects pointing to it must be made aware,
// for this purpose the ArrayBuffer needs to hold WeakReferences to them
bool isDetached;
bool mIsAsmJsBuffer;
bool isBufferCleared;
};
class ArrayBufferParent : public ArrayObject
{
friend ArrayBuffer;
private:
ArrayBuffer* arrayBuffer;
protected:
DEFINE_VTABLE_CTOR_ABSTRACT(ArrayBufferParent, ArrayObject);
ArrayBufferParent(DynamicType * type, uint32 length, ArrayBuffer* arrayBuffer)
: ArrayObject(type, /*initSlots*/true, length),
arrayBuffer(arrayBuffer)
{
arrayBuffer->AddParent(this);
}
void ClearArrayBuffer()
{
if (this->arrayBuffer != nullptr)
{
this->arrayBuffer->RemoveParent(this);
this->arrayBuffer = nullptr;
}
}
void SetArrayBuffer(ArrayBuffer* arrayBuffer)
{
this->ClearArrayBuffer();
if (arrayBuffer != nullptr)
{
this->arrayBuffer->AddParent(this);
this->arrayBuffer = arrayBuffer;
}
}
public:
ArrayBuffer* GetArrayBuffer() const
{
return this->arrayBuffer;
}
};
// Normally we use malloc/free; for ArrayBuffer created from projection we need to use different allocator.
class JavascriptArrayBuffer : public ArrayBuffer
{
protected:
DEFINE_VTABLE_CTOR(JavascriptArrayBuffer, ArrayBuffer);
DEFINE_MARSHAL_OBJECT_TO_SCRIPT_CONTEXT(JavascriptArrayBuffer);
public:
static JavascriptArrayBuffer* Create(__declspec(guard(overflow)) uint32 length, DynamicType * type);
static JavascriptArrayBuffer* Create(byte* buffer, __declspec(guard(overflow)) uint32 length, DynamicType * type);
virtual void Dispose(bool isShutdown) override;
virtual void Finalize(bool isShutdown) override;
static void*__cdecl AllocWrapper(__declspec(guard(overflow)) size_t length)
{
#if _WIN64
LPVOID address = VirtualAlloc(nullptr, MAX_ASMJS_ARRAYBUFFER_LENGTH, MEM_RESERVE, PAGE_NOACCESS);
//throw out of memory
if (!address)
{
Js::Throw::OutOfMemory();
}
LPVOID arrayAddress = VirtualAlloc(address, length, MEM_COMMIT, PAGE_READWRITE);
if (!arrayAddress)
{
VirtualFree(address, 0, MEM_RELEASE);
Js::Throw::OutOfMemory();
}
return arrayAddress;
#else
Assert(false);
return nullptr;
#endif
}
static void FreeMemAlloc(Var ptr)
{
BOOL fSuccess = VirtualFree((LPVOID)ptr, 0, MEM_RELEASE);
Assert(fSuccess);
}
virtual bool IsValidAsmJsBufferLength(uint length, bool forceCheck = false) override;
virtual bool IsValidVirtualBufferLength(uint length) override;
protected:
JavascriptArrayBuffer(DynamicType * type);
virtual ArrayBufferDetachedStateBase* CreateDetachedState(BYTE* buffer, __declspec(guard(overflow)) uint32 bufferLength) override;
virtual ArrayBuffer * TransferInternal(__declspec(guard(overflow)) uint32 newBufferLength) override;
private:
JavascriptArrayBuffer(uint32 length, DynamicType * type);
JavascriptArrayBuffer(byte* buffer, uint32 length, DynamicType * type);
};
// the memory must be allocated via CoTaskMemAlloc.
class ProjectionArrayBuffer : public ArrayBuffer
{
protected:
DEFINE_VTABLE_CTOR(ProjectionArrayBuffer, ArrayBuffer);
DEFINE_MARSHAL_OBJECT_TO_SCRIPT_CONTEXT(ProjectionArrayBuffer);
typedef void __stdcall FreeFn(LPVOID ptr);
virtual ArrayBufferDetachedStateBase* CreateDetachedState(BYTE* buffer, __declspec(guard(overflow)) uint32 bufferLength) override
{
return HeapNew(ArrayBufferDetachedState<FreeFn>, buffer, bufferLength, CoTaskMemFree, ArrayBufferAllocationType::CoTask);
}
virtual ArrayBuffer * TransferInternal(__declspec(guard(overflow)) uint32 newBufferLength) override;
public:
// Create constructor. script engine creates a buffer allocated via CoTaskMemAlloc.
static ProjectionArrayBuffer* Create(__declspec(guard(overflow)) uint32 length, DynamicType * type);
// take over ownership. a CoTaskMemAlloc'ed buffer passed in via projection.
static ProjectionArrayBuffer* Create(byte* buffer, __declspec(guard(overflow)) uint32 length, DynamicType * type);
virtual void Dispose(bool isShutdown) override;
virtual void Finalize(bool isShutdown) override {};
private:
ProjectionArrayBuffer(uint32 length, DynamicType * type);
ProjectionArrayBuffer(byte* buffer, uint32 length, DynamicType * type);
};
// non-owning ArrayBuffer used for wrapping external data
class ExternalArrayBuffer : public ArrayBuffer
{
protected:
DEFINE_VTABLE_CTOR(ExternalArrayBuffer, ArrayBuffer);
DEFINE_MARSHAL_OBJECT_TO_SCRIPT_CONTEXT(ExternalArrayBuffer);
public:
ExternalArrayBuffer(byte *buffer, __declspec(guard(overflow)) uint32 length, DynamicType *type);
protected:
virtual ArrayBufferDetachedStateBase* CreateDetachedState(BYTE* buffer, __declspec(guard(overflow)) uint32 bufferLength) override { Assert(UNREACHED); Throw::InternalError(); };
virtual ArrayBuffer * TransferInternal(__declspec(guard(overflow)) uint32 newBufferLength) override { Assert(UNREACHED); Throw::InternalError(); };
};
}