diff --git a/src/IotjsDebugger.ts b/src/IotjsDebugger.ts index e04f491..f61d075 100644 --- a/src/IotjsDebugger.ts +++ b/src/IotjsDebugger.ts @@ -17,7 +17,7 @@ 'use strict'; import { - DebugSession, InitializedEvent, OutputEvent, Thread, Source, + DebugSession, Handles, InitializedEvent, OutputEvent, Thread, Scope, Source, StoppedEvent, StackFrame, TerminatedEvent, Event, ErrorDestination } from 'vscode-debugadapter'; import { DebugProtocol } from 'vscode-debugprotocol'; @@ -30,7 +30,7 @@ import { IAttachRequestArguments, ILaunchRequestArguments, SourceSendingOptions, import { JerryDebuggerClient, JerryDebuggerOptions } from './JerryDebuggerClient'; import { JerryDebugProtocolDelegate, JerryDebugProtocolHandler, JerryMessageScriptParsed, JerryEvalResult, - JerryMessageExceptionHit, JerryMessageBreakpointHit, JerryBacktraceResult + JerryMessageExceptionHit, JerryMessageBreakpointHit, JerryBacktraceResult, JerryScopeVariable, JerryScopeChain } from './JerryProtocolHandler'; import { EVAL_RESULT_SUBTYPE, CLIENT as CLIENT_PACKAGE } from './JerryProtocolConstants'; import { Breakpoint } from './JerryBreakpoints'; @@ -48,6 +48,7 @@ class IotjsDebugSession extends DebugSession { private _debuggerClient: JerryDebuggerClient; private _protocolhandler: JerryDebugProtocolHandler; private _sourceSendingOptions: SourceSendingOptions; + private _variableHandles = new Handles(); public constructor() { super(); @@ -451,6 +452,55 @@ class IotjsDebugSession extends DebugSession { } } + + protected async scopesRequest(response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments + ): Promise { + try { + const scopesArray: Array = await this._protocolhandler.requestScopes(); + const scopes = new Array(); + + for (const scope of scopesArray) { + scopes.push(new Scope(scope.name, + this._variableHandles.create(scope.variablesReference.toString()), + scope.expensive)); + } + + response.body = { + scopes: scopes + }; + + this.sendResponse(response); + } catch (error) { + this.log(error.message, LOG_LEVEL.ERROR); + this.sendErrorResponse(response, 0, (error).message); + } + } + + protected async variablesRequest(response: DebugProtocol.VariablesResponse, + args: DebugProtocol.VariablesArguments + ): Promise { + try { + const variables = new Array(); + const id = this._variableHandles.get(args.variablesReference); + const scopeVariables: Array = await this._protocolhandler.requestVariables(Number(id)); + + for (const variable of scopeVariables) { + variables.push({name: variable.name, + type: variable.type, + value: variable.value, + variablesReference: 0}); + } + + response.body = { + variables: variables + }; + this.sendResponse(response); + } catch (error) { + this.log(error.message, LOG_LEVEL.ERROR); + this.sendErrorResponse(response, 0, (error).message); + } + } + protected customRequest(command: string, response: DebugProtocol.Response, args: any): void { switch (command) { case 'sendSource': { diff --git a/src/JerryProtocolConstants.ts b/src/JerryProtocolConstants.ts index de9b753..41302f8 100644 --- a/src/JerryProtocolConstants.ts +++ b/src/JerryProtocolConstants.ts @@ -17,7 +17,7 @@ 'use strict'; // Expected JerryScript debugger protocol version. -export const JERRY_DEBUGGER_VERSION = 6; +export const JERRY_DEBUGGER_VERSION = 7; // Packages sent from the server to the client. export enum SERVER { @@ -47,7 +47,11 @@ export enum SERVER { JERRY_DEBUGGER_EVAL_RESULT_END = 24, JERRY_DEBUGGER_WAIT_FOR_SOURCE = 25, JERRY_DEBUGGER_OUTPUT_RESULT = 26, - JERRY_DEBUGGER_OUTPUT_RESULT_END = 27 + JERRY_DEBUGGER_OUTPUT_RESULT_END = 27, + JERRY_DEBUGGER_SCOPE_CHAIN = 28, + JERRY_DEBUGGER_SCOPE_CHAIN_END = 29, + JERRY_DEBUGGER_SCOPE_VARIABLES = 30, + JERRY_DEBUGGER_SCOPE_VARIABLES_END = 31 } // Subtypes of eval. @@ -91,5 +95,29 @@ export enum CLIENT { JERRY_DEBUGGER_FINISH = 15, JERRY_DEBUGGER_GET_BACKTRACE = 16, JERRY_DEBUGGER_EVAL = 17, - JERRY_DEBUGGER_EVAL_PART = 18 + JERRY_DEBUGGER_EVAL_PART = 18, + JERRY_DEBUGGER_GET_SCOPE_CHAIN = 19, + JERRY_DEBUGGER_GET_SCOPE_VARIABLES = 20 +} + +// Types of scope chain. +export enum JERRY_DEBUGGER_SCOPE_TYPE { + JERRY_DEBUGGER_SCOPE_WITH = 1, + JERRY_DEBUGGER_SCOPE_LOCAL = 2, + JERRY_DEBUGGER_SCOPE_CLOSURE = 3, + JERRY_DEBUGGER_SCOPE_GLOBAL = 4, + JERRY_DEBUGGER_SCOPE_NON_CLOSURE = 5 +} + +// Type of scope variables. +export enum JERRY_DEBUGGER_SCOPE_VARIABLES { + JERRY_DEBUGGER_VALUE_NONE = 1, + JERRY_DEBUGGER_VALUE_UNDEFINED = 2, + JERRY_DEBUGGER_VALUE_NULL = 3, + JERRY_DEBUGGER_VALUE_BOOLEAN = 4, + JERRY_DEBUGGER_VALUE_NUMBER = 5, + JERRY_DEBUGGER_VALUE_STRING = 6, + JERRY_DEBUGGER_VALUE_FUNCTION = 7, + JERRY_DEBUGGER_VALUE_ARRAY = 8, + JERRY_DEBUGGER_VALUE_OBJECT = 9 } diff --git a/src/JerryProtocolHandler.ts b/src/JerryProtocolHandler.ts index 1b64743..262668e 100644 --- a/src/JerryProtocolHandler.ts +++ b/src/JerryProtocolHandler.ts @@ -123,6 +123,18 @@ class PendingRequest { } } +export interface JerryScopeChain { + name: string; + variablesReference: number; + expensive: boolean; +} + +export interface JerryScopeVariable { + name: string; + type: string; + value: string; +} + // abstracts away the details of the protocol export class JerryDebugProtocolHandler { public debuggerClient?: JerryDebuggerClient; @@ -149,6 +161,8 @@ export class JerryDebugProtocolHandler { private functions: FunctionMap = {}; private newFunctions: FunctionMap = {}; private backtraceData: JerryBacktraceResult = {totalFrames : 0, backtrace: []}; + private scopeMessage?: Array = []; + private scopeVariableMessage?: string = ''; private nextScriptID: number = 1; private exceptionData?: Uint8Array; @@ -197,7 +211,11 @@ export class JerryDebugProtocolHandler { [SP.SERVER.JERRY_DEBUGGER_BACKTRACE_END]: this.onBacktrace, [SP.SERVER.JERRY_DEBUGGER_EVAL_RESULT]: this.onEvalResult, [SP.SERVER.JERRY_DEBUGGER_EVAL_RESULT_END]: this.onEvalResult, - [SP.SERVER.JERRY_DEBUGGER_WAIT_FOR_SOURCE]: this.onWaitForSource + [SP.SERVER.JERRY_DEBUGGER_WAIT_FOR_SOURCE]: this.onWaitForSource, + [SP.SERVER.JERRY_DEBUGGER_SCOPE_CHAIN]: this.onScopeChain, + [SP.SERVER.JERRY_DEBUGGER_SCOPE_CHAIN_END]: this.onScopeChainEnd, + [SP.SERVER.JERRY_DEBUGGER_SCOPE_VARIABLES]: this.onScopeVariables, + [SP.SERVER.JERRY_DEBUGGER_SCOPE_VARIABLES_END]: this.onScopeVariablesEnd }; this.requestQueue = []; @@ -586,6 +604,132 @@ export class JerryDebugProtocolHandler { return this.backtraceData; } + public onScopeChain(data: Uint8Array): void { + this.logPacket('ScopeChain'); + + for (let i = 1; i < data.byteLength; i++) { + this.scopeMessage.push(data[i]); + } + } + + public onScopeChainEnd(data: Uint8Array): Array { + this.logPacket('ScopeChainEnd'); + + for (let i = 1; i < data.byteLength; i++) { + this.scopeMessage.push(data[i]); + } + + const scopes: Array = []; + for (let i = 0; i < this.scopeMessage.length; i++) { + switch (this.scopeMessage[i]) { + case SP.JERRY_DEBUGGER_SCOPE_TYPE.JERRY_DEBUGGER_SCOPE_WITH: { + scopes.push({name: 'with', variablesReference: i, expensive: true}); + break; + } + case SP.JERRY_DEBUGGER_SCOPE_TYPE.JERRY_DEBUGGER_SCOPE_LOCAL: { + scopes.push({name: 'local', variablesReference: i, expensive: true}); + break; + } + case SP.JERRY_DEBUGGER_SCOPE_TYPE.JERRY_DEBUGGER_SCOPE_CLOSURE: { + scopes.push({name: 'closure', variablesReference: i, expensive: true}); + break; + } + case SP.JERRY_DEBUGGER_SCOPE_TYPE.JERRY_DEBUGGER_SCOPE_GLOBAL: { + scopes.push({name: 'global', variablesReference: i, expensive: true}); + break; + } + case SP.JERRY_DEBUGGER_SCOPE_TYPE.JERRY_DEBUGGER_SCOPE_NON_CLOSURE: { + scopes.push({name: 'catch', variablesReference: i, expensive: true}); + break; + } + default: { + throw new Error('Invalid scope chain type!'); + } + } + } + this.scopeMessage = []; + + return scopes; + } + + public onScopeVariables(data: Uint8Array): void { + this.logPacket('ScopeVariables'); + for (let i = 1; i < data.byteLength; i++) { + this.scopeVariableMessage += String.fromCharCode(data[i]); + } + } + + public onScopeVariablesEnd(data: Uint8Array): Array { + this.logPacket('ScopeVariablesEnd'); + + for (let i = 1; i < data.byteLength; i++) { + this.scopeVariableMessage += String.fromCharCode(data[i]); + } + + let buff_pos = 0; + const scopeVariablesArray: Array = []; + + while (buff_pos < this.scopeVariableMessage.length) { + let scopeVariable: JerryScopeVariable = {name: '', type: '', value: ''}; + + // Process name. + const name_length = this.scopeVariableMessage[buff_pos++].charCodeAt(0); + scopeVariable.name = this.scopeVariableMessage.substring(buff_pos, buff_pos + name_length); + + buff_pos += name_length; + + // Process type + const value_type: SP.JERRY_DEBUGGER_SCOPE_VARIABLES = this.scopeVariableMessage[buff_pos++].charCodeAt(0); + const value_length = this.scopeVariableMessage[buff_pos++].charCodeAt(0); + + scopeVariable.value = this.scopeVariableMessage.substring(buff_pos, buff_pos + value_length); + buff_pos += value_length; + + switch (value_type) { + case (SP.JERRY_DEBUGGER_SCOPE_VARIABLES.JERRY_DEBUGGER_VALUE_UNDEFINED): { + scopeVariable.type = 'undefined'; + break; + } + case (SP.JERRY_DEBUGGER_SCOPE_VARIABLES.JERRY_DEBUGGER_VALUE_NULL): { + scopeVariable.type = 'Null'; + break; + } + case (SP.JERRY_DEBUGGER_SCOPE_VARIABLES.JERRY_DEBUGGER_VALUE_BOOLEAN): { + scopeVariable.type = 'Boolean'; + break; + } + case (SP.JERRY_DEBUGGER_SCOPE_VARIABLES.JERRY_DEBUGGER_VALUE_NUMBER): { + scopeVariable.type = 'Number'; + break; + } + case (SP.JERRY_DEBUGGER_SCOPE_VARIABLES.JERRY_DEBUGGER_VALUE_STRING): { + scopeVariable.type = 'String'; + break; + } + case (SP.JERRY_DEBUGGER_SCOPE_VARIABLES.JERRY_DEBUGGER_VALUE_FUNCTION): { + scopeVariable.type = 'Function'; + break; + } + case (SP.JERRY_DEBUGGER_SCOPE_VARIABLES.JERRY_DEBUGGER_VALUE_ARRAY): { + scopeVariable.type = 'Array'; + scopeVariable.value = '[' + scopeVariable.value + ']'; + break; + } + case (SP.JERRY_DEBUGGER_SCOPE_VARIABLES.JERRY_DEBUGGER_VALUE_OBJECT): { + scopeVariable.type = 'Object'; + break; + } + default: { + throw new Error('Invalid scope variable type!'); + } + } + scopeVariablesArray.push(scopeVariable); + } + this.scopeVariableMessage = ''; + + return scopeVariablesArray; + } + public onEvalResult(data: Uint8Array): JerryEvalResult { this.logPacket('Eval Result'); @@ -776,6 +920,26 @@ export class JerryDebugProtocolHandler { 1])); } + public requestScopes(): Promise { + if (!this.lastBreakpointHit) { + return Promise.reject(new Error('scope chain not allowed while app running')); + } + + return this.sendRequest(encodeMessage(this.byteConfig, 'B', + [SP.CLIENT.JERRY_DEBUGGER_GET_SCOPE_CHAIN])); + } + + public requestVariables(level?: number): Promise { + if (!this.lastBreakpointHit) { + return Promise.reject(new Error('scope variables not allowed while app running')); + } + + return this.sendRequest(encodeMessage(this.byteConfig, 'BI', + [SP.CLIENT.JERRY_DEBUGGER_GET_SCOPE_VARIABLES, + level])); + + } + logPacket(description: string, ignorable: boolean = false) { // certain packets are ignored while evals are pending const ignored = (ignorable && this.evalsPending) ? 'Ignored: ' : '';