JavascriptWeakMap.cpp 15 KB

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