blob: abd9042816c299fb41217c594694d6b1f64eeb9c [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
{
JavascriptWeakMap::JavascriptWeakMap(DynamicType* type)
: DynamicObject(type),
keySet(type->GetScriptContext()->GetRecycler())
{
}
bool JavascriptWeakMap::Is(Var aValue)
{
return JavascriptOperators::GetTypeId(aValue) == TypeIds_WeakMap;
}
JavascriptWeakMap* JavascriptWeakMap::FromVar(Var aValue)
{
AssertMsg(Is(aValue), "Ensure var is actually a 'JavascriptWeakMap'");
return static_cast<JavascriptWeakMap *>(RecyclableObject::FromVar(aValue));
}
JavascriptWeakMap::WeakMapKeyMap* JavascriptWeakMap::GetWeakMapKeyMapFromKey(DynamicObject* key) const
{
Var weakMapKeyData = nullptr;
if (!key->GetInternalProperty(key, InternalPropertyIds::WeakMapKeyMap, &weakMapKeyData, nullptr, key->GetScriptContext()))
{
return nullptr;
}
if (key->GetScriptContext()->GetLibrary()->GetUndefined() == weakMapKeyData)
{
// Assert to find out where this can happen.
Assert(false);
return nullptr;
}
return static_cast<WeakMapKeyMap*>(weakMapKeyData);
}
JavascriptWeakMap::WeakMapKeyMap* JavascriptWeakMap::AddWeakMapKeyMapToKey(DynamicObject* key)
{
// The internal property may exist on an object that has had DynamicObject::ResetObject called on itself.
// In that case the value stored in the property slot should be null.
DebugOnly(Var unused = nullptr);
Assert(!key->GetInternalProperty(key, InternalPropertyIds::WeakMapKeyMap, &unused, nullptr, key->GetScriptContext()) || unused == nullptr);
WeakMapKeyMap* weakMapKeyData = RecyclerNew(GetScriptContext()->GetRecycler(), WeakMapKeyMap, GetScriptContext()->GetRecycler());
BOOL success = key->SetInternalProperty(InternalPropertyIds::WeakMapKeyMap, weakMapKeyData, PropertyOperation_Force, nullptr);
Assert(success);
return weakMapKeyData;
}
bool JavascriptWeakMap::KeyMapGet(WeakMapKeyMap* map, Var* value) const
{
if (map->ContainsKey(GetWeakMapId()))
{
*value = map->Item(GetWeakMapId());
return true;
}
return false;
}
Var JavascriptWeakMap::NewInstance(RecyclableObject* function, CallInfo callInfo, ...)
{
PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
ARGUMENTS(args, callInfo);
ScriptContext* scriptContext = function->GetScriptContext();
JavascriptLibrary* library = scriptContext->GetLibrary();
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);
CHAKRATEL_LANGSTATS_INC_DATACOUNT(ES6_WeakMap);
JavascriptWeakMap* weakMapObject = nullptr;
if (callInfo.Flags & CallFlags_New)
{
weakMapObject = library->CreateWeakMap();
}
else
{
JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("WeakMap"), _u("WeakMap"));
}
Assert(weakMapObject != nullptr);
Var iterable = (args.Info.Count > 1) ? args[1] : library->GetUndefined();
RecyclableObject* iter = nullptr;
RecyclableObject* adder = nullptr;
if (JavascriptConversion::CheckObjectCoercible(iterable, scriptContext))
{
iter = JavascriptOperators::GetIterator(iterable, scriptContext);
Var adderVar = JavascriptOperators::GetProperty(weakMapObject, PropertyIds::set, scriptContext);
if (!JavascriptConversion::IsCallable(adderVar))
{
JavascriptError::ThrowTypeError(scriptContext, JSERR_NeedFunction);
}
adder = RecyclableObject::FromVar(adderVar);
}
if (iter != nullptr)
{
Var undefined = library->GetUndefined();
JavascriptOperators::DoIteratorStepAndValue(iter, scriptContext, [&](Var nextItem) {
if (!JavascriptOperators::IsObject(nextItem))
{
JavascriptError::ThrowTypeError(scriptContext, JSERR_NeedObject);
}
RecyclableObject* obj = RecyclableObject::FromVar(nextItem);
Var key, value;
if (!JavascriptOperators::GetItem(obj, 0u, &key, scriptContext))
{
key = undefined;
}
if (!JavascriptOperators::GetItem(obj, 1u, &value, scriptContext))
{
value = undefined;
}
CALL_FUNCTION(scriptContext->GetThreadContext(), adder, CallInfo(CallFlags_Value, 3), weakMapObject, key, value);
});
}
#if ENABLE_TTD
//TODO: right now we always GC before snapshots (assuming we have a weak collection)
// may want to optimize this and use a notify here that we have a weak container -- also update post inflate and post snap
#endif
return isCtorSuperCall ?
JavascriptOperators::OrdinaryCreateFromConstructor(RecyclableObject::FromVar(newTarget), weakMapObject, nullptr, scriptContext) :
weakMapObject;
}
Var JavascriptWeakMap::EntryDelete(RecyclableObject* function, CallInfo callInfo, ...)
{
PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
ARGUMENTS(args, callInfo);
ScriptContext* scriptContext = function->GetScriptContext();
if (!JavascriptWeakMap::Is(args[0]))
{
JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("WeakMap.prototype.delete"), _u("WeakMap"));
}
JavascriptWeakMap* weakMap = JavascriptWeakMap::FromVar(args[0]);
Var key = (args.Info.Count > 1) ? args[1] : scriptContext->GetLibrary()->GetUndefined();
bool didDelete = false;
if (JavascriptOperators::IsObject(key) && JavascriptOperators::GetTypeId(key) != TypeIds_HostDispatch)
{
DynamicObject* keyObj = DynamicObject::FromVar(key);
didDelete = weakMap->Delete(keyObj);
}
#if ENABLE_TTD
if(scriptContext->IsTTDRecordOrReplayModeEnabled())
{
if(scriptContext->IsTTDRecordModeEnabled())
{
function->GetScriptContext()->GetThreadContext()->TTDLog->RecordWeakCollectionContainsEvent(didDelete);
}
else
{
didDelete = function->GetScriptContext()->GetThreadContext()->TTDLog->ReplayWeakCollectionContainsEvent();
}
}
#endif
return scriptContext->GetLibrary()->CreateBoolean(didDelete);
}
Var JavascriptWeakMap::EntryGet(RecyclableObject* function, CallInfo callInfo, ...)
{
PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
ARGUMENTS(args, callInfo);
ScriptContext* scriptContext = function->GetScriptContext();
if (!JavascriptWeakMap::Is(args[0]))
{
JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("WeakMap.prototype.get"), _u("WeakMap"));
}
JavascriptWeakMap* weakMap = JavascriptWeakMap::FromVar(args[0]);
Var key = (args.Info.Count > 1) ? args[1] : scriptContext->GetLibrary()->GetUndefined();
bool loaded = false;
Var value = nullptr;
if (JavascriptOperators::IsObject(key) && JavascriptOperators::GetTypeId(key) != TypeIds_HostDispatch)
{
DynamicObject* keyObj = DynamicObject::FromVar(key);
loaded = weakMap->Get(keyObj, &value);
}
#if ENABLE_TTD
if(scriptContext->IsTTDRecordOrReplayModeEnabled())
{
if(scriptContext->IsTTDRecordModeEnabled())
{
function->GetScriptContext()->GetThreadContext()->TTDLog->RecordWeakCollectionContainsEvent(loaded);
}
else
{
loaded = function->GetScriptContext()->GetThreadContext()->TTDLog->ReplayWeakCollectionContainsEvent();
}
}
#endif
return loaded ? value : scriptContext->GetLibrary()->GetUndefined();
}
Var JavascriptWeakMap::EntryHas(RecyclableObject* function, CallInfo callInfo, ...)
{
PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
ARGUMENTS(args, callInfo);
ScriptContext* scriptContext = function->GetScriptContext();
if (!JavascriptWeakMap::Is(args[0]))
{
JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("WeakMap.prototype.has"), _u("WeakMap"));
}
JavascriptWeakMap* weakMap = JavascriptWeakMap::FromVar(args[0]);
Var key = (args.Info.Count > 1) ? args[1] : scriptContext->GetLibrary()->GetUndefined();
bool hasValue = false;
if (JavascriptOperators::IsObject(key) && JavascriptOperators::GetTypeId(key) != TypeIds_HostDispatch)
{
DynamicObject* keyObj = DynamicObject::FromVar(key);
hasValue = weakMap->Has(keyObj);
}
#if ENABLE_TTD
if(scriptContext->IsTTDRecordOrReplayModeEnabled())
{
if(scriptContext->IsTTDRecordModeEnabled())
{
function->GetScriptContext()->GetThreadContext()->TTDLog->RecordWeakCollectionContainsEvent(hasValue);
}
else
{
hasValue = function->GetScriptContext()->GetThreadContext()->TTDLog->ReplayWeakCollectionContainsEvent();
}
}
#endif
return scriptContext->GetLibrary()->CreateBoolean(hasValue);
}
Var JavascriptWeakMap::EntrySet(RecyclableObject* function, CallInfo callInfo, ...)
{
PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
ARGUMENTS(args, callInfo);
ScriptContext* scriptContext = function->GetScriptContext();
if (!JavascriptWeakMap::Is(args[0]))
{
JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("WeakMap.prototype.set"), _u("WeakMap"));
}
JavascriptWeakMap* weakMap = JavascriptWeakMap::FromVar(args[0]);
Var key = (args.Info.Count > 1) ? args[1] : scriptContext->GetLibrary()->GetUndefined();
Var value = (args.Info.Count > 2) ? args[2] : scriptContext->GetLibrary()->GetUndefined();
if (!JavascriptOperators::IsObject(key) || JavascriptOperators::GetTypeId(key) == TypeIds_HostDispatch)
{
// HostDispatch can not expand so can't have internal property added to it.
// TODO: Support HostDispatch as WeakMap key
JavascriptError::ThrowTypeError(scriptContext, JSERR_WeakMapSetKeyNotAnObject, _u("WeakMap.prototype.set"));
}
DynamicObject* keyObj = DynamicObject::FromVar(key);
#if ENABLE_TTD
//In replay we need to pin the object (and will release at snapshot points) -- in record we don't need to do anything
if(scriptContext->IsTTDReplayModeEnabled())
{
scriptContext->TTDContextInfo->TTDWeakReferencePinSet->AddNew(keyObj);
}
#endif
weakMap->Set(keyObj, value);
return weakMap;
}
void JavascriptWeakMap::Clear()
{
keySet.Map([&](DynamicObject* key, bool value, const RecyclerWeakReference<DynamicObject>* weakRef) {
WeakMapKeyMap* keyMap = GetWeakMapKeyMapFromKey(key);
// It may be the case that a CEO has been reset and the keyMap is now null.
// Just ignore it in this case, the keyMap has already been collected.
if (keyMap != nullptr)
{
// It may also be the case that a CEO has been reset and then added to a separate WeakMap,
// creating a new WeakMapKeyMap on the CEO. In this case GetWeakMapId() may not be in the
// keyMap, so don't assert successful removal here.
keyMap->Remove(GetWeakMapId());
}
});
keySet.Clear();
}
bool JavascriptWeakMap::Delete(DynamicObject* key)
{
WeakMapKeyMap* keyMap = GetWeakMapKeyMapFromKey(key);
if (keyMap != nullptr)
{
bool unused = false;
bool inSet = keySet.TryGetValueAndRemove(key, &unused);
bool inData = keyMap->Remove(GetWeakMapId());
Assert(inSet == inData);
return inData;
}
return false;
}
bool JavascriptWeakMap::Get(DynamicObject* key, Var* value) const
{
WeakMapKeyMap* keyMap = GetWeakMapKeyMapFromKey(key);
if (keyMap != nullptr)
{
return KeyMapGet(keyMap, value);
}
return false;
}
bool JavascriptWeakMap::Has(DynamicObject* key) const
{
WeakMapKeyMap* keyMap = GetWeakMapKeyMapFromKey(key);
if (keyMap != nullptr)
{
return keyMap->ContainsKey(GetWeakMapId());
}
return false;
}
void JavascriptWeakMap::Set(DynamicObject* key, Var value)
{
WeakMapKeyMap* keyMap = GetWeakMapKeyMapFromKey(key);
if (keyMap == nullptr)
{
keyMap = AddWeakMapKeyMapToKey(key);
}
keyMap->Item(GetWeakMapId(), value);
keySet.Item(key, true);
}
BOOL JavascriptWeakMap::GetDiagTypeString(StringBuilder<ArenaAllocator>* stringBuilder, ScriptContext* requestContext)
{
stringBuilder->AppendCppLiteral(_u("WeakMap"));
return TRUE;
}
#if ENABLE_TTD
void JavascriptWeakMap::MarkVisitKindSpecificPtrs(TTD::SnapshotExtractor* extractor)
{
//All weak things should be reachable from another root so no need to mark but do need to repopulate the pin sets if in replay mode
Js::ScriptContext* scriptContext = this->GetScriptContext();
if(scriptContext->IsTTDReplayModeEnabled())
{
this->Map([&](DynamicObject* key, Js::Var value)
{
scriptContext->TTDContextInfo->TTDWeakReferencePinSet->AddNew(key);
});
}
//Keys are weak so are always reachable from somewhere else but values are not so we must walk them
this->Map([&](DynamicObject* key, Js::Var value)
{
extractor->MarkVisitVar(value);
});
}
TTD::NSSnapObjects::SnapObjectType JavascriptWeakMap::GetSnapTag_TTD() const
{
return TTD::NSSnapObjects::SnapObjectType::SnapMapObject;
}
void JavascriptWeakMap::ExtractSnapObjectDataInto(TTD::NSSnapObjects::SnapObject* objData, TTD::SlabAllocator& alloc)
{
TTD::NSSnapObjects::SnapMapInfo* smi = alloc.SlabAllocateStruct<TTD::NSSnapObjects::SnapMapInfo>();
uint32 mapCountEst = this->Size() * 2;
smi->MapSize = 0;
smi->MapKeyValueArray = alloc.SlabReserveArraySpace<TTD::TTDVar>(mapCountEst + 1); //always reserve at least 1 element
this->Map([&](DynamicObject* key, Js::Var value)
{
AssertMsg(smi->MapSize + 1 < mapCountEst, "We are writting junk");
smi->MapKeyValueArray[smi->MapSize] = key;
smi->MapKeyValueArray[smi->MapSize + 1] = value;
smi->MapSize += 2;
});
if(smi->MapSize == 0)
{
smi->MapKeyValueArray = nullptr;
alloc.SlabAbortArraySpace<TTD::TTDVar>(mapCountEst + 1);
}
else
{
alloc.SlabCommitArraySpace<TTD::TTDVar>(smi->MapSize, mapCountEst + 1);
}
TTD::NSSnapObjects::StdExtractSetKindSpecificInfo<TTD::NSSnapObjects::SnapMapInfo*, TTD::NSSnapObjects::SnapObjectType::SnapMapObject>(objData, smi);
}
#endif
}