blob: 850880cb991a070e643d41226bb6c425f5f0e7f3 [file]
//-------------------------------------------------------------------------------------------------------
// 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);
}
}
}
}
}
void Js::MutationBreakpoint::HandleDeleteProperty(ScriptContext *scriptContext, Var instance, Js::JavascriptString *propertyNameString)
{
PropertyRecord const *propertyRecord = nullptr;
DynamicObject *obj = DynamicObject::FromVar(instance);
if (JavascriptOperators::ShouldTryDeleteProperty(obj, propertyNameString, &propertyRecord))
{
Assert(propertyRecord);
HandleDeleteProperty(scriptContext, instance, propertyRecord->GetPropertyId());
}
}
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 char16 * 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 char16 * 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 _u("");
}
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 char16 * Js::MutationBreakpoint::GetBreakMutationTypeName(MutationType mutationType)
{
switch (mutationType)
{
case MutationTypeUpdate: return _u("Changing");
case MutationTypeDelete: return _u("Deleting");
case MutationTypeAdd: return _u("Adding");
default: AssertMsg(false, "Unhandled break reason mutation type. Did we add a new mutation type?"); return _u("");
}
}
const char16 * Js::MutationBreakpoint::GetMutationTypeForConditionalEval(MutationType mutationType)
{
switch (mutationType)
{
case MutationTypeUpdate: return _u("update");
case MutationTypeDelete: return _u("delete");
case MutationTypeAdd: return _u("add");
default: AssertMsg(false, "Unhandled mutation type in conditional object mutation breakpoint."); return _u("");
}
}
// 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