blob: 6673a5bad0707dc7b4478a06b17b66bf03dd4f9a [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"
#include "Language/JavascriptStackWalker.h"
#include "Language/InterpreterStackFrame.h"
namespace Js
{
ProbeContainer::ProbeContainer() :
diagProbeList(nullptr),
pScriptContext(nullptr),
debugManager(nullptr),
haltCallbackProbe(nullptr),
debuggerOptionsCallback(nullptr),
pAsyncHaltCallback(nullptr),
jsExceptionObject(nullptr),
framePointers(nullptr),
debugSessionNumber(0),
tmpRegCount(0),
bytecodeOffset(0),
IsNextStatementChanged(false),
isThrowInternal(false),
forceBypassDebugEngine(false),
isPrimaryBrokenToDebuggerContext(false),
isForcedToEnterScriptStart(false),
registeredFuncContextList(nullptr)
{
}
ProbeContainer::~ProbeContainer()
{
this->Close();
}
void ProbeContainer::Close()
{
// Probe manager instance may go down early.
if (this->pScriptContext)
{
debugManager = this->pScriptContext->GetThreadContext()->GetDebugManager();
}
else
{
debugManager = nullptr;
}
if (debugManager != nullptr && debugManager->stepController.pActivatedContext == pScriptContext)
{
debugManager->stepController.Deactivate();
}
#ifdef ENABLE_MUTATION_BREAKPOINT
this->RemoveMutationBreakpointListIfNeeded();
#endif
pScriptContext = nullptr;
debugManager = nullptr;
}
void ProbeContainer::Initialize(ScriptContext* pScriptContext)
{
if (!diagProbeList)
{
ArenaAllocator* global = pScriptContext->AllocatorForDiagnostics();
diagProbeList = ProbeList::New(global);
pendingProbeList = ProbeList::New(global);
this->pScriptContext = pScriptContext;
this->debugManager = this->pScriptContext->GetThreadContext()->GetDebugManager();
this->pinnedPropertyRecords = JsUtil::List<const Js::PropertyRecord*>::New(this->pScriptContext->GetRecycler());
this->pScriptContext->BindReference((void *)this->pinnedPropertyRecords);
}
}
void ProbeContainer::StartRecordingCall()
{
this->debugManager->stepController.StartRecordingCall();
}
void ProbeContainer::EndRecordingCall(Js::Var returnValue, Js::JavascriptFunction * function)
{
this->debugManager->stepController.EndRecordingCall(returnValue, function);
}
ReturnedValueList* ProbeContainer::GetReturnedValueList() const
{
return this->debugManager->stepController.GetReturnedValueList();
}
void ProbeContainer::ResetReturnedValueList()
{
this->debugManager->stepController.ResetReturnedValueList();
}
void ProbeContainer::UpdateFramePointers(bool fMatchWithCurrentScriptContext, DWORD_PTR dispatchHaltFrameAddress)
{
ArenaAllocator* pDiagArena = debugManager->GetDiagnosticArena()->Arena();
framePointers = Anew(pDiagArena, DiagStack, pDiagArena);
JavascriptStackWalker walker(pScriptContext, !fMatchWithCurrentScriptContext, nullptr/*returnAddress*/, true/*forceFullWalk*/);
DiagStack* tempFramePointers = Anew(pDiagArena, DiagStack, pDiagArena);
const bool isLibraryFrameEnabledDebugger = IsLibraryStackFrameSupportEnabled();
walker.WalkUntil([&](JavascriptFunction* func, ushort frameIndex) -> bool
{
if (isLibraryFrameEnabledDebugger || !func->IsLibraryCode())
{
DiagStackFrame* frm = nullptr;
InterpreterStackFrame *interpreterFrame = walker.GetCurrentInterpreterFrame();
ScriptContext* frameScriptContext = walker.GetCurrentScriptContext();
Assert(frameScriptContext);
if (!fMatchWithCurrentScriptContext && !frameScriptContext->IsScriptContextInDebugMode() && tempFramePointers->Count() == 0)
{
// this means the top frame is not in the debug mode. We shouldn't be stopping for this break.
// This could happen if the exception happens on the diagnosticsScriptEngine.
return true;
}
// Ignore frames which are not in debug mode, which can happen when diag engine calls into user engine under debugger
// -- topmost frame is under debugger but some frames could be in non-debug mode as they are from diag engine.
if (frameScriptContext->IsScriptContextInDebugMode() &&
(!fMatchWithCurrentScriptContext || frameScriptContext == pScriptContext))
{
if (interpreterFrame)
{
if (dispatchHaltFrameAddress == 0 || interpreterFrame->GetStackAddress() > dispatchHaltFrameAddress)
{
frm = Anew(pDiagArena, DiagInterpreterStackFrame, interpreterFrame);
}
}
else
{
void* stackAddress = walker.GetCurrentArgv();
if (dispatchHaltFrameAddress == 0 || reinterpret_cast<DWORD_PTR>(stackAddress) > dispatchHaltFrameAddress)
{
#if ENABLE_NATIVE_CODEGEN
if (func->IsScriptFunction())
{
frm = Anew(pDiagArena, DiagNativeStackFrame,
ScriptFunction::FromVar(walker.GetCurrentFunction()), walker.GetByteCodeOffset(), stackAddress, walker.GetCurrentCodeAddr());
}
else
#else
Assert(!func->IsScriptFunction());
#endif
{
frm = Anew(pDiagArena, DiagRuntimeStackFrame, func, walker.GetCurrentNativeLibraryEntryName(), stackAddress);
}
}
}
}
if (frm)
{
tempFramePointers->Push(frm);
}
}
return false;
});
OUTPUT_TRACE(Js::DebuggerPhase, _u("ProbeContainer::UpdateFramePointers: detected %d frames (this=%p, fMatchWithCurrentScriptContext=%d)\n"),
tempFramePointers->Count(), this, fMatchWithCurrentScriptContext);
while (tempFramePointers->Count())
{
framePointers->Push(tempFramePointers->Pop());
}
}
WeakDiagStack * ProbeContainer::GetFramePointers(DWORD_PTR dispatchHaltFrameAddress)
{
if (framePointers == nullptr || this->debugSessionNumber < debugManager->GetDebugSessionNumber())
{
UpdateFramePointers(/*fMatchWithCurrentScriptContext*/true, dispatchHaltFrameAddress);
this->debugSessionNumber = debugManager->GetDebugSessionNumber();
if ((framePointers->Count() > 0) &&
debugManager->IsMatchTopFrameStackAddress(framePointers->Peek(0)))
{
framePointers->Peek(0)->SetIsTopFrame();
}
}
ReferencedArenaAdapter* pRefArena = debugManager->GetDiagnosticArena();
return HeapNew(WeakDiagStack,pRefArena,framePointers);
}
bool ProbeContainer::InitializeLocation(InterpreterHaltState* pHaltState, bool fMatchWithCurrentScriptContext)
{
Assert(debugManager);
debugManager->SetCurrentInterpreterLocation(pHaltState);
ArenaAllocator* pDiagArena = debugManager->GetDiagnosticArena()->Arena();
UpdateFramePointers(fMatchWithCurrentScriptContext);
pHaltState->framePointers = framePointers;
pHaltState->stringBuilder = Anew(pDiagArena, StringBuilder<ArenaAllocator>, pDiagArena);
if (pHaltState->framePointers->Count() > 0)
{
pHaltState->topFrame = pHaltState->framePointers->Peek(0);
pHaltState->topFrame->SetIsTopFrame();
}
OUTPUT_TRACE(Js::DebuggerPhase, _u("ProbeContainer::InitializeLocation (end): this=%p, pHaltState=%p, fMatch=%d, topFrame=%p\n"),
this, pHaltState, fMatchWithCurrentScriptContext, pHaltState->topFrame);
return true;
}
void ProbeContainer::DestroyLocation()
{
OUTPUT_TRACE(Js::DebuggerPhase, _u("ProbeContainer::DestroyLocation (start): this=%p, IsNextStatementChanged=%d, haltCallbackProbe=%p\n"),
this, this->IsNextStatementChanged, haltCallbackProbe);
if (IsNextStatementChanged)
{
Assert(bytecodeOffset != debugManager->stepController.byteOffset);
// Note: when we dispatching an exception bytecodeOffset would be same as pProbeManager->pCurrentInterpreterLocation->GetCurrentOffset().
debugManager->pCurrentInterpreterLocation->SetCurrentOffset(bytecodeOffset);
IsNextStatementChanged = false;
}
framePointers = nullptr;
// Reset the exception object.
jsExceptionObject = nullptr;
Assert(debugManager);
debugManager->UnsetCurrentInterpreterLocation();
pinnedPropertyRecords->Reset();
// Guarding if the probe engine goes away when we are sitting at breakpoint.
if (haltCallbackProbe)
{
// The clean up is called here to scriptengine's object to remove all DebugStackFrames
haltCallbackProbe->CleanupHalt();
}
}
bool ProbeContainer::CanDispatchHalt(InterpreterHaltState* pHaltState)
{
if (!haltCallbackProbe || haltCallbackProbe->IsInClosedState() || debugManager->IsAtDispatchHalt())
{
OUTPUT_VERBOSE_TRACE(Js::DebuggerPhase, _u("ProbeContainer::CanDispatchHalt: Not in break mode. pHaltState = %p\n"), pHaltState);
return false;
}
return true;
}
void ProbeContainer::DispatchStepHandler(InterpreterHaltState* pHaltState, OpCode* pOriginalOpcode)
{
OUTPUT_TRACE(Js::DebuggerPhase, _u("ProbeContainer::DispatchStepHandler: start: this=%p, pHaltState=%p, pOriginalOpcode=0x%x\n"), this, pHaltState, pOriginalOpcode);
if (!CanDispatchHalt(pHaltState))
{
return;
}
TryFinally([&]()
{
InitializeLocation(pHaltState);
OUTPUT_TRACE(Js::DebuggerPhase, _u("ProbeContainer::DispatchStepHandler: initialized location: pHaltState=%p, pHaltState->IsValid()=%d\n"),
pHaltState, pHaltState->IsValid());
if (pHaltState->IsValid()) // Only proceed if we find a valid top frame and that is the executing function
{
if (debugManager->stepController.IsStepComplete(pHaltState, haltCallbackProbe, *pOriginalOpcode))
{
OpCode oldOpcode = *pOriginalOpcode;
pHaltState->GetFunction()->ProbeAtOffset(pHaltState->GetCurrentOffset(), pOriginalOpcode);
pHaltState->GetFunction()->CheckAndRegisterFuncToDiag(pScriptContext);
debugManager->stepController.Deactivate(pHaltState);
haltCallbackProbe->DispatchHalt(pHaltState);
if (oldOpcode == OpCode::Break && debugManager->stepController.stepType == STEP_DOCUMENT)
{
// That means we have delivered the stepping to the debugger, where we had the breakpoint
// already, however it is possible that debugger can initiate the step_document. In that
// case debugger did not break due to break. So we have break as a breakpoint reason.
*pOriginalOpcode = OpCode::Break;
}
else if (OpCode::Break == *pOriginalOpcode)
{
debugManager->stepController.stepCompleteOnInlineBreakpoint = true;
}
}
}
},
[&](bool)
{
DestroyLocation();
});
OUTPUT_TRACE(Js::DebuggerPhase, _u("ProbeContainer::DispatchStepHandler: end: pHaltState=%p\n"), pHaltState);
}
void ProbeContainer::DispatchAsyncBreak(InterpreterHaltState* pHaltState)
{
OUTPUT_TRACE(Js::DebuggerPhase, _u("ProbeContainer::DispatchAsyncBreak: start: this=%p, pHaltState=%p\n"), this, pHaltState);
if (!this->pAsyncHaltCallback || !CanDispatchHalt(pHaltState))
{
return;
}
TryFinally([&]()
{
InitializeLocation(pHaltState, /* We don't need to match script context, stop at any available script function */ false);
OUTPUT_TRACE(Js::DebuggerPhase, _u("ProbeContainer::DispatchAsyncBreak: initialized location: pHaltState=%p, pHaltState->IsValid()=%d\n"),
pHaltState, pHaltState->IsValid());
if (pHaltState->IsValid())
{
// Activate the current haltCallback with asyncStepController.
debugManager->asyncBreakController.Activate(this->pAsyncHaltCallback);
if (debugManager->asyncBreakController.IsAtStoppingLocation(pHaltState))
{
OUTPUT_TRACE(Js::DebuggerPhase, _u("ProbeContainer::DispatchAsyncBreak: IsAtStoppingLocation: pHaltState=%p\n"), pHaltState);
pHaltState->GetFunction()->CheckAndRegisterFuncToDiag(pScriptContext);
debugManager->stepController.Deactivate(pHaltState);
debugManager->asyncBreakController.DispatchAndReset(pHaltState);
}
}
},
[&](bool)
{
DestroyLocation();
});
OUTPUT_TRACE(Js::DebuggerPhase, _u("ProbeContainer::DispatchAsyncBreak: end: pHaltState=%p\n"), pHaltState);
}
void ProbeContainer::DispatchInlineBreakpoint(InterpreterHaltState* pHaltState)
{
OUTPUT_TRACE(Js::DebuggerPhase, _u("ProbeContainer::DispatchInlineBreakpoint: start: this=%p, pHaltState=%p\n"), this, pHaltState);
if (!CanDispatchHalt(pHaltState))
{
return;
}
Assert(pHaltState->stopType == STOP_INLINEBREAKPOINT);
TryFinally([&]()
{
InitializeLocation(pHaltState);
OUTPUT_TRACE(Js::DebuggerPhase, _u("ProbeContainer::DispatchInlineBreakpoint: initialized location: pHaltState=%p, pHaltState->IsValid()=%d\n"),
pHaltState, pHaltState->IsValid());
Assert(pHaltState->IsValid());
// The ByteCodeReader should be available at this point, but because of possibility of garbled frame, we shouldn't hit AV
if (pHaltState->IsValid())
{
#if DBG
pHaltState->GetFunction()->MustBeInDebugMode();
#endif
// an inline breakpoint is being dispatched deactivate other stopping controllers
debugManager->stepController.Deactivate(pHaltState);
debugManager->asyncBreakController.Deactivate();
pHaltState->GetFunction()->CheckAndRegisterFuncToDiag(pScriptContext);
haltCallbackProbe->DispatchHalt(pHaltState);
}
},
[&](bool)
{
DestroyLocation();
});
OUTPUT_TRACE(Js::DebuggerPhase, _u("ProbeContainer::DispatchInlineBreakpoint: end: pHaltState=%p\n"), pHaltState);
}
bool ProbeContainer::DispatchExceptionBreakpoint(InterpreterHaltState* pHaltState)
{
OUTPUT_TRACE(Js::DebuggerPhase, _u("ProbeContainer::DispatchExceptionBreakpoint: start: this=%p, pHaltState=%p\n"), this, pHaltState);
bool fSuccess = false;
if (!haltCallbackProbe || haltCallbackProbe->IsInClosedState() || debugManager->IsAtDispatchHalt())
{
OUTPUT_TRACE(Js::DebuggerPhase, _u("ProbeContainer::DispatchExceptionBreakpoint: not in break mode: pHaltState=%p\n"), pHaltState);
// Will not be able to handle multiple break-hits.
return fSuccess;
}
Assert(pHaltState->stopType == STOP_EXCEPTIONTHROW);
jsExceptionObject = pHaltState->exceptionObject->GetThrownObject(nullptr);
// Will store current offset of the bytecode block.
int currentOffset = -1;
TryFinally([&]()
{
InitializeLocation(pHaltState, false);
OUTPUT_TRACE(Js::DebuggerPhase, _u("ProbeContainer::DispatchExceptionBreakpoint: initialized location: pHaltState=%p, IsInterpreterFrame=%d\n"),
pHaltState, pHaltState->IsValid(), pHaltState->topFrame && pHaltState->topFrame->IsInterpreterFrame());
// The ByteCodeReader should be available at this point, but because of possibility of garbled frame, we shouldn't hit AV
if (pHaltState->IsValid() && pHaltState->GetFunction()->GetScriptContext()->IsScriptContextInDebugMode())
{
#if DBG
pHaltState->GetFunction()->MustBeInDebugMode();
#endif
// For interpreter frames, change the current location pointer of bytecode block, as it might be pointing to the next statement on the body.
// In order to generated proper binding of break on exception to the statement, the bytecode offset needed to be on the same span
// of the statement.
// For native frames the offset is always current.
// Move back a single byte to ensure that it falls under on the same statement.
if (pHaltState->topFrame->IsInterpreterFrame())
{
currentOffset = pHaltState->GetCurrentOffset();
Assert(currentOffset > 0);
pHaltState->SetCurrentOffset(currentOffset - 1);
}
// an inline breakpoint is being dispatched deactivate other stopping controllers
debugManager->stepController.Deactivate(pHaltState);
debugManager->asyncBreakController.Deactivate();
pHaltState->GetFunction()->CheckAndRegisterFuncToDiag(pScriptContext);
ScriptContext *pTopFuncContext = pHaltState->GetFunction()->GetScriptContext();
// If the top function's context is different from the current context, that means current frame is not alive anymore and breaking here cannot not happen.
// So in that case we will consider the top function's context and break on that context.
if (pTopFuncContext != pScriptContext)
{
OUTPUT_TRACE(Js::DebuggerPhase, _u("ProbeContainer::DispatchExceptionBreakpoint: top function's context is different from the current context: pHaltState=%p, haltCallbackProbe=%p\n"),
pHaltState, pTopFuncContext->GetDebugContext()->GetProbeContainer()->haltCallbackProbe);
if (pTopFuncContext->GetDebugContext()->GetProbeContainer()->haltCallbackProbe)
{
pTopFuncContext->GetDebugContext()->GetProbeContainer()->haltCallbackProbe->DispatchHalt(pHaltState);
fSuccess = true;
}
}
else
{
haltCallbackProbe->DispatchHalt(pHaltState);
fSuccess = true;
}
}
},
[&](bool)
{
// If the next statement has changed, we need to log that to exception object so that it will not try to advance to next statement again.
pHaltState->exceptionObject->SetIgnoreAdvanceToNextStatement(IsNextStatementChanged);
// Restore the current offset;
if (currentOffset != -1 && pHaltState->topFrame->IsInterpreterFrame())
{
pHaltState->SetCurrentOffset(currentOffset);
}
DestroyLocation();
});
OUTPUT_TRACE(Js::DebuggerPhase, _u("ProbeContainer::DispatchExceptionBreakpoint: end: pHaltState=%p, fSuccess=%d\n"), pHaltState, fSuccess);
return fSuccess;
}
void ProbeContainer::DispatchMutationBreakpoint(InterpreterHaltState* pHaltState)
{
Assert(pHaltState->stopType == STOP_MUTATIONBREAKPOINT);
OUTPUT_TRACE(Js::DebuggerPhase, _u("ProbeContainer::DispatchMutationBreakpoint: start: this=%p, pHaltState=%p\n"), this, pHaltState);
if (!CanDispatchHalt(pHaltState))
{
return;
}
// will store Current offset of the bytecode block.
int currentOffset = -1;
TryFinally([&]()
{
InitializeLocation(pHaltState);
OUTPUT_TRACE(Js::DebuggerPhase, _u("ProbeContainer::DispatchMutationBreakpoint: initialized location: pHaltState=%p, pHaltState->IsValid()=%d\n"),
pHaltState, pHaltState->IsValid());
if (pHaltState->IsValid())
{
// For interpreter frames, change the current location pointer of bytecode block, as it might be pointing to the next statement on the body.
// In order to generated proper binding of mutation statement, the bytecode offset needed to be on the same span of the statement.
// For native frames the offset is always current.
// Move back a single byte to ensure that it falls under on the same statement.
if (pHaltState->topFrame->IsInterpreterFrame())
{
currentOffset = pHaltState->GetCurrentOffset();
Assert(currentOffset > 0);
pHaltState->SetCurrentOffset(currentOffset - 1);
}
debugManager->stepController.Deactivate(pHaltState);
debugManager->asyncBreakController.Deactivate();
pHaltState->GetFunction()->CheckAndRegisterFuncToDiag(pScriptContext);
Assert(pHaltState->GetFunction()->GetScriptContext() == pScriptContext);
haltCallbackProbe->DispatchHalt(pHaltState);
}
},
[&](bool)
{
// Restore the current offset;
if (currentOffset != -1 && pHaltState->topFrame->IsInterpreterFrame())
{
pHaltState->SetCurrentOffset(currentOffset);
}
DestroyLocation();
});
}
void ProbeContainer::DispatchProbeHandlers(InterpreterHaltState* pHaltState)
{
if (!CanDispatchHalt(pHaltState))
{
return;
}
TryFinally([&]()
{
InitializeLocation(pHaltState);
if (pHaltState->IsValid())
{
Js::ProbeList * localPendingProbeList = this->pendingProbeList;
diagProbeList->Map([pHaltState, localPendingProbeList](int index, Probe * probe)
{
if (probe->CanHalt(pHaltState))
{
localPendingProbeList->Add(probe);
}
});
if (localPendingProbeList->Count() != 0)
{
localPendingProbeList->MapUntil([&](int index, Probe * probe)
{
if (haltCallbackProbe && !haltCallbackProbe->IsInClosedState())
{
debugManager->stepController.Deactivate(pHaltState);
debugManager->asyncBreakController.Deactivate();
haltCallbackProbe->DispatchHalt(pHaltState);
}
// If SetNextStatement happened between multiple BPs on same location, IP changed so rest of dispatch are not valid.
return this->IsSetNextStatementCalled();
});
}
}
},
[&](bool)
{
pendingProbeList->Clear();
DestroyLocation();
});
}
void ProbeContainer::UpdateStep(bool fDuringSetupDebugApp/*= false*/)
{
// This function indicate that when the page is being refreshed and the last action we have done was stepping.
// so update the state of the current stepController.
if (debugManager)
{
// Usually we need to be in debug mode to UpdateStep. But during setting up new engine to debug mode we have an
// ordering issue and the new engine will enter debug mode after this. So allow non-debug mode if fDuringSetupDebugApp.
AssertMsg(fDuringSetupDebugApp || (pScriptContext && pScriptContext->IsScriptContextInDebugMode()), "Why UpdateStep when we are not in debug mode?");
debugManager->stepController.stepType = STEP_IN;
}
}
void ProbeContainer::DeactivateStep()
{
if (debugManager)
{
debugManager->stepController.stepType = STEP_NONE;
}
}
void ProbeContainer::InitializeInlineBreakEngine(HaltCallback* probe)
{
AssertMsg(!haltCallbackProbe || probe == haltCallbackProbe, "Overwrite of Inline bp probe with different probe");
haltCallbackProbe = probe;
}
void ProbeContainer::UninstallInlineBreakpointProbe(HaltCallback* probe)
{
haltCallbackProbe = nullptr;
}
void ProbeContainer::InitializeDebuggerScriptOptionCallback(DebuggerOptionsCallback* debuggerOptionsCallback)
{
Assert(this->debuggerOptionsCallback == nullptr);
this->debuggerOptionsCallback = debuggerOptionsCallback;
}
void ProbeContainer::UninstallDebuggerScriptOptionCallback()
{
this->debuggerOptionsCallback = nullptr;
}
void ProbeContainer::AddProbe(Probe* pProbe)
{
if (pProbe->Install(nullptr))
{
diagProbeList->Add(pProbe);
}
}
void ProbeContainer::RemoveProbe(Probe* pProbe)
{
if (pProbe->Uninstall(nullptr))
{
diagProbeList->Remove(pProbe);
}
}
void ProbeContainer::RemoveAllProbes()
{
#ifdef ENABLE_MUTATION_BREAKPOINT
if (HasMutationBreakpoints())
{
ClearMutationBreakpoints();
}
#endif
for (int i = 0; i < diagProbeList->Count(); i++)
{
diagProbeList->Item(i)->Uninstall(nullptr);
}
diagProbeList->Clear();
}
// Retrieves the offset of next statement in JavaScript user code for advancing from current statement
// (normal flow-control is respected).
// Returns true on success, false if it's not possible to get next statement for advance from current.
bool ProbeContainer::GetNextUserStatementOffsetForAdvance(Js::FunctionBody* functionBody, ByteCodeReader* reader, int currentOffset, int* nextStatementOffset)
{
int originalCurrentOffset = currentOffset;
while (GetNextUserStatementOffsetHelper(functionBody, currentOffset, FunctionBody::SAT_FromCurrentToNext, nextStatementOffset))
{
Js::DebuggerScope *debuggerScope = functionBody->GetDiagCatchScopeObjectAt(currentOffset);
if (debuggerScope != nullptr && !debuggerScope->IsOffsetInScope(*nextStatementOffset))
{
// Our next statement is not within this catch block, So we cannot just jump to it, we need to return false so the stack unwind will happen.
return false;
}
Assert(currentOffset < *nextStatementOffset);
if (IsTmpRegCountIncreased(functionBody, reader, originalCurrentOffset, *nextStatementOffset, true /*restoreOffset*/))
{
currentOffset = *nextStatementOffset;
}
else
{
return true;
}
}
return false;
}
// Retrieves the offset of beginning of next statement in JavaScript user code for explicit set next statement
// (normal flow-control is not respected, just get start next statement).
// Returns true on success, false if it's not possible to get next statement for advance from current.
bool ProbeContainer::GetNextUserStatementOffsetForSetNext(Js::FunctionBody* functionBody, int currentOffset, int* nextStatementOffset)
{
return GetNextUserStatementOffsetHelper(functionBody, currentOffset, FunctionBody::SAT_NextStatementStart, nextStatementOffset);
}
// Retrieves the offset of beginning of next statement in JavaScript user code for scenario specified by adjType.
// Returns true on success, false if it's not possible to get next statement for advance from current.
bool ProbeContainer::GetNextUserStatementOffsetHelper(
Js::FunctionBody* functionBody, int currentOffset, FunctionBody::StatementAdjustmentType adjType, int* nextStatementOffset)
{
Assert(functionBody);
Assert(nextStatementOffset);
FunctionBody::StatementMapList* pStatementMaps = functionBody->GetStatementMaps();
if (pStatementMaps && pStatementMaps->Count() > 1)
{
for (int index = 0; index < pStatementMaps->Count() - 1; index++)
{
FunctionBody::StatementMap* pStatementMap = pStatementMaps->Item(index);
if (!pStatementMap->isSubexpression && pStatementMap->byteCodeSpan.Includes(currentOffset))
{
int nextMapIndex = index;
FunctionBody::StatementMap* pNextStatementMap = Js::FunctionBody::GetNextNonSubexpressionStatementMap(pStatementMaps, ++nextMapIndex);
if (!pNextStatementMap)
{
break;
}
// We are trying to find out the Branch opcode, between current and next statement. Skipping that would give use incorrect execution order.
FunctionBody::StatementAdjustmentRecord adjRecord;
if (pNextStatementMap->byteCodeSpan.begin > pStatementMap->byteCodeSpan.end &&
functionBody->GetBranchOffsetWithin(pStatementMap->byteCodeSpan.end, pNextStatementMap->byteCodeSpan.begin, &adjRecord) &&
(adjRecord.GetAdjustmentType() & adjType))
{
Assert(adjRecord.GetByteCodeOffset() > (uint)pStatementMap->byteCodeSpan.end);
*nextStatementOffset = adjRecord.GetByteCodeOffset();
}
else
{
*nextStatementOffset = pNextStatementMap->byteCodeSpan.begin;
}
return true;
}
}
}
*nextStatementOffset = -1;
return false;
}
bool ProbeContainer::FetchTmpRegCount(Js::FunctionBody * functionBody, Js::ByteCodeReader * reader, int atOffset, uint32 *pTmpRegCount, Js::OpCode *pOp)
{
Assert(pTmpRegCount);
Assert(pOp);
Js::LayoutSize layoutSize;
reader->SetCurrentOffset(atOffset);
*pOp = reader->ReadOp(layoutSize);
if (*pOp == Js::OpCode::Break)
{
// User might have put breakpoint on the skipped or target statement, get the original opcode;
if (functionBody->ProbeAtOffset(atOffset, pOp))
{
if (Js::OpCodeUtil::IsPrefixOpcode(*pOp))
{
*pOp = reader->ReadPrefixedOp(layoutSize, *pOp);
}
}
}
if (*pOp == Js::OpCode::EmitTmpRegCount)
{
switch (layoutSize)
{
case Js::SmallLayout:
{
const unaligned Js::OpLayoutReg1_Small * playout = reader->Reg1_Small();
*pTmpRegCount = (uint32)playout->R0;
}
break;
case Js::MediumLayout:
{
const unaligned Js::OpLayoutReg1_Medium * playout = reader->Reg1_Medium();
*pTmpRegCount = (uint32)playout->R0;
}
break;
case Js::LargeLayout:
{
const unaligned Js::OpLayoutReg1_Large * playout = reader->Reg1_Large();
*pTmpRegCount = (uint32)playout->R0;
}
break;
default:
Assert(false);
__assume(false);
}
return true;
}
return false;
}
// The logic below makes use of number of tmp (temp) registers of A and B.
// Set next statement is not allowed.
// if numberOfTmpReg(A) < numberOfTmpReg(B)
// or if any statement between A and B has number of tmpReg more than the lowest found.
//
// Get the temp register count for the A
// This is a base and will store the lowest tmp reg count we have got yet, while walking the skipped statements.
bool ProbeContainer::IsTmpRegCountIncreased(Js::FunctionBody* functionBody, ByteCodeReader* reader, int currentOffset, int nextStmOffset, bool restoreOffset)
{
Js::FunctionBody::StatementMapList* pStatementMaps = functionBody->GetStatementMaps();
Assert(pStatementMaps && pStatementMaps->Count() > 0);
int direction = currentOffset < nextStmOffset ? 1 : -1;
int startIndex = functionBody->GetEnclosingStatementIndexFromByteCode(currentOffset, true);
uint32 tmpRegCountLowest = 0;
// In the native code-gen (or interpreter which created from bailout points) the EmitTmpRegCount is not handled,
// so lets calculate it by going through all statements backward from the current offset
int index = startIndex;
for (; index > 0; index--)
{
Js::FunctionBody::StatementMap* pStatementMap = pStatementMaps->Item(index);
Js::OpCode op;
if (!pStatementMap->isSubexpression && FetchTmpRegCount(functionBody, reader, pStatementMap->byteCodeSpan.begin, &tmpRegCountLowest, &op))
{
break;
}
}
// Reset to the current offset.
reader->SetCurrentOffset(currentOffset);
uint32 tmpRegCountOnNext = tmpRegCountLowest; // Will fetch the tmp reg count till the B and skipped statements.
Assert(startIndex != -1);
index = startIndex + direction;
while (index > 0 && index < pStatementMaps->Count())
{
Js::FunctionBody::StatementMap* pStatementMap = pStatementMaps->Item(index);
if (pStatementMap->isSubexpression)
{
index += direction;
continue;
}
if (direction == 1) // NOTE: Direction & corresponding condition
{
if (nextStmOffset < pStatementMap->byteCodeSpan.begin) // check only till nextstatement offset
{
break;
}
}
Js::OpCode op;
FetchTmpRegCount(functionBody, reader, pStatementMap->byteCodeSpan.begin, &tmpRegCountOnNext, &op);
if (tmpRegCountOnNext < tmpRegCountLowest)
{
tmpRegCountLowest = tmpRegCountOnNext;
}
// On the reverse direction stop only when we find the tmpRegCount info for the setnext or below.
if (direction == -1 && (op == Js::OpCode::EmitTmpRegCount))
{
if (nextStmOffset >= pStatementMap->byteCodeSpan.begin)
{
break;
}
}
index += direction;
}
// On the reverse way if we have reached the first statement, then our tmpRegCountOnNext is 0.
if (direction == -1 && index == 0)
{
tmpRegCountOnNext = 0;
}
if (restoreOffset)
{
// Restore back the original IP.
reader->SetCurrentOffset(currentOffset);
}
return (tmpRegCountOnNext > tmpRegCountLowest);
}
bool ProbeContainer::AdvanceToNextUserStatement(Js::FunctionBody* functionBody, ByteCodeReader* reader)
{
// Move back a byte to make sure we are within the bounds of
// our current statement (See DispatchExceptionBreakpoint)
int currentOffset = reader->GetCurrentOffset() - 1;
int nextStatementOffset;
if (this->GetNextUserStatementOffsetForAdvance(functionBody, reader, currentOffset, &nextStatementOffset))
{
reader->SetCurrentOffset(nextStatementOffset);
return true;
}
return false;
}
void ProbeContainer::SetNextStatementAt(int _bytecodeOffset)
{
Assert(_bytecodeOffset != debugManager->pCurrentInterpreterLocation->GetCurrentOffset());
this->bytecodeOffset = _bytecodeOffset;
Assert(IsNextStatementChanged == false);
this->IsNextStatementChanged = true;
}
void ProbeContainer::AsyncActivate(HaltCallback* haltCallback)
{
OUTPUT_TRACE(Js::DebuggerPhase, _u("Async break activated\n"));
InterlockedExchangePointer((PVOID*)&this->pAsyncHaltCallback, haltCallback);
Assert(debugManager);
debugManager->asyncBreakController.Activate(haltCallback);
}
void ProbeContainer::AsyncDeactivate()
{
InterlockedExchangePointer((PVOID*)&this->pAsyncHaltCallback, nullptr);
Assert(debugManager);
debugManager->asyncBreakController.Deactivate();
}
bool ProbeContainer::IsAsyncActivate() const
{
return this->pAsyncHaltCallback != nullptr;
}
void ProbeContainer::PrepDiagForEnterScript()
{
// This will be called from ParseScriptText.
// This is to ensure the every script will call EnterScript back to host once, in-order to synchronize PDM with document.
Assert(this->pScriptContext);
if (this->pScriptContext->IsScriptContextInDebugMode())
{
isForcedToEnterScriptStart = true;
}
}
void ProbeContainer::RegisterContextToDiag(DWORD_PTR context, ArenaAllocator *alloc)
{
Assert(this->pScriptContext->IsScriptContextInSourceRundownOrDebugMode());
Assert(alloc);
if (registeredFuncContextList == nullptr)
{
registeredFuncContextList = JsUtil::List<DWORD_PTR, ArenaAllocator>::New(alloc);
}
registeredFuncContextList->Add(context);
}
bool ProbeContainer::IsContextRegistered(DWORD_PTR context)
{
return registeredFuncContextList != nullptr && registeredFuncContextList->Contains(context);
}
FunctionBody * ProbeContainer::GetGlobalFunc(ScriptContext* scriptContext, DWORD_PTR secondaryHostSourceContext)
{
return scriptContext->FindFunction([&secondaryHostSourceContext] (FunctionBody* pFunc) {
return ((pFunc->GetSecondaryHostSourceContext() == secondaryHostSourceContext) &&
pFunc->GetIsGlobalFunc());
});
}
bool ProbeContainer::HasAllowedForException(__in JavascriptExceptionObject* exceptionObject)
{
// We do not want to break on internal exception.
if (isThrowInternal)
{
return false;
}
bool fIsFirstChance = false;
bool fHasAllowed = false;
bool fIsInNonUserCode = false;
if (this->IsExceptionReportingEnabled() && (debugManager != nullptr))
{
fHasAllowed = !debugManager->pThreadContext->HasCatchHandler();
if (!fHasAllowed)
{
if (IsFirstChanceExceptionEnabled())
{
fHasAllowed = fIsFirstChance = true;
}
// We must determine if the exception is in user code AND if it's first chance as some debuggers
// ask for both and filter later.
// first validate if the throwing function is NonUserCode function, if not then verify if the exception is being caught in nonuser code.
if (exceptionObject && exceptionObject->GetFunctionBody() != nullptr && !exceptionObject->GetFunctionBody()->IsNonUserCode())
{
fIsInNonUserCode = IsNonUserCodeSupportEnabled() && !debugManager->pThreadContext->IsUserCode();
}
if (!fHasAllowed)
{
fHasAllowed = fIsInNonUserCode;
}
}
}
if (exceptionObject)
{
exceptionObject->SetIsFirstChance(fIsFirstChance);
exceptionObject->SetIsExceptionCaughtInNonUserCode(fIsInNonUserCode);
}
return fHasAllowed;
}
bool ProbeContainer::IsExceptionReportingEnabled()
{
return this->debuggerOptionsCallback == nullptr || this->debuggerOptionsCallback->IsExceptionReportingEnabled();
}
bool ProbeContainer::IsFirstChanceExceptionEnabled()
{
return this->debuggerOptionsCallback != nullptr && this->debuggerOptionsCallback->IsFirstChanceExceptionEnabled();
}
// Mentions if the debugger has enabled the support to differentiate the exception kind.
bool ProbeContainer::IsNonUserCodeSupportEnabled()
{
return this->debuggerOptionsCallback != nullptr && this->debuggerOptionsCallback->IsNonUserCodeSupportEnabled();
}
// Mentions if the debugger has enabled the support to display library stack frame.
bool ProbeContainer::IsLibraryStackFrameSupportEnabled()
{
return CONFIG_FLAG(LibraryStackFrameDebugger) || (this->debuggerOptionsCallback != nullptr && this->debuggerOptionsCallback->IsLibraryStackFrameSupportEnabled());
}
void ProbeContainer::PinPropertyRecord(const Js::PropertyRecord *propertyRecord)
{
Assert(propertyRecord);
this->pinnedPropertyRecords->Add(propertyRecord);
}
#ifdef ENABLE_MUTATION_BREAKPOINT
bool ProbeContainer::HasMutationBreakpoints()
{
return mutationBreakpointList && !mutationBreakpointList->Empty();
}
void ProbeContainer::InsertMutationBreakpoint(MutationBreakpoint *mutationBreakpoint)
{
Assert(mutationBreakpoint);
RecyclerWeakReference<Js::MutationBreakpoint>* weakBp = nullptr;
pScriptContext->GetRecycler()->FindOrCreateWeakReferenceHandle(mutationBreakpoint, &weakBp);
Assert(weakBp);
// Make sure list is created prior to insertion
InitMutationBreakpointListIfNeeded();
if (mutationBreakpointList->Contains(weakBp))
{
return;
}
mutationBreakpointList->Add(weakBp);
}
void ProbeContainer::ClearMutationBreakpoints()
{
mutationBreakpointList->Map([=](uint i, RecyclerWeakReference<Js::MutationBreakpoint>* weakBp) {
if (mutationBreakpointList->IsItemValid(i))
{
Js::MutationBreakpoint* mutationBreakpoint = weakBp->Get();
if (mutationBreakpoint)
{
mutationBreakpoint->Reset();
}
}
});
mutationBreakpointList->ClearAndZero();
}
void ProbeContainer::InitMutationBreakpointListIfNeeded()
{
if (!mutationBreakpointList && Js::MutationBreakpoint::IsFeatureEnabled(pScriptContext))
{
Recycler *recycler = pScriptContext->GetRecycler();
mutationBreakpointList.Root(RecyclerNew(recycler, MutationBreakpointList, recycler), recycler);
}
}
void ProbeContainer::RemoveMutationBreakpointListIfNeeded()
{
if (mutationBreakpointList)
{
if (HasMutationBreakpoints())
{
ClearMutationBreakpoints();
}
else
{
mutationBreakpointList->ClearAndZero();
}
mutationBreakpointList.Unroot(pScriptContext->GetRecycler());
}
}
#endif
} // namespace Js.