//------------------------------------------------------------------------------------------------------- // 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(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