JavascriptWeakMap.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444
  1. //-------------------------------------------------------------------------------------------------------
  2. // Copyright (C) Microsoft. All rights reserved.
  3. // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
  4. //-------------------------------------------------------------------------------------------------------
  5. #include "RuntimeLibraryPch.h"
  6. namespace Js
  7. {
  8. JavascriptWeakMap::JavascriptWeakMap(DynamicType* type)
  9. : DynamicObject(type),
  10. keySet(type->GetScriptContext()->GetRecycler())
  11. {
  12. }
  13. bool JavascriptWeakMap::Is(Var aValue)
  14. {
  15. return JavascriptOperators::GetTypeId(aValue) == TypeIds_WeakMap;
  16. }
  17. JavascriptWeakMap* JavascriptWeakMap::FromVar(Var aValue)
  18. {
  19. AssertMsg(Is(aValue), "Ensure var is actually a 'JavascriptWeakMap'");
  20. return static_cast<JavascriptWeakMap *>(RecyclableObject::FromVar(aValue));
  21. }
  22. JavascriptWeakMap::WeakMapKeyMap* JavascriptWeakMap::GetWeakMapKeyMapFromKey(DynamicObject* key) const
  23. {
  24. Var weakMapKeyData = nullptr;
  25. if (!key->GetInternalProperty(key, InternalPropertyIds::WeakMapKeyMap, &weakMapKeyData, nullptr, key->GetScriptContext()))
  26. {
  27. return nullptr;
  28. }
  29. if (key->GetScriptContext()->GetLibrary()->GetUndefined() == weakMapKeyData)
  30. {
  31. // Assert to find out where this can happen.
  32. Assert(false);
  33. return nullptr;
  34. }
  35. return static_cast<WeakMapKeyMap*>(weakMapKeyData);
  36. }
  37. JavascriptWeakMap::WeakMapKeyMap* JavascriptWeakMap::AddWeakMapKeyMapToKey(DynamicObject* key)
  38. {
  39. // The internal property may exist on an object that has had DynamicObject::ResetObject called on itself.
  40. // In that case the value stored in the property slot should be null.
  41. DebugOnly(Var unused = nullptr);
  42. Assert(!key->GetInternalProperty(key, InternalPropertyIds::WeakMapKeyMap, &unused, nullptr, key->GetScriptContext()) || unused == nullptr);
  43. WeakMapKeyMap* weakMapKeyData = RecyclerNew(GetScriptContext()->GetRecycler(), WeakMapKeyMap, GetScriptContext()->GetRecycler());
  44. BOOL success = key->SetInternalProperty(InternalPropertyIds::WeakMapKeyMap, weakMapKeyData, PropertyOperation_Force, nullptr);
  45. Assert(success);
  46. return weakMapKeyData;
  47. }
  48. bool JavascriptWeakMap::KeyMapGet(WeakMapKeyMap* map, Var* value) const
  49. {
  50. if (map->ContainsKey(GetWeakMapId()))
  51. {
  52. *value = map->Item(GetWeakMapId());
  53. return true;
  54. }
  55. return false;
  56. }
  57. Var JavascriptWeakMap::NewInstance(RecyclableObject* function, CallInfo callInfo, ...)
  58. {
  59. PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
  60. ARGUMENTS(args, callInfo);
  61. ScriptContext* scriptContext = function->GetScriptContext();
  62. JavascriptLibrary* library = scriptContext->GetLibrary();
  63. Var newTarget = callInfo.Flags & CallFlags_NewTarget ? args.Values[args.Info.Count] : args[0];
  64. bool isCtorSuperCall = (callInfo.Flags & CallFlags_New) && newTarget != nullptr && !JavascriptOperators::IsUndefined(newTarget);
  65. Assert(isCtorSuperCall || !(callInfo.Flags & CallFlags_New) || args[0] == nullptr);
  66. CHAKRATEL_LANGSTATS_INC_DATACOUNT(ES6_WeakMap);
  67. JavascriptWeakMap* weakMapObject = nullptr;
  68. if (callInfo.Flags & CallFlags_New)
  69. {
  70. weakMapObject = library->CreateWeakMap();
  71. }
  72. else
  73. {
  74. JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("WeakMap"), _u("WeakMap"));
  75. }
  76. Assert(weakMapObject != nullptr);
  77. Var iterable = (args.Info.Count > 1) ? args[1] : library->GetUndefined();
  78. RecyclableObject* iter = nullptr;
  79. RecyclableObject* adder = nullptr;
  80. if (JavascriptConversion::CheckObjectCoercible(iterable, scriptContext))
  81. {
  82. iter = JavascriptOperators::GetIterator(iterable, scriptContext);
  83. Var adderVar = JavascriptOperators::GetProperty(weakMapObject, PropertyIds::set, scriptContext);
  84. if (!JavascriptConversion::IsCallable(adderVar))
  85. {
  86. JavascriptError::ThrowTypeError(scriptContext, JSERR_NeedFunction);
  87. }
  88. adder = RecyclableObject::FromVar(adderVar);
  89. }
  90. if (iter != nullptr)
  91. {
  92. Var undefined = library->GetUndefined();
  93. JavascriptOperators::DoIteratorStepAndValue(iter, scriptContext, [&](Var nextItem) {
  94. if (!JavascriptOperators::IsObject(nextItem))
  95. {
  96. JavascriptError::ThrowTypeError(scriptContext, JSERR_NeedObject);
  97. }
  98. RecyclableObject* obj = RecyclableObject::FromVar(nextItem);
  99. Var key, value;
  100. if (!JavascriptOperators::GetItem(obj, 0u, &key, scriptContext))
  101. {
  102. key = undefined;
  103. }
  104. if (!JavascriptOperators::GetItem(obj, 1u, &value, scriptContext))
  105. {
  106. value = undefined;
  107. }
  108. CALL_FUNCTION(scriptContext->GetThreadContext(), adder, CallInfo(CallFlags_Value, 3), weakMapObject, key, value);
  109. });
  110. }
  111. #if ENABLE_TTD
  112. //TODO: right now we always GC before snapshots (assuming we have a weak collection)
  113. // may want to optimize this and use a notify here that we have a weak container -- also update post inflate and post snap
  114. #endif
  115. return isCtorSuperCall ?
  116. JavascriptOperators::OrdinaryCreateFromConstructor(RecyclableObject::FromVar(newTarget), weakMapObject, nullptr, scriptContext) :
  117. weakMapObject;
  118. }
  119. Var JavascriptWeakMap::EntryDelete(RecyclableObject* function, CallInfo callInfo, ...)
  120. {
  121. PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
  122. ARGUMENTS(args, callInfo);
  123. ScriptContext* scriptContext = function->GetScriptContext();
  124. if (!JavascriptWeakMap::Is(args[0]))
  125. {
  126. JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("WeakMap.prototype.delete"), _u("WeakMap"));
  127. }
  128. JavascriptWeakMap* weakMap = JavascriptWeakMap::FromVar(args[0]);
  129. Var key = (args.Info.Count > 1) ? args[1] : scriptContext->GetLibrary()->GetUndefined();
  130. bool didDelete = false;
  131. if (JavascriptOperators::IsObject(key) && JavascriptOperators::GetTypeId(key) != TypeIds_HostDispatch)
  132. {
  133. DynamicObject* keyObj = DynamicObject::FromVar(key);
  134. didDelete = weakMap->Delete(keyObj);
  135. }
  136. #if ENABLE_TTD
  137. if(scriptContext->IsTTDRecordOrReplayModeEnabled())
  138. {
  139. if(scriptContext->IsTTDRecordModeEnabled())
  140. {
  141. function->GetScriptContext()->GetThreadContext()->TTDLog->RecordWeakCollectionContainsEvent(didDelete);
  142. }
  143. else
  144. {
  145. didDelete = function->GetScriptContext()->GetThreadContext()->TTDLog->ReplayWeakCollectionContainsEvent();
  146. }
  147. }
  148. #endif
  149. return scriptContext->GetLibrary()->CreateBoolean(didDelete);
  150. }
  151. Var JavascriptWeakMap::EntryGet(RecyclableObject* function, CallInfo callInfo, ...)
  152. {
  153. PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
  154. ARGUMENTS(args, callInfo);
  155. ScriptContext* scriptContext = function->GetScriptContext();
  156. if (!JavascriptWeakMap::Is(args[0]))
  157. {
  158. JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("WeakMap.prototype.get"), _u("WeakMap"));
  159. }
  160. JavascriptWeakMap* weakMap = JavascriptWeakMap::FromVar(args[0]);
  161. Var key = (args.Info.Count > 1) ? args[1] : scriptContext->GetLibrary()->GetUndefined();
  162. bool loaded = false;
  163. Var value = nullptr;
  164. if (JavascriptOperators::IsObject(key) && JavascriptOperators::GetTypeId(key) != TypeIds_HostDispatch)
  165. {
  166. DynamicObject* keyObj = DynamicObject::FromVar(key);
  167. loaded = weakMap->Get(keyObj, &value);
  168. }
  169. #if ENABLE_TTD
  170. if(scriptContext->IsTTDRecordOrReplayModeEnabled())
  171. {
  172. if(scriptContext->IsTTDRecordModeEnabled())
  173. {
  174. function->GetScriptContext()->GetThreadContext()->TTDLog->RecordWeakCollectionContainsEvent(loaded);
  175. }
  176. else
  177. {
  178. loaded = function->GetScriptContext()->GetThreadContext()->TTDLog->ReplayWeakCollectionContainsEvent();
  179. }
  180. }
  181. #endif
  182. return loaded ? value : scriptContext->GetLibrary()->GetUndefined();
  183. }
  184. Var JavascriptWeakMap::EntryHas(RecyclableObject* function, CallInfo callInfo, ...)
  185. {
  186. PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
  187. ARGUMENTS(args, callInfo);
  188. ScriptContext* scriptContext = function->GetScriptContext();
  189. if (!JavascriptWeakMap::Is(args[0]))
  190. {
  191. JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("WeakMap.prototype.has"), _u("WeakMap"));
  192. }
  193. JavascriptWeakMap* weakMap = JavascriptWeakMap::FromVar(args[0]);
  194. Var key = (args.Info.Count > 1) ? args[1] : scriptContext->GetLibrary()->GetUndefined();
  195. bool hasValue = false;
  196. if (JavascriptOperators::IsObject(key) && JavascriptOperators::GetTypeId(key) != TypeIds_HostDispatch)
  197. {
  198. DynamicObject* keyObj = DynamicObject::FromVar(key);
  199. hasValue = weakMap->Has(keyObj);
  200. }
  201. #if ENABLE_TTD
  202. if(scriptContext->IsTTDRecordOrReplayModeEnabled())
  203. {
  204. if(scriptContext->IsTTDRecordModeEnabled())
  205. {
  206. function->GetScriptContext()->GetThreadContext()->TTDLog->RecordWeakCollectionContainsEvent(hasValue);
  207. }
  208. else
  209. {
  210. hasValue = function->GetScriptContext()->GetThreadContext()->TTDLog->ReplayWeakCollectionContainsEvent();
  211. }
  212. }
  213. #endif
  214. return scriptContext->GetLibrary()->CreateBoolean(hasValue);
  215. }
  216. Var JavascriptWeakMap::EntrySet(RecyclableObject* function, CallInfo callInfo, ...)
  217. {
  218. PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
  219. ARGUMENTS(args, callInfo);
  220. ScriptContext* scriptContext = function->GetScriptContext();
  221. if (!JavascriptWeakMap::Is(args[0]))
  222. {
  223. JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("WeakMap.prototype.set"), _u("WeakMap"));
  224. }
  225. JavascriptWeakMap* weakMap = JavascriptWeakMap::FromVar(args[0]);
  226. Var key = (args.Info.Count > 1) ? args[1] : scriptContext->GetLibrary()->GetUndefined();
  227. Var value = (args.Info.Count > 2) ? args[2] : scriptContext->GetLibrary()->GetUndefined();
  228. if (!JavascriptOperators::IsObject(key) || JavascriptOperators::GetTypeId(key) == TypeIds_HostDispatch)
  229. {
  230. // HostDispatch can not expand so can't have internal property added to it.
  231. // TODO: Support HostDispatch as WeakMap key
  232. JavascriptError::ThrowTypeError(scriptContext, JSERR_WeakMapSetKeyNotAnObject, _u("WeakMap.prototype.set"));
  233. }
  234. DynamicObject* keyObj = DynamicObject::FromVar(key);
  235. #if ENABLE_TTD
  236. //In replay we need to pin the object (and will release at snapshot points) -- in record we don't need to do anything
  237. if(scriptContext->IsTTDReplayModeEnabled())
  238. {
  239. scriptContext->TTDContextInfo->TTDWeakReferencePinSet->AddNew(keyObj);
  240. }
  241. #endif
  242. weakMap->Set(keyObj, value);
  243. return weakMap;
  244. }
  245. void JavascriptWeakMap::Clear()
  246. {
  247. keySet.Map([&](DynamicObject* key, bool value, const RecyclerWeakReference<DynamicObject>* weakRef) {
  248. WeakMapKeyMap* keyMap = GetWeakMapKeyMapFromKey(key);
  249. // It may be the case that a CEO has been reset and the keyMap is now null.
  250. // Just ignore it in this case, the keyMap has already been collected.
  251. if (keyMap != nullptr)
  252. {
  253. // It may also be the case that a CEO has been reset and then added to a separate WeakMap,
  254. // creating a new WeakMapKeyMap on the CEO. In this case GetWeakMapId() may not be in the
  255. // keyMap, so don't assert successful removal here.
  256. keyMap->Remove(GetWeakMapId());
  257. }
  258. });
  259. keySet.Clear();
  260. }
  261. bool JavascriptWeakMap::Delete(DynamicObject* key)
  262. {
  263. WeakMapKeyMap* keyMap = GetWeakMapKeyMapFromKey(key);
  264. if (keyMap != nullptr)
  265. {
  266. bool unused = false;
  267. bool inSet = keySet.TryGetValueAndRemove(key, &unused);
  268. bool inData = keyMap->Remove(GetWeakMapId());
  269. Assert(inSet == inData);
  270. return inData;
  271. }
  272. return false;
  273. }
  274. bool JavascriptWeakMap::Get(DynamicObject* key, Var* value) const
  275. {
  276. WeakMapKeyMap* keyMap = GetWeakMapKeyMapFromKey(key);
  277. if (keyMap != nullptr)
  278. {
  279. return KeyMapGet(keyMap, value);
  280. }
  281. return false;
  282. }
  283. bool JavascriptWeakMap::Has(DynamicObject* key) const
  284. {
  285. WeakMapKeyMap* keyMap = GetWeakMapKeyMapFromKey(key);
  286. if (keyMap != nullptr)
  287. {
  288. return keyMap->ContainsKey(GetWeakMapId());
  289. }
  290. return false;
  291. }
  292. void JavascriptWeakMap::Set(DynamicObject* key, Var value)
  293. {
  294. WeakMapKeyMap* keyMap = GetWeakMapKeyMapFromKey(key);
  295. if (keyMap == nullptr)
  296. {
  297. keyMap = AddWeakMapKeyMapToKey(key);
  298. }
  299. keyMap->Item(GetWeakMapId(), value);
  300. keySet.Item(key, true);
  301. }
  302. BOOL JavascriptWeakMap::GetDiagTypeString(StringBuilder<ArenaAllocator>* stringBuilder, ScriptContext* requestContext)
  303. {
  304. stringBuilder->AppendCppLiteral(_u("WeakMap"));
  305. return TRUE;
  306. }
  307. #if ENABLE_TTD
  308. void JavascriptWeakMap::MarkVisitKindSpecificPtrs(TTD::SnapshotExtractor* extractor)
  309. {
  310. //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
  311. Js::ScriptContext* scriptContext = this->GetScriptContext();
  312. if(scriptContext->IsTTDReplayModeEnabled())
  313. {
  314. this->Map([&](DynamicObject* key, Js::Var value)
  315. {
  316. scriptContext->TTDContextInfo->TTDWeakReferencePinSet->AddNew(key);
  317. });
  318. }
  319. //Keys are weak so are always reachable from somewhere else but values are not so we must walk them
  320. this->Map([&](DynamicObject* key, Js::Var value)
  321. {
  322. extractor->MarkVisitVar(value);
  323. });
  324. }
  325. TTD::NSSnapObjects::SnapObjectType JavascriptWeakMap::GetSnapTag_TTD() const
  326. {
  327. return TTD::NSSnapObjects::SnapObjectType::SnapMapObject;
  328. }
  329. void JavascriptWeakMap::ExtractSnapObjectDataInto(TTD::NSSnapObjects::SnapObject* objData, TTD::SlabAllocator& alloc)
  330. {
  331. TTD::NSSnapObjects::SnapMapInfo* smi = alloc.SlabAllocateStruct<TTD::NSSnapObjects::SnapMapInfo>();
  332. uint32 mapCountEst = this->Size() * 2;
  333. smi->MapSize = 0;
  334. smi->MapKeyValueArray = alloc.SlabReserveArraySpace<TTD::TTDVar>(mapCountEst + 1); //always reserve at least 1 element
  335. this->Map([&](DynamicObject* key, Js::Var value)
  336. {
  337. AssertMsg(smi->MapSize + 1 < mapCountEst, "We are writting junk");
  338. smi->MapKeyValueArray[smi->MapSize] = key;
  339. smi->MapKeyValueArray[smi->MapSize + 1] = value;
  340. smi->MapSize += 2;
  341. });
  342. if(smi->MapSize == 0)
  343. {
  344. smi->MapKeyValueArray = nullptr;
  345. alloc.SlabAbortArraySpace<TTD::TTDVar>(mapCountEst + 1);
  346. }
  347. else
  348. {
  349. alloc.SlabCommitArraySpace<TTD::TTDVar>(smi->MapSize, mapCountEst + 1);
  350. }
  351. TTD::NSSnapObjects::StdExtractSetKindSpecificInfo<TTD::NSSnapObjects::SnapMapInfo*, TTD::NSSnapObjects::SnapObjectType::SnapMapObject>(objData, smi);
  352. }
  353. #endif
  354. }