diff --git a/CHANGELOG.md b/CHANGELOG.md index cbb419ef..79604afb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,21 @@ Versioning]. ## Unreleased +### Added + +- fix missing output of variable type for structure ([@henryriley0]) +- add static variable support ([@henryriley0]) +- fix gdb check error when debug beginning ([@henryriley0]) +- fix implicitly type error in log message when build vsix ([@henryriley0]) +- check for configured debugger before start to provide a nicer error message + ([@GitMensch]) +- New `frameFilters` option for GDB that allows using custom frame filters, + enabled by default ([@JacquesLucke]) +- Suppress error for hover as the user may just play with the mouse ([@oltolm]). +- solve the problem of failed parsing of containers ([@henryriley0]) +- Fixes #421 - Added `registerLimit` option to specify the registers to + display - PR #444 ([@chenzhiy2001]) + ### Fixed - close invalid existing sockets from previous usage of this extension during @@ -22,6 +37,8 @@ Versioning]. - Added registers view ([@nomtats]) #242 - Enabled breakpoints inside `riscv` files ([@William-An]) #404 +[0.27.0]: https://github.com/WebFreak001/code-debug/compare/v0.26.1...v0.27.0 + ## [0.26.1] - 2022-12-31 ### Fixed @@ -233,6 +250,7 @@ Versioning]. [@abussy-aldebaran]: https://github.com/abussy-aldebaran [@anshulrouthu]: https://github.com/anshulrouthu [@brownts]: https://github.com/brownts +[@chenzhiy2001]: https://github.com/chenzhiy2001 [@coldencullen]: https://github.com/ColdenCullen [@eamousing]: https://github.com/eamousing [@evangrayk]: https://github.com/evangrayk @@ -240,6 +258,8 @@ Versioning]. [@gentoo90]: https://github.com/gentoo90 [@gitmensch]: https://github.com/GitMensch [@haronk]: https://github.com/HaronK +[@henryriley0]: https://github.com/HenryRiley0 +[@jacqueslucke]: https://github.com/JacquesLucke [@jelleroets]: https://github.com/JelleRoets [@karljs]: https://github.com/karljs [@kvinwang]: https://github.com/kvinwang @@ -252,7 +272,7 @@ Versioning]. [@reznikmm]: https://github.com/reznikmm [@simark]: https://github.com/simark [@webfreak001]: https://github.com/WebFreak001 -[@William-An]: https://github.com/William-An +[@william-an]: https://github.com/William-An [@yanpas]: https://github.com/Yanpas diff --git a/README.md b/README.md index b9bda930..603bdf8c 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,20 @@ that location prior to continuing execution. Note that stopping at the entry po attach configuration assumes that the entry point has not yet been entered at the time of attach, otherwise this will have no affect. +There is a Registers view in the VARIABLES view. As we fetch all registers at once, there can +be cases where a register that cannot be fetched causes the entire register request to fail, +corrupting the entire Registers output. If this happens, you might need to set the +`registerLimit` option to specify which registers you want the debugger to fetch +automatically. + +For example, to display only registers `rax` and `rip` in an x64 debug session, send the +command `-data-list-register-names` in the debug console of an active x64 debug session. +You will then receive a response containing an array starting with `["rax","rbx" ...]`. +In this array, the index of `rax` is 0 and `rip` is 16, so set the option as +`"registerLimit": "0 16"`. If you find the response text hard to navigate, you can paste +it into a browser's developer tools console and press enter to get an expandable response +object with array elements' indices explicitly displayed. + ### Attaching to existing processes Attaching to existing processes currently only works by specifying the PID in the @@ -213,4 +227,10 @@ differently based on whether the remote system is a POSIX or a Windows system. You may need to experiment to find the correct escaping necessary for the command to be sent to the debugger as you intended. +### LogMessage + +LogMessage will print a message in the debug console when breakpoint is hit. Expressions within {} are interpolated. + +![LogMessage](images/logMessage.gif) + ## [Issues](https://github.com/WebFreak001/code-debug) diff --git a/images/logMessage.gif b/images/logMessage.gif new file mode 100644 index 00000000..4bdb9ac3 Binary files /dev/null and b/images/logMessage.gif differ diff --git a/package-lock.json b/package-lock.json index 68d7bce7..aa8b7454 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "debug", - "version": "0.27.0", + "version": "0.27.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "debug", - "version": "0.27.0", + "version": "0.27.1", "license": "public domain", "dependencies": { "ssh2": "^1.6.0", @@ -17,6 +17,7 @@ "@istanbuljs/nyc-config-typescript": "^1.0.2", "@types/mocha": "^5.2.6", "@types/node": "^11.11.3", + "@types/ssh2": "^1.15.0", "@types/vscode": "^1.55.0", "@typescript-eslint/eslint-plugin": "^5.22.0", "@typescript-eslint/parser": "^5.22.0", @@ -30,7 +31,7 @@ "nyc": "^15.1.0", "prettier": "^2.6.2", "ts-node": "^10.8.0", - "typescript": "^3.9.3" + "typescript": "^4.3.2" }, "engines": { "vscode": "^1.55.0" @@ -972,6 +973,24 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "node_modules/@types/ssh2": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@types/ssh2/-/ssh2-1.15.0.tgz", + "integrity": "sha512-YcT8jP5F8NzWeevWvcyrrLB3zcneVjzYY9ZDSMAMboI+2zR1qYWFhwsyOFVzT7Jorn67vqxC0FRiw8YyG9P1ww==", + "dev": true, + "dependencies": { + "@types/node": "^18.11.18" + } + }, + "node_modules/@types/ssh2/node_modules/@types/node": { + "version": "18.19.24", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.24.tgz", + "integrity": "sha512-eghAz3gnbQbvnHqB+mgB2ZR3aH6RhdEmHGS48BnV75KceQPHqabkxKI0BbUSsqhqy2Ddhc2xD/VAR9ySZd57Lw==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, "node_modules/@types/vscode": { "version": "1.62.0", "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.62.0.tgz", @@ -5343,9 +5362,9 @@ } }, "node_modules/typescript": { - "version": "3.9.10", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.10.tgz", - "integrity": "sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.2.tgz", + "integrity": "sha512-zZ4hShnmnoVnAHpVHWpTcxdv7dWP60S2FsydQLV8V5PbS3FifjWFFRiHSWpDJahly88PRyV5teTSLoq4eG7mKw==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -5361,6 +5380,12 @@ "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", "dev": true }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, "node_modules/unique-string": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", @@ -6445,6 +6470,26 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "@types/ssh2": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@types/ssh2/-/ssh2-1.15.0.tgz", + "integrity": "sha512-YcT8jP5F8NzWeevWvcyrrLB3zcneVjzYY9ZDSMAMboI+2zR1qYWFhwsyOFVzT7Jorn67vqxC0FRiw8YyG9P1ww==", + "dev": true, + "requires": { + "@types/node": "^18.11.18" + }, + "dependencies": { + "@types/node": { + "version": "18.19.24", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.24.tgz", + "integrity": "sha512-eghAz3gnbQbvnHqB+mgB2ZR3aH6RhdEmHGS48BnV75KceQPHqabkxKI0BbUSsqhqy2Ddhc2xD/VAR9ySZd57Lw==", + "dev": true, + "requires": { + "undici-types": "~5.26.4" + } + } + } + }, "@types/vscode": { "version": "1.62.0", "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.62.0.tgz", @@ -9643,9 +9688,9 @@ } }, "typescript": { - "version": "3.9.10", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.10.tgz", - "integrity": "sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.2.tgz", + "integrity": "sha512-zZ4hShnmnoVnAHpVHWpTcxdv7dWP60S2FsydQLV8V5PbS3FifjWFFRiHSWpDJahly88PRyV5teTSLoq4eG7mKw==", "dev": true }, "uc.micro": { @@ -9654,6 +9699,12 @@ "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", "dev": true }, + "undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, "unique-string": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", diff --git a/package.json b/package.json index 1ddc57e2..107d5131 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "debug" ], "license": "public domain", - "version": "0.27.0", + "version": "0.27.1", "publisher": "webfreak", "icon": "images/icon.png", "engines": { @@ -187,13 +187,18 @@ "valuesFormatting": { "type": "string", "description": "Set the way of showing variable values. 'disabled' - show value as is, 'parseText' - parse debuggers output text into structure, 'prettyPrinters' - enable debuggers custom pretty-printers if there are any", - "default": "parseText", + "default": "prettyPrinters", "enum": [ "disabled", "parseText", "prettyPrinters" ] }, + "frameFilters": { + "type": "boolean", + "description": "Use frame filters registered in GDB", + "default": true + }, "printCalls": { "type": "boolean", "description": "Prints all GDB calls to the console", @@ -294,6 +299,11 @@ "description": "Content will be executed on the SSH host before the debugger call." } } + }, + "registerLimit": { + "type": "string", + "description": "List of numbers specifying the registers to display. An empty string indicates that the contents of all the registers must be returned.", + "default": "" } } }, @@ -322,6 +332,11 @@ "prettyPrinters" ] }, + "frameFilters": { + "type": "boolean", + "description": "Use frame filters registered in GDB", + "default": true + }, "printCalls": { "type": "boolean", "description": "Prints all GDB calls to the console", @@ -458,6 +473,11 @@ "description": "Content will be executed on the SSH host before the debugger call." } } + }, + "registerLimit": { + "type": "string", + "description": "List of numbers specifying the registers to display. An empty string indicates that the contents of all the registers must be returned.", + "default": "" } } } @@ -756,6 +776,11 @@ "description": "Content will be executed on the SSH host before the debugger call." } } + }, + "registerLimit": { + "type": "string", + "description": "List of numbers specifying the registers to display. An empty string indicates that the contents of all the registers must be returned.", + "default": "" } } }, @@ -836,6 +861,11 @@ ], "description": "Whether debugger should stop at application entry point", "default": false + }, + "registerLimit": { + "type": "string", + "description": "List of numbers specifying the registers to display. An empty string indicates that the contents of all the registers must be returned.", + "default": "" } } } @@ -982,6 +1012,11 @@ "type": "array", "description": "mago commands to run when starting to debug", "default": [] + }, + "registerLimit": { + "type": "string", + "description": "List of numbers specifying the registers to display. An empty string indicates that the contents of all the registers must be returned.", + "default": "" } } }, @@ -1047,6 +1082,11 @@ "type": "boolean", "description": "Whether debugger should stop after connecting to target", "default": false + }, + "registerLimit": { + "type": "string", + "description": "List of numbers specifying the registers to display. An empty string indicates that the contents of all the registers must be returned.", + "default": "" } } } @@ -1112,6 +1152,7 @@ "@istanbuljs/nyc-config-typescript": "^1.0.2", "@types/mocha": "^5.2.6", "@types/node": "^11.11.3", + "@types/ssh2": "^1.15.0", "@types/vscode": "^1.55.0", "@typescript-eslint/eslint-plugin": "^5.22.0", "@typescript-eslint/parser": "^5.22.0", @@ -1125,7 +1166,7 @@ "nyc": "^15.1.0", "prettier": "^2.6.2", "ts-node": "^10.8.0", - "typescript": "^3.9.3" + "typescript": "^4.3.2" }, "__metadata": { "id": "2fd22b8e-b3b8-4e7f-9a28-a5e2d1bdd0d4", diff --git a/src/backend/backend.ts b/src/backend/backend.ts index d47f89b3..e26bd369 100644 --- a/src/backend/backend.ts +++ b/src/backend/backend.ts @@ -4,11 +4,13 @@ import { DebugProtocol } from "vscode-debugprotocol/lib/debugProtocol"; export type ValuesFormattingMode = "disabled" | "parseText" | "prettyPrinters"; export interface Breakpoint { + id?:number; file?: string; line?: number; raw?: string; condition: string; countCondition?: string; + logMessage?: string; } export interface Thread { @@ -60,8 +62,8 @@ export interface IBackend { attach(cwd: string, executable: string, target: string, autorun: string[]): Thenable; connect(cwd: string, executable: string, target: string, autorun: string[]): Thenable; start(runToStart: boolean): Thenable; - stop(); - detach(); + stop(): void; + detach(): void; interrupt(): Thenable; continue(): Thenable; next(): Thenable; @@ -141,29 +143,25 @@ export interface MIError extends Error { readonly source: string; } export interface MIErrorConstructor { - new (message: string, source: string): MIError; + new(message: string, source: string): MIError; readonly prototype: MIError; } -export const MIError: MIErrorConstructor = class MIError { - readonly name: string; - readonly message: string; - readonly source: string; +export const MIError: MIErrorConstructor = class MIError { + private readonly _message: string; + private readonly _source: string; public constructor(message: string, source: string) { - Object.defineProperty(this, 'name', { - get: () => (this.constructor as any).name, - }); - Object.defineProperty(this, 'message', { - get: () => message, - }); - Object.defineProperty(this, 'source', { - get: () => source, - }); + this._message = message; + this._source = source; Error.captureStackTrace(this, this.constructor); } + get name() { return this.constructor.name; } + get message() { return this._message; } + get source() { return this._source; } + public toString() { - return `${this.message} (from ${this.source})`; + return `${this.message} (from ${this._source})`; } }; Object.setPrototypeOf(MIError as any, Object.create(Error.prototype)); diff --git a/src/backend/gdb_expansion.ts b/src/backend/gdb_expansion.ts index d2c83e41..ba81fb8c 100644 --- a/src/backend/gdb_expansion.ts +++ b/src/backend/gdb_expansion.ts @@ -1,7 +1,8 @@ +import { VariableObject } from "./backend"; import { MINode } from "./mi_parse"; -const resultRegex = /^([a-zA-Z_\-][a-zA-Z0-9_\-]*|\[\d+\])\s*=\s*/; -const variableRegex = /^[a-zA-Z_\-][a-zA-Z0-9_\-]*/; +const resultRegex = /^([a-zA-Z_\-][a-zA-Z0-9_\-<> :(),]*|\[\d+\])\s*=\s*/; +const variableRegex = /^[a-zA-Z_\-\'\(][a-zA-Z0-9_\-\|\>\ \\\'\)\:]*/; const errorRegex = /^\<.+?\>/; const referenceStringRegex = /^(0x[0-9a-fA-F]+\s*)"/; const referenceRegex = /^0x[0-9a-fA-F]+/; @@ -29,7 +30,7 @@ export function isExpandable(value: string): number { else return 0; } -export function expandValue(variableCreate: Function, value: string, root: string = "", extra: any = undefined): any { +export function expandValue(variableCreate: (arg: VariableObject | string, options?: any) => any, value: string, root: string = "", extra: any = undefined): any { const parseCString = () => { value = value.trim(); if (value[0] != '"' && value[0] != '\'') @@ -56,10 +57,10 @@ export function expandValue(variableCreate: Function, value: string, root: strin }; const stack = [root]; - let parseValue, parseCommaResult, parseCommaValue, parseResult, createValue; + let parseValue: () => any, parseCommaResult: (pushToStack: boolean) => any, parseCommaValue: () => any, parseResult: (pushToStack: boolean) => any, createValue: (name: string, val: any) => any; let variable = ""; - const getNamespace = (variable) => { + const getNamespace = (variable: string) => { let namespace = ""; let prefix = ""; stack.push(variable); @@ -104,14 +105,18 @@ export function expandValue(variableCreate: Function, value: string, root: strin const eqPos = value.indexOf("="); const newValPos1 = value.indexOf("{"); const newValPos2 = value.indexOf(","); + const newValPos3 = value.indexOf("}"); let newValPos = newValPos1; if (newValPos2 != -1 && newValPos2 < newValPos1) newValPos = newValPos2; - if (newValPos != -1 && eqPos > newValPos || eqPos == -1) { // is value list + if (newValPos != -1 && eqPos > newValPos || eqPos == -1 || eqPos > newValPos3 && newValPos2 != -1 || value.startsWith("std::")) { // is value list const values = []; stack.push("[0]"); let val = parseValue(); stack.pop(); + if(typeof val == "string" && val.endsWith('>')){ + val = val.substring(0, val.length - 2); + } values.push(createValue("[0]", val)); const remaining = value; let i = 0; @@ -190,17 +195,26 @@ export function expandValue(variableCreate: Function, value: string, root: strin return parseCString(); else if (value[0] == '{') return parseTupleOrList(); + else if(value.startsWith("std::")){ + const eqPos = value.indexOf("="); + value = value.substring(eqPos + 2); + return parseValue(); + } else return parsePrimitive(); }; parseResult = (pushToStack: boolean = false) => { - value = value.trim(); + value = value.replace(/^ 1 && !name.includes("anonymous union") && !name.includes(',')){ + name = tmpName[tmpName.length - 1]; + } if (pushToStack) stack.push(variable); const val = parseValue(); @@ -209,7 +223,7 @@ export function expandValue(variableCreate: Function, value: string, root: strin return createValue(name, val); }; - createValue = (name, val) => { + createValue = (name: string, val: any) => { let ref = 0; if (typeof val == "object") { ref = variableCreate(val); @@ -223,12 +237,15 @@ export function expandValue(variableCreate: Function, value: string, root: strin val = "Object@" + val; } } else if (typeof val == "string" && val.startsWith("@0x")) { - ref = variableCreate(getNamespace("*&" + name.substr)); + ref = variableCreate(getNamespace("*&" + name.substring(1))); val = "Ref" + val; } else if (typeof val == "string" && val.startsWith("<...>")) { ref = variableCreate(getNamespace(name)); val = "..."; } + value = value.trim(); + value = value.replace(/^, /, ""); + return { name: name, value: val, diff --git a/src/backend/mi2/mi2.ts b/src/backend/mi2/mi2.ts index e6411609..5b91e593 100644 --- a/src/backend/mi2/mi2.ts +++ b/src/backend/mi2/mi2.ts @@ -6,7 +6,7 @@ import * as linuxTerm from '../linux/console'; import * as net from "net"; import * as fs from "fs"; import * as path from "path"; -import { Client } from "ssh2"; +import { Client, ClientChannel, ExecOptions } from "ssh2"; export function escape(str: string) { return str.replace(/\\/g, "\\\\").replace(/"/g, "\\\""); @@ -24,12 +24,56 @@ function couldBeOutput(line: string) { const trace = false; +class LogMessage { + protected logMsgVar = ""; + protected logMsgVarProcess = ""; + protected logMsgRplNum = 0; + protected logMsgRplItem: string[] = []; + protected logMsgMatch = /(^\$[0-9]*[\ ]*=[\ ]*)(.*)/; + protected logReplaceTest = /{([^}]*)}/g; + public logMsgBrkList: Breakpoint[] = []; + + logMsgOutput(record:any){ + if ((record.type === 'console')) { + if(record.content.startsWith("$")){ + const content = record.content; + const variableMatch = this.logMsgMatch.exec(content); + if (variableMatch) { + const value = content.substr(variableMatch[1].length).trim(); + this.logMsgRplItem.push(value); + this.logMsgRplNum--; + if(this.logMsgRplNum == 0){ + for(let i = 0; i < this.logMsgRplItem.length; i++){ + this.logMsgVarProcess = this.logMsgVarProcess.replace("placeHolderForVariable", this.logMsgRplItem[i]); + } + return "Log Message:" + this.logMsgVarProcess; + } + } + } + return undefined; + } + } + + logMsgProcess(parsed:MINode){ + this.logMsgBrkList.forEach((brk)=>{ + if(parsed.outOfBandRecord[0].output[0][1] == "breakpoint-hit" && parsed.outOfBandRecord[0].output[2][1] == brk.id){ + this.logMsgVar = brk?.logMessage; + const matches = this.logMsgVar.match(this.logReplaceTest); + const count = matches ? matches.length : 0; + this.logMsgRplNum = count; + this.logMsgVarProcess = this.logMsgVar.replace(this.logReplaceTest, "placeHolderForVariable"); + this.logMsgRplItem = []; + } + }); + } +} + export class MI2 extends EventEmitter implements IBackend { constructor(public application: string, public preargs: string[], public extraargs: string[], procEnv: any, public extraCommands: string[] = []) { super(); if (procEnv) { - const env = {}; + const env: { [key: string]: string } = {}; // Duplicate process.env so we don't override it for (const key in process.env) if (process.env.hasOwnProperty(key)) @@ -47,6 +91,7 @@ export class MI2 extends EventEmitter implements IBackend { this.procEnv = env; } } + protected logMessage:LogMessage = new LogMessage; load(cwd: string, target: string, procArgs: string, separateConsole: string, autorun: string[]): Thenable { if (!path.isAbsolute(target)) @@ -57,8 +102,8 @@ export class MI2 extends EventEmitter implements IBackend { this.process = ChildProcess.spawn(this.application, args, { cwd: cwd, env: this.procEnv }); this.process.stdout.on("data", this.stdout.bind(this)); this.process.stderr.on("data", this.stderr.bind(this)); - this.process.on("exit", (() => { this.emit("quit"); }).bind(this)); - this.process.on("error", ((err) => { this.emit("launcherror", err); }).bind(this)); + this.process.on("exit", () => this.emit("quit")); + this.process.on("error", err => this.emit("launcherror", err)); const promises = this.initCommands(target, cwd); if (procArgs && procArgs.length) promises.push(this.sendCommand("exec-arguments " + procArgs)); @@ -137,7 +182,7 @@ export class MI2 extends EventEmitter implements IBackend { this.sshConn.on("ready", () => { this.log("stdout", "Running " + this.application + " over ssh..."); - const execArgs: any = {}; + const execArgs: ExecOptions = {}; if (args.forwardX11) { execArgs.x11 = { single: false, @@ -150,7 +195,7 @@ export class MI2 extends EventEmitter implements IBackend { if (err) { this.log("stderr", "Could not run " + this.application + "(" + sshCMD + ") over ssh!"); if (err === undefined) { - err = ""; + err = new Error(""); } this.log("stderr", err.toString()); this.emit("quit"); @@ -161,10 +206,10 @@ export class MI2 extends EventEmitter implements IBackend { this.stream = stream; stream.on("data", this.stdout.bind(this)); stream.stderr.on("data", this.stderr.bind(this)); - stream.on("exit", (() => { + stream.on("exit", () => { this.emit("quit"); this.sshConn.end(); - }).bind(this)); + }); const promises = this.initCommands(target, cwd, attach); promises.push(this.sendCommand("environment-cd \"" + escape(cwd) + "\"")); if (attach) { @@ -181,7 +226,7 @@ export class MI2 extends EventEmitter implements IBackend { }).on("error", (err) => { this.log("stderr", "Error running " + this.application + " over ssh!"); if (err === undefined) { - err = ""; + err = new Error(""); } this.log("stderr", err.toString()); this.emit("quit"); @@ -219,6 +264,8 @@ export class MI2 extends EventEmitter implements IBackend { cmds.push(this.sendCommand("file-exec-and-symbols \"" + escape(target) + "\"")); if (this.prettyPrint) cmds.push(this.sendCommand("enable-pretty-printing")); + if (this.frameFilters) + cmds.push(this.sendCommand("enable-frame-filters")); for (const cmd of this.extraCommands) { cmds.push(this.sendCommand(cmd)); } @@ -235,8 +282,8 @@ export class MI2 extends EventEmitter implements IBackend { this.process = ChildProcess.spawn(this.application, args, { cwd: cwd, env: this.procEnv }); this.process.stdout.on("data", this.stdout.bind(this)); this.process.stderr.on("data", this.stderr.bind(this)); - this.process.on("exit", (() => { this.emit("quit"); }).bind(this)); - this.process.on("error", ((err) => { this.emit("launcherror", err); }).bind(this)); + this.process.on("exit", () => this.emit("quit")); + this.process.on("error", err => this.emit("launcherror", err)); const promises = this.initCommands(target, cwd, true); if (target.startsWith("extended-remote")) { promises.push(this.sendCommand("target-select " + target)); @@ -267,8 +314,8 @@ export class MI2 extends EventEmitter implements IBackend { this.process = ChildProcess.spawn(this.application, args, { cwd: cwd, env: this.procEnv }); this.process.stdout.on("data", this.stdout.bind(this)); this.process.stderr.on("data", this.stderr.bind(this)); - this.process.on("exit", (() => { this.emit("quit"); }).bind(this)); - this.process.on("error", ((err) => { this.emit("launcherror", err); }).bind(this)); + this.process.on("exit", () => this.emit("quit")); + this.process.on("error", err => this.emit("launcherror", err)); const promises = this.initCommands(target, cwd, true); promises.push(this.sendCommand("target-select remote " + target)); promises.push(...autorun.map(value => { return this.sendUserInput(value); })); @@ -279,7 +326,7 @@ export class MI2 extends EventEmitter implements IBackend { }); } - stdout(data) { + stdout(data: any) { if (trace) this.log("stderr", "stdout: " + data); if (typeof data == "string") @@ -298,7 +345,7 @@ export class MI2 extends EventEmitter implements IBackend { } } - stderr(data) { + stderr(data: any) { if (typeof data == "string") this.errbuf += data; else @@ -314,14 +361,14 @@ export class MI2 extends EventEmitter implements IBackend { } } - onOutputStderr(lines) { - lines = lines.split('\n'); + onOutputStderr(str: string) { + const lines = str.split('\n'); lines.forEach(line => { this.log("stderr", line); }); } - onOutputPartial(line) { + onOutputPartial(line: string) { if (couldBeOutput(line)) { this.logNoNewLine("stdout", line); return true; @@ -329,8 +376,8 @@ export class MI2 extends EventEmitter implements IBackend { return false; } - onOutput(lines) { - lines = lines.split('\n'); + onOutput(str: string) { + const lines = str.split('\n'); lines.forEach(line => { if (couldBeOutput(line)) { if (!gdbMatch.exec(line)) @@ -354,6 +401,10 @@ export class MI2 extends EventEmitter implements IBackend { parsed.outOfBandRecord.forEach(record => { if (record.isStream) { this.log(record.type, record.content); + const logOutput = this.logMessage.logMsgOutput(record); + if(logOutput){ + this.log("console", logOutput); + } } else { if (record.type == "exec") { this.emit("exec-async-output", parsed); @@ -373,6 +424,7 @@ export class MI2 extends EventEmitter implements IBackend { switch (reason) { case "breakpoint-hit": this.emit("breakpoint", parsed); + this.logMessage.logMsgProcess(parsed); break; case "watchpoint-trigger": case "read-watchpoint-trigger": @@ -391,13 +443,13 @@ export class MI2 extends EventEmitter implements IBackend { case "solib-event": case "syscall-entry": case "syscall-return": - // TODO: inform the user + // TODO: inform the user this.emit("step-end", parsed); break; case "fork": case "vfork": case "exec": - // TODO: inform the user, possibly add second inferior + // TODO: inform the user, possibly add second inferior this.emit("step-end", parsed); break; case "signal-received": @@ -563,19 +615,35 @@ export class MI2 extends EventEmitter implements IBackend { loadBreakPoints(breakpoints: Breakpoint[]): Thenable<[boolean, Breakpoint][]> { if (trace) this.log("stderr", "loadBreakPoints"); - const promisses = []; + const promisses: Thenable<[boolean, Breakpoint]>[] = []; breakpoints.forEach(breakpoint => { promisses.push(this.addBreakPoint(breakpoint)); }); return Promise.all(promisses); } - setBreakPointCondition(bkptNum, condition): Thenable { + setBreakPointCondition(bkptNum: number, condition: string): Thenable { if (trace) this.log("stderr", "setBreakPointCondition"); return this.sendCommand("break-condition " + bkptNum + " " + condition); } + setLogPoint(bkptNum:number, command:string): Thenable { + const regex = /{([a-z0-9A-Z-_\.\>\&\*\[\]]*)}/gm; + let m:RegExpExecArray; + let commands:string = ""; + + while ((m = regex.exec(command))) { + if (m.index === regex.lastIndex) { + regex.lastIndex++; + } + if (m[1]) { + commands += `\"print ${m[1]}\" `; + } + } + return this.sendCommand("break-commands " + bkptNum + " " + commands); + } + setEntryBreakPoint(entryPoint: string): Thenable { return this.sendCommand("break-insert -t -f " + entryPoint); } @@ -607,10 +675,12 @@ export class MI2 extends EventEmitter implements IBackend { if (result.resultRecords.resultClass == "done") { const bkptNum = parseInt(result.result("bkpt.number")); const newBrk = { + id: bkptNum, file: breakpoint.file ? breakpoint.file : result.result("bkpt.file"), raw: breakpoint.raw, line: parseInt(result.result("bkpt.line")), - condition: breakpoint.condition + condition: breakpoint.condition, + logMessage: breakpoint?.logMessage, }; if (breakpoint.condition) { this.setBreakPointCondition(bkptNum, breakpoint.condition).then((result) => { @@ -621,6 +691,17 @@ export class MI2 extends EventEmitter implements IBackend { resolve([false, undefined]); } }, reject); + } else if (breakpoint.logMessage) { + this.setLogPoint(bkptNum, breakpoint.logMessage).then((result) => { + if (result.resultRecords.resultClass == "done") { + breakpoint.id = newBrk.id; + this.breakpoints.set(newBrk, bkptNum); + this.logMessage.logMsgBrkList.push(breakpoint); + resolve([true, newBrk]); + } else { + resolve([false, undefined]); + } + }, reject); } else { this.breakpoints.set(newBrk, bkptNum); resolve([true, newBrk]); @@ -651,7 +732,7 @@ export class MI2 extends EventEmitter implements IBackend { if (trace) this.log("stderr", "clearBreakPoints"); return new Promise((resolve, reject) => { - const promises = []; + const promises: Thenable[] = []; const breakpoints = this.breakpoints; this.breakpoints = new Map(); breakpoints.forEach((k, index) => { @@ -675,15 +756,16 @@ export class MI2 extends EventEmitter implements IBackend { const result = await this.sendCommand(command); const threads = result.result("threads"); const ret: Thread[] = []; + if (!Array.isArray(threads)) { // workaround for lldb-mi bug: `'^done,threads="[]"'` + return ret; + } return threads.map(element => { const ret: Thread = { id: parseInt(MINode.valueOf(element, "id")), - targetId: MINode.valueOf(element, "target-id") + targetId: MINode.valueOf(element, "target-id"), + name: MINode.valueOf(element, "name") || MINode.valueOf(element, "details") }; - ret.name = MINode.valueOf(element, "details") - || undefined; - return ret; }); } @@ -708,12 +790,17 @@ export class MI2 extends EventEmitter implements IBackend { const result = await this.sendCommand(["stack-list-frames"].concat(options).join(" ")); const stack = result.result("stack"); - return stack.map(element => { + return stack.map((element: any) => { const level = MINode.valueOf(element, "@frame.level"); const addr = MINode.valueOf(element, "@frame.addr"); const func = MINode.valueOf(element, "@frame.func"); const filename = MINode.valueOf(element, "@frame.file"); let file: string = MINode.valueOf(element, "@frame.fullname"); + if (!file) { + // Fallback to using `file` if `fullname` is not provided. + // GDB does this for some reason when frame filters are used. + file = MINode.valueOf(element, "@frame.file"); + } if (file) { if (this.isSSH) file = path.posix.normalize(file); @@ -796,7 +883,7 @@ export class MI2 extends EventEmitter implements IBackend { async getRegisterValues(): Promise { if (trace) this.log("stderr", "getRegisterValues"); - const result = await this.sendCommand("data-list-register-values N"); + const result = await this.sendCommand("data-list-register-values --skip-unavailable N " + this.registerLimit); const nodes = result.result('register-values'); if (!Array.isArray(nodes)) { throw new Error('Failed to retrieve register values.'); @@ -804,7 +891,7 @@ export class MI2 extends EventEmitter implements IBackend { const ret: RegisterValue[] = nodes.map(node => { const index = parseInt(MINode.valueOf(node, "number")); const value = MINode.valueOf(node, "value"); - return {index: index, value: value}; + return { index: index, value: value }; }); return ret; } @@ -832,10 +919,14 @@ export class MI2 extends EventEmitter implements IBackend { return await this.sendCommand(command); } - async varCreate(expression: string, name: string = "-", frame: string = "@"): Promise { + async varCreate(threadId: number, frameLevel: number, expression: string, name: string = "-", frame: string = "@"): Promise { if (trace) this.log("stderr", "varCreate"); - const res = await this.sendCommand(`var-create ${this.quote(name)} ${frame} "${expression}"`); + let miCommand = "var-create "; + if (threadId != 0) { + miCommand += `--thread ${threadId} --frame ${frameLevel}`; + } + const res = await this.sendCommand(`${miCommand} ${this.quote(name)} ${frame} "${expression}"`); return new VariableObject(res.result("")); } @@ -851,7 +942,7 @@ export class MI2 extends EventEmitter implements IBackend { //TODO: add `from` and `to` arguments const res = await this.sendCommand(`var-list-children --all-values ${this.quote(name)}`); const children = res.result("children") || []; - const omg: VariableObject[] = children.map(child => new VariableObject(child[1])); + const omg: VariableObject[] = children.map((child: any) => new VariableObject(child[1])); return omg; } @@ -928,10 +1019,12 @@ export class MI2 extends EventEmitter implements IBackend { } prettyPrint: boolean = true; + frameFilters: boolean = true; printCalls: boolean; debugOutput: boolean; features: string[]; public procEnv: any; + public registerLimit: string; protected isSSH: boolean; protected sshReady: boolean; protected currentToken: number = 1; @@ -940,6 +1033,6 @@ export class MI2 extends EventEmitter implements IBackend { protected buffer: string; protected errbuf: string; protected process: ChildProcess.ChildProcess; - protected stream; - protected sshConn; + protected stream: ClientChannel; + protected sshConn: Client; } diff --git a/src/backend/mi2/mi2lldb.ts b/src/backend/mi2/mi2lldb.ts index 44999144..600c7931 100644 --- a/src/backend/mi2/mi2lldb.ts +++ b/src/backend/mi2/mi2lldb.ts @@ -4,7 +4,7 @@ import * as ChildProcess from "child_process"; import * as path from "path"; export class MI2_LLDB extends MI2 { - protected initCommands(target: string, cwd: string, attach: boolean = false) { + protected override initCommands(target: string, cwd: string, attach: boolean = false) { // We need to account for the possibility of the path type used by the debugger being different // than the path type where the extension is running (e.g., SSH from Linux to Windows machine). // Since the CWD is expected to be an absolute path in the debugger's environment, we can test @@ -35,14 +35,14 @@ export class MI2_LLDB extends MI2 { return cmds; } - attach(cwd: string, executable: string, target: string, autorun: string[]): Thenable { + override attach(cwd: string, executable: string, target: string, autorun: string[]): Thenable { return new Promise((resolve, reject) => { const args = this.preargs.concat(this.extraargs || []); this.process = ChildProcess.spawn(this.application, args, { cwd: cwd, env: this.procEnv }); this.process.stdout.on("data", this.stdout.bind(this)); this.process.stderr.on("data", this.stderr.bind(this)); - this.process.on("exit", (() => { this.emit("quit"); }).bind(this)); - this.process.on("error", ((err) => { this.emit("launcherror", err); }).bind(this)); + this.process.on("exit", () => this.emit("quit")); + this.process.on("error", err => this.emit("launcherror", err)); const promises = this.initCommands(target, cwd, true); promises.push(this.sendCommand("file-exec-and-symbols \"" + escape(executable) + "\"")); promises.push(this.sendCommand("target-attach " + target)); @@ -54,11 +54,11 @@ export class MI2_LLDB extends MI2 { }); } - setBreakPointCondition(bkptNum, condition): Thenable { + override setBreakPointCondition(bkptNum: number, condition: string): Thenable { return this.sendCommand("break-condition " + bkptNum + " \"" + escape(condition) + "\" 1"); } - goto(filename: string, line: number): Thenable { + override goto(filename: string, line: number): Thenable { return new Promise((resolve, reject) => { // LLDB parses the file differently than GDB... // GDB doesn't allow quoting only the file but only the whole argument diff --git a/src/backend/mi2/mi2mago.ts b/src/backend/mi2/mi2mago.ts index 78e9f254..fc63d6a6 100644 --- a/src/backend/mi2/mi2mago.ts +++ b/src/backend/mi2/mi2mago.ts @@ -3,14 +3,14 @@ import { Stack } from "../backend"; import { MINode } from "../mi_parse"; export class MI2_Mago extends MI2_LLDB { - getStack(startFrame: number, maxLevels: number, thread: number): Promise { + override getStack(startFrame: number, maxLevels: number, thread: number): Promise { return new Promise((resolve, reject) => { const command = "stack-list-frames"; this.sendCommand(command).then((result) => { const stack = result.resultRecords.results; const ret: Stack[] = []; - const remaining = []; - const addToStack = (element) => { + const remaining: any = []; + const addToStack = (element: any) => { const level = MINode.valueOf(element, "frame.level"); const addr = MINode.valueOf(element, "frame.addr"); const func = MINode.valueOf(element, "frame.func"); diff --git a/src/backend/mi_parse.ts b/src/backend/mi_parse.ts index 75a64ae3..f3f4cf8b 100644 --- a/src/backend/mi_parse.ts +++ b/src/backend/mi_parse.ts @@ -150,19 +150,19 @@ export function parseMI(output: string): MINode { */ let token = undefined; - const outOfBandRecord = []; + const outOfBandRecord: { isStream: boolean, type: string, asyncClass: string, output: [string, any][], content: string }[] = []; let resultRecords = undefined; const asyncRecordType = { "*": "exec", "+": "status", "=": "notify" - }; + } as const; const streamRecordType = { "~": "console", "@": "target", "&": "log" - }; + } as const; const parseCString = () => { if (output[0] != '"') @@ -192,7 +192,7 @@ export function parseMI(output: string): MINode { return str; }; - let parseValue, parseCommaResult, parseCommaValue, parseResult; + let parseValue: () => any, parseCommaResult: () => any, parseCommaValue: () => any, parseResult: () => any; const parseTupleOrList = () => { if (output[0] != '{' && output[0] != '[') @@ -274,9 +274,10 @@ export function parseMI(output: string): MINode { output = output.substring(classMatch[0].length); const asyncRecord = { isStream: false, - type: asyncRecordType[match[2]], + type: asyncRecordType[match[2] as keyof typeof asyncRecordType], asyncClass: classMatch[0], - output: [] + output: [] as any, + content: "" }; let result; while (result = parseCommaResult()) @@ -285,8 +286,10 @@ export function parseMI(output: string): MINode { } else if (match[3]) { const streamRecord = { isStream: true, - type: streamRecordType[match[3]], - content: parseCString() + type: streamRecordType[match[3] as keyof typeof streamRecordType], + content: parseCString(), + output: [] as [string, any][], + asyncClass: "" }; outOfBandRecord.push(streamRecord); } @@ -310,5 +313,5 @@ export function parseMI(output: string): MINode { output = output.replace(newlineRegex, ""); } - return new MINode(token, outOfBandRecord || [], resultRecords); + return new MINode(token, outOfBandRecord || [], resultRecords); } diff --git a/src/frontend/extension.ts b/src/frontend/extension.ts index 222be7b3..eaa65583 100644 --- a/src/frontend/extension.ts +++ b/src/frontend/extension.ts @@ -70,7 +70,7 @@ function examineMemory() { else return vscode.window.showErrorMessage("No debugging sessions available"); } - const pickedFile = (file) => { + const pickedFile = (file: string) => { vscode.window.showInputBox({ placeHolder: "Memory Location or Range", validateInput: range => getMemoryRange(range) === undefined ? "Range must either be in format 0xF00-0xF01, 0xF100+32 or 0xABC154" : "" }).then(range => { vscode.window.showTextDocument(vscode.Uri.parse("debugmemory://" + file + "?" + getMemoryRange(range))); }); @@ -90,7 +90,7 @@ class MemoryContentProvider implements vscode.TextDocumentContentProvider { provideTextDocumentContent(uri: vscode.Uri, token: vscode.CancellationToken): Thenable { return new Promise((resolve, reject) => { const conn = net.connect(path.join(os.tmpdir(), "code-debug-sockets", uri.authority.toLowerCase())); - let from, to; + let from: number, to: number; let highlightAt = -1; const splits = uri.query.split("&"); if (splits[0].split("=")[0] == "at") { diff --git a/src/gdb.ts b/src/gdb.ts index 3c0357bf..c1e178c0 100644 --- a/src/gdb.ts +++ b/src/gdb.ts @@ -17,8 +17,10 @@ export interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArgum stopAtEntry: boolean | string; ssh: SSHArguments; valuesFormatting: ValuesFormattingMode; + frameFilters: boolean; printCalls: boolean; showDevDebugOutput: boolean; + registerLimit: string; } export interface AttachRequestArguments extends DebugProtocol.AttachRequestArguments { @@ -35,12 +37,14 @@ export interface AttachRequestArguments extends DebugProtocol.AttachRequestArgum stopAtEntry: boolean | string; ssh: SSHArguments; valuesFormatting: ValuesFormattingMode; + frameFilters: boolean; printCalls: boolean; showDevDebugOutput: boolean; + registerLimit: string; } class GDBDebugSession extends MI2DebugSession { - protected initializeRequest(response: DebugProtocol.InitializeResponse, args: DebugProtocol.InitializeRequestArguments): void { + protected override initializeRequest(response: DebugProtocol.InitializeResponse, args: DebugProtocol.InitializeRequestArguments): void { response.body.supportsGotoTargetsRequest = true; response.body.supportsHitConditionalBreakpoints = true; response.body.supportsConfigurationDoneRequest = true; @@ -49,11 +53,17 @@ class GDBDebugSession extends MI2DebugSession { response.body.supportsEvaluateForHovers = true; response.body.supportsSetVariable = true; response.body.supportsStepBack = true; + response.body.supportsLogPoints = true; this.sendResponse(response); } - protected launchRequest(response: DebugProtocol.LaunchResponse, args: LaunchRequestArguments): void { - this.miDebugger = new MI2(args.gdbpath || "gdb", ["-q", "--interpreter=mi2"], args.debugger_args, args.env); + protected override launchRequest(response: DebugProtocol.LaunchResponse, args: LaunchRequestArguments): void { + const dbgCommand = args.gdbpath || "gdb"; + if (!this.checkCommand(dbgCommand)) { + this.sendErrorResponse(response, 104, `Configured debugger ${dbgCommand} not found.`); + return; + } + this.miDebugger = new MI2(dbgCommand, ["-q", "--interpreter=mi2"], args.debugger_args, args.env); this.setPathSubstitutions(args.pathSubstitutions); this.initDebugger(); this.quit = false; @@ -63,9 +73,11 @@ class GDBDebugSession extends MI2DebugSession { this.started = false; this.crashed = false; this.setValuesFormattingMode(args.valuesFormatting); + this.miDebugger.frameFilters = !!args.frameFilters; this.miDebugger.printCalls = !!args.printCalls; this.miDebugger.debugOutput = !!args.showDevDebugOutput; this.stopAtEntry = args.stopAtEntry; + this.miDebugger.registerLimit = args.registerLimit ?? ""; if (args.ssh !== undefined) { if (args.ssh.forwardX11 === undefined) args.ssh.forwardX11 = true; @@ -93,8 +105,13 @@ class GDBDebugSession extends MI2DebugSession { } } - protected attachRequest(response: DebugProtocol.AttachResponse, args: AttachRequestArguments): void { - this.miDebugger = new MI2(args.gdbpath || "gdb", ["-q", "--interpreter=mi2"], args.debugger_args, args.env); + protected override attachRequest(response: DebugProtocol.AttachResponse, args: AttachRequestArguments): void { + const dbgCommand = args.gdbpath || "gdb"; + if (!this.checkCommand(dbgCommand)) { + this.sendErrorResponse(response, 104, `Configured debugger ${dbgCommand} not found.`); + return; + } + this.miDebugger = new MI2(dbgCommand, ["-q", "--interpreter=mi2"], args.debugger_args, args.env); this.setPathSubstitutions(args.pathSubstitutions); this.initDebugger(); this.quit = false; @@ -102,9 +119,11 @@ class GDBDebugSession extends MI2DebugSession { this.initialRunCommand = args.stopAtConnect ? RunCommand.NONE : RunCommand.CONTINUE; this.isSSH = false; this.setValuesFormattingMode(args.valuesFormatting); + this.miDebugger.frameFilters = !!args.frameFilters; this.miDebugger.printCalls = !!args.printCalls; this.miDebugger.debugOutput = !!args.showDevDebugOutput; this.stopAtEntry = args.stopAtEntry; + this.miDebugger.registerLimit = args.registerLimit ?? ""; if (args.ssh !== undefined) { if (args.ssh.forwardX11 === undefined) args.ssh.forwardX11 = true; diff --git a/src/lldb.ts b/src/lldb.ts index 79b7e709..0d7bbd46 100644 --- a/src/lldb.ts +++ b/src/lldb.ts @@ -18,6 +18,7 @@ export interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArgum valuesFormatting: ValuesFormattingMode; printCalls: boolean; showDevDebugOutput: boolean; + registerLimit: string; } export interface AttachRequestArguments extends DebugProtocol.AttachRequestArguments { @@ -34,10 +35,11 @@ export interface AttachRequestArguments extends DebugProtocol.AttachRequestArgum valuesFormatting: ValuesFormattingMode; printCalls: boolean; showDevDebugOutput: boolean; + registerLimit: string; } class LLDBDebugSession extends MI2DebugSession { - protected initializeRequest(response: DebugProtocol.InitializeResponse, args: DebugProtocol.InitializeRequestArguments): void { + protected override initializeRequest(response: DebugProtocol.InitializeResponse, args: DebugProtocol.InitializeRequestArguments): void { response.body.supportsGotoTargetsRequest = true; response.body.supportsHitConditionalBreakpoints = true; response.body.supportsConfigurationDoneRequest = true; @@ -47,8 +49,13 @@ class LLDBDebugSession extends MI2DebugSession { this.sendResponse(response); } - protected launchRequest(response: DebugProtocol.LaunchResponse, args: LaunchRequestArguments): void { - this.miDebugger = new MI2_LLDB(args.lldbmipath || "lldb-mi", [], args.debugger_args, args.env); + protected override launchRequest(response: DebugProtocol.LaunchResponse, args: LaunchRequestArguments): void { + const dbgCommand = args.lldbmipath || "lldb-mi"; + if (!this.checkCommand(dbgCommand)) { + this.sendErrorResponse(response, 104, `Configured debugger ${dbgCommand} not found.`); + return; + } + this.miDebugger = new MI2_LLDB(dbgCommand, [], args.debugger_args, args.env); this.setPathSubstitutions(args.pathSubstitutions); this.initDebugger(); this.quit = false; @@ -61,6 +68,7 @@ class LLDBDebugSession extends MI2DebugSession { this.miDebugger.printCalls = !!args.printCalls; this.miDebugger.debugOutput = !!args.showDevDebugOutput; this.stopAtEntry = args.stopAtEntry; + this.miDebugger.registerLimit = args.registerLimit ?? ""; if (args.ssh !== undefined) { if (args.ssh.forwardX11 === undefined) args.ssh.forwardX11 = true; @@ -88,8 +96,13 @@ class LLDBDebugSession extends MI2DebugSession { } } - protected attachRequest(response: DebugProtocol.AttachResponse, args: AttachRequestArguments): void { - this.miDebugger = new MI2_LLDB(args.lldbmipath || "lldb-mi", [], args.debugger_args, args.env); + protected override attachRequest(response: DebugProtocol.AttachResponse, args: AttachRequestArguments): void { + const dbgCommand = args.lldbmipath || "lldb-mi"; + if (!this.checkCommand(dbgCommand)) { + this.sendErrorResponse(response, 104, `Configured debugger ${dbgCommand} not found.`); + return; + } + this.miDebugger = new MI2_LLDB(dbgCommand, [], args.debugger_args, args.env); this.setPathSubstitutions(args.pathSubstitutions); this.initDebugger(); this.quit = false; @@ -100,6 +113,7 @@ class LLDBDebugSession extends MI2DebugSession { this.miDebugger.printCalls = !!args.printCalls; this.miDebugger.debugOutput = !!args.showDevDebugOutput; this.stopAtEntry = args.stopAtEntry; + this.miDebugger.registerLimit = args.registerLimit ?? ""; this.miDebugger.attach(args.cwd, args.executable, args.target, args.autorun || []).then(() => { this.sendResponse(response); }, err => { diff --git a/src/mago.ts b/src/mago.ts index d9d8fd0d..20f7f2fe 100644 --- a/src/mago.ts +++ b/src/mago.ts @@ -15,6 +15,7 @@ export interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArgum valuesFormatting: ValuesFormattingMode; printCalls: boolean; showDevDebugOutput: boolean; + registerLimit: string; } export interface AttachRequestArguments extends DebugProtocol.AttachRequestArguments { @@ -29,6 +30,7 @@ export interface AttachRequestArguments extends DebugProtocol.AttachRequestArgum valuesFormatting: ValuesFormattingMode; printCalls: boolean; showDevDebugOutput: boolean; + registerLimit: string; } class MagoDebugSession extends MI2DebugSession { @@ -36,7 +38,7 @@ class MagoDebugSession extends MI2DebugSession { super(debuggerLinesStartAt1, isServer); } - protected initializeRequest(response: DebugProtocol.InitializeResponse, args: DebugProtocol.InitializeRequestArguments): void { + protected override initializeRequest(response: DebugProtocol.InitializeResponse, args: DebugProtocol.InitializeRequestArguments): void { response.body.supportsHitConditionalBreakpoints = true; response.body.supportsConfigurationDoneRequest = true; response.body.supportsConditionalBreakpoints = true; @@ -49,8 +51,13 @@ class MagoDebugSession extends MI2DebugSession { return 0; } - protected launchRequest(response: DebugProtocol.LaunchResponse, args: LaunchRequestArguments): void { - this.miDebugger = new MI2_Mago(args.magomipath || "mago-mi", ["-q"], args.debugger_args, args.env); + protected override launchRequest(response: DebugProtocol.LaunchResponse, args: LaunchRequestArguments): void { + const dbgCommand = args.magomipath || "mago-mi"; + if (!this.checkCommand(dbgCommand)) { + this.sendErrorResponse(response, 104, `Configured debugger ${dbgCommand} not found.`); + return; + } + this.miDebugger = new MI2_Mago(dbgCommand, ["-q"], args.debugger_args, args.env); this.initDebugger(); this.quit = false; this.attached = false; @@ -61,6 +68,7 @@ class MagoDebugSession extends MI2DebugSession { this.setValuesFormattingMode(args.valuesFormatting); this.miDebugger.printCalls = !!args.printCalls; this.miDebugger.debugOutput = !!args.showDevDebugOutput; + this.miDebugger.registerLimit = args.registerLimit ?? ""; this.miDebugger.load(args.cwd, args.target, args.arguments, undefined, args.autorun || []).then(() => { this.sendResponse(response); }, err => { @@ -68,8 +76,13 @@ class MagoDebugSession extends MI2DebugSession { }); } - protected attachRequest(response: DebugProtocol.AttachResponse, args: AttachRequestArguments): void { - this.miDebugger = new MI2_Mago(args.magomipath || "mago-mi", [], args.debugger_args, args.env); + protected override attachRequest(response: DebugProtocol.AttachResponse, args: AttachRequestArguments): void { + const dbgCommand = args.magomipath || "mago-mi"; + if (!this.checkCommand(dbgCommand)) { + this.sendErrorResponse(response, 104, `Configured debugger ${dbgCommand} not found.`); + return; + } + this.miDebugger = new MI2_Mago(dbgCommand, ["-q"], args.debugger_args, args.env); this.initDebugger(); this.quit = false; this.attached = true; @@ -78,6 +91,7 @@ class MagoDebugSession extends MI2DebugSession { this.setValuesFormattingMode(args.valuesFormatting); this.miDebugger.printCalls = !!args.printCalls; this.miDebugger.debugOutput = !!args.showDevDebugOutput; + this.miDebugger.registerLimit = args.registerLimit ?? ""; this.miDebugger.attach(args.cwd, args.executable, args.target, args.autorun || []).then(() => { this.sendResponse(response); }, err => { diff --git a/src/mibase.ts b/src/mibase.ts index e45ac9a9..274888e5 100644 --- a/src/mibase.ts +++ b/src/mibase.ts @@ -5,6 +5,7 @@ import { Breakpoint, IBackend, Variable, VariableObject, ValuesFormattingMode, M import { MINode } from './backend/mi_parse'; import { expandValue, isExpandable } from './backend/gdb_expansion'; import { MI2 } from './backend/mi2/mi2'; +import { execSync } from 'child_process'; import * as systemPath from "path"; import * as net from "net"; import * as os from "os"; @@ -12,15 +13,15 @@ import * as fs from "fs"; import { SourceFileMap } from "./source_file_map"; class ExtendedVariable { - constructor(public name, public options) { + constructor(public name: string, public options: { "arg": any }) { } } class VariableScope { - constructor (public readonly name: string, public readonly threadId: number, public readonly level: number) { + constructor(public readonly name: string, public readonly threadId: number, public readonly level: number) { } - public static variableName (handle: number, name: string): string { + public static variableName(handle: number, name: string): string { return `var_${handle}_${name}`; } } @@ -78,7 +79,7 @@ export class MI2DebugSession extends DebugSession { func = rawCmd.substring(0, spaceIndex); args = JSON.parse(rawCmd.substring(spaceIndex + 1)); } - Promise.resolve(this.miDebugger[func].apply(this.miDebugger, args)).then(data => { + Promise.resolve((this.miDebugger as any)[func].apply(this.miDebugger, args)).then(data => { c.write(data instanceof Object ? JSON.stringify(data).toString() : data.toString()); }); }); @@ -96,6 +97,17 @@ export class MI2DebugSession extends DebugSession { } } + // verifies that the specified command can be executed + protected checkCommand(debuggerName: string): boolean { + try { + const command = process.platform === 'win32' ? 'where' : 'command -v'; + execSync(`${command} ${debuggerName}`, { stdio: 'ignore' }); + return true; + } catch (error) { + return false; + } + } + protected setValuesFormattingMode(mode: ValuesFormattingMode) { switch (mode) { case "disabled": @@ -174,7 +186,7 @@ export class MI2DebugSession extends DebugSession { this.quitEvent(); } - protected disconnectRequest(response: DebugProtocol.DisconnectResponse, args: DebugProtocol.DisconnectArguments): void { + protected override disconnectRequest(response: DebugProtocol.DisconnectResponse, args: DebugProtocol.DisconnectArguments): void { if (this.attached) this.miDebugger.detach(); else @@ -184,7 +196,7 @@ export class MI2DebugSession extends DebugSession { this.sendResponse(response); } - protected async setVariableRequest(response: DebugProtocol.SetVariableResponse, args: DebugProtocol.SetVariableArguments): Promise { + protected override async setVariableRequest(response: DebugProtocol.SetVariableResponse, args: DebugProtocol.SetVariableArguments): Promise { try { if (this.useVarObjects) { let name = args.name; @@ -211,16 +223,16 @@ export class MI2DebugSession extends DebugSession { } } - protected setFunctionBreakPointsRequest(response: DebugProtocol.SetFunctionBreakpointsResponse, args: DebugProtocol.SetFunctionBreakpointsArguments): void { - const all = []; + protected override setFunctionBreakPointsRequest(response: DebugProtocol.SetFunctionBreakpointsResponse, args: DebugProtocol.SetFunctionBreakpointsArguments): void { + const all: Thenable<[boolean, Breakpoint]>[] = []; args.breakpoints.forEach(brk => { all.push(this.miDebugger.addBreakPoint({ raw: brk.name, condition: brk.condition, countCondition: brk.hitCondition })); }); Promise.all(all).then(brkpoints => { - const finalBrks = []; + const finalBrks: DebugProtocol.Breakpoint[] = []; brkpoints.forEach(brkp => { if (brkp[0]) - finalBrks.push({ line: brkp[1].line }); + finalBrks.push({ line: brkp[1].line, verified: true }); }); response.body = { breakpoints: finalBrks @@ -231,7 +243,7 @@ export class MI2DebugSession extends DebugSession { }); } - protected setBreakPointsRequest(response: DebugProtocol.SetBreakpointsResponse, args: DebugProtocol.SetBreakpointsArguments): void { + protected override setBreakPointsRequest(response: DebugProtocol.SetBreakpointsResponse, args: DebugProtocol.SetBreakpointsArguments): void { let path = args.source.path; if (this.isSSH) { // convert local path to ssh path @@ -239,10 +251,10 @@ export class MI2DebugSession extends DebugSession { } this.miDebugger.clearBreakPoints(path).then(() => { const all = args.breakpoints.map(brk => { - return this.miDebugger.addBreakPoint({ file: path, line: brk.line, condition: brk.condition, countCondition: brk.hitCondition }); + return this.miDebugger.addBreakPoint({ file: path, line: brk.line, condition: brk.condition, countCondition: brk.hitCondition, logMessage: brk.logMessage }); }); Promise.all(all).then(brkpoints => { - const finalBrks = []; + const finalBrks: DebugProtocol.Breakpoint[] = []; brkpoints.forEach(brkp => { // TODO: Currently all breakpoints returned are marked as verified, // which leads to verified breakpoints on a broken lldb. @@ -261,7 +273,7 @@ export class MI2DebugSession extends DebugSession { }); } - protected threadsRequest(response: DebugProtocol.ThreadsResponse): void { + protected override threadsRequest(response: DebugProtocol.ThreadsResponse): void { if (!this.miDebugger) { this.sendResponse(response); return; @@ -275,7 +287,11 @@ export class MI2DebugSession extends DebugSession { response.body.threads.push(new Thread(thread.id, thread.id + ":" + threadName)); } this.sendResponse(response); - }).catch(error => { + }).catch((error: MIError) => { + if (error.message === 'Selected thread is running.') { + this.sendResponse(response); + return; + } this.sendErrorResponse(response, 17, `Could not get threads: ${error}`); }); } @@ -288,7 +304,7 @@ export class MI2DebugSession extends DebugSession { return [frameId & 0xffff, frameId >> 16]; } - protected stackTraceRequest(response: DebugProtocol.StackTraceResponse, args: DebugProtocol.StackTraceArguments): void { + protected override stackTraceRequest(response: DebugProtocol.StackTraceResponse, args: DebugProtocol.StackTraceArguments): void { this.miDebugger.getStack(args.startFrame, args.levels, args.threadId).then(stack => { const ret: StackFrame[] = []; stack.forEach(element => { @@ -308,7 +324,7 @@ export class MI2DebugSession extends DebugSession { ret.push(new StackFrame( this.threadAndLevelToFrameId(args.threadId, element.level), - element.function + "@" + element.address, + element.function + (element.address ? "@" + element.address : ""), source, element.line, 0)); @@ -322,7 +338,7 @@ export class MI2DebugSession extends DebugSession { }); } - protected configurationDoneRequest(response: DebugProtocol.ConfigurationDoneResponse, args: DebugProtocol.ConfigurationDoneArguments): void { + protected override configurationDoneRequest(response: DebugProtocol.ConfigurationDoneResponse, args: DebugProtocol.ConfigurationDoneArguments): void { const promises: Thenable[] = []; let entryPoint: string | undefined = undefined; let runToStart: boolean = false; @@ -391,7 +407,7 @@ export class MI2DebugSession extends DebugSession { }); } - protected scopesRequest(response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments): void { + protected override scopesRequest(response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments): void { const scopes = new Array(); const [threadId, level] = this.frameIdToThreadAndLevel(args.frameId); @@ -418,13 +434,13 @@ export class MI2DebugSession extends DebugSession { this.sendResponse(response); } - protected async variablesRequest(response: DebugProtocol.VariablesResponse, args: DebugProtocol.VariablesArguments): Promise { + protected override async variablesRequest(response: DebugProtocol.VariablesResponse, args: DebugProtocol.VariablesArguments): Promise { const variables: DebugProtocol.Variable[] = []; const id: VariableScope | string | VariableObject | ExtendedVariable = this.variableHandles.get(args.variablesReference); - const createVariable = (arg, options?) => { + const createVariable = (arg: string | VariableObject, options?: any) => { if (options) - return this.variableHandles.create(new ExtendedVariable(arg, options)); + return this.variableHandles.create(new ExtendedVariable(typeof arg === 'string' ? arg : arg.name, options)); else return this.variableHandles.create(arg); }; @@ -461,7 +477,7 @@ export class MI2DebugSession extends DebugSession { try { const changes = await this.miDebugger.varUpdate(varObjName); const changelist = changes.result("changelist"); - changelist.forEach((change) => { + changelist.forEach((change: any) => { const name = MINode.valueOf(change, "name"); const vId = this.variableHandlesReverse[name]; const v = this.variableHandles.get(vId) as any; @@ -470,8 +486,8 @@ export class MI2DebugSession extends DebugSession { const varId = this.variableHandlesReverse[varObjName]; varObj = this.variableHandles.get(varId) as any; } catch (err) { - if (err instanceof MIError && err.message == "Variable object not found") { - varObj = await this.miDebugger.varCreate(variable.name, varObjName); + if (err instanceof MIError && (err.message == "Variable object not found" || err.message.endsWith("does not exist"))) { + varObj = await this.miDebugger.varCreate(id.threadId, id.level, variable.name, varObjName); const varId = findOrCreateVariable(varObj); varObj.exp = variable.name; varObj.id = varId; @@ -505,7 +521,7 @@ export class MI2DebugSession extends DebugSession { variables.push({ name: variable.name, type: variable.type, - value: "", + value: variable.type, variablesReference: createVariable(variable.name) }); } @@ -524,7 +540,14 @@ export class MI2DebugSession extends DebugSession { // TODO: this evaluates on an (effectively) unknown thread for multithreaded programs. const variable = await this.miDebugger.evalExpression(JSON.stringify(id), 0, 0); try { - let expanded = expandValue(createVariable, variable.result("value"), id, variable); + let variableValue = variable.result("value"); + const pattern = /'([^']*)' /g; + variableValue = variableValue.replace(pattern, (_: any, char: string, count: string) => { + const repeatCount = parseInt(count, 10) + 1; + const repeatedArray = Array(repeatCount).fill(char); + return `{${repeatedArray.map(item => `'${item}'`).join(', ')}}`; + }); + let expanded = expandValue(createVariable, variableValue, id, variable); if (!expanded) { this.sendErrorResponse(response, 2, `Could not expand variable`); } else { @@ -569,7 +592,7 @@ export class MI2DebugSession extends DebugSession { } else if (id instanceof ExtendedVariable) { const varReq = id; if (varReq.options.arg) { - const strArr = []; + const strArr: DebugProtocol.Variable[] = []; let argsPart = true; let arrIndex = 0; const submit = () => { @@ -636,7 +659,7 @@ export class MI2DebugSession extends DebugSession { } } - protected pauseRequest(response: DebugProtocol.ContinueResponse, args: DebugProtocol.ContinueArguments): void { + protected override pauseRequest(response: DebugProtocol.ContinueResponse, args: DebugProtocol.ContinueArguments): void { this.miDebugger.interrupt().then(done => { this.sendResponse(response); }, msg => { @@ -644,7 +667,7 @@ export class MI2DebugSession extends DebugSession { }); } - protected reverseContinueRequest(response: DebugProtocol.ReverseContinueResponse, args: DebugProtocol.ReverseContinueArguments): void { + protected override reverseContinueRequest(response: DebugProtocol.ReverseContinueResponse, args: DebugProtocol.ReverseContinueArguments): void { this.miDebugger.continue(true).then(done => { this.sendResponse(response); }, msg => { @@ -652,7 +675,7 @@ export class MI2DebugSession extends DebugSession { }); } - protected continueRequest(response: DebugProtocol.ContinueResponse, args: DebugProtocol.ContinueArguments): void { + protected override continueRequest(response: DebugProtocol.ContinueResponse, args: DebugProtocol.ContinueArguments): void { this.miDebugger.continue().then(done => { this.sendResponse(response); }, msg => { @@ -660,7 +683,7 @@ export class MI2DebugSession extends DebugSession { }); } - protected stepBackRequest(response: DebugProtocol.StepBackResponse, args: DebugProtocol.StepBackArguments): void { + protected override stepBackRequest(response: DebugProtocol.StepBackResponse, args: DebugProtocol.StepBackArguments): void { this.miDebugger.step(true).then(done => { this.sendResponse(response); }, msg => { @@ -668,7 +691,7 @@ export class MI2DebugSession extends DebugSession { }); } - protected stepInRequest(response: DebugProtocol.NextResponse, args: DebugProtocol.NextArguments): void { + protected override stepInRequest(response: DebugProtocol.NextResponse, args: DebugProtocol.NextArguments): void { this.miDebugger.step().then(done => { this.sendResponse(response); }, msg => { @@ -676,7 +699,7 @@ export class MI2DebugSession extends DebugSession { }); } - protected stepOutRequest(response: DebugProtocol.NextResponse, args: DebugProtocol.NextArguments): void { + protected override stepOutRequest(response: DebugProtocol.NextResponse, args: DebugProtocol.NextArguments): void { this.miDebugger.stepOut().then(done => { this.sendResponse(response); }, msg => { @@ -684,7 +707,7 @@ export class MI2DebugSession extends DebugSession { }); } - protected nextRequest(response: DebugProtocol.NextResponse, args: DebugProtocol.NextArguments): void { + protected override nextRequest(response: DebugProtocol.NextResponse, args: DebugProtocol.NextArguments): void { this.miDebugger.next().then(done => { this.sendResponse(response); }, msg => { @@ -692,7 +715,7 @@ export class MI2DebugSession extends DebugSession { }); } - protected evaluateRequest(response: DebugProtocol.EvaluateResponse, args: DebugProtocol.EvaluateArguments): void { + protected override evaluateRequest(response: DebugProtocol.EvaluateResponse, args: DebugProtocol.EvaluateArguments): void { const [threadId, level] = this.frameIdToThreadAndLevel(args.frameId); if (args.context == "watch" || args.context == "hover") { this.miDebugger.evalExpression(args.expression, threadId, level).then((res) => { @@ -702,7 +725,12 @@ export class MI2DebugSession extends DebugSession { }; this.sendResponse(response); }, msg => { - this.sendErrorResponse(response, 7, msg.toString()); + if (args.context == "hover") { + // suppress error for hover as the user may just play with the mouse + this.sendResponse(response); + } else { + this.sendErrorResponse(response, 7, msg.toString()); + } }); } else { this.miDebugger.sendUserInput(args.expression, threadId, level).then(output => { @@ -723,7 +751,7 @@ export class MI2DebugSession extends DebugSession { } } - protected gotoTargetsRequest(response: DebugProtocol.GotoTargetsResponse, args: DebugProtocol.GotoTargetsArguments): void { + protected override gotoTargetsRequest(response: DebugProtocol.GotoTargetsResponse, args: DebugProtocol.GotoTargetsArguments): void { const path: string = this.isSSH ? this.sourceFileMap.toRemotePath(args.source.path) : args.source.path; this.miDebugger.goto(path, args.line).then(done => { response.body = { @@ -731,7 +759,7 @@ export class MI2DebugSession extends DebugSession { id: 1, label: args.source.name, column: args.column, - line : args.line + line: args.line }] }; this.sendResponse(response); @@ -740,13 +768,13 @@ export class MI2DebugSession extends DebugSession { }); } - protected gotoRequest(response: DebugProtocol.GotoResponse, args: DebugProtocol.GotoArguments): void { + protected override gotoRequest(response: DebugProtocol.GotoResponse, args: DebugProtocol.GotoArguments): void { this.sendResponse(response); } protected setSourceFileMap(configMap: { [index: string]: string }, fallbackGDB: string, fallbackIDE: string): void { if (configMap === undefined) { - this.sourceFileMap = new SourceFileMap({[fallbackGDB]: fallbackIDE}); + this.sourceFileMap = new SourceFileMap({ [fallbackGDB]: fallbackIDE }); } else { this.sourceFileMap = new SourceFileMap(configMap, fallbackGDB); } @@ -754,7 +782,7 @@ export class MI2DebugSession extends DebugSession { } -function prettyStringArray(strings) { +function prettyStringArray(strings: any) { if (typeof strings == "object") { if (strings.length !== undefined) return strings.join(", "); diff --git a/src/test/unit/gdb_expansion.test.ts b/src/test/unit/gdb_expansion.test.ts index 9f00a8cc..a455b876 100644 --- a/src/test/unit/gdb_expansion.test.ts +++ b/src/test/unit/gdb_expansion.test.ts @@ -1,8 +1,9 @@ import * as assert from 'assert'; import { expandValue, isExpandable } from '../../backend/gdb_expansion'; +import { VariableObject } from '../../backend/backend'; suite("GDB Value Expansion", () => { - const variableCreate = (variable) => { return { expanded: variable }; }; + const variableCreate = (variable: string) => ({ expanded: variable }); test("Various values", () => { assert.strictEqual(isExpandable(`false`), 0); assert.strictEqual(expandValue(variableCreate, `false`), "false"); @@ -303,4 +304,92 @@ suite("GDB Value Expansion", () => { { name: "floatval2", value: "234.45", variablesReference: 0 } ]); }); + test("std container values", () => { + let node = `std::vector of length 4, capacity 6 = {1, 2, 3, 4}`; + let variables = expandValue(variableCreate, node); + assert.strictEqual(variables[3].value, `4`); + node = `std::deque with 7 elements = {0, 1, 2, 3, 4, 5, 6}`; + variables = expandValue(variableCreate, node); + assert.strictEqual(variables[6].value, `6`); + node = `std::__cxx11::list = {[0] = 6, [1] = 7, [2] = 8, [3] = 9, [4] = 10}`; + variables = expandValue(variableCreate, node); + assert.strictEqual(variables[4].value, `10`); + node = `std::vector of length 4, capacity 6 = {std::vector of length 3, capacity 3 = {1, 2, 3}, std::vector of length 3, capacity 3 = {4, 5, 6}, std::vector of length 3, capacity 3 = {7, 8, 9}, std::vector of length 4, capacity 4 = {1, 2, 3, 4}}`; + variables = expandValue(variableCreate, node); + assert.strictEqual(variables[3].variablesReference.expanded[3].value, `4`); + node = `std::deque with 3 elements = {std::deque with 2 elements = {10, 20}, std::deque with 2 elements = {30, 40}, std::deque with 7 elements = {0, 1, 2, 3, 4, 5, 6}}`; + variables = expandValue(variableCreate, node); + assert.strictEqual(variables[2].variablesReference.expanded[6].value, `6`); + node = `std::__cxx11::list = {[0] = std::__cxx11::list = {[0] = 11, [1] = 12}, [1] = std::__cxx11::list = {[0] = 13, [1] = 14}, [2] = std::__cxx11::list = {[0] = 6, [1] = 7, [2] = 8, [3] = 9, [4] = 10}}`; + variables = expandValue(variableCreate, node); + assert.strictEqual(variables[2].variablesReference.expanded[4].value, `10`); + node = `std::forward_list = {[0] = 0, [1] = 1, [2] = 2, [3] = 3}`; + variables = expandValue(variableCreate, node); + assert.strictEqual(variables[3].value, `3`); + node = `std::set with 5 elements = {[0] = 1, [1] = 2, [2] = 3, [3] = 4, [4] = 5}`; + variables = expandValue(variableCreate, node); + assert.strictEqual(variables[4].value, `5`); + node = `std::multiset with 6 elements = {[0] = 1, [1] = 2, [2] = 2, [3] = 2, [4] = 3, [5] = 4}`; + variables = expandValue(variableCreate, node); + assert.strictEqual(variables[5].value, `4`); + node = `std::map with 3 elements = {[1] = \"one\", [2] = \"two\", [3] = \"three\"}`; + variables = expandValue(variableCreate, node); + assert.strictEqual(variables[2].value, `"three"`); + node = `std::multimap with 3 elements = {[1] = \"one\", [2] = \"two\", [2] = \"another two\"}`; + variables = expandValue(variableCreate, node); + assert.strictEqual(variables[2].value, `"another two"`); + node = `std::unordered_set with 5 elements = {[0] = 5, [1] = 2, [2] = 3, [3] = 1, [4] = 4}`; + variables = expandValue(variableCreate, node); + assert.strictEqual(variables[4].value, `4`); + node = `std::unordered_multiset with 6 elements = {[0] = 1, [1] = 2, [2] = 2, [3] = 2, [4] = 3, [5] = 4}`; + variables = expandValue(variableCreate, node); + assert.strictEqual(variables[5].value, `4`); + node = `std::unordered_map with 3 elements = {[3] = \"three\", [1] = \"one\", [2] = \"two\"}`; + variables = expandValue(variableCreate, node); + assert.strictEqual(variables[2].value, `"two"`); + node = `std::unordered_multimap with 3 elements = {[2] = \"another two\", [2] = \"two\", [1] = \"one\"}`; + variables = expandValue(variableCreate, node); + assert.strictEqual(variables[2].value, `"one"`); + node = `std::stack wrapping: std::deque with 2 elements = {1, 2}`; + variables = expandValue(variableCreate, node); + assert.strictEqual(variables[1].value, `2`); + node = `std::queue wrapping: std::deque with 2 elements = {1, 2}`; + variables = expandValue(variableCreate, node); + assert.strictEqual(variables[1].value, `2`); + node = `std::priority_queue wrapping: std::vector of length 3, capacity 4 = {4, 1, 3}`; + variables = expandValue(variableCreate, node); + assert.strictEqual(variables[2].value, `3`); + node = `std::bitset = {[0] = 1, [2] = 1, [4] = 1, [6] = 1, [7] = 1}`; + variables = expandValue(variableCreate, node); + assert.strictEqual(variables[3].value, `1`); + node = `{_M_elems = {1, 2, 3}}`; + variables = expandValue(variableCreate, node); + assert.strictEqual(variables[0].variablesReference.expanded[2].value, `3`); + node = `std::unique_ptr = {get() = 0x5555555d7d20}`; + variables = expandValue(variableCreate, node); + assert.strictEqual(variables[0].name, `get()`); + assert.strictEqual(variables[0].value, `Object@*0x5555555d7d20`); + node = `std::unique_ptr = {get() = 0x5555555d7d40}`; + variables = expandValue(variableCreate, node); + assert.strictEqual(variables[0].name, `get()`); + assert.strictEqual(variables[0].value, `Object@*0x5555555d7d40`); + node = `std::shared_ptr (use count 1, weak count 1) = {get() = 0x5555555d7d70}`; + variables = expandValue(variableCreate, node); + assert.strictEqual(variables[0].name, `get()`); + assert.strictEqual(variables[0].value, `Object@*0x5555555d7d70`); + node = `{> = {static _S_alignment = 4, _M_i = 0}, }`; + variables = expandValue(variableCreate, node); + assert.strictEqual(variables[0].name, `std::__atomic_base>`); + assert.strictEqual(variables[0].variablesReference.expanded[1].name, `_M_i`); + assert.strictEqual(variables[0].variablesReference.expanded[1].value, `0`); + node = `{ = {_M_mutex = {__data = {__lock = 0, __count = 0, __owner = 0, __nusers = 0, __kind = 0, __spins = 0, __elision = 0, __list = {__prev = 0x0, __next = 0x0}}, __size = '\\000' , __align = 0}}, }`; + variables = expandValue(variableCreate, node); + assert.strictEqual(variables[0].variablesReference.expanded[0].variablesReference.expanded[0].variablesReference.expanded[7].variablesReference.expanded[1].name, `__next`); + assert.strictEqual(variables[0].variablesReference.expanded[0].variablesReference.expanded[0].variablesReference.expanded[7].variablesReference.expanded[1].value, ``); + node = `{static icase = , static nosubs = , static optimize = , static collate = , static ECMAScript = (unknown: 16), static basic = , static extended = , static awk = , static grep = , static egrep = , _M_flags = (unknown: 16), _M_loc = {static none = 0, static ctype = 1, static numeric = 2, static collate = 4, static time = 8, static monetary = 16, static messages = 32, static all = 63, _M_impl = 0x7ffff7fa8ce0, static _S_classic = , static _S_global = , static _S_categories = , static _S_once = , static _S_twinned_facets = }, _M_automaton = std::shared_ptr >> (use count 1, weak count 0) = {get() = 0x5555555d81b0}}`; + variables = expandValue(variableCreate, node); + assert.strictEqual(variables[12].name, `_M_automaton`); + assert.strictEqual(variables[12].variablesReference.expanded[0].value, `Object@*0x5555555d81b0`); + + }); }); diff --git a/tsconfig.json b/tsconfig.json index 915ff8fc..97824ed7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,7 +12,8 @@ { "name": "tslint-language-service" } - ] + ], + "noImplicitAny": true }, "exclude": [ "node_modules",