MutationBreakpoint.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525
  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 "RuntimeDebugPch.h"
  6. #ifdef ENABLE_MUTATION_BREAKPOINT
  7. Js::MutationBreakpoint::MutationBreakpoint(ScriptContext *scriptContext, DynamicObject *obj, const PropertyRecord *pr, MutationType type, Js::PropertyId parentPropertyId)
  8. : isValid(true)
  9. , didCauseBreak(false)
  10. , mFlag(MutationTypeNone)
  11. , obj(nullptr)
  12. , properties(nullptr)
  13. , mutationBreakpointDelegate(nullptr)
  14. , breakMutationType(MutationTypeNone)
  15. , propertyRecord(nullptr)
  16. , newValue(nullptr)
  17. , parentPropertyId(Constants::NoProperty)
  18. {
  19. // create weak reference to object
  20. this->obj = scriptContext->GetRecycler()->CreateWeakReferenceHandle(obj);
  21. // initialize property mutation list
  22. this->properties = RecyclerNew(scriptContext->GetRecycler(), PropertyMutationList, scriptContext->GetRecycler());
  23. // Save the property id of parent object
  24. this->parentPropertyId = parentPropertyId;
  25. // set breakpoint
  26. this->SetBreak(type, pr);
  27. }
  28. Js::MutationBreakpoint::~MutationBreakpoint()
  29. {}
  30. bool Js::MutationBreakpoint::HandleSetProperty(Js::ScriptContext *scriptContext, RecyclableObject *object, Js::PropertyId propertyId, Var newValue)
  31. {
  32. Assert(scriptContext);
  33. Assert(object);
  34. ScriptContext *objectContext = object->GetScriptContext();
  35. if (IsFeatureEnabled(scriptContext)
  36. && objectContext->HasMutationBreakpoints())
  37. {
  38. MutationBreakpoint *bp = nullptr;
  39. DynamicObject *dynObj = DynamicObject::FromVar(object);
  40. if (dynObj->GetInternalProperty(object, InternalPropertyIds::MutationBp, reinterpret_cast<Var*>(&bp), nullptr, objectContext)
  41. && bp)
  42. {
  43. if (!bp->IsValid())
  44. {
  45. bp->Reset();
  46. }
  47. else
  48. {
  49. MutationType mutationType = MutationTypeUpdate;
  50. if (!object->HasProperty(propertyId))
  51. {
  52. mutationType = MutationTypeAdd;
  53. }
  54. if (bp->ShouldBreak(mutationType, propertyId))
  55. {
  56. const PropertyRecord *pr = scriptContext->GetPropertyName(propertyId);
  57. bp->newValue = newValue;
  58. bp->Break(scriptContext, mutationType, pr);
  59. bp->newValue = nullptr;
  60. return true;
  61. }
  62. else
  63. {
  64. // Mutation breakpoint exists; do not update cache
  65. return true;
  66. }
  67. }
  68. }
  69. }
  70. return false;
  71. }
  72. void Js::MutationBreakpoint::HandleDeleteProperty(ScriptContext *scriptContext, Var instance, PropertyId propertyId)
  73. {
  74. Assert(scriptContext);
  75. Assert(instance);
  76. if (MutationBreakpoint::CanSet(instance))
  77. {
  78. DynamicObject *obj = DynamicObject::FromVar(instance);
  79. if (obj->GetScriptContext()->HasMutationBreakpoints())
  80. {
  81. MutationBreakpoint *bp = nullptr;
  82. if (obj->GetInternalProperty(obj, InternalPropertyIds::MutationBp, reinterpret_cast<Var *>(&bp), nullptr, scriptContext)
  83. && bp)
  84. {
  85. if (!bp->IsValid())
  86. {
  87. bp->Reset();
  88. }
  89. else if (bp->ShouldBreak(MutationTypeDelete, propertyId))
  90. {
  91. const PropertyRecord *pr = scriptContext->GetPropertyName(propertyId);
  92. bp->Break(scriptContext, MutationTypeDelete, pr);
  93. }
  94. }
  95. }
  96. }
  97. }
  98. bool Js::MutationBreakpoint::DeleteProperty(PropertyRecord *pr)
  99. {
  100. Assert(pr != nullptr);
  101. for (int i = 0; i < this->properties->Count(); i++)
  102. {
  103. PropertyMutation pm = this->properties->Item(i);
  104. if (pm.pr == pr)
  105. {
  106. this->properties->RemoveAt(i);
  107. return true;
  108. }
  109. }
  110. return false;
  111. }
  112. const Js::Var Js::MutationBreakpoint::GetMutationObjectVar() const
  113. {
  114. Var retVar = nullptr;
  115. Assert(this->didCauseBreak);
  116. if (this->obj != nullptr)
  117. {
  118. DynamicObject *dynObj = this->obj->Get();
  119. if (dynObj != nullptr)
  120. {
  121. retVar = static_cast<Js::Var>(dynObj);
  122. }
  123. }
  124. return retVar;
  125. }
  126. const Js::Var Js::MutationBreakpoint::GetBreakNewValueVar() const
  127. {
  128. Assert(this->didCauseBreak);
  129. return this->newValue;
  130. }
  131. bool Js::MutationBreakpoint::IsFeatureEnabled(ScriptContext *scriptContext)
  132. {
  133. Assert(scriptContext != nullptr);
  134. return scriptContext->IsScriptContextInDebugMode() && !PHASE_OFF1(Js::ObjectMutationBreakpointPhase);
  135. }
  136. bool Js::MutationBreakpoint::CanSet(Var object)
  137. {
  138. if (!object)
  139. {
  140. return false;
  141. }
  142. TypeId id = JavascriptOperators::GetTypeId(object);
  143. return JavascriptOperators::IsObjectType(id) && !JavascriptOperators::IsSpecialObjectType(id);
  144. }
  145. Js::MutationBreakpoint * Js::MutationBreakpoint::New(ScriptContext *scriptContext, DynamicObject *obj, const PropertyRecord *pr, MutationType type, Js::PropertyId propertyId)
  146. {
  147. return RecyclerNewFinalized(scriptContext->GetRecycler(), MutationBreakpoint, scriptContext, obj, pr, type, propertyId);
  148. }
  149. Js::MutationBreakpointDelegate * Js::MutationBreakpoint::GetDelegate()
  150. {
  151. // Create a new breakpoint object if needed
  152. if (!mutationBreakpointDelegate)
  153. {
  154. mutationBreakpointDelegate = Js::MutationBreakpointDelegate::New(this);
  155. // NOTE: no need to add ref here, as a new MutationBreakpointDelegate is initialized with 1 ref count
  156. }
  157. mutationBreakpointDelegate->AddRef();
  158. return mutationBreakpointDelegate;
  159. }
  160. bool Js::MutationBreakpoint::IsValid() const
  161. {
  162. return isValid;
  163. }
  164. void Js::MutationBreakpoint::Invalidate()
  165. {
  166. AssertMsg(isValid, "Breakpoint already invalid");
  167. isValid = false;
  168. }
  169. // Return true if breakpoint should break on object with a specific mutation type
  170. bool Js::MutationBreakpoint::ShouldBreak(MutationType type)
  171. {
  172. return ShouldBreak(type, Constants::NoProperty);
  173. }
  174. // Return true if breakpoint should break on object, or a property pid, with
  175. // a specific mutation type
  176. bool Js::MutationBreakpoint::ShouldBreak(MutationType type, PropertyId pid)
  177. {
  178. Assert(isValid);
  179. if (mFlag == MutationTypeNone && pid == Constants::NoProperty)
  180. {
  181. return false;
  182. }
  183. else if (type != MutationTypeNone && (type & mFlag) == type)
  184. {
  185. // Break on object
  186. return true;
  187. }
  188. // search properties vector
  189. for (int i = 0; i < properties->Count(); i++)
  190. {
  191. PropertyMutation pm = properties->Item(i);
  192. if (pm.pr->GetPropertyId() == pid)
  193. {
  194. if (pm.mFlag == MutationTypeNone)
  195. {
  196. return false;
  197. }
  198. else if (type != MutationTypeNone && (pm.mFlag & type) == type)
  199. {
  200. return true;
  201. }
  202. break;
  203. }
  204. }
  205. return false;
  206. }
  207. bool Js::MutationBreakpoint::Reset()
  208. {
  209. // Invalidate breakpoint
  210. if (isValid)
  211. {
  212. this->Invalidate();
  213. }
  214. // Release existing delegate object
  215. if (mutationBreakpointDelegate)
  216. {
  217. mutationBreakpointDelegate->Release();
  218. mutationBreakpointDelegate = nullptr;
  219. }
  220. // Clear all property records
  221. if (properties)
  222. {
  223. properties->ClearAndZero();
  224. }
  225. // Get object and remove strong ref
  226. DynamicObject *obj = this->obj->Get();
  227. return obj && obj->SetInternalProperty(InternalPropertyIds::MutationBp, nullptr, PropertyOperation_SpecialValue, NULL);
  228. }
  229. void Js::MutationBreakpoint::SetBreak(MutationType type, const PropertyRecord *pr)
  230. {
  231. // Break on entire object if pid is NoProperty
  232. if (!pr)
  233. {
  234. if (type == MutationTypeNone)
  235. {
  236. mFlag = MutationTypeNone;
  237. }
  238. else
  239. {
  240. mFlag = static_cast<MutationType>(mFlag | type);
  241. }
  242. return;
  243. }
  244. // Check if property is already added
  245. for (int i = 0; i < properties->Count(); i++)
  246. {
  247. PropertyMutation& pm = properties->Item(i);
  248. if (pm.pr == pr)
  249. {
  250. // added to existing property mutation struct
  251. if (type == MutationTypeNone)
  252. {
  253. pm.mFlag = MutationTypeNone;
  254. }
  255. else
  256. {
  257. pm.mFlag = static_cast<MutationType>(pm.mFlag | type);
  258. }
  259. return;
  260. }
  261. }
  262. // if not in list, add new property mutation
  263. PropertyMutation pm = {
  264. pr,
  265. type
  266. };
  267. properties->Add(pm);
  268. }
  269. void Js::MutationBreakpoint::Break(ScriptContext *scriptContext, MutationType mutationType, const PropertyRecord *pr)
  270. {
  271. this->didCauseBreak = true;
  272. this->breakMutationType = mutationType;
  273. this->propertyRecord = pr;
  274. InterpreterHaltState haltState(STOP_MUTATIONBREAKPOINT, /*executingFunction*/nullptr, this);
  275. scriptContext->GetDebugContext()->GetProbeContainer()->DispatchMutationBreakpoint(&haltState);
  276. this->didCauseBreak = false;
  277. }
  278. bool Js::MutationBreakpoint::GetDidCauseBreak() const
  279. {
  280. return this->didCauseBreak;
  281. }
  282. const wchar_t * Js::MutationBreakpoint::GetBreakPropertyName() const
  283. {
  284. Assert(this->didCauseBreak);
  285. Assert(this->propertyRecord);
  286. return this->propertyRecord->GetBuffer();
  287. }
  288. const Js::PropertyId Js::MutationBreakpoint::GetParentPropertyId() const
  289. {
  290. Assert(this->didCauseBreak);
  291. return this->parentPropertyId;
  292. }
  293. const wchar_t * Js::MutationBreakpoint::GetParentPropertyName() const
  294. {
  295. Assert(this->didCauseBreak);
  296. const PropertyRecord *pr = nullptr;
  297. if ((this->parentPropertyId != Constants::NoProperty) && (this->obj != nullptr))
  298. {
  299. DynamicObject *dynObj = this->obj->Get();
  300. if (dynObj != nullptr)
  301. {
  302. pr = dynObj->GetScriptContext()->GetPropertyName(this->parentPropertyId);
  303. return pr->GetBuffer();
  304. }
  305. }
  306. return L"";
  307. }
  308. MutationType Js::MutationBreakpoint::GetBreakMutationType() const
  309. {
  310. Assert(this->didCauseBreak);
  311. return this->breakMutationType;
  312. }
  313. const Js::PropertyId Js::MutationBreakpoint::GetBreakPropertyId() const
  314. {
  315. Assert(this->didCauseBreak);
  316. Assert(this->propertyRecord);
  317. return this->propertyRecord->GetPropertyId();
  318. }
  319. const wchar_t * Js::MutationBreakpoint::GetBreakMutationTypeName(MutationType mutationType)
  320. {
  321. switch (mutationType)
  322. {
  323. case MutationTypeUpdate: return L"Changing";
  324. case MutationTypeDelete: return L"Deleting";
  325. case MutationTypeAdd: return L"Adding";
  326. default: AssertMsg(false, "Unhandled break reason mutation type. Did we add a new mutation type?"); return L"";
  327. }
  328. }
  329. const wchar_t * Js::MutationBreakpoint::GetMutationTypeForConditionalEval(MutationType mutationType)
  330. {
  331. switch (mutationType)
  332. {
  333. case MutationTypeUpdate: return L"update";
  334. case MutationTypeDelete: return L"delete";
  335. case MutationTypeAdd: return L"add";
  336. default: AssertMsg(false, "Unhandled mutation type in conditional object mutation breakpoint."); return L"";
  337. }
  338. }
  339. // setOnObject - Is true if we are watching the parent object
  340. // parentPropertyId - Property ID of object on which Mutation is set
  341. // propertyId - Property ID of property. If setOnObject is false we are watching a specific property (propertyId)
  342. Js::MutationBreakpointDelegate * Js::MutationBreakpoint::Set(ScriptContext *scriptContext, Var obj, BOOL setOnObject, MutationType type, PropertyId parentPropertyId, PropertyId propertyId)
  343. {
  344. Assert(obj);
  345. Assert(scriptContext);
  346. if (!CanSet(obj))
  347. {
  348. return nullptr;
  349. }
  350. DynamicObject *dynObj = static_cast<DynamicObject*>(obj);
  351. const PropertyRecord *pr = nullptr;
  352. if (!setOnObject && (propertyId != Constants::NoProperty))
  353. {
  354. pr = scriptContext->GetPropertyName(propertyId);
  355. Assert(pr);
  356. }
  357. MutationBreakpoint *bp = nullptr;
  358. // Breakpoint exists; update it.
  359. if (dynObj->GetInternalProperty(dynObj, InternalPropertyIds::MutationBp, reinterpret_cast<Var*>(&bp), nullptr, scriptContext)
  360. && bp)
  361. {
  362. // Valid bp; update it.
  363. if (bp->IsValid())
  364. {
  365. Assert(bp->mutationBreakpointDelegate); // Delegate must already exist
  366. // set breakpoint
  367. bp->SetBreak(type, pr);
  368. return bp->GetDelegate();
  369. }
  370. // bp invalidated by haven't got cleaned up yet, so reset it right here
  371. // and then create new bp
  372. else
  373. {
  374. bp->Reset();
  375. }
  376. }
  377. // Create new breakpoint
  378. bp = MutationBreakpoint::New(scriptContext, dynObj, const_cast<PropertyRecord *>(pr), type, parentPropertyId);
  379. // Adding reference to dynamic object
  380. dynObj->SetInternalProperty(InternalPropertyIds::MutationBp, bp, PropertyOperation_SpecialValue, nullptr);
  381. // Track in object's own script context
  382. dynObj->GetScriptContext()->InsertMutationBreakpoint(bp);
  383. return bp->GetDelegate();
  384. }
  385. void Js::MutationBreakpoint::Finalize(bool isShutdown) {}
  386. void Js::MutationBreakpoint::Dispose(bool isShutdown)
  387. {
  388. // TODO (t-shchan): If removed due to detaching debugger, do not fire event
  389. // TODO (t-shchan): Fire debugger event for breakpoint removal
  390. if (mutationBreakpointDelegate)
  391. {
  392. mutationBreakpointDelegate->Release();
  393. }
  394. }
  395. void Js::MutationBreakpoint::Mark(Recycler * recycler) {}
  396. /*
  397. MutationBreakpointDelegate definition
  398. */
  399. Js::MutationBreakpointDelegate::MutationBreakpointDelegate(Js::MutationBreakpoint *bp)
  400. : m_refCount(1), m_breakpoint(bp), m_didCauseBreak(false), m_propertyRecord(nullptr), m_type(MutationTypeNone)
  401. {
  402. Assert(bp != nullptr);
  403. }
  404. Js::MutationBreakpointDelegate * Js::MutationBreakpointDelegate::New(Js::MutationBreakpoint *bp)
  405. {
  406. return HeapNew(Js::MutationBreakpointDelegate, bp);
  407. }
  408. /*
  409. IMutationBreakpoint interface definition
  410. */
  411. STDMETHODIMP_(ULONG) Js::MutationBreakpointDelegate::AddRef()
  412. {
  413. return (ulong)InterlockedIncrement(&m_refCount);
  414. }
  415. STDMETHODIMP_(ULONG) Js::MutationBreakpointDelegate::Release()
  416. {
  417. ulong refCount = (ulong)InterlockedDecrement(&m_refCount);
  418. if (0 == refCount)
  419. {
  420. HeapDelete(this);
  421. }
  422. return refCount;
  423. }
  424. STDMETHODIMP Js::MutationBreakpointDelegate::QueryInterface(REFIID iid, void ** ppv)
  425. {
  426. if (!ppv)
  427. {
  428. return E_INVALIDARG;
  429. }
  430. if (__uuidof(IUnknown) == iid || __uuidof(IMutationBreakpoint) == iid)
  431. {
  432. *ppv = static_cast<IUnknown*>(static_cast<IMutationBreakpoint*>(this));
  433. }
  434. else
  435. {
  436. *ppv = NULL;
  437. return E_NOINTERFACE;
  438. }
  439. AddRef();
  440. return S_OK;
  441. }
  442. STDMETHODIMP Js::MutationBreakpointDelegate::Delete(void)
  443. {
  444. this->m_breakpoint->Invalidate();
  445. return S_OK;
  446. }
  447. STDMETHODIMP Js::MutationBreakpointDelegate::DidCauseBreak(
  448. /* [out] */ __RPC__out BOOL *didCauseBreak)
  449. {
  450. if (!didCauseBreak)
  451. {
  452. return E_INVALIDARG;
  453. }
  454. *didCauseBreak = this->m_breakpoint->GetDidCauseBreak();
  455. return S_OK;
  456. }
  457. #endif