HRESULT SourceEventListener::onRemoveChild(_In_ IDebugApplicationNode* prddpChild)
{
    ATLENSURE_RETURN_HR(ThreadHelpers::IsOnPDMThread(m_dispatchThreadId), E_UNEXPECTED);
    ATLENSURE_RETURN_HR(prddpChild != nullptr, E_INVALIDARG);

    return m_spSourceController->OnRemoveChild(m_ownerId, prddpChild);
}
HRESULT SourceEventListener::Initialize(_In_ DWORD dispatchThreadId, _In_ SourceController* pSourceController, _In_ ULONG ownerId, _In_ IDebugApplicationNode* pDebugApplicationNode, _In_opt_ IDebugDocumentText* pDebugDocumentText)
{
    ATLENSURE_RETURN_HR(ThreadHelpers::IsOnPDMThread(dispatchThreadId), E_UNEXPECTED);
    ATLENSURE_RETURN_HR(pSourceController != nullptr, E_INVALIDARG);
    ATLENSURE_RETURN_HR(pDebugApplicationNode != nullptr, E_INVALIDARG);

    m_dispatchThreadId = dispatchThreadId;
    m_spSourceController = pSourceController;
    m_ownerId = ownerId;
    m_spDebugApplicationNode = pDebugApplicationNode;
    m_spDebugDocumentText = pDebugDocumentText;

    // Connect node event sink
    HRESULT hr = ATL::AtlAdvise(m_spDebugApplicationNode, GetUnknown(), IID_IDebugApplicationNodeEvents, &m_dwNodeCookie);
    m_advisedNodeEvents = (hr == S_OK);

    // Not all nodes have the IDebugDocumentText interface, depending on the host that is managing it.
    // We only want to sink text events if this interface exists, otherwise we can just safely ignore it.
    if (m_spDebugDocumentText.p != nullptr)
    {
        hr = ATL::AtlAdvise(m_spDebugDocumentText, GetUnknown(), IID_IDebugDocumentTextEvents, &m_dwTextCookie);
        m_advisedTextEvents = (hr == S_OK);
    }

    return S_OK;
}
HRESULT ThreadController::BeginBreakNotification()
{
    // This needs to be called from the debugger dispatch due to an issue in the IE pause notification.
    // Since the IE pause UI is drawn with no timeout, it will flash on/off everytime we hit a break that gets resumed straight away
    // For example during a conditional breakpoint with a false condition.
    // To prevent the flash we call this asynchronously from the debugger dispatch instead of synchronously from our OnPDMBreak event
    // This does mean that there is a possibility of a deadlock occuring when we are docked and the IE threads are still tied to together after hitting a break.
    // The current version has this issue, but we require a change in IE (to separate the pause UI from the thread decoupling) to allow us to call this
    // synchronously with our ODM break event.

    ATLENSURE_RETURN_HR(ThreadHelpers::IsOnDispatchThread(m_dispatchThreadId), E_UNEXPECTED); // Should really be synchronous with PDM thread (see comment above)

    // This gets called on the pdm thread, but it makes a synchronous call to the IE thread to notify it of the break.
    // When IE has finished processing the break, m_hBreakNotificationComplete gets signalled.
    // During this wait, we need to process very specific messages to allow docked debugging to work correctly.

    HRESULT hr = S_OK;

    // Reset the event used by the notification
    ::ResetEvent(m_hBreakNotificationComplete);

    // We need to use send message to make sure that the pipe handler has sent the message to the BHO 
    // before we continue and start waiting on the break event
    // This prevents a freeze where we haven't managed to tell IE about the break before we start waiting for it on this thread.
    ::SendMessageW(m_hwndDebugPipeHandler, WM_BREAKMODECHANGED, /*IsAtBreak=*/ TRUE, NULL);

    if (m_isDocked)
    {
        // Get the IRemoteDebugApplication using our helper function that keeps it thread safe
        CComPtr<IRemoteDebugApplication> spDebugAppKeepAlive;
        hr = this->GetRemoteDebugApplication(spDebugAppKeepAlive);
        if (hr == S_OK)
        {
            // While docked we also need to pump focus messages on the IE UI thread so that the IE Frame has chance to detach from the tab's input queue
            CComQIPtr<IDebugApplication110> spDebugApp110 = spDebugAppKeepAlive;
            ATLENSURE_RETURN_HR(spDebugApp110 != nullptr, E_NOINTERFACE);

            // We should only pump messages if we have stopped on the UI thread. If we attempt to call the PDM thread switch onto the UI thread
            // when we are not broken on the UI thread, we will cause a deadlock since all other (non-broken) threads are halted.
            if (m_mainIEScriptThreadId == m_currentPDMThreadId && m_mainIEScriptThreadId != 0)
            {
                // Create a callback that we can use to switch threads in the PDM
                CComObject<PDMThreadCallback>* pCall;
                hr = CComObject<PDMThreadCallback>::CreateInstance(&pCall);
                BPT_FAIL_IF_NOT_S_OK(hr);

                CComObjPtr<PDMThreadCallback> spCall(pCall);

                // We need to use the synchronous call because PDM processes ALL async calls before ANY sync calls (opposite of NT).
                // If we were to use the async mechanism, we could get into a state where synch calls never get a chance to run on the UI thread.
                hr = spDebugApp110->SynchronousCallInMainThread(spCall, (DWORD_PTR)PDMThreadCallbackMethod::WaitForBreak, (DWORD_PTR)this, (DWORD_PTR)nullptr);
                BPT_FAIL_IF_NOT_S_OK(hr);
            }
        }
    }

    return hr;
}
HRESULT ThreadController::PopulateCallStack()
{
    ATLENSURE_RETURN_HR(ThreadHelpers::IsOnPDMThread(m_dispatchThreadId), E_UNEXPECTED);
    ATLENSURE_RETURN_HR(this->IsAtBreak(), E_NOT_VALID_STATE);

    // Lock the access to the callstack so that we are thread safe
    CComCritSecLock<CComAutoCriticalSection> lock(m_csCallFramesLock);

    CComPtr<IEnumDebugStackFrames> pStackFrameEnum;
    HRESULT hr = m_spCurrentBrokenThread->EnumStackFrames(&pStackFrameEnum);
    if (hr == S_OK)
    {
        if ((hr = pStackFrameEnum->Reset()) != S_OK)
        {
            return hr;
        }

        shared_ptr<DebugStackFrameDescriptor> spFrameDescriptor(new DebugStackFrameDescriptor());
        ULONG nFetched;
        for (ULONG ulIndex = 0; (pStackFrameEnum->Next(1, spFrameDescriptor.get(), &nFetched) == S_OK) && (nFetched == 1); ulIndex++)
        {
            // Get a new identifier for this source node
            ULONG newFrameId = ThreadController::CreateUniqueFrameId();
            ULONG newPropertyId = ThreadController::CreateUniquePropertyId();

            // Create object that represents the source file
            CComObject<CallFrame>* pFrame;
            hr = CComObject<CallFrame>::CreateInstance(&pFrame);
            BPT_FAIL_IF_NOT_S_OK(hr);

            hr = pFrame->Initialize(m_dispatchThreadId, newFrameId, spFrameDescriptor, m_spSourceController);
            BPT_FAIL_IF_NOT_S_OK(hr);

            // Add to our maps
            m_callFrameMap[newFrameId] = pFrame;
            m_callFrames.push_back(newFrameId);

            // Get the information about this call frame
            shared_ptr<CallFrameInfo> spFrameInfo;
            CComPtr<IDebugStackFrame> spStackFrame;
            CComPtr<IDebugProperty> spLocalsDebugProperty;
            hr = pFrame->GetCallFrameInfo(spFrameInfo, spStackFrame, spLocalsDebugProperty);
            BPT_FAIL_IF_NOT_S_OK(hr);

            // Store that info in our maps
            m_callFramesInfoMap[newFrameId] = spFrameInfo;
            m_debugFrameMap[newFrameId] = spStackFrame;
            m_propertyMap[newPropertyId] = spLocalsDebugProperty;

            // Link the locals to the frame id for later lookup
            m_localsMap[newFrameId] = newPropertyId;
        }
    }

    return S_OK;
}
LRESULT IEDiagnosticsAdapter::OnMessageFromIE(UINT nMsg, WPARAM wParam, LPARAM lParam, _Inout_ BOOL& /*bHandled*/)
{
    CString message;

    // Scope for the copied data
    {
        // Take ownership of the copydata struct memory
        unique_ptr<COPYDATASTRUCT, void(*)(COPYDATASTRUCT*)> spParams(reinterpret_cast<PCOPYDATASTRUCT>(lParam), ::FreeCopyDataStructCopy);

        // Get the string message from the structure
        CopyDataPayload_StringMessage_Data* pMessage = reinterpret_cast<CopyDataPayload_StringMessage_Data*>(spParams->lpData);
        LPCWSTR lpString = reinterpret_cast<LPCWSTR>(reinterpret_cast<BYTE*>(pMessage)+pMessage->uMessageOffset);
        message = lpString;
    }

    HWND proxyHwnd = reinterpret_cast<HWND>(wParam);
    if (m_proxyConnections.find(proxyHwnd) != m_proxyConnections.end())
    {
        string utf8;
        int length = message.GetLength();
        LPWSTR buffer = message.GetBuffer();

        // Convert the message into valid UTF-8 text
        int utf8Length = ::WideCharToMultiByte(CP_UTF8, 0, buffer, length, nullptr, 0, nullptr, nullptr);
        if (utf8Length == 0)
        {
            message.ReleaseBuffer();
            ATLENSURE_RETURN_HR(false, ::GetLastError());
        }

        utf8.resize(utf8Length);
        utf8Length = ::WideCharToMultiByte(CP_UTF8, 0, buffer, length, &utf8[0], static_cast<int>(utf8.length()), nullptr, nullptr);
        message.ReleaseBuffer();
        ATLENSURE_RETURN_HR(utf8Length > 0, ::GetLastError());

        // Forward the message to the websocket
        try
        {
            m_server.send(m_proxyConnections[proxyHwnd], utf8, websocketpp::frame::opcode::text);
        }
        catch (const std::exception & e)
        {
            std::cout << "Execption during send: " << e.what() << std::endl;
        }
        catch (websocketpp::lib::error_code e)
        {
            std::cout << "Error during send: " << e.message() << std::endl;
        }
        catch (...)
        {
            std::cout << "Unknown exception during send" << std::endl;
        }
    }

    return 0;
}
HRESULT ThreadController::PopulatePropertyInfo(_Inout_ DebugPropertyInfo& propInfo, _Out_ shared_ptr<PropertyInfo>& spPropertyInfo)
{
    ATLENSURE_RETURN_HR(ThreadHelpers::IsOnDispatchThread(m_dispatchThreadId), E_UNEXPECTED);
    ATLENSURE_RETURN_HR(propInfo.m_pDebugProp != nullptr, E_INVALIDARG);

    // Store this property for later enumeration
    ULONG newPropertyId = ThreadController::CreateUniquePropertyId();
    m_propertyMap[newPropertyId] = propInfo.m_pDebugProp;

    // Create the info and assign to the out param
    spPropertyInfo.reset(new PropertyInfo());
    spPropertyInfo->propertyId = newPropertyId;

    // Use attach to ensure we free the actual BSTR from the PDM when we are finished
    spPropertyInfo->type.Attach(((propInfo.m_dwValidFields & DBGPROP_INFO_TYPE) == DBGPROP_INFO_TYPE) ? propInfo.m_bstrType : ::SysAllocString(L""));
    spPropertyInfo->value.Attach(((propInfo.m_dwValidFields & DBGPROP_INFO_VALUE) == DBGPROP_INFO_VALUE) ? propInfo.m_bstrValue : ::SysAllocString(L""));

    spPropertyInfo->isExpandable =  ((propInfo.m_dwAttrib & DBGPROP_ATTRIB_VALUE_IS_EXPANDABLE) == DBGPROP_ATTRIB_VALUE_IS_EXPANDABLE);
    spPropertyInfo->isReadOnly =    ((propInfo.m_dwAttrib & DBGPROP_ATTRIB_VALUE_READONLY) == DBGPROP_ATTRIB_VALUE_READONLY);
    spPropertyInfo->isFake =        ((propInfo.m_dwAttrib & DBGPROP_ATTRIB_VALUE_IS_FAKE) == DBGPROP_ATTRIB_VALUE_IS_FAKE);
    spPropertyInfo->isInvalid =     ((propInfo.m_dwAttrib & DBGPROP_ATTRIB_VALUE_IS_INVALID) == DBGPROP_ATTRIB_VALUE_IS_INVALID);
    spPropertyInfo->isReturnValue = ((propInfo.m_dwAttrib & DBGPROP_ATTRIB_VALUE_IS_RETURN_VALUE) == DBGPROP_ATTRIB_VALUE_IS_RETURN_VALUE);


    // Calculate the name if it has one
    spPropertyInfo->name.Attach(((propInfo.m_dwValidFields & DBGPROP_INFO_NAME) == DBGPROP_INFO_NAME) ? propInfo.m_bstrName : ::SysAllocString(L""));

    // Remove /*F12Eval Q:<id>*/ from name, if present
    CString name(spPropertyInfo->name);
    if (name.Find(ThreadController::s_f12EvalIdentifierPrefix) == 0 &&
        name.GetLength() >= ThreadController::s_f12EvalIdentifierPrefix.GetLength() + ThreadController::s_f12EvalIdentifierPrefixEnd.GetLength() + 1)
    {
        name = name.Mid(name.Find(ThreadController::s_f12EvalIdentifierPrefixEnd) + ThreadController::s_f12EvalIdentifierPrefixEnd.GetLength());
        spPropertyInfo->name.Attach(name.AllocSysString());
    }

    // Calculate the full name if it has one
    spPropertyInfo->fullName.Attach(((propInfo.m_dwValidFields & DBGPROP_INFO_FULLNAME) == DBGPROP_INFO_FULLNAME) ? propInfo.m_bstrFullName : ::SysAllocString(L""));

    // Remove /*F12Eval Q:<id>*/ from fullname, if present
    CString fullName(spPropertyInfo->fullName);
    if (fullName.Find(ThreadController::s_f12EvalIdentifierPrefix) == 0 &&
        fullName.GetLength() >= ThreadController::s_f12EvalIdentifierPrefix.GetLength() + ThreadController::s_f12EvalIdentifierPrefixEnd.GetLength() + 1)
    {
        fullName = fullName.Mid(fullName.Find(ThreadController::s_f12EvalIdentifierPrefixEnd) + ThreadController::s_f12EvalIdentifierPrefixEnd.GetLength());
        spPropertyInfo->fullName.Attach(fullName.AllocSysString());
    }

    return S_OK;
}
HRESULT ThreadController::SetPropertyValueAsString(_In_ ULONG propertyId, _In_ BSTR* pValue, _In_ UINT radix)
{
    ATLENSURE_RETURN_HR(ThreadHelpers::IsOnDispatchThread(m_dispatchThreadId), E_UNEXPECTED);

    // Lock the callframe access
    CComCritSecLock<CComAutoCriticalSection> lock(m_csCallFramesLock);

    if (!this->IsConnected() || !this->IsAtBreak())
    {
        return E_NOT_VALID_STATE;
    }

    HRESULT hr = S_OK;

    auto it = m_propertyMap.find(propertyId);
    if (it != m_propertyMap.end())
    {
        hr = it->second->SetValueAsString(*pValue, radix);
    }
    else
    {
        hr = E_NOT_FOUND;
    }

    return hr;
}
HRESULT ThreadController::CallDebuggerThread(_In_ PDMThreadCallbackMethod method, _In_opt_ DWORD_PTR pController, _In_opt_ DWORD_PTR pArgs)
{
    // Get the IRemoteDebugApplication using our helper function that keeps it thread safe
    CComPtr<IRemoteDebugApplication> spDebugAppKeepAlive;
    HRESULT hr = this->GetRemoteDebugApplication(spDebugAppKeepAlive);

    // We need to check if spDebugAppKeepAlive is null, since the debugger may have been activated
    // while debugging was disabled (e.g.: after the profiler started).
    // If that happened, then m_spDebugApplication would be null when we tried to AddRef it.
    if (hr == S_OK && spDebugAppKeepAlive.p != nullptr)
    {
        // Get the IDebugApplication interface used to make the actual thread switch call
        CComQIPtr<IDebugApplication> spDebugApp(spDebugAppKeepAlive);
        ATLENSURE_RETURN_HR(spDebugApp.p != nullptr, E_NOINTERFACE);

        // Create an object for thread switching on to the debugger thread
        CComObject<PDMThreadCallback>* pCall;
        hr = CComObject<PDMThreadCallback>::CreateInstance(&pCall);
        BPT_FAIL_IF_NOT_S_OK(hr);

        if (pController == NULL)
        {
            // Use the IDebugApplication by default
            pController = (DWORD_PTR)spDebugApp.p;
        }

        // Begin the call into the debugger thread using the supplied parameters
        CComObjPtr<PDMThreadCallback> spCall(pCall);
        hr = spDebugApp->SynchronousCallInDebuggerThread(spCall, (DWORD)method, (DWORD_PTR)pController, (DWORD_PTR)pArgs);
        ATLASSERT(hr == S_OK);
    }

    return hr;
}
HRESULT ThreadController::Initialize(_In_ HWND hwndDebugPipeHandler, _In_ HANDLE hBreakNotificationComplete, _In_ bool isDocked, _In_ PDMEventMessageQueue* pMessageQueue, _In_ SourceController* pSourceController)
{
    ATLENSURE_RETURN_HR(ThreadHelpers::IsOnDispatchThread(m_dispatchThreadId), E_UNEXPECTED);
    ATLENSURE_RETURN_HR(hBreakNotificationComplete != nullptr, E_INVALIDARG);
    ATLENSURE_RETURN_HR(hwndDebugPipeHandler != nullptr, E_INVALIDARG);
    ATLENSURE_RETURN_HR(pMessageQueue != nullptr, E_INVALIDARG);
    ATLENSURE_RETURN_HR(pSourceController != nullptr, E_INVALIDARG);

    m_hwndDebugPipeHandler = hwndDebugPipeHandler;
    m_hBreakNotificationComplete = hBreakNotificationComplete;
    m_isDocked = isDocked;
    m_spMessageQueue = pMessageQueue;
    m_spSourceController = pSourceController;
    
    return S_OK;
}
 HRESULT ThreadController::SetEnabledState(_In_ bool isEnabled)
 {
     ATLENSURE_RETURN_HR(ThreadHelpers::IsOnDispatchThread(m_dispatchThreadId), E_UNEXPECTED);

     // Set the enabled state of the source controller
     m_spSourceController->SetEnabledState(isEnabled);
     return S_OK;
 }
 HRESULT ThreadController::SetDockState(_In_ bool isDocked)
 {
     ATLENSURE_RETURN_HR(ThreadHelpers::IsOnDispatchThread(m_dispatchThreadId), E_UNEXPECTED);

     m_isDocked = isDocked;

     return S_OK;
 }
