JavascriptWeakMap.cpp 16 KB

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