| /******************************************************************************* |
| * Copyright (c) 2011 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| |
| |
| #include "StdAfx.h" |
| #include "CrossfireContext.h" |
| |
| #include "JSONParser.h" |
| |
| /* initialize constants */ |
| const wchar_t* CrossfireContext::ABOUT_BLANK = L"about:blank"; |
| const wchar_t* CrossfireContext::ID_PREAMBLE = L"xfIE::"; |
| const wchar_t* CrossfireContext::NUMBER_NaN = L"NaN"; |
| const wchar_t* CrossfireContext::NUMBER_INFINITY = L"Infinity"; |
| const wchar_t* CrossfireContext::NUMBER_NEGATIVEINFINITY = L"-Infinity"; |
| const wchar_t* CrossfireContext::PDM_DLL = L"pdm.dll"; |
| const wchar_t* CrossfireContext::SCHEME_SCRIPT = L"script://"; |
| const wchar_t* CrossfireContext::VALUE_NaN = L"NaN"; |
| const wchar_t* CrossfireContext::VALUE_INFINITY = L"Infinity"; |
| const wchar_t* CrossfireContext::VALUE_NEGATIVEINFINITY = L"-Infinity"; |
| |
| /* command: backtrace */ |
| const wchar_t* CrossfireContext::COMMAND_BACKTRACE = L"backtrace"; |
| const wchar_t* CrossfireContext::KEY_FRAMES = L"frames"; |
| const wchar_t* CrossfireContext::KEY_FROMFRAME = L"fromFrame"; |
| const wchar_t* CrossfireContext::KEY_TOFRAME = L"toFrame"; |
| const wchar_t* CrossfireContext::KEY_TOTALFRAMES = L"totalFrames"; |
| |
| /* command: continue */ |
| const wchar_t* CrossfireContext::COMMAND_CONTINUE = L"continue"; |
| |
| /* command: evaluate */ |
| const wchar_t* CrossfireContext::COMMAND_EVALUATE = L"evaluate"; |
| const wchar_t* CrossfireContext::KEY_EXPRESSION = L"expression"; |
| const wchar_t* CrossfireContext::KEY_RESULT = L"result"; |
| |
| /* command: frame */ |
| const wchar_t* CrossfireContext::COMMAND_FRAME = L"frame"; |
| const wchar_t* CrossfireContext::KEY_FRAME = L"frame"; |
| const wchar_t* CrossfireContext::KEY_INDEX = L"index"; |
| |
| /* command: inspect */ |
| const wchar_t* CrossfireContext::COMMAND_INSPECT = L"inspect"; |
| |
| /* command: lookup */ |
| const wchar_t* CrossfireContext::COMMAND_LOOKUP = L"lookup"; |
| const wchar_t* CrossfireContext::KEY_HANDLES = L"handles"; |
| const wchar_t* CrossfireContext::KEY_VALUES = L"values"; |
| |
| /* command: scopes */ |
| const wchar_t* CrossfireContext::COMMAND_SCOPES = L"scopes"; |
| const wchar_t* CrossfireContext::KEY_FROMSCOPE = L"fromScope"; |
| const wchar_t* CrossfireContext::KEY_SCOPES = L"scopes"; |
| const wchar_t* CrossfireContext::KEY_TOSCOPE = L"toScope"; |
| const wchar_t* CrossfireContext::KEY_TOTALSCOPECOUNT = L"totalScopeCount"; |
| |
| /* command: scripts */ |
| const wchar_t* CrossfireContext::COMMAND_SCRIPTS = L"scripts"; |
| const wchar_t* CrossfireContext::KEY_SCRIPTS = L"scripts"; |
| const wchar_t* CrossfireContext::KEY_URLS = L"urls"; |
| |
| /* command: suspend */ |
| const wchar_t* CrossfireContext::COMMAND_SUSPEND = L"suspend"; |
| const wchar_t* CrossfireContext::KEY_STEPACTION = L"stepAction"; |
| const wchar_t* CrossfireContext::VALUE_IN = L"in"; |
| const wchar_t* CrossfireContext::VALUE_NEXT = L"next"; |
| const wchar_t* CrossfireContext::VALUE_OUT = L"out"; |
| |
| /* event: onBreak */ |
| const wchar_t* CrossfireContext::EVENT_ONBREAK = L"onBreak"; |
| const wchar_t* CrossfireContext::KEY_CAUSE = L"cause"; |
| const wchar_t* CrossfireContext::KEY_MESSAGE = L"message"; |
| const wchar_t* CrossfireContext::KEY_TITLE = L"title"; |
| |
| /* event: onResume */ |
| const wchar_t* CrossfireContext::EVENT_ONRESUME = L"onResume"; |
| |
| /* event: onScript */ |
| const wchar_t* CrossfireContext::EVENT_ONSCRIPT = L"onScript"; |
| const wchar_t* CrossfireContext::KEY_SCRIPT = L"script"; |
| |
| /* event: onToggleBreakpoint */ |
| const wchar_t* CrossfireContext::EVENT_ONTOGGLEBREAKPOINT = L"onToggleBreakpoint"; |
| const wchar_t* CrossfireContext::KEY_SET = L"set"; |
| |
| /* shared */ |
| const wchar_t* CrossfireContext::KEY_BREAKPOINT = L"breakpoint"; |
| const wchar_t* CrossfireContext::KEY_CONTEXTID = L"contextId"; |
| const wchar_t* CrossfireContext::KEY_FRAMEINDEX = L"frameIndex"; |
| const wchar_t* CrossfireContext::KEY_HANDLE = L"handle"; |
| const wchar_t* CrossfireContext::KEY_LOCATION = L"location"; |
| const wchar_t* CrossfireContext::KEY_INCLUDESCOPES = L"includeScopes"; |
| const wchar_t* CrossfireContext::KEY_INCLUDESOURCE = L"includeSource"; |
| const wchar_t* CrossfireContext::KEY_LINE = L"line"; |
| const wchar_t* CrossfireContext::KEY_TYPE = L"type"; |
| const wchar_t* CrossfireContext::KEY_URL = L"url"; |
| |
| /* breakpoint objects */ |
| const wchar_t* CrossfireContext::BPTYPE_LINE = L"line"; |
| |
| /* frame objects */ |
| const wchar_t* CrossfireContext::KEY_FUNCTIONNAME = L"functionName"; |
| |
| /* object objects */ |
| const wchar_t* CrossfireContext::JSVALUE_BOOLEAN = L"Boolean"; |
| const wchar_t* CrossfireContext::JSVALUE_FUNCTION = L"\"function\""; |
| const wchar_t* CrossfireContext::JSVALUE_NUMBER = L"Number"; |
| const wchar_t* CrossfireContext::JSVALUE_NULL = L"Null"; |
| const wchar_t* CrossfireContext::JSVALUE_STRING = L"String"; |
| const wchar_t* CrossfireContext::JSVALUE_TRUE = L"true"; |
| const wchar_t* CrossfireContext::JSVALUE_UNDEFINED = L"Undefined"; |
| const wchar_t* CrossfireContext::KEY_LOCALS = L"locals"; |
| const wchar_t* CrossfireContext::KEY_THIS = L"this"; |
| const wchar_t* CrossfireContext::KEY_VALUE = L"value"; |
| const wchar_t* CrossfireContext::VALUE_BOOLEAN = L"boolean"; |
| const wchar_t* CrossfireContext::VALUE_FUNCTION = L"function"; |
| const wchar_t* CrossfireContext::VALUE_NUMBER = L"number"; |
| const wchar_t* CrossfireContext::VALUE_OBJECT = L"object"; |
| const wchar_t* CrossfireContext::VALUE_STRING = L"string"; |
| const wchar_t* CrossfireContext::VALUE_UNDEFINED = L"undefined"; |
| |
| /* script objects */ |
| const wchar_t* CrossfireContext::KEY_COLUMNOFFSET = L"columnOffset"; |
| const wchar_t* CrossfireContext::KEY_LINECOUNT = L"lineCount"; |
| const wchar_t* CrossfireContext::KEY_LINEOFFSET = L"lineOffset"; |
| const wchar_t* CrossfireContext::KEY_SOURCE = L"source"; |
| const wchar_t* CrossfireContext::KEY_SOURCELENGTH = L"sourceLength"; |
| const wchar_t* CrossfireContext::VALUE_EVALCODE = L"eval code"; |
| const wchar_t* CrossfireContext::VALUE_EVALLEVEL = L"eval-level"; |
| const wchar_t* CrossfireContext::VALUE_TOPLEVEL = L"top-level"; |
| |
| |
| CrossfireContext::CrossfireContext(DWORD processId, DWORD threadId, wchar_t* url, CrossfireServer* server) { |
| static int s_counter = 0; |
| m_processId = processId; |
| m_threadId = threadId; |
| std::wstringstream stream; |
| stream << ID_PREAMBLE; |
| stream << m_processId; |
| stream << "-"; |
| stream << s_counter++; |
| m_name = _wcsdup((wchar_t*)stream.str().c_str()); |
| |
| CComObject<IEDebugger>* result = NULL; |
| HRESULT hr = CComObject<IEDebugger>::CreateInstance(&result); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext(): CoCreateInstance(IIEDebugger) failed", hr); |
| return; |
| } |
| m_asyncEvals = new std::vector<JSEvalCallback*>; |
| m_debugger = result; |
| m_debugger->AddRef(); /* CComObject::CreateInstance gives initial ref count of 0 */ |
| m_breakpoints = new std::map<unsigned int, CrossfireBreakpoint*>; |
| m_cpcApplicationNodeEvents = 0; |
| m_server = server; |
| m_debugApplicationThread = NULL; |
| m_debuggerHooked = false; |
| m_lastInitializedScriptNode = NULL; |
| m_nextObjectHandle = 1; |
| m_objects = new std::map<unsigned int, JSObject*>; |
| m_pendingScriptLoads = new std::map<IDebugApplicationNode*, PendingScriptLoad*>; |
| m_running = true; |
| m_scriptNodes = NULL; |
| m_url = _wcsdup(url); |
| hookDebugger(); |
| } |
| |
| CrossfireContext::~CrossfireContext() { |
| if (m_asyncEvals) { |
| std::vector<JSEvalCallback*>::iterator iterator = m_asyncEvals->begin(); |
| while (iterator != m_asyncEvals->end()) { |
| delete *iterator; |
| iterator++; |
| } |
| delete m_asyncEvals; |
| } |
| |
| if (m_breakpoints) { |
| std::map<unsigned int, CrossfireBreakpoint*>::iterator iterator = m_breakpoints->begin(); |
| while (iterator != m_breakpoints->end()) { |
| delete iterator->second; |
| iterator++; |
| } |
| delete m_breakpoints; |
| } |
| |
| if (m_lastInitializedScriptNode) { |
| m_lastInitializedScriptNode->Release(); |
| } |
| |
| if (m_pendingScriptLoads) { |
| std::map<IDebugApplicationNode*, PendingScriptLoad*>::iterator iterator = m_pendingScriptLoads->begin(); |
| while (iterator != m_pendingScriptLoads->end()) { |
| iterator->first->Release(); |
| iterator->second->Release(); |
| iterator++; |
| } |
| delete m_pendingScriptLoads; |
| } |
| |
| if (m_scriptNodes) { |
| std::map<std::wstring, IDebugApplicationNode*>::iterator iterator = m_scriptNodes->begin(); |
| while (iterator != m_scriptNodes->end()) { |
| iterator->second->Release(); |
| iterator++; |
| } |
| delete m_scriptNodes; |
| } |
| |
| if (m_cpcApplicationNodeEvents) { |
| CComPtr<IRemoteDebugApplication> debugApplication = NULL; |
| if (!getDebugApplication(&debugApplication)) { |
| Logger::error("~CrossfireContext(): cannot Unadvise() the root node"); |
| } else { |
| CComPtr<IDebugApplicationNode> rootNode = NULL; |
| HRESULT hr = debugApplication->GetRootNode(&rootNode); |
| if (FAILED(hr)) { |
| Logger::error("~CrossfireContext(): GetRootNode() failed", hr); |
| } else { |
| CComPtr<IConnectionPointContainer> connectionPointContainer = NULL; |
| hr = rootNode->QueryInterface(IID_IConnectionPointContainer, (void**)&connectionPointContainer); |
| if (FAILED(hr)) { |
| Logger::error("~CrossfireContext(): QI(IConnectionPointContainer) failed", hr); |
| } else { |
| CComPtr<IConnectionPoint> connectionPoint = NULL; |
| hr = connectionPointContainer->FindConnectionPoint(IID_IDebugApplicationNodeEvents, &connectionPoint); |
| if (FAILED(hr)) { |
| Logger::error("~CrossfireContext(): FindConnectionPoint() failed", hr); |
| } else { |
| hr = connectionPoint->Unadvise(m_cpcApplicationNodeEvents); |
| if (FAILED(hr)) { |
| Logger::error("~CrossfireContext(): Unadvise() failed", hr); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| if (m_name) { |
| free(m_name); |
| } |
| if (m_url) { |
| free(m_url); |
| } |
| if (m_objects) { |
| clearObjects(); |
| delete m_objects; |
| } |
| if (m_debuggerHooked) { |
| unhookDebugger(); |
| } |
| if (m_debugger) { |
| m_debugger->Release(); |
| } |
| if (m_debugApplicationThread) { |
| m_debugApplicationThread->Release(); |
| } |
| } |
| |
| /* IBreakpointTarget */ |
| |
| bool CrossfireContext::breakpointAttributeChanged(unsigned int handle, wchar_t* name, Value* value) { |
| std::map<unsigned int, CrossfireBreakpoint*>::iterator iterator = m_breakpoints->find(handle); |
| if (iterator == m_breakpoints->end()) { |
| Logger::error("CrossfireContext.breakpointAttributeChanged(): unknown breakpoint handle", handle); |
| return false; |
| } |
| CrossfireBreakpoint* breakpoint = iterator->second; |
| |
| if (wcscmp(name, CrossfireLineBreakpoint::ATTRIBUTE_ENABLED) == 0) { |
| return setBreakpointEnabled((CrossfireLineBreakpoint*)breakpoint, value->getBooleanValue()); |
| } |
| if (wcscmp(name, CrossfireLineBreakpoint::ATTRIBUTE_CONDITION) == 0) { |
| ((CrossfireLineBreakpoint*)breakpoint)->setCondition(value->getStringValue()); |
| return true; |
| } |
| if (wcscmp(name, CrossfireLineBreakpoint::ATTRIBUTE_HITCOUNT) == 0) { |
| ((CrossfireLineBreakpoint*)breakpoint)->setHitCount((unsigned int)value->getNumberValue()); |
| return true; |
| } |
| |
| /* receiver does not do anything for any other breakpoint attributes, so just answer success */ |
| return true; |
| } |
| |
| bool CrossfireContext::setBreakpointEnabled(CrossfireBreakpoint* breakpoint, bool enabled) { |
| CrossfireLineBreakpoint* lineBp = (CrossfireLineBreakpoint*)breakpoint; |
| IDebugApplicationNode* node = getScriptNode((URL*)lineBp->getUrl()); |
| if (!node) { |
| Logger::error("CrossfireContext.setBreakpointEnabled(): unknown target url"); |
| return false; |
| } |
| |
| CComPtr<IDebugDocument> document = NULL; |
| HRESULT hr = node->GetDocument(&document); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.setBreakpointEnabled(): GetDocument() failed", hr); |
| return false; |
| } |
| |
| CComPtr<IDebugDocumentText> documentText = NULL; |
| hr = document->QueryInterface(IID_IDebugDocumentText, (void**)&documentText); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.setBreakpointEnabled(): QI(IDebugDocumentText) failed", hr); |
| return false; |
| } |
| |
| ULONG characterPosition = 0; |
| hr = documentText->GetPositionOfLine(lineBp->getLine() - 1, &characterPosition); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.setBreakpointEnabled(): GetPositionOfLine() failed [1]", hr); |
| return false; |
| } |
| |
| CComPtr<IDebugDocumentContext> documentContext = NULL; |
| hr = documentText->GetContextOfPosition(characterPosition, 1, &documentContext); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.setBreakpointEnabled(): GetContextOfPosition() failed", hr); |
| return false; |
| } |
| |
| CComPtr<IEnumDebugCodeContexts> codeContexts = NULL; |
| hr = documentContext->EnumCodeContexts(&codeContexts); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.setBreakpointEnabled(): EnumCodeContexts() failed", hr); |
| return false; |
| } |
| |
| ULONG fetched = 0; |
| IDebugCodeContext* codeContext = NULL; |
| hr = codeContexts->Next(1, &codeContext, &fetched); |
| if (FAILED(hr) || fetched == 0) { |
| Logger::error("CrossfireContext.setBreakpointEnabled(): Next() failed", hr); |
| return false; |
| } |
| |
| /* |
| * Feature of JScript9. A distinct breakpoint is created for each breakpoint that |
| * is set at a given location. As a result, setting multiple breakpoints at a location |
| * can cause multiple breaks to occur when the location is hit. To ensure that a |
| * location never has more than one breakpoint, delete the breakpoint (if any) at the |
| * location before setting the new breakpoint. |
| */ |
| codeContext->SetBreakPoint(BREAKPOINT_DELETED); |
| |
| hr = codeContext->SetBreakPoint(enabled ? BREAKPOINT_ENABLED : BREAKPOINT_DISABLED); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.setBreakpointEnabled(): SetBreakPoint() failed", hr); |
| return false; |
| } |
| |
| lineBp->setEnabled(enabled); |
| return true; |
| } |
| |
| bool CrossfireContext::deleteBreakpoint(unsigned int handle) { |
| std::map<unsigned int, CrossfireBreakpoint*>::iterator iterator = m_breakpoints->find(handle); |
| if (iterator == m_breakpoints->end()) { |
| Logger::error("CrossfireContext.deleteBreakpoint(): unknown breakpoint handle", handle); |
| return false; |
| } |
| CrossfireLineBreakpoint* breakpoint = (CrossfireLineBreakpoint*)iterator->second; |
| |
| IDebugApplicationNode* node = getScriptNode((URL*)breakpoint->getUrl()); |
| if (!node) { |
| Logger::error("CrossfireContext.deleteBreakpoint(): unknown target url"); |
| return false; |
| } |
| |
| CComPtr<IDebugDocument> document = NULL; |
| HRESULT hr = node->GetDocument(&document); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.deleteBreakpoint(): GetDocument() failed", hr); |
| return false; |
| } |
| |
| CComPtr<IDebugDocumentText> documentText = NULL; |
| hr = document->QueryInterface(IID_IDebugDocumentText,(void**)&documentText); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.deleteBreakpoint(): QI(IDebugDocumentText) failed", hr); |
| return false; |
| } |
| |
| ULONG characterPosition = 0; |
| hr = documentText->GetPositionOfLine(breakpoint->getLine() - 1, &characterPosition); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.deleteBreakpoint(): GetPositionOfLine() failed [1]", hr); |
| return false; |
| } |
| |
| CComPtr<IDebugDocumentContext> documentContext = NULL; |
| hr = documentText->GetContextOfPosition(characterPosition, 1, &documentContext); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.deleteBreakpoint(): GetContextOfPosition() failed", hr); |
| return false; |
| } |
| |
| CComPtr<IEnumDebugCodeContexts> codeContexts = NULL; |
| hr = documentContext->EnumCodeContexts(&codeContexts); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.deleteBreakpoint(): EnumCodeContexts() failed", hr); |
| return false; |
| } |
| |
| ULONG fetched = 0; |
| IDebugCodeContext* codeContext = NULL; |
| hr = codeContexts->Next(1, &codeContext, &fetched); |
| if (FAILED(hr) || fetched == 0) { |
| Logger::error("CrossfireContext.deleteBreakpoint(): Next() failed", hr); |
| return false; |
| } |
| |
| hr = codeContext->SetBreakPoint(BREAKPOINT_DELETED); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.deleteBreakpoint(): SetBreakPoint() failed", hr); |
| return false; |
| } |
| |
| CrossfireEvent toggleEvent; |
| toggleEvent.setName(EVENT_ONTOGGLEBREAKPOINT); |
| Value body; |
| body.addObjectValue(KEY_SET, &Value(false)); |
| Value* value_breakpoint = NULL; |
| breakpoint->toValueObject(&value_breakpoint); |
| body.addObjectValue(KEY_BREAKPOINT, value_breakpoint); |
| delete value_breakpoint; |
| toggleEvent.setBody(&body); |
| sendEvent(&toggleEvent); |
| |
| delete iterator->second; |
| m_breakpoints->erase(iterator); |
| return true; |
| } |
| |
| CrossfireBreakpoint* CrossfireContext::getBreakpoint(unsigned int handle) { |
| std::map<unsigned int, CrossfireBreakpoint*>::iterator iterator = m_breakpoints->find(handle); |
| if (iterator == m_breakpoints->end()) { |
| Logger::error("CrossfireContext.getBreakpoint(): unknown breakpoint handle", handle); |
| return false; |
| } |
| return iterator->second; |
| } |
| |
| void CrossfireContext::getBreakpoints(CrossfireBreakpoint*** ___values) { |
| size_t size = m_breakpoints->size(); |
| CrossfireBreakpoint** breakpoints = new CrossfireBreakpoint*[size + 1]; |
| |
| std::map<unsigned int, CrossfireBreakpoint*>::iterator it = m_breakpoints->begin(); |
| int index = 0; |
| while (it != m_breakpoints->end()) { |
| it->second->clone(&breakpoints[index++]); |
| it++; |
| } |
| breakpoints[index] = NULL; |
| |
| *___values = breakpoints; |
| } |
| |
| bool CrossfireContext::setBreakpoint(CrossfireBreakpoint *breakpoint) { |
| // TODO uncomment the following once Refreshes cause new contexts to be created |
| |
| // unsigned int handle = breakpoint->getHandle(); |
| // std::map<unsigned int, CrossfireBreakpoint*>::iterator iterator = m_breakpoints->find(handle); |
| // if (iterator != m_breakpoints->end()) { |
| // /* this breakpoint is already set on this context */ |
| // return true; |
| // } |
| |
| CrossfireLineBreakpoint* lineBp = (CrossfireLineBreakpoint*)breakpoint; |
| IDebugApplicationNode* node = NULL; |
| if (m_currentScriptNode) { |
| node = m_currentScriptNode; |
| } else { |
| node = getScriptNode((URL*)lineBp->getUrl()); |
| if (!node) { |
| Logger::error("CrossfireContext.setBreakpoint(): unknown target url"); |
| return false; |
| } |
| } |
| |
| CComPtr<IDebugDocument> document = NULL; |
| HRESULT hr = node->GetDocument(&document); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.setBreakpoint(): GetDocument() failed", hr); |
| return false; |
| } |
| |
| CComPtr<IDebugDocumentText> documentText = NULL; |
| hr = document->QueryInterface(IID_IDebugDocumentText, (void**)&documentText); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.setBreakpoint(): QI(IDebugDocumentText) failed", hr); |
| return false; |
| } |
| |
| ULONG characterPosition = 0; |
| hr = documentText->GetPositionOfLine(lineBp->getLine() - 1, &characterPosition); |
| if (FAILED(hr)) { |
| /* |
| * In this context E_INVALIDARG failures are often caused by the target document |
| * not being adequately loaded yet. If this happens then a subsequent attempt |
| * to set the breakpoint should be made once its source has loaded. |
| */ |
| if (hr != E_INVALIDARG) { |
| Logger::error("CrossfireContext.setBreakpoint(): GetPositionOfLine() failed [1]", hr); |
| } |
| return false; |
| } |
| |
| CComPtr<IDebugDocumentContext> documentContext = NULL; |
| hr = documentText->GetContextOfPosition(characterPosition, 1, &documentContext); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.setBreakpoint(): GetContextOfPosition() failed", hr); |
| return false; |
| } |
| |
| CComPtr<IEnumDebugCodeContexts> codeContexts = NULL; |
| hr = documentContext->EnumCodeContexts(&codeContexts); |
| if (FAILED(hr)) { |
| /* |
| * In this context E_INVALIDARG failures are often caused by the target document |
| * not being adequately loaded yet. If this happens then a subsequent attempt |
| * to set the breakpoint should be made once its source has loaded. |
| */ |
| if (hr != E_INVALIDARG) { |
| Logger::error("CrossfireContext.setBreakpoint(): EnumCodeContexts() failed", hr); |
| } |
| return false; |
| } |
| |
| ULONG fetched = 0; |
| CComPtr<IDebugCodeContext> codeContext = NULL; |
| hr = codeContexts->Next(1, &codeContext, &fetched); |
| if (FAILED(hr) || fetched == 0) { |
| Logger::error("CrossfireContext.setBreakpoint(): Next() failed", hr); |
| return false; |
| } |
| |
| /* |
| * Feature of JScript9. A distinct breakpoint is created for each breakpoint that |
| * is set at a given location. As a result, setting multiple breakpoints at a location |
| * causes multiple breaks to occur when the location is hit. To ensure that a |
| * location never has more than one breakpoint, delete the breakpoint (if any) at the |
| * location before setting the new breakpoint. |
| */ |
| codeContext->SetBreakPoint(BREAKPOINT_DELETED); |
| |
| hr = codeContext->SetBreakPoint(lineBp->isEnabled() ? BREAKPOINT_ENABLED : BREAKPOINT_DISABLED); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.setBreakpoint(): SetBreakPoint() failed", hr); |
| return false; |
| } |
| |
| /* Determine the line number the breakpoint was set on (it may not match the requested line number) */ |
| |
| CComPtr<IDebugDocumentContext> bpDocumentContext = NULL; |
| hr = codeContext->GetDocumentContext(&bpDocumentContext); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.setBreakpoint(): GetDocumentContext() failed", hr); |
| return false; |
| } |
| |
| characterPosition = 0; |
| ULONG numChars = 0; |
| hr = documentText->GetPositionOfContext(bpDocumentContext, &characterPosition, &numChars); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.setBreakpoint(): GetPositionOfContext() failed", hr); |
| return false; |
| } |
| |
| ULONG bpLineNumber = 0; |
| ULONG offset = 0; |
| hr = documentText->GetLineOfPosition(characterPosition, &bpLineNumber, &offset); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.setBreakpoint(): GetLineOfPosition() failed", hr); |
| return false; |
| } |
| |
| /* if a breakpoint with a duplicate handle exists then replace it with the new breakpoint */ |
| unsigned int handle = lineBp->getHandle(); |
| std::map<unsigned int, CrossfireBreakpoint*>::iterator iterator = m_breakpoints->find(handle); |
| if (iterator != m_breakpoints->end()) { |
| delete iterator->second; |
| m_breakpoints->erase(iterator); |
| } |
| |
| CrossfireLineBreakpoint* copy = NULL; |
| lineBp->clone((CrossfireBreakpoint**)©); |
| copy->setLine(bpLineNumber + 1); |
| copy->setContextId(&std::wstring(m_name)); |
| m_breakpoints->insert(std::pair<unsigned int, CrossfireBreakpoint*>(handle, copy)); |
| |
| CrossfireEvent toggleEvent; |
| toggleEvent.setName(EVENT_ONTOGGLEBREAKPOINT); |
| Value body; |
| body.addObjectValue(KEY_SET, &Value(true)); |
| Value* value_breakpoint = NULL; |
| copy->toValueObject(&value_breakpoint); |
| body.addObjectValue(KEY_BREAKPOINT, value_breakpoint); |
| delete value_breakpoint; |
| toggleEvent.setBody(&body); |
| sendEvent(&toggleEvent); |
| return true; |
| } |
| |
| void CrossfireContext::clearObjects() { |
| std::map<unsigned int, JSObject*>::iterator iterator = m_objects->begin(); |
| while (iterator != m_objects->end()) { |
| JSObject* jsObject = iterator->second; |
| jsObject->debugProperty->Release(); |
| jsObject->stackFrame->Release(); |
| delete jsObject; |
| iterator++; |
| } |
| m_objects->clear(); |
| } |
| |
| /* IJSEvalHandler methods */ |
| |
| void CrossfireContext::evalComplete(IDebugProperty* value, void* data) { |
| /* |
| * Currently only breakpoint conditions are evaluated asynchronously, so it's assumed |
| * that data is a breakpoint. If more asynchronous evaluations are introduced in the |
| * future then a new class implementing IJSEvalHandler should be created for handling |
| * breakpoint condition evals. |
| */ |
| |
| bool resume = false; |
| DebugPropertyInfo propertyInfo; |
| HRESULT hr = value->GetPropertyInfo(DBGPROP_INFO_ATTRIBUTES | DBGPROP_INFO_TYPE | DBGPROP_INFO_VALUE, 10, &propertyInfo); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.evalComplete(): GetPropertyInfo() failed", hr); |
| resume = true; |
| } else { |
| BSTR type = propertyInfo.m_bstrType; |
| if (wcscmp(type, JSVALUE_BOOLEAN) != 0) { |
| resume = true; |
| } else { |
| BSTR stringValue = propertyInfo.m_bstrValue; |
| if (wcscmp(stringValue, JSVALUE_TRUE) != 0) { |
| resume = true; |
| } |
| } |
| } |
| |
| if (resume && resumeFromBreak(BREAKRESUMEACTION_CONTINUE)) { |
| return; |
| } |
| |
| CrossfireLineBreakpoint* breakpoint = (CrossfireLineBreakpoint*)data; |
| CrossfireEvent onBreakEvent; |
| onBreakEvent.setName(EVENT_ONBREAK); |
| Value value_body; |
| Value location; |
| location.addObjectValue(KEY_LINE, &Value((double)breakpoint->getLine())); |
| location.addObjectValue(KEY_URL, &Value(((URL*)breakpoint->getUrl())->getString())); |
| value_body.addObjectValue(KEY_LOCATION, &location); |
| Value cause; |
| cause.addObjectValue(KEY_TITLE, &Value(L"breakpoint")); |
| value_body.addObjectValue(KEY_CAUSE, &cause); |
| onBreakEvent.setBody(&value_body); |
| sendEvent(&onBreakEvent); |
| } |
| |
| /* CrossfireContext */ |
| |
| void CrossfireContext::breakpointHit(IRemoteDebugApplicationThread *pDebugAppThread, BREAKREASON br, IActiveScriptErrorDebug *pScriptErrorDebug) { |
| m_running = false; |
| |
| /* |
| * IE has just entered a suspended state. If any of the steps preceeding the |
| * sending of the onBreak event fail then a resume (step out) must be done so |
| * that the server is not left in a suspended state without the client's knowledge. |
| */ |
| CComPtr<IEnumDebugStackFrames> stackFrames = NULL; |
| HRESULT hr = pDebugAppThread->EnumStackFrames(&stackFrames); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.breakpointHit(): EnumStackFrames() failed", hr); |
| resumeFromBreak(BREAKRESUMEACTION_STEP_OUT); |
| return; |
| } |
| |
| DebugStackFrameDescriptor stackFrameDescriptor; |
| ULONG numFetched = 0; |
| hr = stackFrames->Next(1, &stackFrameDescriptor, &numFetched); |
| if (FAILED(hr) || numFetched != 1) { |
| Logger::error("CrossfireContext.breakpointHit(): EnumStackFrames->Next() failed", hr); |
| resumeFromBreak(BREAKRESUMEACTION_STEP_OUT); |
| return; |
| } |
| |
| IDebugStackFrame* frame = stackFrameDescriptor.pdsf; |
| CComPtr<IDebugCodeContext> codeContext = NULL; |
| hr = frame->GetCodeContext(&codeContext); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.breakpointHit(): GetCodeContext() failed", hr); |
| resumeFromBreak(BREAKRESUMEACTION_STEP_OUT); |
| return; |
| } |
| |
| CComPtr<IDebugDocumentContext> documentContext = NULL; |
| hr = codeContext->GetDocumentContext(&documentContext); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.breakpointHit(): GetDocumentContext() failed", hr); |
| resumeFromBreak(BREAKRESUMEACTION_STEP_OUT); |
| return; |
| } |
| |
| CComPtr<IDebugDocument> document = NULL; |
| hr = documentContext->GetDocument(&document); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.breakpointHit(): GetDocument() failed", hr); |
| resumeFromBreak(BREAKRESUMEACTION_STEP_OUT); |
| return; |
| } |
| |
| CComPtr<IDebugDocumentText> documentText = NULL; |
| hr = document->QueryInterface(IID_IDebugDocumentText, (void**)&documentText); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.breakpointHit(): QueryInterface() failed", hr); |
| resumeFromBreak(BREAKRESUMEACTION_STEP_OUT); |
| return; |
| } |
| |
| ULONG position, numChars; |
| hr = documentText->GetPositionOfContext(documentContext, &position, &numChars); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.breakpointHit(): GetPositionOfContext() failed", hr); |
| resumeFromBreak(BREAKRESUMEACTION_STEP_OUT); |
| return; |
| } |
| |
| ULONG lineNumber, column; |
| hr = documentText->GetLineOfPosition(position, &lineNumber, &column); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.breakpointHit(): GetLineOfContext() failed", hr); |
| resumeFromBreak(BREAKRESUMEACTION_STEP_OUT); |
| return; |
| } |
| lineNumber++; |
| |
| CComBSTR bstrUrl; |
| hr = document->GetName(DOCUMENTNAMETYPE_TITLE, &bstrUrl); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.breakpointHit(): GetName() failed", hr); |
| resumeFromBreak(BREAKRESUMEACTION_STEP_OUT); |
| return; |
| } |
| URL url(bstrUrl); |
| |
| /* |
| * If the cause of the break is a breakpoint then locate the breakpoint and |
| * determine whether the onBreak event should be sent (eg.- does the breakpoint |
| * have a hit count to respect, a condition to evaluate, etc.). |
| */ |
| if (br == BREAKREASON_BREAKPOINT) { |
| CrossfireBreakpoint* breakpoint = NULL; |
| std::map<unsigned int, CrossfireBreakpoint*>::iterator iterator = m_breakpoints->begin(); |
| while (iterator != m_breakpoints->end()) { |
| CrossfireBreakpoint* current = iterator->second; |
| if (current->getType() == CrossfireLineBreakpoint::BPTYPE_LINE) { |
| CrossfireLineBreakpoint* lineBp = (CrossfireLineBreakpoint*)current; |
| if (lineBp->getLine() == lineNumber && ((URL*)lineBp->getUrl())->isEqual(&url)) { |
| lineBp->breakpointHit(); |
| if (!lineBp->matchesHitCount() && resumeFromBreak(BREAKRESUMEACTION_CONTINUE)) { |
| return; |
| } |
| const std::wstring* conditionString = lineBp->getCondition(); |
| if (conditionString) { |
| wchar_t* condition = (wchar_t*)conditionString->c_str(); |
| if (evaluateAsync(frame, condition, DEBUG_TEXT_RETURNVALUE | DEBUG_TEXT_NOSIDEEFFECTS, this, lineBp)) { |
| return; |
| } |
| } |
| break; |
| } |
| } |
| iterator++; |
| } |
| } |
| |
| CrossfireEvent onBreakEvent; |
| onBreakEvent.setName(EVENT_ONBREAK); |
| Value body; |
| Value location; |
| location.addObjectValue(KEY_LINE, &Value((double)lineNumber)); |
| location.addObjectValue(KEY_URL, &Value(url.getString())); |
| body.addObjectValue(KEY_LOCATION, &location); |
| Value cause; |
| switch (br) { |
| case BREAKREASON_ERROR: { |
| cause.addObjectValue(KEY_TITLE, &Value(L"error")); |
| EXCEPINFO excepInfo; |
| HRESULT hr = pScriptErrorDebug->GetExceptionInfo(&excepInfo); |
| if (FAILED(hr)) { |
| Logger::error("IEDebugger::onHandleBreakPoint(): GetExceptionInfo() failed", hr); |
| } else { |
| if (excepInfo.bstrDescription) { |
| cause.addObjectValue(KEY_MESSAGE, &Value(excepInfo.bstrDescription)); |
| } |
| } |
| break; |
| } |
| case BREAKREASON_DEBUGGER_HALT: { |
| cause.addObjectValue(KEY_TITLE, &Value(L"suspend")); |
| break; |
| } |
| case BREAKREASON_STEP: { |
| cause.addObjectValue(KEY_TITLE, &Value(L"step")); |
| break; |
| } |
| case BREAKREASON_BREAKPOINT: { |
| cause.addObjectValue(KEY_TITLE, &Value(L"breakpoint")); |
| break; |
| } |
| default: { |
| cause.addObjectValue(KEY_TITLE, &Value(L"suspend")); |
| break; |
| } |
| } |
| body.addObjectValue(KEY_CAUSE, &cause); |
| onBreakEvent.setBody(&body); |
| sendEvent(&onBreakEvent); |
| } |
| |
| bool CrossfireContext::createValueForFrame(IDebugStackFrame* stackFrame, unsigned int frameIndex, bool includeScopes, Value** _value) { |
| *_value = NULL; |
| |
| CComBSTR description = NULL; |
| HRESULT hr = stackFrame->GetDescriptionString(true, &description); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.createValueForFrame(): GetDescriptionString() failed", hr); |
| return false; |
| } |
| |
| ULONG lineNumber = 0, column; |
| Value* locals = NULL; |
| |
| CComPtr<IDebugCodeContext> codeContext = NULL; |
| hr = stackFrame->GetCodeContext(&codeContext); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.createValueForFrame(): GetCodeContext() failed", hr); |
| return false; |
| } |
| |
| CComPtr<IDebugDocumentContext> documentContext = NULL; |
| hr = codeContext->GetDocumentContext(&documentContext); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.createValueForFrame(): GetDocumentContext() failed", hr); |
| return false; |
| } |
| |
| CComPtr<IDebugDocument> document = NULL; |
| hr = documentContext->GetDocument(&document); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.createValueForFrame(): GetDocument() failed", hr); |
| return false; |
| } |
| |
| CComPtr<IDebugDocumentText> documentText = NULL; |
| hr = document->QueryInterface(IID_IDebugDocumentText, (void**)&documentText); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.createValueForFrame(): QI(IDebugDocumentText) failed", hr); |
| return false; |
| } |
| |
| ULONG position, numChars; |
| hr = documentText->GetPositionOfContext(documentContext, &position, &numChars); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.createValueForFrame(): GetPositionOfContext() failed", hr); |
| return false; |
| } |
| |
| hr = documentText->GetLineOfPosition(position, &lineNumber, &column); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.createValueForFrame(): GetLineOfPosition() failed", hr); |
| return false; |
| } |
| |
| /* get the locals */ |
| |
| CComPtr<IDebugProperty> debugProperty = NULL; |
| hr = stackFrame->GetDebugProperty(&debugProperty); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.createValueForFrame(): GetDebugProperty() failed", hr); |
| return false; |
| } |
| |
| // TODO this frameObject is currently created temporarily, but to |
| // get the full benefit of handle reuse it should be stored and |
| // reused for cases where the frame command is invoked on the same |
| // frame multiple times |
| JSObject* newObject = new JSObject(); |
| newObject->debugProperty = debugProperty; |
| //newObject->debugProperty->AddRef(); |
| newObject->stackFrame = stackFrame; |
| //newObject->stackFrame->AddRef(); |
| if (!createValueForObject(newObject, true, &locals)) { |
| delete newObject; |
| return false; |
| } |
| delete newObject; |
| |
| /* get "this" */ |
| |
| Value* value_this = NULL; |
| CComPtr<IDebugProperty> debugProperty2 = NULL; |
| if (evaluate(stackFrame, L"this", DEBUG_TEXT_RETURNVALUE, &debugProperty2)) { |
| // TODO this thisObject is currently created temporarily, but to |
| // get the full benefit of handle reuse it should be stored and |
| // reused for cases where the frame command is invoked on the same |
| // frame multiple times |
| JSObject* newObject = new JSObject(); |
| newObject->debugProperty = debugProperty2; |
| //newObject->debugProperty->AddRef(); |
| newObject->stackFrame = stackFrame; |
| //newObject->stackFrame->AddRef(); |
| createValueForObject(newObject, true, &value_this); /* proceed even if fails */ |
| delete newObject; |
| } |
| |
| if (value_this) { |
| locals->addObjectValue(KEY_THIS, value_this); |
| delete value_this; |
| } else { |
| /* create an empty "this" value */ |
| Value value_thisChildren; |
| value_thisChildren.setType(TYPE_OBJECT); |
| Value value_this2; |
| value_this2.addObjectValue(KEY_TYPE, &Value(VALUE_OBJECT)); |
| value_this2.addObjectValue(KEY_VALUE, &value_thisChildren); |
| locals->addObjectValue(KEY_THIS, &value_this2); |
| } |
| |
| URL url; |
| CComBSTR bstrUrl = NULL; |
| hr = document->GetName(DOCUMENTNAMETYPE_URL, &bstrUrl); |
| if (SUCCEEDED(hr)) { |
| url.setString(bstrUrl); |
| } else { |
| /* |
| * Failure to get the URL indicates that the node represents something like a JScript |
| * block or eval code. For these cases get the node's title instead and prepend a |
| * scheme to make it url-like. |
| */ |
| hr = document->GetName(DOCUMENTNAMETYPE_TITLE, &bstrUrl); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.createValueForFrame(): GetName() failed", hr); |
| return false; |
| } |
| |
| int length = wcslen(SCHEME_SCRIPT) + wcslen(bstrUrl.m_str) + 1; |
| wchar_t* string = new wchar_t[length]; |
| ZeroMemory(string, length * sizeof(wchar_t)); |
| wcscat_s(string, length, SCHEME_SCRIPT); |
| wcscat_s(string, length, bstrUrl.m_str); |
| url.setString(string); |
| delete[] string; |
| } |
| |
| if (!locals) { |
| locals = new Value(); |
| locals->setType(TYPE_OBJECT); |
| } |
| |
| Value* result = new Value(); |
| result->addObjectValue(KEY_FUNCTIONNAME, &Value(description)); |
| result->addObjectValue(KEY_INDEX, &Value((double)frameIndex)); |
| result->addObjectValue(KEY_LINE, &Value((double)lineNumber + 1)); |
| result->addObjectValue(KEY_LOCALS, locals); |
| result->addObjectValue(KEY_URL, &Value(url.getString())); |
| // TODO includeScopes |
| delete locals; |
| |
| *_value = result; |
| return true; |
| } |
| |
| bool CrossfireContext::createValueForObject(JSObject* object, bool resolveChildObjects, Value** _value) { |
| *_value = NULL; |
| |
| DebugPropertyInfo propertyInfo; |
| IDebugProperty* debugProperty = object->debugProperty; |
| HRESULT hr = debugProperty->GetPropertyInfo(DBGPROP_INFO_NAME | DBGPROP_INFO_FULLNAME | DBGPROP_INFO_TYPE | DBGPROP_INFO_VALUE | DBGPROP_INFO_ATTRIBUTES | DBGPROP_INFO_DEBUGPROP, 10, &propertyInfo); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.createValueForObject(): GetPropertyInfo() failed", hr); |
| return false; |
| } |
| |
| BSTR type = propertyInfo.m_bstrType; |
| Value result; |
| if (wcscmp(type, JSVALUE_NULL) == 0) { |
| result.setType(TYPE_NULL); |
| } else if (wcscmp(type, JSVALUE_UNDEFINED) == 0) { |
| result.setValue(VALUE_UNDEFINED); |
| } else { |
| BSTR stringValue = propertyInfo.m_bstrValue; |
| if (wcscmp(type, JSVALUE_NUMBER) == 0) { |
| wchar_t* endPtr = 0; |
| result.addObjectValue(KEY_TYPE, &Value(VALUE_NUMBER)); |
| if (wcscmp(stringValue, NUMBER_NaN) == 0) { |
| result.addObjectValue(KEY_VALUE, &Value(VALUE_NaN)); |
| } else if (wcscmp(stringValue, NUMBER_INFINITY) == 0) { |
| result.addObjectValue(KEY_VALUE, &Value(VALUE_INFINITY)); |
| } else if (wcscmp(stringValue, NUMBER_NEGATIVEINFINITY) == 0) { |
| result.addObjectValue(KEY_VALUE, &Value(VALUE_NEGATIVEINFINITY)); |
| } else { |
| double value = wcstod(stringValue, &endPtr); |
| result.addObjectValue(KEY_VALUE, &Value(value)); |
| } |
| } else if (wcscmp(type, JSVALUE_BOOLEAN) == 0) { |
| result.addObjectValue(KEY_TYPE, &Value(VALUE_BOOLEAN)); |
| if (wcscmp(stringValue, JSVALUE_TRUE) == 0) { |
| result.addObjectValue(KEY_VALUE, &Value(true)); |
| } else { |
| result.addObjectValue(KEY_VALUE, &Value(false)); |
| } |
| } else if (wcscmp(type, JSVALUE_STRING) == 0) { |
| std::wstring string(stringValue); |
| string = string.substr(1, string.length() - 2); |
| result.addObjectValue(KEY_TYPE, &Value(VALUE_STRING)); |
| result.addObjectValue(KEY_VALUE, &Value(&string)); |
| } else if ((propertyInfo.m_dwAttrib & DBGPROP_ATTRIB_VALUE_IS_INVALID) != 0) { |
| // TODO error object, should fail? |
| } else if ((propertyInfo.m_dwAttrib & DBGPROP_ATTRIB_VALUE_IS_EXPANDABLE) == 0) { |
| /* object is a function */ |
| result.addObjectValue(KEY_TYPE, &Value(VALUE_FUNCTION)); |
| if (resolveChildObjects) { |
| // TODO |
| Value value_empty; |
| value_empty.setType(TYPE_OBJECT); |
| result.addObjectValue(KEY_VALUE, &value_empty); |
| } |
| } else { |
| if (!resolveChildObjects) { |
| result.addObjectValue(KEY_TYPE, &Value(VALUE_OBJECT)); |
| } else { |
| CComPtr<IEnumDebugPropertyInfo> enumPropertyInfo = NULL; |
| HRESULT hr = debugProperty->EnumMembers( |
| DBGPROP_INFO_NAME | DBGPROP_INFO_FULLNAME | DBGPROP_INFO_TYPE | DBGPROP_INFO_VALUE | DBGPROP_INFO_ATTRIBUTES | DBGPROP_INFO_DEBUGPROP, |
| 10, |
| IID_IDebugPropertyEnumType_LocalsPlusArgs, |
| &enumPropertyInfo); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.createValueForObject(): EnumMembers() failed", hr); |
| return false; |
| } |
| |
| /* |
| * Bug in JScript9. For some reason the enumPropertyInfo->Next() invocation below fails |
| * when it is the first invocation on a debug property info enum from a stack frame. |
| * The workaround is to warm up the enumeration by first getting its count. |
| */ |
| ULONG count = 0; |
| enumPropertyInfo->GetCount(&count); |
| |
| Value children; |
| children.setType(TYPE_OBJECT); |
| ULONG fetched; |
| do { |
| DebugPropertyInfo propertyInfo; |
| hr = enumPropertyInfo->Next(1, &propertyInfo, &fetched); |
| if (SUCCEEDED(hr) && fetched) { |
| Value* value_child = NULL; |
| IDebugStackFrame* stackFrame = object->stackFrame; |
| JSObject childObject; |
| childObject.debugProperty = propertyInfo.m_pDebugProp; |
| childObject.stackFrame = stackFrame; |
| if (createValueForObject(&childObject, false, &value_child)) { |
| if (value_child->getType() == TYPE_OBJECT) { |
| Value* value_type = value_child->getObjectValue(KEY_TYPE); |
| const wchar_t* type = value_type->getStringValue()->c_str(); |
| if (wcscmp(type, VALUE_OBJECT) == 0 || (wcscmp(type, VALUE_FUNCTION) == 0)) { |
| std::map<std::wstring, unsigned int> objects = object->children; |
| std::map<std::wstring, unsigned int>::iterator iterator = objects.find(propertyInfo.m_bstrName); |
| Value value_handle; |
| if (iterator != objects.end()) { |
| value_handle.setValue((double)iterator->second); |
| } else { |
| value_handle.setValue((double)m_nextObjectHandle); |
| JSObject* newObject = new JSObject(); |
| newObject->debugProperty = propertyInfo.m_pDebugProp; |
| newObject->debugProperty->AddRef(); |
| newObject->isObject = wcscmp(type, VALUE_OBJECT) == 0; |
| newObject->stackFrame = stackFrame; |
| newObject->stackFrame->AddRef(); |
| m_objects->insert(std::pair<unsigned int, JSObject*>(m_nextObjectHandle++, newObject)); |
| } |
| value_child->addObjectValue(KEY_HANDLE, &value_handle); |
| } |
| } |
| children.addObjectValue(propertyInfo.m_bstrName, value_child); |
| delete value_child; |
| } |
| } |
| } while (fetched); |
| result.addObjectValue(KEY_TYPE, &Value(VALUE_OBJECT)); |
| result.addObjectValue(KEY_VALUE, &children); |
| } |
| } |
| } |
| |
| Value* copy = NULL; |
| result.clone(©); |
| *_value = copy; |
| return true; |
| } |
| |
| bool CrossfireContext::createValueForScript(IDebugApplicationNode* node, bool includeSource, bool failIfEmpty, Value** _value) { |
| *_value = NULL; |
| |
| CComPtr<IDebugDocument> document = NULL; |
| HRESULT hr = node->GetDocument(&document); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.createValueForScript(): GetDocument() failed", hr); |
| return false; |
| } |
| |
| CComPtr<IDebugDocumentText> documentText = NULL; |
| hr = document->QueryInterface(IID_IDebugDocumentText, (void**)&documentText); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.createValueForScript(): QI(IDebugDocumentText) failed", hr); |
| return false; |
| } |
| |
| ULONG numLines = 0; |
| ULONG numChars = 0; |
| hr = documentText->GetSize(&numLines, &numChars); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.createValueForScript(): GetSize() failed", hr); |
| return false; |
| } |
| if (failIfEmpty && numChars == 0) { |
| return false; |
| } |
| |
| URL* url = NULL; |
| if (!getScriptUrl(node, &url)) { |
| Logger::error("CrossfireContext.createValueForScript(): unknown script"); |
| return false; |
| } |
| |
| Value* result = new Value(); |
| result->addObjectValue(KEY_URL, &Value(url->getString())); |
| result->addObjectValue(KEY_LINEOFFSET, &Value((double)0)); // TODO right? |
| result->addObjectValue(KEY_COLUMNOFFSET, &Value((double)0)); |
| result->addObjectValue(KEY_SOURCELENGTH, &Value((double)numChars)); |
| result->addObjectValue(KEY_LINECOUNT, &Value((double)numLines)); |
| if (wcsstr(url->getString(), VALUE_EVALCODE)) { |
| result->addObjectValue(KEY_TYPE, &Value(VALUE_EVALLEVEL)); // TODO right? |
| } else { |
| result->addObjectValue(KEY_TYPE, &Value(VALUE_TOPLEVEL)); // TODO right? |
| } |
| delete url; |
| |
| if (includeSource) { |
| wchar_t* sourceChars = new wchar_t[numChars + 1]; |
| ULONG charsRead = 0; |
| hr = documentText->GetText(0, sourceChars, NULL, &charsRead, numChars); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.createValueForScript(): GetText()[2] failed", hr); |
| return false; |
| } |
| sourceChars[numChars] = NULL; |
| result->addObjectValue(KEY_SOURCE, &Value(sourceChars)); |
| delete[] sourceChars; |
| } |
| |
| *_value = result; |
| return true; |
| } |
| |
| bool CrossfireContext::evaluate(IDebugStackFrame* stackFrame, wchar_t* expression, int flags, IDebugProperty** _result) { |
| *_result = NULL; |
| |
| CComPtr<IDebugExpressionContext> expressionContext = NULL; |
| HRESULT hr = stackFrame->QueryInterface(IID_IDebugExpressionContext, (void**)&expressionContext); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.evaluate(): QI(IDebugExpressionContext) failed", hr); |
| return false; |
| } |
| |
| CComPtr<IDebugExpression> parsedExpression = NULL; |
| hr = expressionContext->ParseLanguageText( |
| expression, |
| 10, |
| L"", |
| flags, |
| &parsedExpression); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.evaluate(): ParseLanguageText() failed", hr); |
| return false; |
| } |
| |
| hr = parsedExpression->Start(NULL); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.evaluate(): Start() failed", hr); |
| return false; |
| } |
| |
| int ms = 0; |
| while (ms < 3000) { |
| if (parsedExpression->QueryIsComplete() == S_OK) { |
| break; |
| } |
| ms += 10; |
| ::Sleep(10); |
| } |
| if (2000 <= ms) { |
| parsedExpression->Abort(); |
| Logger::error("CrossfireContext.evaluate(): Evaluation took too long"); |
| return false; |
| } |
| |
| HRESULT evalResult; |
| hr = parsedExpression->GetResultAsDebugProperty(&evalResult, _result); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.evaluate(): GetResultAsDebugProperty() failed", hr); |
| return false; |
| } |
| if (FAILED(evalResult)) { |
| Logger::error("CrossfireContext.evaluate(): evaluation of GetResultAsDebugProperty() failed", evalResult); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool CrossfireContext::evaluateAsync(IDebugStackFrame* stackFrame, wchar_t* expression, int flags, IJSEvalHandler* handler, void* data) { |
| CComPtr<IDebugExpressionContext> expressionContext = NULL; |
| HRESULT hr = stackFrame->QueryInterface(IID_IDebugExpressionContext, (void**)&expressionContext); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.evaluateAsync(): QI(IDebugExpressionContext) failed", hr); |
| return false; |
| } |
| |
| CComPtr<IDebugExpression> parsedExpression = NULL; |
| hr = expressionContext->ParseLanguageText( |
| expression, |
| 10, |
| L"", |
| flags, |
| &parsedExpression); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.evaluateAsync(): ParseLanguageText() failed", hr); |
| return false; |
| } |
| |
| CComObject<JSEvalCallback>* listener = NULL; |
| hr = CComObject<JSEvalCallback>::CreateInstance(&listener); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.evaluateAsync(): CreateInstance() failed", hr); |
| return false; |
| } |
| |
| listener->AddRef(); /* CComObject::CreateInstance gives initial ref count of 0 */ |
| m_asyncEvals->push_back(listener); |
| listener->start(parsedExpression, handler, data); |
| return true; |
| } |
| |
| bool CrossfireContext::getDebugApplication(IRemoteDebugApplication** _value) { |
| *_value = NULL; |
| |
| CComPtr<IRemoteDebugApplicationThread> thread = NULL; |
| if (!getDebugApplicationThread(&thread)) { |
| return false; |
| } |
| |
| HRESULT hr = thread->GetApplication(_value); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.getDebugApplication: GetApplication() failed", hr); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool CrossfireContext::getDebugApplicationThread(IRemoteDebugApplicationThread** _value) { |
| *_value = NULL; |
| |
| if (m_debugApplicationThread) { |
| m_debugApplicationThread->AddRef(); |
| *_value = m_debugApplicationThread; |
| return true; |
| } |
| |
| CComPtr<IMachineDebugManager> mdm; |
| HRESULT hr = mdm.CoCreateInstance(CLSID_MachineDebugManager, NULL, CLSCTX_ALL); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.getDebugApplicationThread(): CoCreateInstance(CLSID_MachineDebugManager) failed", hr); |
| return false; |
| } |
| |
| CComPtr<IEnumRemoteDebugApplications> applications; |
| hr = mdm->EnumApplications(&applications); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.getDebugApplicationThread(): EnumApplications() failed", hr); |
| return false; |
| } |
| |
| int failCounter = 0; |
| //Logger::log("CrossfireContext.getDebugApplicationThread(): starting loop, looking for", (int)m_threadId); |
| ULONG fetchedApps = 0; |
| do { |
| CComPtr<IRemoteDebugApplication> currentApplication; |
| hr = applications->Next(1, ¤tApplication, &fetchedApps); |
| if (FAILED(hr)) { |
| fetchedApps = 1; /* continue to try to enum more applications */ |
| failCounter++; |
| // Logger::log("CrossfireContext.getDebugApplicationThread(): Next()[1] failed, keep trying", hr); |
| } else if (fetchedApps) { |
| CComPtr<IEnumRemoteDebugApplicationThreads> threads; |
| hr = currentApplication->EnumThreads(&threads); |
| //if (FAILED(hr)) { |
| // Logger::log("CrossfireContext.getDebugApplicationThread(): EnumThreads() failed, keep trying"); |
| //} |
| if (SUCCEEDED(hr)) { |
| ULONG fetchedThreads = 0; |
| do { |
| CComPtr<IRemoteDebugApplicationThread> currentThread; |
| hr = threads->Next(1, ¤tThread, &fetchedThreads); |
| if (FAILED(hr)) { |
| fetchedThreads = 1; /* continue to try to enum more threads */ |
| //Logger::log("CrossfireContext.getDebugApplicationThread(): Next()[2] failed, keep trying"); |
| } else if (fetchedThreads) { |
| DWORD currentThreadId; |
| hr = currentThread->GetSystemThreadId(¤tThreadId); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.getDebugApplicationThread(): GetSystemThreadId() failed", hr); |
| } else if (m_threadId == currentThreadId) { |
| m_debugApplicationThread = currentThread; |
| m_debugApplicationThread->AddRef(); |
| break; |
| } |
| //else { |
| // Logger::log("CrossfireContext.getDebugApplicationThread(): they didn't match, got", (int)currentThreadId); |
| //} |
| } |
| } while (fetchedThreads); |
| } |
| } |
| } while (!m_debugApplicationThread && fetchedApps && failCounter < 20); |
| |
| if (!m_debugApplicationThread) { |
| Logger::log("CrossfireContext.getDebugApplicationThread() did not find the thread"); |
| return false; |
| } |
| |
| m_debugApplicationThread->AddRef(); |
| *_value = m_debugApplicationThread; |
| return true; |
| } |
| |
| IDebugApplicationNode* CrossfireContext::getLastInitializedScriptNode() { |
| return m_lastInitializedScriptNode; |
| } |
| |
| wchar_t* CrossfireContext::getName() { |
| return m_name; |
| } |
| |
| DWORD CrossfireContext::getProcessId() { |
| return m_processId; |
| } |
| |
| bool CrossfireContext::getScriptUrl(IDebugApplicationNode* node, URL** _value) { |
| *_value = NULL; |
| |
| if (!m_scriptNodes) { |
| return false; |
| } |
| |
| std::map<std::wstring, IDebugApplicationNode*>::iterator iterator = m_scriptNodes->begin(); |
| while (iterator != m_scriptNodes->end()) { |
| if (iterator->second == node) { |
| *_value = new URL((wchar_t*)iterator->first.c_str()); |
| return true; |
| } |
| iterator++; |
| } |
| return false; |
| } |
| |
| IDebugApplicationNode* CrossfireContext::getScriptNode(URL* url) { |
| if (!m_scriptNodes) { |
| return NULL; |
| } |
| |
| std::wstring string(url->getString()); |
| std::map<std::wstring, IDebugApplicationNode*>::iterator iterator = m_scriptNodes->find(string); |
| if (iterator == m_scriptNodes->end()) { |
| return NULL; |
| } |
| |
| return iterator->second; |
| } |
| |
| wchar_t* CrossfireContext::getUrl() { |
| return m_url; |
| } |
| |
| bool CrossfireContext::hookDebugger() { |
| if (m_debuggerHooked) { |
| return true; |
| } |
| |
| CComPtr<IRemoteDebugApplication> application = NULL; |
| if (!getDebugApplication(&application)) { |
| return false; |
| } |
| |
| CComPtr<IApplicationDebugger> currentDebugger = NULL; |
| HRESULT hr = application->GetDebugger(¤tDebugger); |
| if (SUCCEEDED(hr) && currentDebugger) { |
| Logger::log("CrossfireContext.hookDebugger(): the application already has a debugger"); |
| return false; |
| } |
| |
| IEDebugger* ieDebugger = static_cast<IEDebugger*>(m_debugger); |
| ieDebugger->setContext(this); |
| hr = application->ConnectDebugger(ieDebugger); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.hookDebugger(): ConnectDebugger() failed", hr); |
| ieDebugger->setContext(NULL); |
| return false; |
| } |
| |
| Logger::log("CrossfireContext.hookDebugger(): debugger successfully hooked"); |
| m_debuggerHooked = true; |
| return true; |
| } |
| |
| void CrossfireContext::installBreakpoints(std::vector<Value*>* breakpoints) { |
| std::vector<Value*>::iterator iterator = breakpoints->begin(); |
| while (iterator != breakpoints->end()) { |
| Value* current = *iterator; |
| std::wstring* type = current->getObjectValue(KEY_TYPE)->getStringValue(); |
| if (type->compare(std::wstring(BPTYPE_LINE)) == 0) { |
| Value* value_location = current->getObjectValue(KEY_LOCATION); |
| Value* value_url = value_location->getObjectValue(KEY_LINE); |
| if (value_url && value_url->getType() == TYPE_STRING) { |
| |
| } |
| } |
| iterator++; |
| } |
| } |
| |
| bool CrossfireContext::performRequest(CrossfireRequest* request) { |
| if (!m_debuggerHooked) { |
| hookDebugger(); |
| } |
| wchar_t* command = request->getName(); |
| Value* arguments = request->getArguments(); |
| Value* responseBody = NULL; |
| wchar_t* message = NULL; |
| int code = CODE_OK; |
| |
| if (wcscmp(command, COMMAND_BACKTRACE) == 0) { |
| code = commandBacktrace(arguments, &responseBody, &message); |
| } else if (wcscmp(command, COMMAND_CONTINUE) == 0) { |
| code = commandContinue(arguments, &responseBody, &message); |
| } else if (wcscmp(command, COMMAND_EVALUATE) == 0) { |
| code = commandEvaluate(arguments, &responseBody, &message); |
| } else if (wcscmp(command, COMMAND_FRAME) == 0) { |
| code = commandFrame(arguments, &responseBody, &message); |
| } else if (wcscmp(command, COMMAND_LOOKUP) == 0) { |
| code = commandLookup(arguments, &responseBody, &message); |
| } else if (wcscmp(command, COMMAND_SCRIPTS) == 0) { |
| code = commandScripts(arguments, &responseBody, &message); |
| } else if (wcscmp(command, COMMAND_SCOPES) == 0) { |
| code = commandScopes(arguments, &responseBody, &message); |
| } else if (wcscmp(command, COMMAND_SUSPEND) == 0) { |
| code = commandSuspend(arguments, &responseBody, &message); |
| } else if (wcscmp(command, COMMAND_INSPECT) == 0) { |
| code = CODE_COMMAND_NOT_IMPLEMENTED; |
| } else { |
| return false; /* command not handled */ |
| } |
| |
| CrossfireResponse response; |
| response.setContextId(&std::wstring(m_name)); |
| response.setName(command); |
| response.setRequestSeq(request->getSeq()); |
| response.setRunning(m_running); |
| response.setCode(code); |
| response.setMessage(message); |
| if (message) { |
| free(message); |
| } |
| if (code == CODE_OK) { |
| response.setBody(responseBody); |
| } else { |
| Value emptyBody; |
| emptyBody.setType(TYPE_OBJECT); |
| response.setBody(&emptyBody); |
| } |
| if (responseBody) { |
| delete responseBody; |
| } |
| m_server->sendResponse(&response); |
| return true; |
| } |
| |
| bool CrossfireContext::registerScript(IDebugApplicationNode* applicationNode, bool recurse) { |
| CComBSTR value = NULL; |
| wchar_t* id = NULL; |
| URL url; |
| HRESULT hr = applicationNode->GetName(DOCUMENTNAMETYPE_URL, &value); |
| if (SUCCEEDED(hr)) { |
| /* don't register about:blank as a script */ |
| if (wcscmp(value.m_str, ABOUT_BLANK) == 0) { |
| return false; |
| } |
| url.setString(value.m_str); |
| } else { |
| /* |
| * Failure to get the URL indicates that the node represents something like a JScript |
| * block or eval code. For these cases get the node's title instead and prepend a |
| * scheme to make it url-like. |
| */ |
| hr = applicationNode->GetName(DOCUMENTNAMETYPE_TITLE, &value); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.registerScript(): GetName() failed", hr); |
| return false; |
| } |
| int length = wcslen(SCHEME_SCRIPT) + wcslen(value.m_str) + 2; |
| wchar_t* string = new wchar_t[length]; |
| ZeroMemory(string, length * sizeof(wchar_t)); |
| wcscat_s(string, length, SCHEME_SCRIPT); |
| wcscat_s(string, length, value.m_str); |
| wcscat_s(string, length, L"/"); |
| url.setString(string); |
| delete[] string; |
| } |
| |
| if (!url.isValid()) { |
| return false; |
| } |
| |
| if (!m_scriptNodes) { |
| m_scriptNodes = new std::map<std::wstring, IDebugApplicationNode*>; |
| } |
| |
| /* |
| * There could be another script with the same id already registered for id's that |
| * are not urls. In this case append a qualifier to the id to make it unique. |
| */ |
| int qualifierIndex = 1; |
| std::wstring key(url.getString()); |
| applicationNode->AddRef(); |
| while (true) { |
| if (m_scriptNodes->insert(std::pair<std::wstring, IDebugApplicationNode*>(key, applicationNode)).second) { |
| /* script was successfully inserted */ |
| break; |
| } |
| key.assign(url.getString()); |
| wchar_t qualifierString[4]; |
| _ltow_s(qualifierIndex++, qualifierString, 4, 10); /* trailing linebreak */ |
| key += qualifierString; |
| } |
| |
| if (recurse) { |
| CComPtr<IEnumDebugApplicationNodes> nodes = NULL; |
| hr = applicationNode->EnumChildren(&nodes); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.registerScript(): EnumChildren() failed", hr); |
| } else { |
| IDebugApplicationNode* current = NULL; |
| ULONG count = 0; |
| hr = nodes->Next(1, ¤t, &count); |
| while (SUCCEEDED(hr) && count) { |
| registerScript(current, true); |
| current->Release(); |
| hr = nodes->Next(1, ¤t, &count); |
| } |
| } |
| } |
| |
| return true; |
| } |
| |
| bool CrossfireContext::resumeFromBreak(BREAKRESUMEACTION action) { |
| CComPtr<IRemoteDebugApplicationThread> thread = NULL; |
| HRESULT hr = getDebugApplicationThread(&thread); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.resumeFromBreak(): getDebugApplicationThread() failed", hr); |
| return false; |
| } |
| CComPtr<IRemoteDebugApplication> application = NULL; |
| hr = thread->GetApplication(&application); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.resumeFromBreak(): GetApplication() failed", hr); |
| return false; |
| } |
| hr = application->ResumeFromBreakPoint(thread, action, ERRORRESUMEACTION_SkipErrorStatement); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.resumeFromBreak(): ResumeFromBreakPoint() failed", hr); |
| return false; |
| } |
| return true; |
| } |
| |
| bool CrossfireContext::scriptInitialized(IDebugApplicationNode *applicationNode, bool isFromAnotherContext) { |
| /* |
| * When navigating from one page to another, the first script node for the new |
| * page is initialized while the context for the old page is still active, and |
| * therefore becomes registered in the wrong context. The CrossfireServer works |
| * around this by re-initializing the script node in the new context where it |
| * belongs. This workaround is aided here by keeping a reference to the last |
| * script node that has been initialized from the document tree (but not from |
| * another context) in case it needs to be accessible to the CrossfireServer for |
| * re-initialization in a new context. |
| */ |
| if (m_lastInitializedScriptNode) { |
| m_lastInitializedScriptNode->Release(); |
| m_lastInitializedScriptNode = NULL; |
| } |
| if (!isFromAnotherContext) { |
| m_lastInitializedScriptNode = applicationNode; |
| m_lastInitializedScriptNode->AddRef(); |
| } |
| |
| registerScript(applicationNode, false); |
| scriptLoaded(applicationNode, true); |
| |
| /* |
| * Script loading is typically not complete when a script node is initialized, and |
| * there is no way to determine whether it is complete, so create a listener that |
| * will repeatedly invoke #scriptLoaded() as loading of the script progresses. |
| * |
| * This is not done for scripts from another context that are being re-initialized |
| * here because such scripts are advised when this context's debugger is created. |
| */ |
| if (!isFromAnotherContext) { |
| CComObject<PendingScriptLoad>* pendingScriptLoad = NULL; |
| HRESULT hr = CComObject<PendingScriptLoad>::CreateInstance(&pendingScriptLoad); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.scriptInitialized(): CreateInstance(CLSID_PendingScriptLoad) failed [1]", hr); |
| return false; |
| } |
| |
| if (pendingScriptLoad->attach(applicationNode, this)) { |
| applicationNode->AddRef(); |
| pendingScriptLoad->AddRef(); /* CComObject::CreateInstance gives initial ref count of 0 */ |
| m_pendingScriptLoads->insert(std::pair<IDebugApplicationNode*, PendingScriptLoad*>(applicationNode, pendingScriptLoad)); |
| } |
| } |
| |
| return true; |
| } |
| |
| void CrossfireContext::scriptLoaded(IDebugApplicationNode *applicationNode, bool sendScriptLoadEvent) { |
| /* set the script's breakpoints if possible */ |
| URL* url = NULL; |
| if (!getScriptUrl(applicationNode, &url)) { |
| Logger::error("CrossfireContext.scriptLoaded(): unknown script"); |
| return; |
| } |
| /* |
| * Incoming IBreakpointTarget method invocations should always be for this |
| * application node, so store it temporarily so that it's easily accessible, |
| * rather than repeatedly looking it up for each IBreakpointTarget invocation. |
| */ |
| CrossfireBPManager* bpManager = m_server->getBreakpointManager(); |
| m_currentScriptNode = applicationNode; |
| bpManager->setBreakpointsForScript(url, this); |
| m_currentScriptNode = NULL; |
| delete url; |
| |
| if (!sendScriptLoadEvent) { |
| return; |
| } |
| |
| Value* script = NULL; |
| if (!createValueForScript(applicationNode, false, true, &script)) { |
| /* the script's content has probably not loaded yet */ |
| return; |
| } |
| |
| CrossfireEvent onScriptEvent; |
| onScriptEvent.setName(EVENT_ONSCRIPT); |
| Value body; |
| body.addObjectValue(KEY_SCRIPT, script); |
| delete script; |
| onScriptEvent.setBody(&body); |
| sendEvent(&onScriptEvent); |
| } |
| |
| void CrossfireContext::sendEvent(CrossfireEvent* eventObj) { |
| eventObj->setContextId(&std::wstring(m_name)); |
| m_server->sendEvent(eventObj); |
| } |
| |
| bool CrossfireContext::unhookDebugger() { |
| if (!m_debuggerHooked) { |
| return true; |
| } |
| |
| CComPtr<IRemoteDebugApplication> application = NULL; |
| if (!getDebugApplication(&application)) { |
| return false; |
| } |
| HRESULT hr = application->DisconnectDebugger(); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.unhookDebugger(): DisconnectDebugger() failed", hr); |
| } |
| |
| static_cast<IEDebugger*>(m_debugger)->setContext(NULL); |
| return SUCCEEDED(hr); |
| } |
| |
| /* commands */ |
| |
| int CrossfireContext::commandBacktrace(Value* arguments, Value** _responseBody, wchar_t** _message) { |
| if (m_running) { |
| *_message = _wcsdup(L"'backtrace' request is only valid when execution is suspended"); |
| return CODE_INVALID_STATE; |
| } |
| |
| unsigned int fromFrame = 0; |
| Value* value_fromFrame = arguments->getObjectValue(KEY_FROMFRAME); |
| if (value_fromFrame) { |
| if (value_fromFrame->getType() != TYPE_NUMBER || (unsigned int)value_fromFrame->getNumberValue() < 0) { |
| *_message = _wcsdup(L"'backtrace' command has an invalid 'fromFrame' value"); |
| return CODE_INVALID_ARGUMENT; |
| } |
| fromFrame = (unsigned int)value_fromFrame->getNumberValue(); |
| } |
| |
| unsigned int toFrame = 99; |
| Value* value_toFrame = arguments->getObjectValue(KEY_TOFRAME); |
| if (value_toFrame) { |
| if (value_toFrame->getType() != TYPE_NUMBER || (unsigned int)value_toFrame->getNumberValue() < 0) { |
| *_message = _wcsdup(L"'backtrace' command has an invalid 'toFrame' value"); |
| return CODE_INVALID_ARGUMENT; |
| } |
| toFrame = (unsigned int)value_toFrame->getNumberValue(); |
| if (toFrame < fromFrame) { |
| *_message = _wcsdup(L"'backtrace' command has 'toFrame' value < 'fromFrame' value"); |
| return CODE_INVALID_ARGUMENT; |
| } |
| } |
| |
| bool includeScopes = true; |
| Value* value_includeScopes = arguments->getObjectValue(KEY_INCLUDESCOPES); |
| if (value_includeScopes) { |
| if (value_includeScopes->getType() != TYPE_BOOLEAN) { |
| *_message = _wcsdup(L"'backtrace' command has an invalid 'includeScopes' value"); |
| return CODE_INVALID_ARGUMENT; |
| } |
| includeScopes = value_includeScopes->getBooleanValue(); |
| } |
| |
| CComPtr<IRemoteDebugApplicationThread> applicationThread = NULL; |
| if (!getDebugApplicationThread(&applicationThread)) { |
| return CODE_UNEXPECTED_EXCEPTION; |
| } |
| |
| CComPtr<IEnumDebugStackFrames> stackFrames = NULL; |
| HRESULT hr = applicationThread->EnumStackFrames(&stackFrames); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.commandBacktrace(): EnumStackFrames() failed", hr); |
| return CODE_UNEXPECTED_EXCEPTION; |
| } |
| |
| if (fromFrame > 0) { |
| HRESULT hr = stackFrames->Skip(fromFrame); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.commandBacktrace(): Skip() failed", hr); |
| return CODE_COMMAND_FAILED; |
| } |
| } |
| |
| /* count the available frames, and adjust toFrame accordingly if needed */ |
| |
| Value framesArray; |
| framesArray.setType(TYPE_ARRAY); |
| unsigned int index = 0; |
| for (index = fromFrame; index <= toFrame; index++) { |
| ULONG fetched = 0; |
| DebugStackFrameDescriptor stackFrameDescriptor; |
| hr = stackFrames->Next(1, &stackFrameDescriptor, &fetched); |
| if (FAILED(hr) || fetched == 0) { |
| /* |
| * Have enumerated past the last stack frame, which is expected |
| * to happen if the toFrame value is not specified or is too large. |
| */ |
| break; |
| } |
| |
| Value* frame = NULL; |
| if (!createValueForFrame(stackFrameDescriptor.pdsf, index, includeScopes, &frame)) { |
| /* Attempting to access a frame without available information, break */ |
| break; |
| } |
| |
| framesArray.addArrayValue(frame); |
| delete frame; |
| } |
| |
| /* index now points to one frame beyond the last frame to be returned */ |
| |
| int totalFrameCount = index; |
| |
| /* |
| * If the frame with index toFrame was reached successfully then continue on to |
| * count any remaining frames in order to determine totalFrameCount. |
| */ |
| if (toFrame < index) { |
| while (true) { |
| ULONG fetched = 0; |
| DebugStackFrameDescriptor stackFrameDescriptor; |
| hr = stackFrames->Next(1, &stackFrameDescriptor, &fetched); |
| if (FAILED(hr) || fetched == 0) { |
| break; |
| } |
| totalFrameCount++; |
| } |
| } |
| |
| Value* result = new Value(); |
| result->addObjectValue(KEY_FRAMES, &framesArray); |
| result->addObjectValue(KEY_FROMFRAME, &Value((double)fromFrame)); |
| result->addObjectValue(KEY_TOFRAME, &Value((double)index - 1)); |
| result->addObjectValue(KEY_TOTALFRAMES, &Value((double)totalFrameCount)); |
| *_responseBody = result; |
| return CODE_OK; |
| } |
| |
| int CrossfireContext::commandContinue(Value* arguments, Value** _responseBody, wchar_t** _message) { |
| if (m_running) { |
| *_message = _wcsdup(L"'continue' request is only valid when execution is suspended"); |
| return CODE_INVALID_STATE; |
| } |
| |
| BREAKRESUMEACTION action; |
| Value* value_action = NULL; |
| if (arguments) { |
| value_action = arguments->getObjectValue(KEY_STEPACTION); |
| } |
| if (!value_action) { |
| action = BREAKRESUMEACTION_CONTINUE; |
| } else { |
| if (value_action->getType() != TYPE_STRING) { |
| *_message = _wcsdup(L"'continue' command has invalid 'stepaction' value"); |
| return CODE_INVALID_ARGUMENT; |
| } |
| std::wstring* actionString = value_action->getStringValue(); |
| if (actionString->compare(VALUE_IN) == 0) { |
| action = BREAKRESUMEACTION_STEP_INTO; |
| } else if (actionString->compare(VALUE_NEXT) == 0) { |
| action = BREAKRESUMEACTION_STEP_OVER; |
| } else if (actionString->compare(VALUE_OUT) == 0) { |
| action = BREAKRESUMEACTION_STEP_OUT; |
| } else { |
| *_message = _wcsdup(L"'continue' command has invalid 'stepaction' value"); |
| return CODE_INVALID_ARGUMENT; |
| } |
| } |
| |
| CComPtr<IRemoteDebugApplicationThread> applicationThread = NULL; |
| if (!getDebugApplicationThread(&applicationThread)) { |
| return CODE_UNEXPECTED_EXCEPTION; |
| } |
| CComPtr<IRemoteDebugApplication> application = NULL; |
| HRESULT hr = applicationThread->GetApplication(&application); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.commandContinue(): GetApplication() failed", hr); |
| return CODE_UNEXPECTED_EXCEPTION; |
| } |
| |
| hr = application->ResumeFromBreakPoint(applicationThread, action, ERRORRESUMEACTION_SkipErrorStatement); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.commandContinue(): ResumeFromBreakPoint() failed", hr); |
| return CODE_UNEXPECTED_EXCEPTION; |
| } |
| |
| CrossfireEvent onResumeEvent; |
| onResumeEvent.setName(EVENT_ONRESUME); |
| Value body; |
| body.setType(TYPE_OBJECT); |
| onResumeEvent.setBody(&body); |
| sendEvent(&onResumeEvent); |
| |
| clearObjects(); |
| *_responseBody = new Value(); |
| (*_responseBody)->setType(TYPE_OBJECT); |
| m_running = true; |
| return CODE_OK; |
| } |
| |
| int CrossfireContext::commandEvaluate(Value* arguments, Value** _responseBody, wchar_t** _message) { |
| if (m_running) { |
| *_message = _wcsdup(L"'evaluate' request is only valid when execution is suspended"); |
| return CODE_INVALID_STATE; |
| } |
| |
| unsigned int frame = 0; |
| Value* value_frame = arguments->getObjectValue(KEY_FRAMEINDEX); |
| if (value_frame) { |
| if (value_frame->getType() != TYPE_NUMBER || (unsigned int)value_frame->getNumberValue() < 0) { |
| *_message = _wcsdup(L"'evaluate' command has invalid 'frame' value"); |
| return CODE_INVALID_ARGUMENT; |
| } |
| frame = (unsigned int)value_frame->getNumberValue(); |
| } |
| |
| Value* value_expression = arguments->getObjectValue(KEY_EXPRESSION); |
| if (!value_expression || value_expression->getType() != TYPE_STRING) { |
| *_message = _wcsdup(L"'evaluate' command does not have a valid 'expression' value"); |
| return CODE_INVALID_ARGUMENT; |
| } |
| |
| CComPtr<IRemoteDebugApplicationThread> applicationThread = NULL; |
| if (!getDebugApplicationThread(&applicationThread)) { |
| return CODE_UNEXPECTED_EXCEPTION; |
| } |
| |
| CComPtr<IEnumDebugStackFrames> stackFrames = NULL; |
| HRESULT hr = applicationThread->EnumStackFrames(&stackFrames); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.commandEvaluate(): EnumStackFrames() failed", hr); |
| return CODE_UNEXPECTED_EXCEPTION; |
| } |
| |
| if (frame > 0) { |
| hr = stackFrames->Skip(frame); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.commandEvaluate(): Skip() failed", hr); |
| return CODE_COMMAND_FAILED; |
| } |
| } |
| |
| ULONG fetched = 0; |
| DebugStackFrameDescriptor stackFrameDescriptor; |
| hr = stackFrames->Next(1, &stackFrameDescriptor, &fetched); |
| if (FAILED(hr) || fetched == 0) { |
| Logger::error("CrossfireContext.commandEvaluate(): Next() failed", hr); |
| return CODE_COMMAND_FAILED; |
| } |
| |
| IDebugStackFrame* stackFrame = stackFrameDescriptor.pdsf; |
| CComPtr<IDebugProperty> debugProperty = NULL; |
| if (!evaluate( |
| stackFrame, |
| (wchar_t *)value_expression->getStringValue()->c_str(), |
| DEBUG_TEXT_ISEXPRESSION | DEBUG_TEXT_RETURNVALUE | DEBUG_TEXT_ALLOWBREAKPOINTS | DEBUG_TEXT_ALLOWERRORREPORT, |
| &debugProperty)) { |
| return CODE_COMMAND_FAILED; |
| } |
| |
| JSObject newObject; |
| newObject.debugProperty = debugProperty; |
| newObject.stackFrame = stackFrame; |
| Value* value_result = NULL; |
| if (!createValueForObject(&newObject, true, &value_result)) { |
| return CODE_UNEXPECTED_EXCEPTION; |
| } |
| |
| Value* result = new Value(); |
| result->addObjectValue(KEY_RESULT, value_result); |
| delete value_result; |
| *_responseBody = result; |
| return CODE_OK; |
| } |
| |
| int CrossfireContext::commandFrame(Value* arguments, Value** _responseBody, wchar_t** _message) { |
| if (m_running) { |
| *_message = _wcsdup(L"'frame' request is only valid when execution is suspended"); |
| return CODE_INVALID_STATE; |
| } |
| |
| bool includeScopes = true; |
| Value* value_includeScopes = arguments->getObjectValue(KEY_INCLUDESCOPES); |
| if (value_includeScopes) { |
| if (value_includeScopes->getType() != TYPE_BOOLEAN) { |
| *_message = _wcsdup(L"'frame' command has an invalid 'includeScopes' value"); |
| return CODE_INVALID_ARGUMENT; |
| } |
| includeScopes = value_includeScopes->getBooleanValue(); |
| } |
| |
| unsigned int index = 0; |
| Value* value_index = arguments->getObjectValue(KEY_INDEX); |
| if (value_index) { |
| if (value_index->getType() != TYPE_NUMBER || (unsigned int)value_index->getNumberValue() < 0) { |
| *_message = _wcsdup(L"'frame' command has an invalid 'index' value"); |
| return CODE_INVALID_ARGUMENT; |
| } |
| index = (unsigned int)value_index->getNumberValue(); |
| } |
| |
| CComPtr<IRemoteDebugApplicationThread> applicationThread = NULL; |
| if (!getDebugApplicationThread(&applicationThread)) { |
| return false; |
| } |
| |
| CComPtr<IEnumDebugStackFrames> stackFrames = NULL; |
| HRESULT hr = applicationThread->EnumStackFrames(&stackFrames); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.commandFrame(): EnumStackFrames() failed", hr); |
| return CODE_UNEXPECTED_EXCEPTION; |
| } |
| |
| if (index > 0) { |
| hr = stackFrames->Skip(index); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.commandFrame(): Skip() failed", hr); |
| return CODE_COMMAND_FAILED; |
| } |
| } |
| |
| ULONG fetched = 0; |
| DebugStackFrameDescriptor stackFrameDescriptor; |
| hr = stackFrames->Next(1, &stackFrameDescriptor, &fetched); |
| if (FAILED(hr) || fetched == 0) { |
| Logger::error("CrossfireContext.commandFrame(): Next() failed", hr); |
| return CODE_COMMAND_FAILED; |
| } |
| |
| Value* frame = NULL; |
| if (!createValueForFrame(stackFrameDescriptor.pdsf, index, includeScopes, &frame)) { |
| return CODE_UNEXPECTED_EXCEPTION; |
| } |
| |
| Value* result = new Value(); |
| result->addObjectValue(KEY_FRAME, frame); |
| *_responseBody = result; |
| delete frame; |
| return CODE_OK; |
| } |
| |
| int CrossfireContext::commandLookup(Value* arguments, Value** _responseBody, wchar_t** _message) { |
| if (m_running) { |
| *_message = _wcsdup(L"'lookup' request is only valid when execution is suspended"); |
| return CODE_INVALID_STATE; |
| } |
| |
| Value* value_handles = arguments->getObjectValue(KEY_HANDLES); |
| if (!value_handles || value_handles->getType() != TYPE_ARRAY) { |
| *_message = _wcsdup(L"'lookup' command does not have a valid 'handles' value"); |
| return CODE_INVALID_ARGUMENT; |
| } |
| |
| bool includeSource = false; |
| Value* value_includeSource = arguments->getObjectValue(KEY_INCLUDESOURCE); |
| if (value_includeSource) { |
| if (value_includeSource->getType() != TYPE_BOOLEAN) { |
| *_message = _wcsdup(L"'lookup' command has an invalid 'includeSource' value"); |
| return CODE_INVALID_ARGUMENT; |
| } |
| includeSource = value_includeSource->getBooleanValue(); |
| } |
| |
| Value** handles = NULL; |
| value_handles->getArrayValues(&handles); |
| Value value_values; |
| value_values.setType(TYPE_ARRAY); |
| int index = 0; |
| Value* current = handles[index++]; |
| while (current) { |
| if (current->getType() == TYPE_NUMBER && current->getNumberValue() > 0) { |
| unsigned int handle = (unsigned int)current->getNumberValue(); |
| std::map<unsigned int, JSObject*>::iterator iterator = m_objects->find(handle); |
| if (iterator != m_objects->end()) { |
| JSObject* object = iterator->second; |
| Value* value_object = NULL; |
| if (createValueForObject(object, true, &value_object)) { |
| value_object->addObjectValue(KEY_HANDLE, current); |
| if (includeSource && !object->isObject) { |
| // TODO |
| // GUID iid; |
| // IIDFromString(OLESTR("{94E1E004-0672-423d-AD62-78783DEF1E76}"), &iid); |
| // CComPtr<IDebugProperty3> documentContext = NULL; |
| // HRESULT hr2 = object->debugProperty->QueryInterface(iid, (LPVOID*)&documentContext); |
| // if (SUCCEEDED(hr2)) { |
| // ULONG len = 0; |
| // hr2 = documentContext->GetStringCharLength(&len); |
| // if (SUCCEEDED(hr2)) { |
| // Logger::error("maybe?"); |
| // } |
| // } |
| // VARIANT info; |
| // object->debugProperty->GetExtendedInfo(1, &iid, &info); |
| // if (info.vt == VT_UNKNOWN) { |
| // CComPtr<IDebugDocumentContext2> documentContext = NULL; |
| // IIDFromString(OLESTR("{931516ad-b600-419c-88fc-dcf5183b5fa9}"), &iid); |
| // HRESULT hr = info.punkVal->QueryInterface(/*IID_IDebugDocumentContext2*/iid, (LPVOID*)&documentContext); |
| // if (FAILED(hr)) { |
| // Logger::error("CrossfireContext.commandLookup(): QI(IDebugDocumentContext2) failed", hr); |
| // } else { |
| // TEXT_POSITION start, end; |
| // hr = documentContext->GetSourceRange(&start, &end); |
| // if (FAILED(hr)) { |
| // Logger::error("CrossfireContext.commandLookup(): GetSourceRange() failed", hr); |
| // } |
| // } |
| // } |
| } |
| value_values.addArrayValue(value_object); |
| delete value_object; |
| } |
| } |
| } |
| current = handles[index++]; |
| } |
| delete[] handles; |
| |
| Value* result = new Value(); |
| result->addObjectValue(KEY_VALUES, &value_values); |
| *_responseBody = result; |
| return CODE_OK; |
| } |
| |
| int CrossfireContext::commandScopes(Value* arguments, Value** _responseBody, wchar_t** _message) { |
| if (m_running) { |
| *_message = _wcsdup(L"'scopes' request is only valid when execution is suspended"); |
| return CODE_INVALID_STATE; |
| } |
| |
| // TODO |
| return CODE_COMMAND_NOT_IMPLEMENTED; |
| } |
| |
| int CrossfireContext::commandScripts(Value* arguments, Value** _responseBody, wchar_t** _message) { |
| bool includeSource = false; |
| Value* value_includeSource = arguments->getObjectValue(KEY_INCLUDESOURCE); |
| if (value_includeSource) { |
| if (value_includeSource->getType() != TYPE_BOOLEAN) { |
| *_message = _wcsdup(L"'scripts' command has an invalid 'includeSource' value"); |
| return CODE_INVALID_ARGUMENT; |
| } |
| includeSource = value_includeSource->getBooleanValue(); |
| } |
| |
| Value* value_ids = arguments->getObjectValue(KEY_URLS); |
| Value** ids = NULL; |
| if (value_ids) { |
| if (value_ids->getType() != TYPE_ARRAY) { |
| *_message = _wcsdup(L"'scripts' command has an invalid 'urls' value"); |
| return CODE_INVALID_ARGUMENT; |
| } |
| value_ids->getArrayValues(&ids); |
| } |
| |
| /* |
| * If no scripts have attempted to register yet then this context may have been |
| * created after its page had already loaded (eg.- before a connection to the |
| * crossfire server was established). If the application has a tree of scripts |
| * then register them now, starting one level below the root. |
| */ |
| if (!m_scriptNodes) { |
| CComPtr<IRemoteDebugApplication> application = NULL; |
| if (getDebugApplication(&application)) { |
| CComPtr<IDebugApplicationNode> rootNode = NULL; |
| HRESULT hr = application->GetRootNode(&rootNode); |
| if (SUCCEEDED(hr)) { |
| CComPtr<IEnumDebugApplicationNodes> nodes = NULL; |
| hr = rootNode->EnumChildren(&nodes); |
| if (SUCCEEDED(hr)) { |
| IDebugApplicationNode* current = NULL; |
| ULONG count = 0; |
| hr = nodes->Next(1, ¤t, &count); |
| while (SUCCEEDED(hr) && count) { |
| registerScript(current, true); |
| current->Release(); |
| hr = nodes->Next(1, ¤t, &count); |
| } |
| } |
| } |
| } |
| } |
| |
| Value scriptsArray; |
| scriptsArray.setType(TYPE_ARRAY); |
| if (m_scriptNodes) { |
| std::map<std::wstring, IDebugApplicationNode*>::iterator iterator = m_scriptNodes->begin(); |
| while (iterator != m_scriptNodes->end()) { |
| Value* value = NULL; |
| IDebugApplicationNode* node = iterator->second; |
| bool include = false; |
| if (!ids) { |
| include = true; |
| } else { |
| URL* url = NULL; |
| getScriptUrl(node, &url); |
| int index = 0; |
| Value* current = ids[index++]; |
| while (current) { |
| if (current->getType() == TYPE_STRING) { |
| if (url->isEqual((wchar_t*)current->getStringValue()->c_str())) { |
| include = true; |
| break; |
| } |
| } |
| current = ids[index++]; |
| } |
| delete url; |
| } |
| if (include && createValueForScript(node, includeSource, false, &value)) { |
| scriptsArray.addArrayValue(value); |
| delete value; |
| } |
| iterator++; |
| } |
| } |
| |
| delete[] ids; |
| Value* result = new Value(); |
| result->addObjectValue(KEY_SCRIPTS, &scriptsArray); |
| *_responseBody = result; |
| return CODE_OK; |
| } |
| |
| int CrossfireContext::commandSuspend(Value* arguments, Value** _responseBody, wchar_t** _message) { |
| if (!m_running) { |
| *_message = _wcsdup(L"'suspend' request is not valid when execution is suspended"); |
| return CODE_INVALID_STATE; |
| } |
| |
| CComPtr<IRemoteDebugApplication> application = NULL; |
| if (!getDebugApplication(&application)) { |
| return CODE_UNEXPECTED_EXCEPTION; |
| } |
| |
| HRESULT hr = application->CauseBreak(); |
| if (FAILED(hr)) { |
| Logger::error("CrossfireContext.commandSuspend(): CauseBreak() failed", hr); |
| return CODE_UNEXPECTED_EXCEPTION; |
| } |
| |
| *_responseBody = new Value(); |
| (*_responseBody)->setType(TYPE_OBJECT); |
| return CODE_OK; |
| } |