HRESULT SourceEventListener::onReplaceText(_In_ ULONG /* cCharacterPosition */, _In_ ULONG /* cNumToReplace */)
{
    ATLENSURE_RETURN_HR(ThreadHelpers::IsOnPDMThread(m_dispatchThreadId), E_UNEXPECTED);

    // Even if cNumToReplace is 0, we still need to forward the event to update breakpoint bindings.
    // Deduplication of successive update document events occurs in DebugProvider.
    return this->OnSourceTextChange();
}
HRESULT ThreadController::OnPDMClose()
{
    ATLENSURE_RETURN_HR(ThreadHelpers::IsOnPDMThread(m_dispatchThreadId), E_UNEXPECTED);

    // Inform the DebuggerDispatch about the event
    m_spMessageQueue->Push(PDMEventType::PDMClosed);

    return S_OK;
}
HRESULT ThreadController::Disconnect()
{
    ATLENSURE_RETURN_HR(ThreadHelpers::IsOnDispatchThread(m_dispatchThreadId), E_UNEXPECTED);

    if (!this->IsConnected())
    {
        return S_OK;
    }

    HRESULT hr = S_OK;

    // Get the IRemoteDebugApplication using our helper function that keeps it thread safe
    CComPtr<IRemoteDebugApplication> spDebugAppKeepAlive;
    hr = this->GetRemoteDebugApplication(spDebugAppKeepAlive);
    BPT_FAIL_IF_NOT_S_OK(hr);

    // We need to check if spDebugAppKeepAlive is null, since the debugger may have been activated
    // while debugging was disabled (e.g.: after the profiler started).
    // If that happened, then m_spDebugApplication would be null when we tried to AddRef it.
    if (spDebugAppKeepAlive.p != nullptr)
    {
        // Unbind all the the breakboints and remove them from the engine so we don't hit any once we close (if debugging IE is enabled via checkbox)
        m_spSourceController->UnbindAllCodeBreakpoints(/*removeFromEngine=*/ true);
        m_spSourceController->UnbindAllEventBreakpoints(/*removeListeners=*/ true);

        // Disconnect the debugger first to prevent re-entrancy into handle breakpoint
        if (m_spThreadEventListener.p != nullptr) // If we don't have a listener then we only connected for sources, not with debugging
        {
            hr = spDebugAppKeepAlive->DisconnectDebugger();
            ATLASSERT(hr == S_OK); // Failure to disconnect, should still clean up the rest of the debugger
        }

        if (this->IsAtBreak())
        {
            hr = this->Resume(BREAKRESUMEACTION_CONTINUE);
            BPT_FAIL_IF_NOT_S_OK(hr);
        }
    }

    // Release the listener that handles the pdm callbacks
    if (m_spThreadEventListener.p != nullptr)
    {
        m_spThreadEventListener.Release();
    }

    // Clear all source nodes but don't release the Controller since we will re-use it on next connect
    m_spSourceController->Clear();

    // Do Release the IDebugApplication pointer, since we get a new one when we next connect
    m_spDebugApplication.Release();

    // Set our connected flag
    m_isConnected = false;

    return hr;
}
HRESULT ThreadController::SetNextStatement(_In_ ULONG docId, _In_ ULONG start)
{
    ATLENSURE_RETURN_HR(ThreadHelpers::IsOnDispatchThread(m_dispatchThreadId), E_UNEXPECTED);

    // Lock the callframe access
    CComCritSecLock<CComAutoCriticalSection> lock(m_csCallFramesLock);

    if (!this->IsConnected() || !this->IsAtBreak())
    {
        return E_NOT_VALID_STATE;
    }

    HRESULT hr = S_OK;

    // We use the current call frame, which should be the first one in the m_callFrames list
    ULONG firstFrameId = (m_callFrames.empty() ? 0 : m_callFrames.front());

    auto it = m_debugFrameMap.find(firstFrameId);
    if (it != m_debugFrameMap.end())
    {
        // Get the code context for the passed in map
        // If it is not found, we fail gracefully (no assert) to the caller
        CComPtr<IDebugCodeContext> spDebugCodeContext;
        hr = m_spSourceController->GetCodeContext(docId, start, spDebugCodeContext);
        if (hr == S_OK) // This could fail due to requiring a refresh (the caller will handle the hresult)
        {
            CComQIPtr<IDebugStackFrame> spStackFrame(it->second);
            ATLENSURE_RETURN_HR(spStackFrame.p != nullptr, E_NOINTERFACE);

            CComQIPtr<ISetNextStatement> spSetter(spStackFrame);
            ATLENSURE_RETURN_HR(spSetter.p != nullptr, E_NOINTERFACE);

            hr = spSetter->SetNextStatement(spStackFrame, spDebugCodeContext);
        }
    }
    else
    {
        return E_NOT_FOUND;
    }

    return hr;
}
HRESULT ThreadController::SetNextEvent(_In_ const CString& eventBreakpointType)
{
    ATLENSURE_RETURN_HR(ThreadHelpers::IsOnDispatchThread(m_dispatchThreadId), E_UNEXPECTED);

    CComCritSecLock<CComAutoCriticalSection> lock(m_csCallFramesLock);

    // Store the event break type for the next break (it will be an empty string for a regular async break)
    m_nextBreakEventType = eventBreakpointType;

    return S_OK;
}
HRESULT ThreadController::SetBreakOnFirstChanceExceptions(_In_ bool breakOnFirstChance)
{
    ATLENSURE_RETURN_HR(ThreadHelpers::IsOnDispatchThread(m_dispatchThreadId), E_UNEXPECTED);

    // This function is safe to call while we are not at a breakpoint,
    // So we don't need to make a call to IsAtBreak()
    if (!this->IsConnected())
    {
        return E_FAIL;
    }

    // Get the IRemoteDebugApplication using our helper function that keeps it thread safe
    CComPtr<IRemoteDebugApplication> spDebugAppKeepAlive;
    HRESULT hr = this->GetRemoteDebugApplication(spDebugAppKeepAlive);
    BPT_FAIL_IF_NOT_S_OK(hr);

    CComQIPtr<IRemoteDebugApplication110> spDebugApp110 = spDebugAppKeepAlive;
    ATLENSURE_RETURN_HR(spDebugApp110.p != nullptr, E_NOINTERFACE);

    hr = spDebugApp110->SetDebuggerOptions(SDO_ENABLE_FIRST_CHANCE_EXCEPTIONS, breakOnFirstChance ? SDO_ENABLE_FIRST_CHANCE_EXCEPTIONS : SDO_NONE);

    return hr;
}
Exemple #18
0
    /**
     * Return the path of the file containing the public key.
     *
     * The path is set via SetKeyPaths().
     */
    HRESULT OnPublicKeyFileRequest(BSTR *pbstrPublicKeyFile)
    {
        ATLENSURE_RETURN_HR(pbstrPublicKeyFile, E_POINTER);
        *pbstrPublicKeyFile = NULL;

        try
        {
            *pbstrPublicKeyFile = comet::bstr_t::detach(
                m_publicKey.file_string());
        }
        WINAPI_COM_CATCH_AUTO_INTERFACE();

        return S_OK;
    }
