| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525 |
- //-------------------------------------------------------------------------------------------------------
- // Copyright (C) Microsoft. All rights reserved.
- // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
- //-------------------------------------------------------------------------------------------------------
- #include "RuntimeDebugPch.h"
- #ifdef ENABLE_MUTATION_BREAKPOINT
- Js::MutationBreakpoint::MutationBreakpoint(ScriptContext *scriptContext, DynamicObject *obj, const PropertyRecord *pr, MutationType type, Js::PropertyId parentPropertyId)
- : isValid(true)
- , didCauseBreak(false)
- , mFlag(MutationTypeNone)
- , obj(nullptr)
- , properties(nullptr)
- , mutationBreakpointDelegate(nullptr)
- , breakMutationType(MutationTypeNone)
- , propertyRecord(nullptr)
- , newValue(nullptr)
- , parentPropertyId(Constants::NoProperty)
- {
- // create weak reference to object
- this->obj = scriptContext->GetRecycler()->CreateWeakReferenceHandle(obj);
- // initialize property mutation list
- this->properties = RecyclerNew(scriptContext->GetRecycler(), PropertyMutationList, scriptContext->GetRecycler());
- // Save the property id of parent object
- this->parentPropertyId = parentPropertyId;
- // set breakpoint
- this->SetBreak(type, pr);
- }
- Js::MutationBreakpoint::~MutationBreakpoint()
- {}
- bool Js::MutationBreakpoint::HandleSetProperty(Js::ScriptContext *scriptContext, RecyclableObject *object, Js::PropertyId propertyId, Var newValue)
- {
- Assert(scriptContext);
- Assert(object);
- ScriptContext *objectContext = object->GetScriptContext();
- if (IsFeatureEnabled(scriptContext)
- && objectContext->HasMutationBreakpoints())
- {
- MutationBreakpoint *bp = nullptr;
- DynamicObject *dynObj = DynamicObject::FromVar(object);
- if (dynObj->GetInternalProperty(object, InternalPropertyIds::MutationBp, reinterpret_cast<Var*>(&bp), nullptr, objectContext)
- && bp)
- {
- if (!bp->IsValid())
- {
- bp->Reset();
- }
- else
- {
- MutationType mutationType = MutationTypeUpdate;
- if (!object->HasProperty(propertyId))
- {
- mutationType = MutationTypeAdd;
- }
- if (bp->ShouldBreak(mutationType, propertyId))
- {
- const PropertyRecord *pr = scriptContext->GetPropertyName(propertyId);
- bp->newValue = newValue;
- bp->Break(scriptContext, mutationType, pr);
- bp->newValue = nullptr;
- return true;
- }
- else
- {
- // Mutation breakpoint exists; do not update cache
- return true;
- }
- }
- }
- }
- return false;
- }
- void Js::MutationBreakpoint::HandleDeleteProperty(ScriptContext *scriptContext, Var instance, PropertyId propertyId)
- {
- Assert(scriptContext);
- Assert(instance);
- if (MutationBreakpoint::CanSet(instance))
- {
- DynamicObject *obj = DynamicObject::FromVar(instance);
- if (obj->GetScriptContext()->HasMutationBreakpoints())
- {
- MutationBreakpoint *bp = nullptr;
- if (obj->GetInternalProperty(obj, InternalPropertyIds::MutationBp, reinterpret_cast<Var *>(&bp), nullptr, scriptContext)
- && bp)
- {
- if (!bp->IsValid())
- {
- bp->Reset();
- }
- else if (bp->ShouldBreak(MutationTypeDelete, propertyId))
- {
- const PropertyRecord *pr = scriptContext->GetPropertyName(propertyId);
- bp->Break(scriptContext, MutationTypeDelete, pr);
- }
- }
- }
- }
- }
- bool Js::MutationBreakpoint::DeleteProperty(PropertyRecord *pr)
- {
- Assert(pr != nullptr);
- for (int i = 0; i < this->properties->Count(); i++)
- {
- PropertyMutation pm = this->properties->Item(i);
- if (pm.pr == pr)
- {
- this->properties->RemoveAt(i);
- return true;
- }
- }
- return false;
- }
- const Js::Var Js::MutationBreakpoint::GetMutationObjectVar() const
- {
- Var retVar = nullptr;
- Assert(this->didCauseBreak);
- if (this->obj != nullptr)
- {
- DynamicObject *dynObj = this->obj->Get();
- if (dynObj != nullptr)
- {
- retVar = static_cast<Js::Var>(dynObj);
- }
- }
- return retVar;
- }
- const Js::Var Js::MutationBreakpoint::GetBreakNewValueVar() const
- {
- Assert(this->didCauseBreak);
- return this->newValue;
- }
- bool Js::MutationBreakpoint::IsFeatureEnabled(ScriptContext *scriptContext)
- {
- Assert(scriptContext != nullptr);
- return scriptContext->IsScriptContextInDebugMode() && !PHASE_OFF1(Js::ObjectMutationBreakpointPhase);
- }
- bool Js::MutationBreakpoint::CanSet(Var object)
- {
- if (!object)
- {
- return false;
- }
- TypeId id = JavascriptOperators::GetTypeId(object);
- return JavascriptOperators::IsObjectType(id) && !JavascriptOperators::IsSpecialObjectType(id);
- }
- Js::MutationBreakpoint * Js::MutationBreakpoint::New(ScriptContext *scriptContext, DynamicObject *obj, const PropertyRecord *pr, MutationType type, Js::PropertyId propertyId)
- {
- return RecyclerNewFinalized(scriptContext->GetRecycler(), MutationBreakpoint, scriptContext, obj, pr, type, propertyId);
- }
- Js::MutationBreakpointDelegate * Js::MutationBreakpoint::GetDelegate()
- {
- // Create a new breakpoint object if needed
- if (!mutationBreakpointDelegate)
- {
- mutationBreakpointDelegate = Js::MutationBreakpointDelegate::New(this);
- // NOTE: no need to add ref here, as a new MutationBreakpointDelegate is initialized with 1 ref count
- }
- mutationBreakpointDelegate->AddRef();
- return mutationBreakpointDelegate;
- }
- bool Js::MutationBreakpoint::IsValid() const
- {
- return isValid;
- }
- void Js::MutationBreakpoint::Invalidate()
- {
- AssertMsg(isValid, "Breakpoint already invalid");
- isValid = false;
- }
- // Return true if breakpoint should break on object with a specific mutation type
- bool Js::MutationBreakpoint::ShouldBreak(MutationType type)
- {
- return ShouldBreak(type, Constants::NoProperty);
- }
- // Return true if breakpoint should break on object, or a property pid, with
- // a specific mutation type
- bool Js::MutationBreakpoint::ShouldBreak(MutationType type, PropertyId pid)
- {
- Assert(isValid);
- if (mFlag == MutationTypeNone && pid == Constants::NoProperty)
- {
- return false;
- }
- else if (type != MutationTypeNone && (type & mFlag) == type)
- {
- // Break on object
- return true;
- }
- // search properties vector
- for (int i = 0; i < properties->Count(); i++)
- {
- PropertyMutation pm = properties->Item(i);
- if (pm.pr->GetPropertyId() == pid)
- {
- if (pm.mFlag == MutationTypeNone)
- {
- return false;
- }
- else if (type != MutationTypeNone && (pm.mFlag & type) == type)
- {
- return true;
- }
- break;
- }
- }
- return false;
- }
- bool Js::MutationBreakpoint::Reset()
- {
- // Invalidate breakpoint
- if (isValid)
- {
- this->Invalidate();
- }
- // Release existing delegate object
- if (mutationBreakpointDelegate)
- {
- mutationBreakpointDelegate->Release();
- mutationBreakpointDelegate = nullptr;
- }
- // Clear all property records
- if (properties)
- {
- properties->ClearAndZero();
- }
- // Get object and remove strong ref
- DynamicObject *obj = this->obj->Get();
- return obj && obj->SetInternalProperty(InternalPropertyIds::MutationBp, nullptr, PropertyOperation_SpecialValue, NULL);
- }
- void Js::MutationBreakpoint::SetBreak(MutationType type, const PropertyRecord *pr)
- {
- // Break on entire object if pid is NoProperty
- if (!pr)
- {
- if (type == MutationTypeNone)
- {
- mFlag = MutationTypeNone;
- }
- else
- {
- mFlag = static_cast<MutationType>(mFlag | type);
- }
- return;
- }
- // Check if property is already added
- for (int i = 0; i < properties->Count(); i++)
- {
- PropertyMutation& pm = properties->Item(i);
- if (pm.pr == pr)
- {
- // added to existing property mutation struct
- if (type == MutationTypeNone)
- {
- pm.mFlag = MutationTypeNone;
- }
- else
- {
- pm.mFlag = static_cast<MutationType>(pm.mFlag | type);
- }
- return;
- }
- }
- // if not in list, add new property mutation
- PropertyMutation pm = {
- pr,
- type
- };
- properties->Add(pm);
- }
- void Js::MutationBreakpoint::Break(ScriptContext *scriptContext, MutationType mutationType, const PropertyRecord *pr)
- {
- this->didCauseBreak = true;
- this->breakMutationType = mutationType;
- this->propertyRecord = pr;
- InterpreterHaltState haltState(STOP_MUTATIONBREAKPOINT, /*executingFunction*/nullptr, this);
- scriptContext->GetDebugContext()->GetProbeContainer()->DispatchMutationBreakpoint(&haltState);
- this->didCauseBreak = false;
- }
- bool Js::MutationBreakpoint::GetDidCauseBreak() const
- {
- return this->didCauseBreak;
- }
- const wchar_t * Js::MutationBreakpoint::GetBreakPropertyName() const
- {
- Assert(this->didCauseBreak);
- Assert(this->propertyRecord);
- return this->propertyRecord->GetBuffer();
- }
- const Js::PropertyId Js::MutationBreakpoint::GetParentPropertyId() const
- {
- Assert(this->didCauseBreak);
- return this->parentPropertyId;
- }
- const wchar_t * Js::MutationBreakpoint::GetParentPropertyName() const
- {
- Assert(this->didCauseBreak);
- const PropertyRecord *pr = nullptr;
- if ((this->parentPropertyId != Constants::NoProperty) && (this->obj != nullptr))
- {
- DynamicObject *dynObj = this->obj->Get();
- if (dynObj != nullptr)
- {
- pr = dynObj->GetScriptContext()->GetPropertyName(this->parentPropertyId);
- return pr->GetBuffer();
- }
- }
- return L"";
- }
- MutationType Js::MutationBreakpoint::GetBreakMutationType() const
- {
- Assert(this->didCauseBreak);
- return this->breakMutationType;
- }
- const Js::PropertyId Js::MutationBreakpoint::GetBreakPropertyId() const
- {
- Assert(this->didCauseBreak);
- Assert(this->propertyRecord);
- return this->propertyRecord->GetPropertyId();
- }
- const wchar_t * Js::MutationBreakpoint::GetBreakMutationTypeName(MutationType mutationType)
- {
- switch (mutationType)
- {
- case MutationTypeUpdate: return L"Changing";
- case MutationTypeDelete: return L"Deleting";
- case MutationTypeAdd: return L"Adding";
- default: AssertMsg(false, "Unhandled break reason mutation type. Did we add a new mutation type?"); return L"";
- }
- }
- const wchar_t * Js::MutationBreakpoint::GetMutationTypeForConditionalEval(MutationType mutationType)
- {
- switch (mutationType)
- {
- case MutationTypeUpdate: return L"update";
- case MutationTypeDelete: return L"delete";
- case MutationTypeAdd: return L"add";
- default: AssertMsg(false, "Unhandled mutation type in conditional object mutation breakpoint."); return L"";
- }
- }
- // setOnObject - Is true if we are watching the parent object
- // parentPropertyId - Property ID of object on which Mutation is set
- // propertyId - Property ID of property. If setOnObject is false we are watching a specific property (propertyId)
- Js::MutationBreakpointDelegate * Js::MutationBreakpoint::Set(ScriptContext *scriptContext, Var obj, BOOL setOnObject, MutationType type, PropertyId parentPropertyId, PropertyId propertyId)
- {
- Assert(obj);
- Assert(scriptContext);
- if (!CanSet(obj))
- {
- return nullptr;
- }
- DynamicObject *dynObj = static_cast<DynamicObject*>(obj);
- const PropertyRecord *pr = nullptr;
- if (!setOnObject && (propertyId != Constants::NoProperty))
- {
- pr = scriptContext->GetPropertyName(propertyId);
- Assert(pr);
- }
- MutationBreakpoint *bp = nullptr;
- // Breakpoint exists; update it.
- if (dynObj->GetInternalProperty(dynObj, InternalPropertyIds::MutationBp, reinterpret_cast<Var*>(&bp), nullptr, scriptContext)
- && bp)
- {
- // Valid bp; update it.
- if (bp->IsValid())
- {
- Assert(bp->mutationBreakpointDelegate); // Delegate must already exist
- // set breakpoint
- bp->SetBreak(type, pr);
- return bp->GetDelegate();
- }
- // bp invalidated by haven't got cleaned up yet, so reset it right here
- // and then create new bp
- else
- {
- bp->Reset();
- }
- }
- // Create new breakpoint
- bp = MutationBreakpoint::New(scriptContext, dynObj, const_cast<PropertyRecord *>(pr), type, parentPropertyId);
- // Adding reference to dynamic object
- dynObj->SetInternalProperty(InternalPropertyIds::MutationBp, bp, PropertyOperation_SpecialValue, nullptr);
- // Track in object's own script context
- dynObj->GetScriptContext()->InsertMutationBreakpoint(bp);
- return bp->GetDelegate();
- }
- void Js::MutationBreakpoint::Finalize(bool isShutdown) {}
- void Js::MutationBreakpoint::Dispose(bool isShutdown)
- {
- // TODO (t-shchan): If removed due to detaching debugger, do not fire event
- // TODO (t-shchan): Fire debugger event for breakpoint removal
- if (mutationBreakpointDelegate)
- {
- mutationBreakpointDelegate->Release();
- }
- }
- void Js::MutationBreakpoint::Mark(Recycler * recycler) {}
- /*
- MutationBreakpointDelegate definition
- */
- Js::MutationBreakpointDelegate::MutationBreakpointDelegate(Js::MutationBreakpoint *bp)
- : m_refCount(1), m_breakpoint(bp), m_didCauseBreak(false), m_propertyRecord(nullptr), m_type(MutationTypeNone)
- {
- Assert(bp != nullptr);
- }
- Js::MutationBreakpointDelegate * Js::MutationBreakpointDelegate::New(Js::MutationBreakpoint *bp)
- {
- return HeapNew(Js::MutationBreakpointDelegate, bp);
- }
- /*
- IMutationBreakpoint interface definition
- */
- STDMETHODIMP_(ULONG) Js::MutationBreakpointDelegate::AddRef()
- {
- return (ulong)InterlockedIncrement(&m_refCount);
- }
- STDMETHODIMP_(ULONG) Js::MutationBreakpointDelegate::Release()
- {
- ulong refCount = (ulong)InterlockedDecrement(&m_refCount);
- if (0 == refCount)
- {
- HeapDelete(this);
- }
- return refCount;
- }
- STDMETHODIMP Js::MutationBreakpointDelegate::QueryInterface(REFIID iid, void ** ppv)
- {
- if (!ppv)
- {
- return E_INVALIDARG;
- }
- if (__uuidof(IUnknown) == iid || __uuidof(IMutationBreakpoint) == iid)
- {
- *ppv = static_cast<IUnknown*>(static_cast<IMutationBreakpoint*>(this));
- }
- else
- {
- *ppv = NULL;
- return E_NOINTERFACE;
- }
- AddRef();
- return S_OK;
- }
- STDMETHODIMP Js::MutationBreakpointDelegate::Delete(void)
- {
- this->m_breakpoint->Invalidate();
- return S_OK;
- }
- STDMETHODIMP Js::MutationBreakpointDelegate::DidCauseBreak(
- /* [out] */ __RPC__out BOOL *didCauseBreak)
- {
- if (!didCauseBreak)
- {
- return E_INVALIDARG;
- }
- *didCauseBreak = this->m_breakpoint->GetDidCauseBreak();
- return S_OK;
- }
- #endif
|