blob: 70dee959e9fcca03aa168b0958fc086a343b2eb3 [file] [log] [blame]
//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------
#include "JsrtPch.h"
#ifdef ENABLE_SCRIPT_DEBUGGING
#include "JsrtDebugManager.h"
#include "JsrtDebugEventObject.h"
#include "JsrtDebugUtils.h"
#include "JsrtDebuggerObject.h"
#include "RuntimeDebugPch.h"
#include "screrror.h" // For CompileScriptException
JsrtDebugManager::JsrtDebugManager(ThreadContext* threadContext) :
HostDebugContext(nullptr),
threadContext(threadContext),
debugEventCallback(nullptr),
callbackState(nullptr),
resumeAction(BREAKRESUMEACTION_CONTINUE),
debugObjectArena(nullptr),
debuggerObjectsManager(nullptr),
debugDocumentManager(nullptr),
stackFrames(nullptr),
breakOnExceptionAttributes(JsDiagBreakOnExceptionAttributeUncaught)
{
Assert(threadContext != nullptr);
}
JsrtDebugManager::~JsrtDebugManager()
{
if (this->debuggerObjectsManager != nullptr)
{
Adelete(this->debugObjectArena, this->debuggerObjectsManager);
this->debuggerObjectsManager = nullptr;
}
if (this->debugDocumentManager != nullptr)
{
Adelete(this->debugObjectArena, this->debugDocumentManager);
this->debugDocumentManager = nullptr;
}
if (this->debugObjectArena != nullptr)
{
this->threadContext->GetRecycler()->UnregisterExternalGuestArena(this->debugObjectArena);
HeapDelete(this->debugObjectArena);
this->debugObjectArena = nullptr;
}
this->debugEventCallback = nullptr;
this->callbackState = nullptr;
this->threadContext = nullptr;
}
void JsrtDebugManager::SetDebugEventCallback(JsDiagDebugEventCallback debugEventCallback, void* callbackState)
{
Assert(this->debugEventCallback == nullptr);
Assert(this->callbackState == nullptr);
this->debugEventCallback = debugEventCallback;
this->callbackState = callbackState;
}
void* JsrtDebugManager::GetAndClearCallbackState()
{
void* currentCallbackState = this->callbackState;
this->debugEventCallback = nullptr;
this->callbackState = nullptr;
return currentCallbackState;
}
bool JsrtDebugManager::IsDebugEventCallbackSet() const
{
return this->debugEventCallback != nullptr;
}
bool JsrtDebugManager::CanHalt(Js::InterpreterHaltState* haltState)
{
// This is registered as the callback for inline breakpoints.
// We decide here if we are at a reasonable stop location that has source code.
Assert(haltState->IsValid());
Js::FunctionBody* pCurrentFuncBody = haltState->GetFunction();
int byteOffset = haltState->GetCurrentOffset();
Js::FunctionBody::StatementMap* map = pCurrentFuncBody->GetMatchingStatementMapFromByteCode(byteOffset, false);
// Resolve the dummy ret code.
return map != nullptr && (!pCurrentFuncBody->GetIsGlobalFunc() || !Js::FunctionBody::IsDummyGlobalRetStatement(&map->sourceSpan));
}
void JsrtDebugManager::DispatchHalt(Js::InterpreterHaltState* haltState)
{
switch (haltState->stopType)
{
case Js::STOP_BREAKPOINT: /*JsDiagDebugEventBreakpoint*/
case Js::STOP_INLINEBREAKPOINT: /*JsDiagDebugEventDebuggerStatement*/
case Js::STOP_ASYNCBREAK: /*JsDiagDebugEventAsyncBreak*/
this->ReportBreak(haltState);
break;
case Js::STOP_STEPCOMPLETE: /*JsDiagDebugEventStepComplete*/
this->SetResumeType(BREAKRESUMEACTION_CONTINUE);
this->ReportBreak(haltState);
break;
case Js::STOP_EXCEPTIONTHROW: /*JsDiagDebugEventRuntimeException*/
this->ReportExceptionBreak(haltState);
break;
case Js::STOP_DOMMUTATIONBREAKPOINT:
case Js::STOP_MUTATIONBREAKPOINT:
AssertMsg(false, "Not yet handled");
break;
default:
AssertMsg(false, "Unhandled stop type");
}
this->HandleResume(haltState, this->resumeAction);
}
bool JsrtDebugManager::CanAllowBreakpoints()
{
return true;
}
void JsrtDebugManager::CleanupHalt()
{
}
bool JsrtDebugManager::IsInClosedState()
{
return this->debugEventCallback == nullptr;
}
bool JsrtDebugManager::IsExceptionReportingEnabled()
{
return this->GetBreakOnException() != JsDiagBreakOnExceptionAttributeNone;
}
bool JsrtDebugManager::IsFirstChanceExceptionEnabled()
{
return (this->GetBreakOnException() & JsDiagBreakOnExceptionAttributeFirstChance) == JsDiagBreakOnExceptionAttributeFirstChance;
}
HRESULT JsrtDebugManager::DbgRegisterFunction(Js::ScriptContext* scriptContext, Js::FunctionBody* functionBody, DWORD_PTR dwDebugSourceContext, LPCWSTR title)
{
Js::Utf8SourceInfo* utf8SourceInfo = functionBody->GetUtf8SourceInfo();
if (!utf8SourceInfo->GetIsLibraryCode() && !utf8SourceInfo->HasDebugDocument())
{
JsrtDebugDocumentManager* debugDocumentManager = this->GetDebugDocumentManager();
Assert(debugDocumentManager != nullptr);
Js::DebugDocument* debugDocument = HeapNewNoThrow(Js::DebugDocument, utf8SourceInfo, functionBody);
if (debugDocument != nullptr)
{
utf8SourceInfo->SetDebugDocument(debugDocument);
}
// Raising events during the middle of a source reparse allows the host to reenter the
// script context and cause memory race conditions. Suppressing these events during a
// reparse prevents the issue. Since the host was already expected to call JsDiagGetScripts
// once the attach is completed to get the list of parsed scripts, there is no change in
// behavior.
if (this->debugEventCallback != nullptr &&
!scriptContext->GetDebugContext()->GetIsReparsingSource())
{
JsrtDebugEventObject debugEventObject(scriptContext);
Js::DynamicObject* eventDataObject = debugEventObject.GetEventDataObject();
JsrtDebugUtils::AddSourceMetadataToObject(eventDataObject, utf8SourceInfo);
this->CallDebugEventCallback(JsDiagDebugEventSourceCompile, eventDataObject, scriptContext, false /*isBreak*/);
}
}
return S_OK;
}
#if ENABLE_TTD
void JsrtDebugManager::ReportScriptCompile_TTD(Js::FunctionBody* body, Js::Utf8SourceInfo* utf8SourceInfo, CompileScriptException* compileException, bool notify)
{
if(this->debugEventCallback == nullptr)
{
return;
}
Js::ScriptContext* scriptContext = utf8SourceInfo->GetScriptContext();
JsrtDebugEventObject debugEventObject(scriptContext);
Js::DynamicObject* eventDataObject = debugEventObject.GetEventDataObject();
JsrtDebugDocumentManager* debugDocumentManager = this->GetDebugDocumentManager();
Assert(debugDocumentManager != nullptr);
// Create DebugDocument and then report JsDiagDebugEventSourceCompile event
Js::DebugDocument* debugDocument = HeapNewNoThrow(Js::DebugDocument, utf8SourceInfo, body);
if(debugDocument != nullptr)
{
utf8SourceInfo->SetDebugDocument(debugDocument);
}
JsrtDebugUtils::AddSourceMetadataToObject(eventDataObject, utf8SourceInfo);
if(notify)
{
this->CallDebugEventCallback(JsDiagDebugEventSourceCompile, eventDataObject, scriptContext, false /*isBreak*/);
}
}
#endif
void JsrtDebugManager::ReportScriptCompile(Js::JavascriptFunction* scriptFunction, Js::Utf8SourceInfo* utf8SourceInfo, CompileScriptException* compileException)
{
if (this->debugEventCallback != nullptr)
{
Js::ScriptContext* scriptContext = utf8SourceInfo->GetScriptContext();
JsrtDebugEventObject debugEventObject(scriptContext);
Js::DynamicObject* eventDataObject = debugEventObject.GetEventDataObject();
JsDiagDebugEvent jsDiagDebugEvent = JsDiagDebugEventCompileError;
if (scriptFunction == nullptr)
{
// Report JsDiagDebugEventCompileError event
JsrtDebugUtils::AddPropertyToObject(eventDataObject, JsrtDebugPropertyId::error, compileException->ei.bstrDescription, ::SysStringLen(compileException->ei.bstrDescription), scriptContext);
JsrtDebugUtils::AddPropertyToObject(eventDataObject, JsrtDebugPropertyId::line, compileException->line, scriptContext);
JsrtDebugUtils::AddPropertyToObject(eventDataObject, JsrtDebugPropertyId::column, compileException->ichMin - compileException->ichMinLine - 1, scriptContext); // Converted to 0-based
JsrtDebugUtils::AddPropertyToObject(eventDataObject, JsrtDebugPropertyId::sourceText, compileException->bstrLine, ::SysStringLen(compileException->bstrLine), scriptContext);
}
else
{
JsrtDebugDocumentManager* debugDocumentManager = this->GetDebugDocumentManager();
Assert(debugDocumentManager != nullptr);
// Create DebugDocument and then report JsDiagDebugEventSourceCompile event
Js::DebugDocument* debugDocument = HeapNewNoThrow(Js::DebugDocument, utf8SourceInfo, scriptFunction->GetFunctionBody());
if (debugDocument != nullptr)
{
utf8SourceInfo->SetDebugDocument(debugDocument);
}
jsDiagDebugEvent = JsDiagDebugEventSourceCompile;
}
JsrtDebugUtils::AddSourceMetadataToObject(eventDataObject, utf8SourceInfo);
this->CallDebugEventCallback(jsDiagDebugEvent, eventDataObject, scriptContext, false /*isBreak*/);
}
}
void JsrtDebugManager::ReportBreak(Js::InterpreterHaltState* haltState)
{
if (this->debugEventCallback != nullptr)
{
Js::FunctionBody* functionBody = haltState->GetFunction();
Assert(functionBody != nullptr);
Js::Utf8SourceInfo* utf8SourceInfo = functionBody->GetUtf8SourceInfo();
int currentByteCodeOffset = haltState->GetCurrentOffset();
Js::ScriptContext* scriptContext = utf8SourceInfo->GetScriptContext();
JsDiagDebugEvent jsDiagDebugEvent = this->GetDebugEventFromStopType(haltState->stopType);
JsrtDebugEventObject debugEventObject(scriptContext);
Js::DynamicObject* eventDataObject = debugEventObject.GetEventDataObject();
Js::ProbeContainer* probeContainer = scriptContext->GetDebugContext()->GetProbeContainer();
if (jsDiagDebugEvent == JsDiagDebugEventBreakpoint)
{
UINT bpId = 0;
probeContainer->MapProbesUntil([&](int i, Js::Probe* pProbe)
{
Js::BreakpointProbe* bp = (Js::BreakpointProbe*)pProbe;
if (bp->Matches(functionBody, utf8SourceInfo->GetDebugDocument(), currentByteCodeOffset))
{
bpId = bp->GetId();
return true;
}
return false;
});
AssertMsg(bpId != 0, "How come we don't have a breakpoint id for JsDiagDebugEventBreakpoint");
JsrtDebugUtils::AddPropertyToObject(eventDataObject, JsrtDebugPropertyId::breakpointId, bpId, scriptContext);
}
JsrtDebugUtils::AddScriptIdToObject(eventDataObject, utf8SourceInfo);
JsrtDebugUtils::AddLineColumnToObject(eventDataObject, functionBody, currentByteCodeOffset);
JsrtDebugUtils::AddSourceLengthAndTextToObject(eventDataObject, functionBody, currentByteCodeOffset);
this->CallDebugEventCallbackForBreak(jsDiagDebugEvent, eventDataObject, scriptContext);
}
}
void JsrtDebugManager::ReportExceptionBreak(Js::InterpreterHaltState* haltState)
{
if (this->debugEventCallback != nullptr)
{
Assert(haltState->stopType == Js::STOP_EXCEPTIONTHROW);
Js::Utf8SourceInfo* utf8SourceInfo = haltState->GetFunction()->GetUtf8SourceInfo();
Js::ScriptContext* scriptContext = utf8SourceInfo->GetScriptContext();
JsDiagDebugEvent jsDiagDebugEvent = JsDiagDebugEventRuntimeException;
JsrtDebugEventObject debugEventObject(scriptContext);
Js::DynamicObject* eventDataObject = debugEventObject.GetEventDataObject();
JsrtDebugUtils::AddScriptIdToObject(eventDataObject, utf8SourceInfo);
Js::FunctionBody* functionBody = haltState->topFrame->GetFunction();
Assert(functionBody != nullptr);
int currentByteCodeOffset = haltState->topFrame->GetByteCodeOffset();
JsrtDebugUtils::AddLineColumnToObject(eventDataObject, functionBody, currentByteCodeOffset);
JsrtDebugUtils::AddSourceLengthAndTextToObject(eventDataObject, functionBody, currentByteCodeOffset);
JsrtDebugUtils::AddPropertyToObject(eventDataObject, JsrtDebugPropertyId::uncaught, !haltState->exceptionObject->IsFirstChanceException(), scriptContext);
Js::ResolvedObject resolvedObject;
resolvedObject.scriptContext = scriptContext;
resolvedObject.name = _u("{exception}");
resolvedObject.typeId = Js::TypeIds_Error;
resolvedObject.address = nullptr;
resolvedObject.obj = scriptContext->GetDebugContext()->GetProbeContainer()->GetExceptionObject();
if (resolvedObject.obj == nullptr)
{
resolvedObject.obj = resolvedObject.scriptContext->GetLibrary()->GetUndefined();
}
JsrtDebuggerObjectBase::CreateDebuggerObject<JsrtDebuggerObjectProperty>(this->GetDebuggerObjectsManager(), resolvedObject, scriptContext, /* forceSetValueProp */ false, [&](Js::Var marshaledObj)
{
JsrtDebugUtils::AddPropertyToObject(eventDataObject, JsrtDebugPropertyId::exception, marshaledObj, scriptContext);
});
this->CallDebugEventCallbackForBreak(jsDiagDebugEvent, eventDataObject, scriptContext);
}
}
void JsrtDebugManager::HandleResume(Js::InterpreterHaltState* haltState, BREAKRESUMEACTION resumeAction)
{
Assert(resumeAction != BREAKRESUMEACTION_ABORT);
Js::ScriptContext* scriptContext = haltState->framePointers->Peek()->GetScriptContext();
scriptContext->GetThreadContext()->GetDebugManager()->stepController.HandleResumeAction(haltState, resumeAction);
}
void JsrtDebugManager::SetResumeType(BREAKRESUMEACTION resumeAction)
{
this->resumeAction = resumeAction;
}
bool JsrtDebugManager::EnableAsyncBreak(Js::ScriptContext* scriptContext)
{
if (!scriptContext->IsDebugContextInitialized())
{
// Although the script context exists, it hasn't been fully initialized yet.
return false;
}
Js::ProbeContainer* probeContainer = scriptContext->GetDebugContext()->GetProbeContainer();
// This can be called when we are already at break
if (!probeContainer->IsAsyncActivate())
{
probeContainer->AsyncActivate(this);
if (Js::Configuration::Global.EnableJitInDebugMode())
{
scriptContext->GetThreadContext()->GetDebugManager()->GetDebuggingFlags()->SetForceInterpreter(true);
}
return true;
}
return false;
}
void JsrtDebugManager::CallDebugEventCallback(JsDiagDebugEvent debugEvent, Js::DynamicObject* eventDataObject, Js::ScriptContext* scriptContext, bool isBreak)
{
class AutoClear
{
public:
AutoClear(JsrtDebugManager* jsrtDebug, void* dispatchHaltFrameAddress)
{
this->jsrtDebugManager = jsrtDebug;
this->jsrtDebugManager->GetThreadContext()->GetDebugManager()->SetDispatchHaltFrameAddress(dispatchHaltFrameAddress);
}
~AutoClear()
{
if (jsrtDebugManager->debuggerObjectsManager != nullptr)
{
jsrtDebugManager->GetDebuggerObjectsManager()->ClearAll();
}
if (jsrtDebugManager->stackFrames != nullptr)
{
Adelete(jsrtDebugManager->GetDebugObjectArena(), jsrtDebugManager->stackFrames);
jsrtDebugManager->stackFrames = nullptr;
}
this->jsrtDebugManager->GetThreadContext()->GetDebugManager()->SetDispatchHaltFrameAddress(nullptr);
this->jsrtDebugManager = nullptr;
}
private:
JsrtDebugManager* jsrtDebugManager;
};
auto funcPtr = [&]()
{
if (isBreak)
{
void *frameAddress = _AddressOfReturnAddress();
// If we are reporting break we should clear all objects after call returns
// Save the frame address, when asking for stack we will only give stack which is under this address
// because host can execute javascript after break which should not be part of stack.
AutoClear autoClear(this, frameAddress);
this->debugEventCallback(debugEvent, eventDataObject, this->callbackState);
}
else
{
this->debugEventCallback(debugEvent, eventDataObject, this->callbackState);
}
};
if (scriptContext->GetThreadContext()->IsScriptActive())
{
BEGIN_LEAVE_SCRIPT(scriptContext)
{
funcPtr();
}
END_LEAVE_SCRIPT(scriptContext);
}
else
{
funcPtr();
}
}
void JsrtDebugManager::CallDebugEventCallbackForBreak(JsDiagDebugEvent debugEvent, Js::DynamicObject* eventDataObject, Js::ScriptContext* scriptContext)
{
AutoSetDispatchHaltFlag autoSetDispatchHaltFlag(scriptContext, scriptContext->GetThreadContext());
this->CallDebugEventCallback(debugEvent, eventDataObject, scriptContext, true /*isBreak*/);
for (Js::ScriptContext *tempScriptContext = scriptContext->GetThreadContext()->GetScriptContextList();
tempScriptContext != nullptr && !tempScriptContext->IsClosed();
tempScriptContext = tempScriptContext->next)
{
tempScriptContext->GetDebugContext()->GetProbeContainer()->AsyncDeactivate();
}
if (Js::Configuration::Global.EnableJitInDebugMode())
{
scriptContext->GetThreadContext()->GetDebugManager()->GetDebuggingFlags()->SetForceInterpreter(false);
}
}
Js::DynamicObject* JsrtDebugManager::GetScript(Js::Utf8SourceInfo* utf8SourceInfo)
{
Js::DynamicObject* scriptObject = utf8SourceInfo->GetScriptContext()->GetLibrary()->CreateObject();
JsrtDebugUtils::AddSourceMetadataToObject(scriptObject, utf8SourceInfo);
return scriptObject;
}
Js::JavascriptArray* JsrtDebugManager::GetScripts(Js::ScriptContext* scriptContext)
{
Js::JavascriptArray* scriptsArray = scriptContext->GetLibrary()->CreateArray();
int index = 0;
for (Js::ScriptContext *tempScriptContext = scriptContext->GetThreadContext()->GetScriptContextList();
tempScriptContext != nullptr && !tempScriptContext->IsClosed();
tempScriptContext = tempScriptContext->next)
{
tempScriptContext->MapScript([&](Js::Utf8SourceInfo* utf8SourceInfo)
{
if (!utf8SourceInfo->GetIsLibraryCode() && utf8SourceInfo->HasDebugDocument())
{
bool isCallerLibraryCode = false;
bool isDynamic = utf8SourceInfo->IsDynamic();
if (isDynamic)
{
// If the code is dynamic (eval or new Function) only return the script if parent is non-library
Js::Utf8SourceInfo* callerUtf8SourceInfo = utf8SourceInfo->GetCallerUtf8SourceInfo();
while (callerUtf8SourceInfo != nullptr && !isCallerLibraryCode)
{
isCallerLibraryCode = callerUtf8SourceInfo->GetIsLibraryCode();
callerUtf8SourceInfo = callerUtf8SourceInfo->GetCallerUtf8SourceInfo();
}
}
if (!isCallerLibraryCode)
{
Js::DynamicObject* sourceObj = this->GetScript(utf8SourceInfo);
if (sourceObj != nullptr)
{
Js::Var marshaledObj = Js::CrossSite::MarshalVar(scriptContext, sourceObj);
scriptsArray->DirectSetItemAt(index, marshaledObj);
index++;
}
}
}
});
}
return scriptsArray;
}
Js::DynamicObject* JsrtDebugManager::GetSource(Js::ScriptContext* scriptContext, uint scriptId)
{
Js::Utf8SourceInfo* utf8SourceInfo = nullptr;
for (Js::ScriptContext *tempScriptContext = this->threadContext->GetScriptContextList();
tempScriptContext != nullptr && utf8SourceInfo == nullptr && !tempScriptContext->IsClosed();
tempScriptContext = tempScriptContext->next)
{
tempScriptContext->MapScript([&](Js::Utf8SourceInfo* sourceInfo) -> bool
{
if (sourceInfo->IsInDebugMode() && sourceInfo->GetSourceInfoId() == scriptId)
{
utf8SourceInfo = sourceInfo;
return true;
}
return false;
});
}
Js::DynamicObject* sourceObject = nullptr;
if (utf8SourceInfo != nullptr)
{
sourceObject = (Js::DynamicObject*)Js::CrossSite::MarshalVar(utf8SourceInfo->GetScriptContext(), scriptContext->GetLibrary()->CreateObject());
JsrtDebugUtils::AddSourceMetadataToObject(sourceObject, utf8SourceInfo);
JsrtDebugUtils::AddSourceToObject(sourceObject, utf8SourceInfo);
}
return sourceObject;
}
Js::JavascriptArray* JsrtDebugManager::GetStackFrames(Js::ScriptContext* scriptContext)
{
if (this->stackFrames == nullptr)
{
this->stackFrames = Anew(this->GetDebugObjectArena(), JsrtDebugStackFrames, this);
}
return this->stackFrames->StackFrames(scriptContext);
}
bool JsrtDebugManager::TryGetFrameObjectFromFrameIndex(Js::ScriptContext *scriptContext, uint frameIndex, JsrtDebuggerStackFrame ** debuggerStackFrame)
{
if (this->stackFrames == nullptr)
{
this->GetStackFrames(scriptContext);
}
return this->stackFrames->TryGetFrameObjectFromFrameIndex(frameIndex, debuggerStackFrame);
}
Js::DynamicObject* JsrtDebugManager::SetBreakPoint(Js::ScriptContext* scriptContext, Js::Utf8SourceInfo* utf8SourceInfo, UINT lineNumber, UINT columnNumber)
{
Js::DebugDocument* debugDocument = utf8SourceInfo->GetDebugDocument();
if (debugDocument != nullptr && SUCCEEDED(utf8SourceInfo->EnsureLineOffsetCacheNoThrow()) && lineNumber < utf8SourceInfo->GetLineCount())
{
charcount_t charPosition = 0;
charcount_t byteOffset = 0;
utf8SourceInfo->GetCharPositionForLineInfo((charcount_t)lineNumber, &charPosition, &byteOffset);
long ibos = charPosition + columnNumber + 1;
Js::StatementLocation statement;
if (!debugDocument->GetStatementLocation(ibos, &statement))
{
return nullptr;
}
// Don't see a use case for supporting multiple breakpoints at same location.
// If a breakpoint already exists, just return that
Js::BreakpointProbe* probe = debugDocument->FindBreakpoint(statement);
if (probe == nullptr)
{
probe = debugDocument->SetBreakPoint(statement, BREAKPOINT_ENABLED);
if(probe == nullptr)
{
return nullptr;
}
this->GetDebugDocumentManager()->AddDocument(probe->GetId(), debugDocument);
}
probe->GetStatementLocation(&statement);
Js::DynamicObject* bpObject = (Js::DynamicObject*)Js::CrossSite::MarshalVar(debugDocument->GetUtf8SourceInfo()->GetScriptContext(), scriptContext->GetLibrary()->CreateObject());
JsrtDebugUtils::AddPropertyToObject(bpObject, JsrtDebugPropertyId::breakpointId, probe->GetId(), scriptContext);
JsrtDebugUtils::AddLineColumnToObject(bpObject, statement.function, statement.bytecodeSpan.begin);
JsrtDebugUtils::AddScriptIdToObject(bpObject, utf8SourceInfo);
return bpObject;
}
return nullptr;
}
void JsrtDebugManager::GetBreakpoints(Js::JavascriptArray** bpsArray, Js::ScriptContext* scriptContext)
{
Js::ScriptContext* arrayScriptContext = (*bpsArray)->GetScriptContext();
Js::ProbeContainer* probeContainer = scriptContext->GetDebugContext()->GetProbeContainer();
probeContainer->MapProbes([&](int i, Js::Probe* pProbe)
{
Js::BreakpointProbe* bp = (Js::BreakpointProbe*)pProbe;
Js::DynamicObject* bpObject = scriptContext->GetLibrary()->CreateObject();
JsrtDebugUtils::AddPropertyToObject(bpObject, JsrtDebugPropertyId::breakpointId, bp->GetId(), scriptContext);
JsrtDebugUtils::AddLineColumnToObject(bpObject, bp->GetFunctionBody(), bp->GetBytecodeOffset());
Js::Utf8SourceInfo* utf8SourceInfo = bp->GetDbugDocument()->GetUtf8SourceInfo();
JsrtDebugUtils::AddScriptIdToObject(bpObject, utf8SourceInfo);
Js::Var marshaledObj = Js::CrossSite::MarshalVar(arrayScriptContext, bpObject);
Js::JavascriptOperators::OP_SetElementI((Js::Var)(*bpsArray), Js::JavascriptNumber::ToVar((*bpsArray)->GetLength(), arrayScriptContext), marshaledObj, arrayScriptContext);
});
}
#if ENABLE_TTD
Js::BreakpointProbe* JsrtDebugManager::SetBreakpointHelper_TTD(int64 desiredBpId, Js::ScriptContext* scriptContext, Js::Utf8SourceInfo* utf8SourceInfo, UINT lineNumber, UINT columnNumber, BOOL* isNewBP)
{
*isNewBP = FALSE;
Js::DebugDocument* debugDocument = utf8SourceInfo->GetDebugDocument();
if(debugDocument != nullptr && SUCCEEDED(utf8SourceInfo->EnsureLineOffsetCacheNoThrow()) && lineNumber < utf8SourceInfo->GetLineCount())
{
charcount_t charPosition = 0;
charcount_t byteOffset = 0;
utf8SourceInfo->GetCharPositionForLineInfo((charcount_t)lineNumber, &charPosition, &byteOffset);
long ibos = charPosition + columnNumber + 1;
Js::StatementLocation statement;
if(!debugDocument->GetStatementLocation(ibos, &statement))
{
return nullptr;
}
// Don't see a use case for supporting multiple breakpoints at same location.
// If a breakpoint already exists, just return that
Js::BreakpointProbe* probe = debugDocument->FindBreakpoint(statement);
TTDAssert(probe == nullptr || desiredBpId == -1, "We shouldn't be resetting this BP unless it was cleared earlier!");
if(probe == nullptr)
{
probe = debugDocument->SetBreakPoint_TTDWbpId(desiredBpId, statement);
*isNewBP = TRUE;
this->GetDebugDocumentManager()->AddDocument(probe->GetId(), debugDocument);
}
return probe;
}
return nullptr;
}
#endif
JsrtDebuggerObjectsManager* JsrtDebugManager::GetDebuggerObjectsManager()
{
if (this->debuggerObjectsManager == nullptr)
{
this->debuggerObjectsManager = Anew(this->GetDebugObjectArena(), JsrtDebuggerObjectsManager, this);
}
return this->debuggerObjectsManager;
}
void JsrtDebugManager::ClearDebuggerObjects()
{
if (this->debuggerObjectsManager != nullptr)
{
this->debuggerObjectsManager->ClearAll();
}
}
ArenaAllocator* JsrtDebugManager::GetDebugObjectArena()
{
if (this->debugObjectArena == nullptr)
{
this->debugObjectArena = HeapNew(ArenaAllocator, _u("DebugObjectArena"), this->threadContext->GetPageAllocator(), Js::Throw::OutOfMemory);
this->threadContext->GetRecycler()->RegisterExternalGuestArena(this->debugObjectArena);
}
return this->debugObjectArena;
}
JsrtDebugDocumentManager* JsrtDebugManager::GetDebugDocumentManager()
{
if (this->debugDocumentManager == nullptr)
{
this->debugDocumentManager = Anew(this->GetDebugObjectArena(), JsrtDebugDocumentManager, this);
}
return this->debugDocumentManager;
}
void JsrtDebugManager::ClearDebugDocument(Js::ScriptContext* scriptContext)
{
if (this->debugDocumentManager != nullptr)
{
this->debugDocumentManager->ClearDebugDocument(scriptContext);
}
}
void JsrtDebugManager::ClearBreakpointDebugDocumentDictionary()
{
if (this->debugDocumentManager != nullptr)
{
this->debugDocumentManager->ClearBreakpointDebugDocumentDictionary();
}
}
bool JsrtDebugManager::RemoveBreakpoint(UINT breakpointId)
{
if (this->debugDocumentManager != nullptr)
{
return this->GetDebugDocumentManager()->RemoveBreakpoint(breakpointId);
}
return false;
}
void JsrtDebugManager::SetBreakOnException(JsDiagBreakOnExceptionAttributes exceptionAttributes)
{
this->breakOnExceptionAttributes = exceptionAttributes;
}
JsDiagBreakOnExceptionAttributes JsrtDebugManager::GetBreakOnException()
{
return this->breakOnExceptionAttributes;
}
JsDiagDebugEvent JsrtDebugManager::GetDebugEventFromStopType(Js::StopType stopType)
{
switch (stopType)
{
case Js::STOP_BREAKPOINT: return JsDiagDebugEventBreakpoint;
case Js::STOP_INLINEBREAKPOINT: return JsDiagDebugEventDebuggerStatement;
case Js::STOP_STEPCOMPLETE: return JsDiagDebugEventStepComplete;
case Js::STOP_EXCEPTIONTHROW: return JsDiagDebugEventRuntimeException;
case Js::STOP_ASYNCBREAK: return JsDiagDebugEventAsyncBreak;
case Js::STOP_MUTATIONBREAKPOINT:
default:
Assert("Unhandled stoptype");
break;
}
return JsDiagDebugEventBreakpoint;
}
#endif