HRESULT ThreadController::SetRemoteDebugApplication(_In_ IRemoteDebugApplication* pDebugApplication)
{
    ATLENSURE_RETURN_HR(ThreadHelpers::IsOnDispatchThread(m_dispatchThreadId), E_UNEXPECTED);
    ATLENSURE_RETURN_HR(pDebugApplication != nullptr, E_INVALIDARG);

    CComCritSecLock<CComAutoCriticalSection> lock(m_csThreadControllerLock);

    if (m_spDebugApplication == pDebugApplication)
    {
        // Already connected to this, so do nothing
        return S_OK;
    }

    m_isConnected = false;
    m_spDebugApplication = pDebugApplication;
    m_mainIEScriptThreadId = 0;

    // IRemoteDebugApplication110 is needed to grab the id for the UI thread
    CComQIPtr<IRemoteDebugApplication110> spRemoteDebugApp110(pDebugApplication);
    ATLENSURE_RETURN_HR(spRemoteDebugApp110.p != nullptr, E_NOINTERFACE);

    // Get the thread id for the main IE UI thread which is used to know if we need to pump messages at a break
    CComPtr<IRemoteDebugApplicationThread> spThread;
    HRESULT hr = spRemoteDebugApp110->GetMainThread(&spThread);
    if (hr == S_OK)
    {
        hr = spThread->GetSystemThreadId(&m_mainIEScriptThreadId);
        if (hr != S_OK)
        {
            // Default back to 0 (invalid id) if the call failed for some reason
            m_mainIEScriptThreadId = 0;
        }
    }

    return S_OK;
}
// Break Mode Actions
HRESULT ThreadController::GetCurrentThreadDescription(_Out_ CComBSTR& threadDescription)
{
    ATLENSURE_RETURN_HR(ThreadHelpers::IsOnDispatchThread(m_dispatchThreadId), E_UNEXPECTED);
    
    // Lock the callframe access
    CComCritSecLock<CComAutoCriticalSection> lock(m_csCallFramesLock);

    if (!this->IsConnected() || !this->IsAtBreak())
    {
        return E_NOT_VALID_STATE;
    }

    CComBSTR state;
    return m_spCurrentBrokenThread->GetDescription(&threadDescription, &state);
}
HRESULT ThreadController::SetBreakOnNewWorker(_In_ bool enable)
{
    ATLENSURE_RETURN_HR(ThreadHelpers::IsOnDispatchThread(m_dispatchThreadId), E_UNEXPECTED);

    // This function is safe to call while we are not at a breakpoint,
    // So we don't need to make a call to IsAtBreak()
    if (!this->IsConnected())
    {
        return E_FAIL;
    }

    m_spSourceController->SetBreakOnNewWorker(enable);

    return S_OK;
}
HRESULT ThreadController::GetBreakEventInfo(_Out_ shared_ptr<BreakEventInfo>& spBreakInfo)
{
    ATLENSURE_RETURN_HR(ThreadHelpers::IsOnDispatchThread(m_dispatchThreadId), E_UNEXPECTED);

    // Lock the callframe access
    CComCritSecLock<CComAutoCriticalSection> lock(m_csCallFramesLock);

    if (!this->IsConnected() || !this->IsAtBreak())
    {
        return E_NOT_VALID_STATE;
    }

    spBreakInfo = m_spCurrentBreakInfo;

    return S_OK;
}
HRESULT ThreadController::Pause()
{
    ATLENSURE_RETURN_HR(ThreadHelpers::IsOnDispatchThread(m_dispatchThreadId), E_UNEXPECTED);

    if (!this->IsConnected() || this->IsAtBreak())
    {
        return S_OK;
    }

    // We need to call pause on the debugger thread so that we get the internal pdm locks in the correct order:
    // m_csCrossThreadCallLock then m_csBreakPoint
    // If we call CauseBreak directly, we will end up getting the locks in the reverse order which can cause a deadlock.
    HRESULT hr = this->CallDebuggerThread(PDMThreadCallbackMethod::Pause, (DWORD_PTR)nullptr, (DWORD_PTR)nullptr);
    
    // This could fail if we already disconnected, so don't assert, but do return the hr

    return hr;
}
HRESULT ThreadController::WaitForBreakNotification()
{
    // Process messages on the IE thread so that F12 doesn't get blocked waiting for a synchronous call to finish.
    ATLENSURE_RETURN_HR(ThreadHelpers::IsOnPDMThread(m_dispatchThreadId), E_UNEXPECTED); 

    // Since we are sending the break notification directly from our debugger backend here (instead of via the frame) 
    // we don't want to process our messages until the 'break complete' event has been triggered.
    HRESULT hr = S_OK;
    while (hr == S_OK)
    {
        // Since the IE tab and frame had their input queues synchronized before the break, there may be pending messages
        // on the frame's queue that are blocking us from working while docked.
        // We need to dispose of these messages so that the tab and frame's threads can be detached by the BHO.
        DWORD dwWaitResult = ::MsgWaitForMultipleObjects(1, &m_hBreakNotificationComplete, FALSE, INFINITE, QS_SENDMESSAGE);
        if (dwWaitResult != WAIT_FAILED)
        {
            if (dwWaitResult == WAIT_OBJECT_0)
            {
                break;
            }
            else
            {
                // There was a message put into our queue, so we need to dispose of it
                MSG msg;
                while (::PeekMessage(&msg, NULL, WM_KILLFOCUS, WM_KILLFOCUS, PM_REMOVE))
                {
                    ::TranslateMessage(&msg);
                    ::DispatchMessage(&msg);
                    // Check if the event was triggered before pumping more messages
                    if (::WaitForSingleObject(m_hBreakNotificationComplete, 0) == WAIT_OBJECT_0)
                    {
                        break;
                    }
                }
            }
        }
        else
        {
            hr = ::AtlHresultFromLastError();
        }
    }

    return hr;
}
HRESULT IEDiagnosticsAdapter::ConnectToInstance(_In_ IEInstance& instance)
{
    if (instance.isConnected)
    {
        return E_NOT_VALID_STATE;
    }

    CComPtr<IHTMLDocument2> spDocument;
    HRESULT hr = Helpers::GetDocumentFromHwnd(instance.hwnd, spDocument);
    if (hr == S_OK)
    {
        CString path(m_rootPath);
        path.Append(L"Proxy.dll");

        CComPtr<IOleWindow> spSite;
        hr = Helpers::StartDiagnosticsMode(spDocument, __uuidof(ProxySite), path, __uuidof(spSite), reinterpret_cast<void**>(&spSite.p));
        if (hr == S_OK)
        {
            HWND hwnd;
            hr = spSite->GetWindow(&hwnd);
            FAIL_IF_NOT_S_OK(hr);

            // Send our hwnd to the proxy so it can connect back
            BOOL succeeded = ::PostMessage(hwnd, Get_WM_SET_CONNECTION_HWND(), reinterpret_cast<WPARAM>(m_hWnd), NULL);
            ATLENSURE_RETURN_HR(succeeded, E_FAIL);

            // Inject script onto the browser thread
            hr = this->InjectScript(L"browser", L"Common.js", IDR_COMMON_SCRIPT, hwnd);
            hr = this->InjectScript(L"browser", L"browserMain.js", IDR_BROWSER_SCRIPT, hwnd);
            hr = this->InjectScript(L"browser", L"DOM.js", IDR_DOM_SCRIPT, hwnd);

            // Inject script  onto the debugger thread
            hr = this->InjectScript(L"debugger", L"Common.js", IDR_COMMON_SCRIPT, hwnd);
            hr = this->InjectScript(L"debugger", L"debuggerMain.js", IDR_DEBUGGER_SCRIPT, hwnd);

            // Connected
            instance.isConnected = true;
            instance.spSite = spSite;
            instance.connectionHwnd = hwnd;
        }
    }

    return hr;
}
HRESULT ThreadController::GetThreadDescriptions(_Inout_ vector<CComBSTR>& spThreadDescriptions)
{
    ATLENSURE_RETURN_HR(ThreadHelpers::IsOnDispatchThread(m_dispatchThreadId), E_UNEXPECTED);
    
    // This function is safe to call while we are not at a breakpoint,
    // So we don't need to make a call to IsAtBreak()
    if (!this->IsConnected())
    {
        return E_FAIL;
    }

    // Get the IRemoteDebugApplication using our helper function that keeps it thread safe
    CComPtr<IRemoteDebugApplication> spDebugAppKeepAlive;
    HRESULT hr = this->GetRemoteDebugApplication(spDebugAppKeepAlive);
    BPT_FAIL_IF_NOT_S_OK(hr);

    CComPtr<IEnumRemoteDebugApplicationThreads> spEnumThreads;
    hr = spDebugAppKeepAlive->EnumThreads(&spEnumThreads);
    BPT_FAIL_IF_NOT_S_OK(hr);

    // Enumerate each thread and get its description
    ULONG numFetched = 0;
    do
    {
        CComPtr<IRemoteDebugApplicationThread> spRDAThread;
        if (spEnumThreads->Next(1, &spRDAThread.p, &numFetched) != S_OK || spRDAThread.p == nullptr)
        {
            break;
        }

        // Get the description and add it to the array that we will return
        CComBSTR bstrDescription;
        CComBSTR bstrState;
        hr = spRDAThread->GetDescription(&bstrDescription, &bstrState);
        if (hr == S_OK)
        {
            spThreadDescriptions.push_back(bstrDescription);
        }
    } 
    while (numFetched != 0);

    return hr;
}
HRESULT ThreadController::GetCallFrames(_Inout_ vector<shared_ptr<CallFrameInfo>>& spFrames)
{
    ATLENSURE_RETURN_HR(ThreadHelpers::IsOnDispatchThread(m_dispatchThreadId), E_UNEXPECTED);

    // Lock the callframe access
    CComCritSecLock<CComAutoCriticalSection> lock(m_csCallFramesLock);

    if (!this->IsConnected() || !this->IsAtBreak())
    {
        return E_NOT_VALID_STATE;
    }

    for (auto& frameId : m_callFrames)
    {
        auto it = m_callFramesInfoMap.find(frameId);
        if (it != m_callFramesInfoMap.end())
        {
            spFrames.push_back(it->second);
        }
    }

    return S_OK;
}
HRESULT ThreadController::GetLocals(_In_ ULONG frameId, _Out_ ULONG& propertyId)
{
    ATLENSURE_RETURN_HR(ThreadHelpers::IsOnDispatchThread(m_dispatchThreadId), E_UNEXPECTED);

    // Lock the callframe access
    CComCritSecLock<CComAutoCriticalSection> lock(m_csCallFramesLock);

    if (!this->IsConnected() || !this->IsAtBreak())
    {
        return E_NOT_VALID_STATE;
    }

    auto it = m_localsMap.find(frameId);
    if (it != m_localsMap.end())
    {
        propertyId = it->second;
    }
    else
    {
        return E_NOT_FOUND;
    }

    return S_OK;
}
HRESULT ThreadController::Eval(_In_ ULONG frameId, _In_ const CComBSTR& evalString, _In_ ULONG radix, _Out_ shared_ptr<PropertyInfo>& spPropertyInfo)
{
    ATLENSURE_RETURN_HR(ThreadHelpers::IsOnDispatchThread(m_dispatchThreadId), E_UNEXPECTED);

    // Lock the callframe access
    CComCritSecLock<CComAutoCriticalSection> lock(m_csCallFramesLock);

    if (!this->IsConnected() || !this->IsAtBreak())
    {
        return E_NOT_VALID_STATE;
    }

    HRESULT hr = S_OK;

    if (m_evalCallbacks.size() >= ThreadController::s_maxConcurrentEvalCount)
    {
        // Since evaluating an eval call is relatively quick, having > max running concurrently,
        // probably means that there was a failure in one causing the others to queue up behind it.
        // If we get into this state we should abort the latest eval and we cannot process this new one.
        // This will free up the waiting message pump to see if it can unwind the stack further.
        m_evalCallbacks.back().first->Abort();
        m_evalCallbacks.back().second->Abort();

        return E_ABORT;
    }

    // To help diagnose chakra recursion issues (such as the above queued up evals) we append
    // an identifying string to the eval so that it will appear in debugging information.
    CString evalWithId;
    evalWithId.Format(ThreadController::s_f12EvalIdentifier, m_evalCallbacks.size());
    evalWithId.Append(evalString);

    auto it = m_debugFrameMap.find(frameId);
    if (it != m_debugFrameMap.end())
    {
        CComQIPtr<IDebugExpressionContext> spEvalContext(it->second);
        ATLENSURE_RETURN_HR(spEvalContext.p != nullptr, E_NOINTERFACE);

        CComPtr<IDebugExpression> spDebugExpression;
        hr = spEvalContext->ParseLanguageText(evalWithId, radix, L"" /* not actually used, but required */, DEBUG_TEXT_RETURNVALUE | DEBUG_TEXT_ISEXPRESSION, &spDebugExpression);
        BPT_FAIL_IF_NOT_S_OK(hr);

        // Free the lock as we no longer use the eval context (spEvalContext) past this point,
        // This will allow the PDM thread to update the callstacks on its thread.
        lock.Unlock();

        // Local scope for COM objects.
        {
            // Keep our reference alive until all evals have finished (or been aborted)
            CComObjPtr<ThreadController> spKeepAlive(this);

            // Create expression callback
            CComObject<EvalCallback>* pDebugCallback;
            CComObject<EvalCallback>::CreateInstance(&pDebugCallback);
            ATLENSURE_RETURN_HR(pDebugCallback != nullptr, E_NOINTERFACE);
            
            CComObjPtr<EvalCallback> spDebugCallback(pDebugCallback);
            hr = spDebugCallback->Initialize();
            BPT_FAIL_IF_NOT_S_OK(hr);

            // Add this to our list so we can abandon it if we resume or close before it completes
            m_evalCallbacks.push_back(::make_pair(spDebugExpression, spDebugCallback));

            // Evaluate the expression
            hr = spDebugExpression->Start(spDebugCallback);
            BPT_FAIL_IF_NOT_S_OK(hr);

            hr = spDebugCallback->WaitForCompletion();

            // Remove from our list
            ATLENSURE_RETURN_HR(m_evalCallbacks.back().first == spDebugExpression, E_UNEXPECTED);
            m_evalCallbacks.pop_back();

            // Check if we were closed down before this evaluation completed, if so just return abort failure
            if (hr == E_ABORT)
            {
                return hr;
            }
        }

        HRESULT returnResult;
        CComPtr<IDebugProperty> spDebugProperty;

        // Determine if the expression evaluation was successful
        hr = spDebugExpression->GetResultAsDebugProperty(&returnResult, &spDebugProperty);
        BPT_FAIL_IF_NOT_S_OK(hr);

        // The get result function should always succeed, but the actual result from the eval could fail if
        // chakra was unable to process the request. If that is the case we should return the failure out
        // to the caller, which will result in sending null to the JavaScript which is handled correctly.
        if (returnResult != S_OK)
        {
            return returnResult;
        }

        DebugPropertyInfo propInfo;
        hr = spDebugProperty->GetPropertyInfo(PROP_INFO_ALL, radix, &propInfo);
        if (hr == S_OK)
        {
            hr = this->PopulatePropertyInfo(propInfo, spPropertyInfo);
        }
        else
        {
            return E_NOT_FOUND;
        }
    }
    else
    {
        return E_NOT_FOUND;
    }

    return hr;
}
// PDM Event Notifications
HRESULT ThreadController::OnPDMBreak(_In_ IRemoteDebugApplicationThread* pRDAThread, _In_ BREAKREASON br, _In_ const CComBSTR& description, _In_ WORD errorId, _In_ bool isFirstChance, _In_ bool isUserUnhandled)
{
    ATLENSURE_RETURN_HR(ThreadHelpers::IsOnPDMThread(m_dispatchThreadId), E_UNEXPECTED);

    CComCritSecLock<CComAutoCriticalSection> lock(m_csCallFramesLock);

    // Get the new call stack information
    m_spCurrentBrokenThread = pRDAThread;
    this->PopulateCallStack();

    // If the callframe list is empty, we were not given any information about this break, so default to invalid id (0)
    ULONG firstFrameId = (m_callFrames.empty() ? 0 : m_callFrames.front());

    // Set our new break state
    m_spCurrentBreakInfo.reset(new BreakEventInfo());
    m_spCurrentBreakInfo->firstFrameId = firstFrameId;
    m_spCurrentBreakInfo->errorId = errorId;
    m_spCurrentBreakInfo->breakReason = br;
    m_spCurrentBreakInfo->description = description;
    m_spCurrentBreakInfo->isFirstChanceException = isFirstChance;
    m_spCurrentBreakInfo->isUserUnhandled = isUserUnhandled;

    // Get the PDM thread id of the one that was actually broken
    HRESULT hr = m_spCurrentBrokenThread->GetSystemThreadId(&m_currentPDMThreadId);
    if (hr != S_OK) 
    {
        m_currentPDMThreadId = 0;
    }

    m_spCurrentBreakInfo->systemThreadId = m_currentPDMThreadId;

    // If this is caused by a breakpoint, we need to gather further information.
    // Any condition needs to be evaluated on the other thread since we cannot continue or evaluate
    // synchronously during the onHandleBreakpoint function. 
    // Doing so causes a deadlock in the PDM.
    if (br == BREAKREASON_BREAKPOINT)
    {
        // Find the code breakpoint
        auto it = m_callFramesInfoMap.find(firstFrameId);
        if (it != m_callFramesInfoMap.end())
        {
            shared_ptr<BreakpointInfo> spBreakpointInfo;
            const SourceLocationInfo& sourceLocation = it->second->sourceLocation;
            hr = m_spSourceController->GetBreakpointInfoForBreak(sourceLocation.docId, sourceLocation.charPosition, spBreakpointInfo);
            if (hr == S_OK)
            {
                m_spCurrentBreakInfo->breakpoints.push_back(spBreakpointInfo);
            }
        }
    }
    else if (!m_nextBreakEventType.IsEmpty())
    {
        if (br == BREAKREASON_DEBUGGER_HALT)
        {
            // Find the event breakpoints for this type
            vector<shared_ptr<BreakpointInfo>> breakpointList;
            hr = m_spSourceController->GetBreakpointInfosForEventBreak(m_nextBreakEventType, breakpointList);
            if (hr == S_OK)
            {
                m_spCurrentBreakInfo->breakpoints = std::move(breakpointList);
            }

            m_spCurrentBreakInfo->breakReason = BREAKREASON_BREAKPOINT;
        }

        m_spCurrentBreakInfo->breakEventType = m_nextBreakEventType;

        // If this breakpoint was created with the Create XHR Breakpoint button, we need to tell the frontend that an XHR breakpoint was hit in order to display appropriate strings
        if (m_nextBreakEventType == L"readystatechange") 
        {		
            for (auto it = m_spCurrentBreakInfo->breakpoints.begin(); it != m_spCurrentBreakInfo->breakpoints.end(); ++it) 
            {
                if (std::find((**it).spEventTypes->begin(), (**it).spEventTypes->end(), s_xhrBreakpointFlag) != (**it).spEventTypes->end()) 
                {
                    m_spCurrentBreakInfo->breakEventType = s_xhrBreakpointFlag;
                }
            }
        }
    }

    // Inform the DebuggerDispatch about the event
    m_spMessageQueue->Push(PDMEventType::BreakpointHit);

    return S_OK;
}