diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..9afcdee --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,12 @@ +If submitting a bug please make sure + +- [ ] If you are using gdb + - [ ] `gdb --version` >= 7.7.1 + - [ ] it works on the command line with `gdb` + - [ ] `cwd` and `target` are properly set +- [ ] If you are using lldb + - [ ] `lldb --version` >= 3.7.1 + - [ ] it works on the command line with `lldb-mi` + - [ ] `cwd` and `target` are properly set + +Screenshots are helpful but not required \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..6b1629a --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,100 @@ +name: Release +on: + release: + types: [published] + +jobs: + release: + strategy: + matrix: + os: ['ubuntu-latest'] + node-version: ['16.x'] + runs-on: ${{ matrix.os }} + steps: + # + # Basic Setup + # + - name: Checkout + uses: actions/checkout@v3 + + - name: Install Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + + # + # Audit Versions for Consistency + # + - name: Query package.json Version + id: package + run: node -e "console.log('::set-output name=version::' + require('./package.json').version)" + + - name: Query package-lock.json Version + id: package-lock + run: node -e "console.log('::set-output name=version::' + require('./package-lock.json').version)" + + - name: Query Latest Changelog Version + id: changelog + shell: bash + run: echo ::set-output name=version::$(grep --perl-regexp "^# " --max-count=1 CHANGELOG.md | awk '{print $2}') + + - name: Audit package.json/package-lock.json/CHANGELOG.md/git tag Version Consistency + shell: bash + run: > + test ${{ steps.package.outputs.version }} = ${{ steps.package-lock.outputs.version }} -a \ + ${{ steps.package.outputs.version }} = ${{ steps.changelog.outputs.version }} -a \ + refs/tags/v${{ steps.package.outputs.version }} = ${{ github.ref }} + + # + # Install Dependencies + # + # NOTE: + # Use the `clean-install` instead of just `install` so that the versions identified in the + # package-lock.json file are used instead of attempting to install later versions that might + # exist. This drives consistency between what the developers have been using and what is to + # be released. + # + # NOTE: + # Use the `--no-optional` switch to prevent installation of the `ssh2` optional dependency + # (i.e., `cpu-features`) package which is used to provide accelerated crypto functionality, + # but which is a native add-on and would require platform specific packages. + # + - name: Install Module Dependencies + run: npm clean-install --no-optional + + # + # Package and Upload Extension + # + # NOTE: + # The "vscode:prepublish" script in package.json will be executed to compile the extension + # prior to packaging. + # + - name: Package Extension into .vsix file + id: asset + shell: bash + run: > + npx vsce package; + echo ::set-output name=vsix_path::$(ls *.vsix) + + - name: Upload .vsix file to Github as release asset + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ github.event.release.upload_url }} + asset_path: ${{ steps.asset.outputs.vsix_path }} + asset_name: ${{ steps.asset.outputs.vsix_path }} + asset_content_type: application/zip + + # + # Publish Extension + # + - name: Publish to VSCode Extension Marketplace + env: + VSCE_PAT: ${{ secrets.VS_MARKETPLACE_TOKEN }} + run: npx vsce publish --packagePath ${{ steps.asset.outputs.vsix_path }} + + - name: Publish to Open VSX Registry + env: + OVSX_PAT: ${{ secrets.OPEN_VSX_TOKEN }} + run: npx ovsx publish ${{ steps.asset.outputs.vsix_path }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1262404 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +out +node_modules +*.vsix +.vscode-test \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..84bcea5 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,20 @@ +sudo: false +language: node_js +node_js: + - stable +os: + - osx + - linux + +before_install: + - if [ $TRAVIS_OS_NAME == "linux" ]; then + export CXX="g++-4.9" CC="gcc-4.9" DISPLAY=:99.0; + sh -e /etc/init.d/xvfb start; + sleep 3; + fi + +script: +- | + npm install + tsc + node out/src/test/runTest.js diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..6fc6c05 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,50 @@ +// A launch configuration that compiles the extension and then opens it inside a new window +{ + "version": "0.1.0", + "configurations": [ + { + "name": "Launch Extension", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${execPath}", + "args": ["--extensionDevelopmentPath=${workspaceRoot}" ], + "stopOnEntry": false, + "sourceMaps": true, + "outFiles": [ + "${workspaceRoot}/out/**/*.js" + ], + "preLaunchTask": "npm" + }, + { + "name": "code-debug server", + "type": "node", + "request": "launch", + "runtimeArgs": [ "--nolazy" ], + "program": "${workspaceRoot}/src/gdb.ts", + "stopOnEntry": false, + "args": [ "--server=4711" ], + "sourceMaps": true, + "outFiles": [ + "${workspaceRoot}/out/**/*.js" + ], + "cwd": "${workspaceRoot}", + "preLaunchTask": "npm" + }, + { + "name": "Launch Tests", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${execPath}", + "args": [ + "--extensionDevelopmentPath=${workspaceRoot}", + "--extensionTestsPath=${workspaceRoot}/out/src/test/suite/index" + ], + "stopOnEntry": false, + "sourceMaps": true, + "outFiles": [ + "${workspaceRoot}/out/**/*.js" + ], + "preLaunchTask": "npm" + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..7bfd42c --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,11 @@ +// Place your settings in this file to overwrite default and user settings. +{ + "editor.insertSpaces": false, + "files.exclude": { + "out": false // set this to true to hide the "out" folder with the compiled JS files + }, + "search.exclude": { + "out": true // set this to false to include "out" folder in search results + }, + "typescript.tsdk": "./node_modules/typescript/lib" // we want to use the TS server from our node_modules folder to control its version +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..e14d181 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,28 @@ +// Available variables which can be used inside of strings. +// ${workspaceRoot}: the root folder of the team +// ${file}: the current opened file +// ${fileBasename}: the current opened file's basename +// ${fileDirname}: the current opened file's dirname +// ${fileExtname}: the current opened file's extension +// ${cwd}: the current working directory of the spawned process + +// A task runner that calls a custom npm script that compiles the extension. +{ + "version": "2.0.0", + + // Run in a shell so "npm" command is properly resolved to "npm.cmd" on Windows systems. + "type": "shell", + + // we want to run npm + "command": "npm", + + // we run the custom script "compile" as defined in package.json + "args": ["run", "compile", "--loglevel", "silent"], + + // The tsc compiler is started in background mode + "isBackground": true, + + // use the standard tsc in watch mode problem matcher to find compile problems in the output. + "problemMatcher": "$tsc-watch", + "tasks": [] +} \ No newline at end of file diff --git a/.vscodeignore b/.vscodeignore new file mode 100644 index 0000000..795e714 --- /dev/null +++ b/.vscodeignore @@ -0,0 +1,9 @@ +.vscode/** +typings/** +out/test/** +test/** +src/** +**/*.map +.gitignore +tsconfig.json +vsc-extension-quickstart.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..12302fe --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,82 @@ +# 0.26.0 + +* vscode dependency was increased from 1.28 to 1.55 along with the debug-adapter protocol to get rid of some outdated dependencies (@GitMensch) +* SSH2 module updated from deprecated 0.8.9 to current 1.6.0 (@GitMensch), + allowing connections with more modern key algorithms, improved error handling (including user messages passed on) and other improvements. + See [SSH2 Update Notices](https://github.com/mscdex/ssh2/issues/935) for more details. +* Path Substitutions working with attach+ssh configuration #293 (@brownts) +* Path Substitutions working with LLDB #295 (@brownts) +* Path Substitutions working with Windows-Style paths #294 (@brownts) +* Breakpoints may be deleted when not recognized correctly #259 fixing #230 (@kvinwang) +* New `stopAtConnect` configuration #299, #302 (@brownts) +* New `stopAtEntry` configuration to run debugger to application's entry point #306 (@brownts) +* New `ssh.sourceFileMap` configuration to allow multiple substitutions between local and ssh-remote and separate ssh working directory #298 (@GitMensch) +* fix path translation for SSH to Win32 and for extended-remote without executable (attach to process) #323 (@GitMensch) +* fix for race conditions on startup where breakpoints were not hit #304 (@brownts) +* prevent "Not implemented stop reason (assuming exception)" in many cases #316 (@GitMensch), + initial recognition of watchpoints +* fix additional race conditions with setting breakpoints #313 (@brownts) +* fix stack frame expansion in editor via use of the `startFrame` parameter #312 (@brownts) +* allow specification of port/x11port via variable (as numeric string) #265 (@GitMensch) +* Extra debugger arguments now work in all configurations #316, #338 fixing #206 (@GitMensch, @brownts) +* Attaching to local PID now performs initialization prior to attaching #341 fixing #329 (@brownts) + +# 0.25.1 + +* Remove the need for extra trust for debugging workspaces per guidance "for debug extensions" as noted in the [Workspace Trust Extension Guide](https://github.com/microsoft/vscode/issues/120251#issuecomment-825832603) (@GitMensch) +* Fix simple value formatting list parsing with empty string as first argument (@nomtats) +* don't abort if `set target-async` or `cd` fails in attach (brings in line with existing behavior from launch) + +# 0.25.0 + +(released May 2020) + +* Add support for path substitutions (`{"fromPath": "toPath"}`) for GDB and LLDB (@karljs) +* Support up to 65535 threads instead of 256 threads (@ColdenCullen) +* Improve thread names on embedded GDB, makes not all threads always have the same name (with @anshulrouthu) + +# 0.24.0 + +* Added zig as supported language. +* Fix example Debug Microcontroller template +* Implement "Jump to Cursor" to skip instructions +* Fix memory dump for theia + +# 0.23.1 + +Fixes: +* Breakpoints in SSH in other working directories properly resolved +* Undefined/null paths don't crash stacktrace +* Added kotlin to language list + +# 0.23.0 + +(released March 2019) + +* Normalize file paths in stack trace (fixes duplicate opening of files) +* New Examine memory Location UI +* Breakpoints in SSH on windows fixed (@HaronK) +* Project code improvements (@simark) +* Initial configurations contain valueFormatting now (@Yanpas) + +# 0.22.0 + +(released March 2018) + +* Support for using SSH agent +* Support multi-threading (@LeszekSwirski) +* Fixed GDB expansion logic with floats (Marcel Ball) +* Fixed attach to PID template (@gentoo90) + +# 0.21.0 / 0.21.1 / 0.21.2 + +(0.21.2 is pushed without changes to hopefully fix vscode installation) + +* Several fixes to variable pretty printers by @gentoo90 +* Enabled breakpoints for crystal (@faustinoaq) + +# 0.20.0 + +Added support for pretty printers in variable list (by @gentoo90), enable +with `"valuesFormatting": "prettyPrinters"` if you have a pretty printer +to get potentially improved display of variables. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..cf1ab25 --- /dev/null +++ b/LICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/README.md b/README.md index d1c5df2..b8f0e34 100644 --- a/README.md +++ b/README.md @@ -1,39 +1,202 @@ -# native-debug +# Debug -#### 介绍 -{**以下是 Gitee 平台说明,您可以替换此简介** -Gitee 是 OSCHINA 推出的基于 Git 的代码托管平台(同时支持 SVN)。专为开发者提供稳定、高效、安全的云端软件开发协作平台 -无论是个人、团队、或是企业,都能够用 Gitee 实现代码托管、项目管理、协作开发。企业项目请看 [https://gitee.com/enterprises](https://gitee.com/enterprises)} +Native VSCode debugger. Supports both GDB and LLDB. -#### 软件架构 -软件架构说明 +## Installation +Press ctrl-p (cmd+p on OS X) and run `ext install webfreak.debug` in visual studio code and install GDB/LLDB. See `Usage` for details on how to set it up. -#### 安装教程 +![Preview](images/preview.png) -1. xxxx -2. xxxx -3. xxxx +## Usage -#### 使用说明 +![Image with red circle around a gear and a red arrow pointing at GDB and LLDB](images/tutorial1.png) -1. xxxx -2. xxxx -3. xxxx +Or if you already have an existing debugger in your project setup you can click "Create Configuration" or use the auto completion instead: -#### 参与贡献 +![Visual studio code debugger launch.json auto completion showing alternative way to create debuggers](images/tutorial1-alt.png) -1. Fork 本仓库 -2. 新建 Feat_xxx 分支 -3. 提交代码 -4. 新建 Pull Request +Open your project and click the debug button in your sidebar. At the top right press +the little gear icon and select GDB or LLDB. It will automatically generate the configuration +you need. +*Note: for LLDB you need to have `lldb-mi` in your PATH* -#### 特技 +If you are on OS X you can add `lldb-mi` to your path using +`ln -s /Applications/Xcode.app/Contents/Developer/usr/bin/lldb-mi /usr/local/bin/lldb-mi` if you have Xcode. -1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md -2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com) -3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目 -4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目 -5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help) -6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) +![Default config with a red circle around the target](images/tutorial2.png) + +Now you need to change `target` to the application you want to debug relative +to the cwd. (Which is the workspace root by default) + +Additionally you can set `terminal` if you want to run the program in a separate terminal with +support for input. On Windows set it to an empty string (`""`) to enable this feature. On linux +set it to an empty string (`""`) to use the default terminal emulator specified with `x-terminal-emulator` +or specify a custom one. Note that it must support the `-e` argument. + +Before debugging you need to compile your application first, then you can run it using +the green start button in the debug sidebar. For this you could use the `preLaunchTask` +argument vscode allows you to do. Debugging multithreaded applications is currently not +implemented. Adding breakpoints while the program runs will not interrupt it immediately. +For that you need to pause & resume the program once first. However adding breakpoints +while its paused works as expected. + +Extending variables is very limited as it does not support child values of variables. +Watching expressions works partially but the result does not get properly parsed and +it shows the raw output of the command. It will run `data-evaluate-expression` +to check for variables. + +While running you will get a console where you can manually type GDB/LLDB commands or MI +commands prepended with a hyphen `-`. The console shows all output separated +in `stdout` for the application, `stderr` for errors and `log` for log messages. + +Some exceptions/signals like segmentation faults will be catched and displayed but +it does not support for example most D exceptions. + +Support exists for stopping at the entry point of the application. This is controlled +through the `stopAtEntry` setting. This value may be either a boolean or a string. In +the case of a boolean value of `false` (the default), this setting is disabled. In the +case of a boolean value of `true`, if this is a launch configuration and the debugger +supports the `start` (or `exec-run --start` MI feature, more specifically), than this +will be used to run to the entry point of the application. Note that this appears to +work fine for GDB, but LLDB doesn't necessarily seem to adhere to this, even though it may +indicate that it supports this feature. The alternative configuration option for the +`stopAtEntry` setting is to specify a string where the string represents the entry point +itself. In this situation a temporary breakpoint will be set at the specified entry point +and a normal run will occur for a launch configuration. This (setting a temporary +breakpoint) is also the behavior that occurs when the debugger does not support the +`start` feature and the `stopAtEntry` was set to `true`. In that case the entry point will +default to "main". Thus, the most portable way to use this configuration is to explicitly +specify the entry point of the application. In the case of an attach configuration, similar +behavior will occur, however since there is no equivalent of the `start` command for +attaching, a boolean value of `true` for the `stopAtEntry` setting in a launch configuration +will automatically default to an entry point of "main", while a string value for this +setting will be interpreted as the entry point, causing a temporary breakpoint to be set at +that location prior to continuing execution. Note that stopping at the entry point for the +attach configuration assumes that the entry point has not yet been entered at the time of +attach, otherwise this will have no affect. + +### Attaching to existing processes + +Attaching to existing processes currently only works by specifying the PID in the +`launch.json` and setting `request` to `"attach"`. You also need to specify the executable +path for the debugger to find the debug symbols. + +``` +"request": "attach", +"executable": "./bin/executable", +"target": "4285" +``` + +This will attach to PID 4285 which should already run. GDB will pause the program on entering and LLDB will keep it running. + +### Using `gdbserver` for remote debugging (GDB only) + +You can also connect to a gdbserver instance and debug using that. For that modify the +`launch.json` by setting `request` to `"attach"` and `remote` to `true` and specifing the +port and optionally hostname in `target`. + +``` +"request": "attach", +"executable": "./bin/executable", +"target": ":2345", +"cwd": "${workspaceRoot}", +"remote": true +``` + +This will attach to the running process managed by gdbserver on localhost:2345. You might +need to hit the start button in the debug bar at the top first to start the program. + +Control over whether the debugger should continue executing on connect can be configured +by setting `stopAtConnect`. The default value is `false` so that execution will continue +after connecting. + +### Using ssh for debugging on remote + +Debugging using ssh automatically converts all paths between client & server and also optionally +redirects X11 output from the server to the client. +Simply add a `ssh` object in your `launch` request. + +``` +"request": "launch", +"target": "./executable", +"cwd": "${workspaceRoot}", +"ssh": { + "forwardX11": true, + "host": "192.168.178.57", + "cwd": "/home/remoteUser/project/", + "keyfile": "/path/to/.ssh/key", // OR + "password": "password123", + "user": "remoteUser", + "x11host": "localhost", + // x11port may also be specified as string containing only numbers (useful to use configuration variables) + "x11port": 6000, + // Optional, content will be executed on the SSH host before the debugger call. + "bootstrap": "source /home/remoteUser/some-env" +} +``` + +`ssh.sourceFileMap` will be used to trim off local paths and map them to the server. This is +required for basically everything except watched variables or user commands to work. + +For backward compatibility you can also use `cwd` and `ssh.cwd` for the mapping, this is only used +if the newer `ssh.sourceFileMap` is not configured. + +For X11 forwarding to work you first need to enable it in your Display Manager and allow the +connections. To allow connections you can either add an entry for applications or run `xhost +` +in the console while you are debugging and turn it off again when you are done using `xhost -`. + +Because some builds requires one or more environment files to be sourced before running any +command, you can use the `ssh.bootstrap` option to add some extra commands which will be prepended +to the debugger call (using `&&` to join both). + +### Extra Debugger Arguments + +Additional arguments can be supplied to the debugger if needed. These will be added when +the debugger executable (e.g., gdb, lldb-mi, etc.) is launched. Extra debugger arguments +are supplied via the `debugger_args` setting. Note that the behavior of escaping these +options depends on the environment in which the debugger is started. For non-SSH +debugging, the options are passed directly to the application and therefore no escaping is +necessary (other than what is necessary for the JSON configuration). However, as a result +of the options being passed directly to the application, care must be taken to place +switches and switch values as separate entities in `debugger_args`, if they would normally +be separated by a space. For example, supplying the option and value +`-iex "set $foo = \"bar\""` would consist of the following `debugger_args`: +```json +"debugger_args" : ["-iex", "set $foo = \"bar\""] +``` +If `=` is used to associate switches with their values, than the switch and value should +be placed together instead. In fact, the following example shows 4 different ways in +which to specify the same switch and value, using both short and long format, as well as +switch values supplied as a separate parameter or supplied via the `=`: +- ```json + "debugger_args" : ["-iex", "set $foo = \"bar\""] + ``` +- ```json + "debugger_args" : ["-iex=set $foo = \"bar\""] + ``` +- ```json + "debugger_args" : ["--init-eval-command", "set $foo = \"bar\""] + ``` +- ```json + "debugger_args" : ["--init-eval-command=set $foo = \"bar\""] + ``` +Where escaping is really necessary is when running the debugger over SSH. In this case, +the options are not passed directly to the application, but are instead combined with the +application name, joined together with any other options, and sent to the remote system to +be parsed and executed. Thus, depending on the remote system, different escaping may be +necessary. The following shows how the same command as above needs to be escaped +differently based on whether the remote system is a POSIX or a Windows system. +- SSH to Linux machine: + ```json + "debugger_args": ["-iex", "'set $foo = \"bar\"'"] + ``` +- SSH to Windows machine: + ```json + "debugger_args": ["-iex", "\"set $foo = \\\"bar\\\"\""] + ``` +You may need to experiment to find the correct escaping necessary for the command to be +sent to the debugger as you intended. + +## [Issues](https://github.com/WebFreak001/code-debug) diff --git a/images/icon-plain.svg b/images/icon-plain.svg new file mode 100644 index 0000000..d86c021 --- /dev/null +++ b/images/icon-plain.svg @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/images/icon.png b/images/icon.png new file mode 100644 index 0000000..935ec57 Binary files /dev/null and b/images/icon.png differ diff --git a/images/icon.svg b/images/icon.svg new file mode 100644 index 0000000..faaaa75 --- /dev/null +++ b/images/icon.svg @@ -0,0 +1,181 @@ + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/images/preview.png b/images/preview.png new file mode 100644 index 0000000..dfbcd81 Binary files /dev/null and b/images/preview.png differ diff --git a/images/tutorial1-alt.png b/images/tutorial1-alt.png new file mode 100644 index 0000000..d4cb507 Binary files /dev/null and b/images/tutorial1-alt.png differ diff --git a/images/tutorial1.png b/images/tutorial1.png new file mode 100644 index 0000000..73920d3 Binary files /dev/null and b/images/tutorial1.png differ diff --git a/images/tutorial2.png b/images/tutorial2.png new file mode 100644 index 0000000..2540f0d Binary files /dev/null and b/images/tutorial2.png differ diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..416ec04 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2632 @@ +{ + "name": "debug", + "version": "0.26.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "debug", + "version": "0.26.0", + "license": "public domain", + "dependencies": { + "ssh2": "^1.6.0", + "vscode-debugadapter": "^1.45.0", + "vscode-debugprotocol": "^1.45.0" + }, + "devDependencies": { + "@types/mocha": "^5.2.6", + "@types/node": "^11.11.3", + "@types/vscode": "^1.55.0", + "mocha": "^9.1.3", + "tslint": "^5.20.1", + "typescript": "^3.9.3", + "vscode-test": "1.3.0" + }, + "engines": { + "vscode": "^1.55.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.0.tgz", + "integrity": "sha512-IF4EOMEV+bfYwOmNxGzSnjR2EmQod7f1UXOpZM3l4i4o4QNwzjtJAu/HxdjHq0aYBvdqMuQEY1eg0nqW9ZPORA==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.16.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.15.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", + "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.0.tgz", + "integrity": "sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.15.7", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@types/mocha": { + "version": "5.2.7", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz", + "integrity": "sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==", + "dev": true + }, + "node_modules/@types/node": { + "version": "11.15.54", + "resolved": "https://registry.npmjs.org/@types/node/-/node-11.15.54.tgz", + "integrity": "sha512-1RWYiq+5UfozGsU6MwJyFX6BtktcT10XRjvcAQmskCtMcW3tPske88lM/nHv7BQG1w9KBXI1zPGuu5PnNCX14g==", + "dev": true + }, + "node_modules/@types/vscode": { + "version": "1.62.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.62.0.tgz", + "integrity": "sha512-iGlQJ1w5e3qPUryroO6v4lxg3ql1ztdTCwQW3xEwFawdyPLoeUSv48SYfMwc7kQA7h6ThUqflZIjgKAykeF9oA==", + "dev": true + }, + "node_modules/@ungap/promise-all-settled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", + "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", + "dev": true + }, + "node_modules/agent-base": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", + "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", + "dev": true, + "dependencies": { + "es6-promisify": "^5.0.0" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "node_modules/builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/camelcase": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chokidar": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", + "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "node_modules/cpu-features": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.2.tgz", + "integrity": "sha512-/2yieBqvMcRj8McNzkycjW2v3OIUOibBfd2dLEJ0nWts8NobAxwiyw9phVNS6oDL8x8tz9F7uNVFEVpJncQpeA==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "nan": "^2.14.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", + "dev": true + }, + "node_modules/es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "dev": true, + "dependencies": { + "es6-promise": "^4.0.3" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/glob": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true, + "engines": { + "node": ">=4.x" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/http-proxy-agent": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz", + "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==", + "dev": true, + "dependencies": { + "agent-base": "4", + "debug": "3.1.0" + }, + "engines": { + "node": ">= 4.5.0" + } + }, + "node_modules/http-proxy-agent/node_modules/debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/http-proxy-agent/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "node_modules/https-proxy-agent": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", + "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", + "dev": true, + "dependencies": { + "agent-base": "^4.3.0", + "debug": "^3.1.0" + }, + "engines": { + "node": ">= 4.5.0" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.0.tgz", + "integrity": "sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/log-symbols/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/log-symbols/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/log-symbols/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "node_modules/mkdirp": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.4.tgz", + "integrity": "sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw==", + "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", + "dev": true, + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mocha": { + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.1.3.tgz", + "integrity": "sha512-Xcpl9FqXOAYqI3j79pEtHBBnQgVXIhpULjGQa7DVb0Po+VzmSIK9kanAiWLHoRR/dbZ2qpdPshuXr8l1VaHCzw==", + "dev": true, + "dependencies": { + "@ungap/promise-all-settled": "1.1.2", + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.2", + "debug": "4.3.2", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.1.7", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "3.0.4", + "ms": "2.1.3", + "nanoid": "3.1.25", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "which": "2.0.2", + "workerpool": "6.1.5", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mochajs" + } + }, + "node_modules/mocha/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/mocha/node_modules/debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/mocha/node_modules/debug/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/mocha/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/mocha/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "node_modules/nan": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", + "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==", + "optional": true + }, + "node_modules/nanoid": { + "version": "3.1.25", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.25.tgz", + "integrity": "sha512-rdwtIXaXCLFAQbnfqDRnI6jaRHp9fTcYBjtFKE8eezcZ7LuLjhUaQGNeMXf1HmRoCH32CLz6XwX0TtxEOS/A3Q==", + "dev": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", + "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "dev": true, + "dependencies": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "node_modules/ssh2": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.6.0.tgz", + "integrity": "sha512-lxc+uvXqOxyQ99N2M7k5o4pkYDO5GptOTYduWw7hIM41icxvoBcCNHcj+LTKrjkL0vFcAl+qfZekthoSFRJn2Q==", + "hasInstallScript": true, + "dependencies": { + "asn1": "^0.2.4", + "bcrypt-pbkdf": "^1.0.2" + }, + "engines": { + "node": ">=10.16.0" + }, + "optionalDependencies": { + "cpu-features": "0.0.2", + "nan": "^2.15.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/supports-color/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/tslint": { + "version": "5.20.1", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.20.1.tgz", + "integrity": "sha512-EcMxhzCFt8k+/UP5r8waCf/lzmeSyVlqxqMEDQE7rWYiQky8KpIBz1JAoYXfROHrPZ1XXd43q8yQnULOLiBRQg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^4.0.1", + "glob": "^7.1.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.8.0", + "tsutils": "^2.29.0" + }, + "bin": { + "tslint": "bin/tslint" + }, + "engines": { + "node": ">=4.8.0" + }, + "peerDependencies": { + "typescript": ">=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >=3.0.0-dev || >= 3.1.0-dev || >= 3.2.0-dev" + } + }, + "node_modules/tslint/node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/tslint/node_modules/tsutils": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "peerDependencies": { + "typescript": ">=2.1.0 || >=2.1.0-dev || >=2.2.0-dev || >=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >= 3.0.0-dev || >= 3.1.0-dev" + } + }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + }, + "node_modules/typescript": { + "version": "3.9.10", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.10.tgz", + "integrity": "sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/vscode-debugadapter": { + "version": "1.50.0", + "resolved": "https://registry.npmjs.org/vscode-debugadapter/-/vscode-debugadapter-1.50.0.tgz", + "integrity": "sha512-KHtwd6uHOqB1C2/1hpKQcD4JY/88QXkieubzKQfT1PTLztnoM53Pz+pTNgH+VXrGDI50BzjLV6YOluPTufUaMQ==", + "dependencies": { + "mkdirp": "^1.0.4", + "vscode-debugprotocol": "1.50.1" + } + }, + "node_modules/vscode-debugadapter/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/vscode-debugprotocol": { + "version": "1.50.1", + "resolved": "https://registry.npmjs.org/vscode-debugprotocol/-/vscode-debugprotocol-1.50.1.tgz", + "integrity": "sha512-kIHIipklHnSjBm2KkRqTJLKL2FMZlJl0+/qpJt2y62YOamMNwcxNATnXXRgAwnzKKRMweqp93tJ83UTJ8+O7SQ==" + }, + "node_modules/vscode-test": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/vscode-test/-/vscode-test-1.3.0.tgz", + "integrity": "sha512-LddukcBiSU2FVTDr3c1D8lwkiOvwlJdDL2hqVbn6gIz+rpTqUCkMZSKYm94Y1v0WXlHSDQBsXyY+tchWQgGVsw==", + "dev": true, + "dependencies": { + "http-proxy-agent": "^2.1.0", + "https-proxy-agent": "^2.2.4", + "rimraf": "^2.6.3" + }, + "engines": { + "node": ">=8.9.3" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/workerpool": { + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.5.tgz", + "integrity": "sha512-XdKkCK0Zqc6w3iTxLckiuJ81tiD/o5rBE/m+nXpRCB+/Sq4DqkfXZ/x0jW02DG1tGsfUGXbTJyZDP+eu67haSw==", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.0.tgz", + "integrity": "sha512-IF4EOMEV+bfYwOmNxGzSnjR2EmQod7f1UXOpZM3l4i4o4QNwzjtJAu/HxdjHq0aYBvdqMuQEY1eg0nqW9ZPORA==", + "dev": true, + "requires": { + "@babel/highlight": "^7.16.0" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.15.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", + "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", + "dev": true + }, + "@babel/highlight": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.0.tgz", + "integrity": "sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.15.7", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@types/mocha": { + "version": "5.2.7", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz", + "integrity": "sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==", + "dev": true + }, + "@types/node": { + "version": "11.15.54", + "resolved": "https://registry.npmjs.org/@types/node/-/node-11.15.54.tgz", + "integrity": "sha512-1RWYiq+5UfozGsU6MwJyFX6BtktcT10XRjvcAQmskCtMcW3tPske88lM/nHv7BQG1w9KBXI1zPGuu5PnNCX14g==", + "dev": true + }, + "@types/vscode": { + "version": "1.62.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.62.0.tgz", + "integrity": "sha512-iGlQJ1w5e3qPUryroO6v4lxg3ql1ztdTCwQW3xEwFawdyPLoeUSv48SYfMwc7kQA7h6ThUqflZIjgKAykeF9oA==", + "dev": true + }, + "@ungap/promise-all-settled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", + "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", + "dev": true + }, + "agent-base": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", + "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", + "dev": true, + "requires": { + "es6-promisify": "^5.0.0" + } + }, + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, + "camelcase": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "chokidar": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", + "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + } + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "cpu-features": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.2.tgz", + "integrity": "sha512-/2yieBqvMcRj8McNzkycjW2v3OIUOibBfd2dLEJ0nWts8NobAxwiyw9phVNS6oDL8x8tz9F7uNVFEVpJncQpeA==", + "optional": true, + "requires": { + "nan": "^2.14.1" + } + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true + }, + "diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", + "dev": true + }, + "es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "dev": true, + "requires": { + "es6-promise": "^4.0.3" + } + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "glob": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "http-proxy-agent": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz", + "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==", + "dev": true, + "requires": { + "agent-base": "4", + "debug": "3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "https-proxy-agent": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", + "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", + "dev": true, + "requires": { + "agent-base": "^4.3.0", + "debug": "^3.1.0" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-core-module": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.0.tgz", + "integrity": "sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true + }, + "is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "mkdirp": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.4.tgz", + "integrity": "sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "mocha": { + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.1.3.tgz", + "integrity": "sha512-Xcpl9FqXOAYqI3j79pEtHBBnQgVXIhpULjGQa7DVb0Po+VzmSIK9kanAiWLHoRR/dbZ2qpdPshuXr8l1VaHCzw==", + "dev": true, + "requires": { + "@ungap/promise-all-settled": "1.1.2", + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.2", + "debug": "4.3.2", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.1.7", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "3.0.4", + "ms": "2.1.3", + "nanoid": "3.1.25", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "which": "2.0.2", + "workerpool": "6.1.5", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dev": true, + "requires": { + "ms": "2.1.2" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + } + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "nan": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", + "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==", + "optional": true + }, + "nanoid": { + "version": "3.1.25", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.25.tgz", + "integrity": "sha512-rdwtIXaXCLFAQbnfqDRnI6jaRHp9fTcYBjtFKE8eezcZ7LuLjhUaQGNeMXf1HmRoCH32CLz6XwX0TtxEOS/A3Q==", + "dev": true + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "picomatch": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", + "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "dev": true + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "resolve": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "dev": true, + "requires": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + } + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "ssh2": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.6.0.tgz", + "integrity": "sha512-lxc+uvXqOxyQ99N2M7k5o4pkYDO5GptOTYduWw7hIM41icxvoBcCNHcj+LTKrjkL0vFcAl+qfZekthoSFRJn2Q==", + "requires": { + "asn1": "^0.2.4", + "bcrypt-pbkdf": "^1.0.2", + "cpu-features": "0.0.2", + "nan": "^2.15.0" + } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + } + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "tslint": { + "version": "5.20.1", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.20.1.tgz", + "integrity": "sha512-EcMxhzCFt8k+/UP5r8waCf/lzmeSyVlqxqMEDQE7rWYiQky8KpIBz1JAoYXfROHrPZ1XXd43q8yQnULOLiBRQg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^4.0.1", + "glob": "^7.1.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.8.0", + "tsutils": "^2.29.0" + }, + "dependencies": { + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "tsutils": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + } + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + }, + "typescript": { + "version": "3.9.10", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.10.tgz", + "integrity": "sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q==", + "dev": true + }, + "vscode-debugadapter": { + "version": "1.50.0", + "resolved": "https://registry.npmjs.org/vscode-debugadapter/-/vscode-debugadapter-1.50.0.tgz", + "integrity": "sha512-KHtwd6uHOqB1C2/1hpKQcD4JY/88QXkieubzKQfT1PTLztnoM53Pz+pTNgH+VXrGDI50BzjLV6YOluPTufUaMQ==", + "requires": { + "mkdirp": "^1.0.4", + "vscode-debugprotocol": "1.50.1" + }, + "dependencies": { + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" + } + } + }, + "vscode-debugprotocol": { + "version": "1.50.1", + "resolved": "https://registry.npmjs.org/vscode-debugprotocol/-/vscode-debugprotocol-1.50.1.tgz", + "integrity": "sha512-kIHIipklHnSjBm2KkRqTJLKL2FMZlJl0+/qpJt2y62YOamMNwcxNATnXXRgAwnzKKRMweqp93tJ83UTJ8+O7SQ==" + }, + "vscode-test": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/vscode-test/-/vscode-test-1.3.0.tgz", + "integrity": "sha512-LddukcBiSU2FVTDr3c1D8lwkiOvwlJdDL2hqVbn6gIz+rpTqUCkMZSKYm94Y1v0WXlHSDQBsXyY+tchWQgGVsw==", + "dev": true, + "requires": { + "http-proxy-agent": "^2.1.0", + "https-proxy-agent": "^2.2.4", + "rimraf": "^2.6.3" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "workerpool": { + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.5.tgz", + "integrity": "sha512-XdKkCK0Zqc6w3iTxLckiuJ81tiD/o5rBE/m+nXpRCB+/Sq4DqkfXZ/x0jW02DG1tGsfUGXbTJyZDP+eu67haSw==", + "dev": true + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + }, + "yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true + }, + "yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "requires": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + } + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..1cf66b9 --- /dev/null +++ b/package.json @@ -0,0 +1,1068 @@ +{ + "name": "debug", + "displayName": "Native Debug", + "description": "GDB, LLDB & Mago-MI Debugger support for VSCode", + "keywords": [ + "gdb", + "lldb", + "mago-mi", + "native", + "debug" + ], + "license": "public domain", + "version": "0.26.0", + "publisher": "webfreak", + "icon": "images/icon.png", + "engines": { + "vscode": "^1.55.0" + }, + "main": "./out/src/frontend/extension", + "activationEvents": [ + "onDebug", + "onCommand:code-debug.examineMemoryLocation", + "onCommand:code-debug.getFileNameNoExt", + "onCommand:code-debug.getFileBasenameNoExt", + "onCommand:code-debug.startRecord", + "onCommand:code-debug.stopRecord" + ], + "categories": [ + "Debuggers" + ], + "repository": { + "type": "git", + "url": "https://github.com/WebFreak001/code-debug.git" + }, + "capabilities": { + "untrustedWorkspaces": { + "supported": true + } + }, + "contributes": { + "commands": [ + { + "command": "code-debug.examineMemoryLocation", + "title": "Code-Debug: Examine memory location" + }, + { + "command": "code-debug.startRecord", + "title": "Code-Debug: Start reverse debugging", + "icon":"$(debug-start)" + }, + { + "command": "code-debug.stopRecord", + "title": "Code-Debug: Stop reverse debugging", + "icon":"$(debug-stop)" + } + ], + "menus": { + "editor/title": [ + { + "command": "code-debug.startRecord", + "when": "inDebugMode && debugState == stopped && debugType == 'gdb' && code-debug.enableStartRecord", + "group": "navigation" + }, + { + "command": "code-debug.stopRecord", + "when": "inDebugMode && debugState == stopped && debugType == 'gdb' && !code-debug.enableStartRecord", + "group": "navigation" + } + ] + }, + "breakpoints": [ + {"language": "c"}, + {"language": "cpp"}, + {"language": "d"}, + {"language": "objective-c"}, + {"language": "fortran"}, + {"language": "fortran-modern"}, + {"language": "fortran90"}, + {"language": "fortran_free-form"}, + {"language": "fortran_fixed-form"}, + {"language": "rust"}, + {"language": "pascal"}, + {"language": "objectpascal"}, + {"language": "ada"}, + {"language": "nim"}, + {"language": "arm"}, + {"language": "asm"}, + {"language": "vala"}, + {"language": "crystal"}, + {"language": "kotlin"}, + {"language": "zig"} + ], + "debuggers": [ + { + "type": "gdb", + "program": "./out/src/gdb.js", + "runtime": "node", + "label": "GDB", + "languages": [ + "c", + "cpp", + "d", + "objective-c", + "fortran", + "fortran-modern", + "fortran90", + "fortran_free-form", + "fortran_fixed-form", + "rust", + "pascal", + "objectpascal", + "ada", + "nim", + "arm", + "asm", + "vala", + "crystal", + "kotlin", + "zig" + ], + "variables": { + "FileBasenameNoExt": "code-debug.getFileBasenameNoExt", + "FileNameNoExt": "code-debug.getFileNameNoExt" + }, + "configurationAttributes": { + "launch": { + "required": [ + "target", + "cwd" + ], + "properties": { + "target": { + "type": "string", + "description": "Path of executable" + }, + "arguments": { + "type": "string", + "description": "Arguments to append after the executable. You can also use pipes." + }, + "terminal": { + "type": "string", + "description": "Leave this field undefined to keep program output in the vscode console at the bottom. If this is set to empty string the program will spawn in a new console using x-terminal-emulator on linux, otherwise with the specified terminal. On windows setting this to an empty string spawns the program in a console, but no other console is supported." + }, + "cwd": { + "type": "string", + "description": "project path" + }, + "gdbpath": { + "type": "string", + "description": "Path to the gdb executable or the command if in PATH", + "default": "gdb" + }, + "env": { + "type": "object", + "description": "Environment overriding the gdb (and in turn also the process) environment", + "default": null + }, + "debugger_args": { + "type": "array", + "description": "Additional arguments to pass to GDB", + "default": [] + }, + "pathSubstitutions": { + "type": "object", + "description": "Help GDB find your source using path substitutions (GDB `substitute-path)` variable", + "default": { + "": "" + } + }, + "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", + "enum": [ + "disabled", + "parseText", + "prettyPrinters" + ] + }, + "printCalls": { + "type": "boolean", + "description": "Prints all GDB calls to the console", + "default": false + }, + "showDevDebugOutput": { + "type": "boolean", + "description": "Prints all GDB responses to the console", + "default": false + }, + "autorun": { + "type": "array", + "description": "GDB commands to run when starting to debug", + "default": [] + }, + "stopAtEntry": { + "type": ["boolean", "string"], + "description": "Whether debugger should stop at application entry point", + "default": false + }, + "ssh": { + "required": [ + "host", + "user", + "cwd" + ], + "type": "object", + "description": "If this is set then the extension will connect to an ssh host and run GDB there", + "properties": { + "host": { + "type": "string", + "description": "Remote host name/ip to connect to" + }, + "port": { + "type": ["number", "string"], + "description": "Remote port number", + "default": 22 + }, + "user": { + "type": "string", + "description": "Username to connect as" + }, + "password": { + "type": "string", + "description": "Plain text password (unsafe; if possible use keyfile instead)" + }, + "keyfile": { + "type": "string", + "description": "Absolute path to private key" + }, + "useAgent": { + "type": "boolean", + "description": "Auto-detect the running SSH agent (via SSH_AUTH_SOCK environment variable) and use it to perform authentication", + "default": false + }, + "sourceFileMap": { + "type": "object", + "description": "Mapping of source paths (from GDB on ssh remote) to local (IDE) paths.", + "default": { + "": "" + } + }, + "cwd": { + "type": "string", + "description": "Working directory for the debugger.\nIf `ssh.sourceFileMap` is not set, then this is also the project path on the remote for mapping with `cwd`." + }, + "forwardX11": { + "type": "boolean", + "description": "If true, the server will redirect x11 to the local host", + "default": true + }, + "x11port": { + "type": ["number", "string"], + "description": "Port to redirect X11 data to (by default port = display + 6000)", + "default": 6000 + }, + "x11host": { + "type": "string", + "description": "Hostname/ip to redirect X11 data to", + "default": "localhost" + }, + "remotex11screen": { + "type": "number", + "description": "Screen to start the application on the remote side", + "default": 0 + }, + "bootstrap": { + "type": "string", + "description": "Content will be executed on the SSH host before the debugger call." + } + } + } + } + }, + "attach": { + "required": [ + "target", + "cwd" + ], + "properties": { + "target": { + "type": "string", + "description": "PID of running program or program name or connection arguments (eg :2345) if remote is true" + }, + "remote": { + "type": "boolean", + "description": "If true this will connect to a gdbserver instead of attaching to a PID", + "default": false + }, + "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", + "enum": [ + "disabled", + "parseText", + "prettyPrinters" + ] + }, + "printCalls": { + "type": "boolean", + "description": "Prints all GDB calls to the console", + "default": false + }, + "showDevDebugOutput": { + "type": "boolean", + "description": "Prints all GDB responses to the console", + "default": false + }, + "executable": { + "type": "string", + "description": "Path of executable for debugging symbols" + }, + "gdbpath": { + "type": "string", + "description": "Path to the gdb executable or the command if in PATH", + "default": "gdb" + }, + "env": { + "type": "object", + "description": "Environment overriding the gdb (and in turn also the process) environment", + "default": null + }, + "debugger_args": { + "type": "array", + "description": "Additional arguments to pass to GDB", + "default": [] + }, + "pathSubstitutions": { + "type": "object", + "description": "Help GDB find your source using path substitutions (GDB `substitute-path)` variable", + "default": { + "": "" + } + }, + "cwd": { + "type": "string", + "description": "project path", + "default": "${workspaceRoot}" + }, + "autorun": { + "type": "array", + "description": "GDB commands to run when starting to debug", + "default": [] + }, + "stopAtConnect": { + "type": "boolean", + "description": "Whether debugger should stop after connecting to target", + "default": false + }, + "stopAtEntry": { + "type": ["boolean", "string"], + "description": "Whether debugger should stop at application entry point", + "default": false + }, + "ssh": { + "required": [ + "host", + "cwd", + "user" + ], + "type": "object", + "description": "If this is set then the extension will connect to an ssh host and run GDB there", + "properties": { + "host": { + "type": "string", + "description": "Remote host name/ip to connect to" + }, + "port": { + "type": ["number", "string"], + "description": "Remote port number", + "default": 22 + }, + "user": { + "type": "string", + "description": "Username to connect as" + }, + "password": { + "type": "string", + "description": "Plain text password (unsafe; if possible use keyfile instead)" + }, + "keyfile": { + "type": "string", + "description": "Absolute path to private key" + }, + "useAgent": { + "type": "boolean", + "description": "Auto-detect the running SSH agent (via SSH_AUTH_SOCK environment variable) and use it to perform authentication", + "default": false + }, + "sourceFileMap": { + "type": "object", + "description": "Mapping of source paths (from GDB on ssh remote) to local (IDE) paths.", + "default": { + "": "" + } + }, + "cwd": { + "type": "string", + "description": "Working directory for the debugger.\nIf `ssh.sourceFileMap` is not set, then this is also the project path on the remote for mapping with `cwd`." + }, + "forwardX11": { + "type": "boolean", + "description": "If true, the server will redirect x11 to the local host", + "default": true + }, + "x11port": { + "type": ["number", "string"], + "description": "Port to redirect X11 data to (by default port = display + 6000)", + "default": 6000 + }, + "x11host": { + "type": "string", + "description": "Hostname/ip to redirect X11 data to", + "default": "localhost" + }, + "remotex11screen": { + "type": "number", + "description": "Screen to start the application on the remote side", + "default": 0 + }, + "bootstrap": { + "type": "string", + "description": "Content will be executed on the SSH host before the debugger call." + } + } + } + } + } + }, + "initialConfigurations": [ + { + "name": "Debug", + "type": "gdb", + "request": "launch", + "target": "./bin/executable", + "cwd": "${workspaceRoot}", + "valuesFormatting": "parseText" + } + ], + "configurationSnippets": [ + { + "label": "GDB: Launch Program", + "description": "Starts the program using gdb", + "body": { + "type": "gdb", + "request": "launch", + "name": "${2:Launch Program}", + "target": "${1:./bin/executable}", + "cwd": "^\"\\${workspaceRoot}\"", + "valuesFormatting": "parseText" + } + }, + { + "label": "GDB: Attach to PID", + "description": "Attaches to a running program pid using gdb", + "body": { + "type": "gdb", + "request": "attach", + "name": "${2:Attach to PID}", + "target": "${1:[PID]}", + "cwd": "^\"\\${workspaceRoot}\"", + "valuesFormatting": "parseText" + } + }, + { + "label": "GDB: Connect to gdbserver", + "description": "Connects to a gdbserver for debugging", + "body": { + "type": "gdb", + "request": "attach", + "name": "${3:Attach to gdbserver}", + "executable": "${1:./bin/executable}", + "target": ":${2:2345}", + "remote": true, + "cwd": "^\"\\${workspaceRoot}\"", + "valuesFormatting": "parseText" + } + }, + { + "label": "GDB: Launch over SSH", + "description": "Remotely starts the program using gdb", + "body": { + "type": "gdb", + "request": "launch", + "name": "${6:Launch Program (SSH)}", + "target": "${1:./bin/executable}", + "cwd": "^\"\\${workspaceRoot}\"", + "ssh": { + "host": "${2:127.0.0.1}", + "cwd": "${3:/tmp/working}", + "keyfile": "${4:/home/my_user/.ssh/id_rsa}", + "user": "${5:remote_user}", + "sourceFileMap": { + "${6:/home/remote_user/project/}": "^\"\\${workspaceRoot}\"" + } + }, + "valuesFormatting": "parseText" + } + }, + { + "label": "GDB: Launch GUI over SSH with X11 forwarding", + "description": "Remotely starts the program using gdb with X11 forwarding", + "body": { + "type": "gdb", + "request": "launch", + "name": "${6:Launch Program (SSH + X11)}", + "target": "${1:./bin/executable}", + "cwd": "^\"\\${workspaceRoot}\"", + "ssh": { + "host": "${2:127.0.0.1}", + "cwd": "${3:/home/remote_user/project/}", + "keyfile": "${4:/home/my_user/.ssh/id_rsa}", + "user": "${5:remote_user}", + "forwardX11": true, + "x11host": "localhost", + "x11port": 6000 + }, + "valuesFormatting": "parseText" + } + }, + { + "label": "GDB: Debug external embedded device", + "description": "Debugs an embedded microcontroller supported by GDB by attaching over extended-remote", + "body": { + "type": "gdb", + "request": "attach", + "name": "${6:Debug Microcontroller}", + "target": "extended-remote ${2:/dev/cu.usbmodem00000000}", + "executable": "${1:./bin/executable.elf}", + "cwd": "^\"\\${workspaceRoot}\"", + "autorun": [ + "monitor tpwr enable", + "monitor swdp_scan", + "attach 1", + "load ${1:./bin/executable.elf}" + ] + }, + "valuesFormatting": "parseText" + } + ] + }, + { + "type": "lldb-mi", + "program": "./out/src/lldb.js", + "runtime": "node", + "label": "LLDB", + "languages": [ + "c", + "cpp", + "d", + "objective-c", + "fortran", + "fortran-modern", + "fortran90", + "fortran_free-form", + "fortran_fixed-form", + "rust", + "pascal", + "objectpascal", + "ada", + "nim", + "arm", + "asm", + "vala", + "crystal", + "kotlin", + "zig" + ], + "variables": { + "FileBasenameNoExt": "code-debug.getFileBasenameNoExt", + "FileNameNoExt": "code-debug.getFileNameNoExt" + }, + "configurationAttributes": { + "launch": { + "required": [ + "target" + ], + "properties": { + "target": { + "type": "string", + "description": "Path of executable" + }, + "arguments": { + "type": "string", + "description": "Arguments to append after the executable" + }, + "cwd": { + "type": "string", + "description": "project path" + }, + "lldbmipath": { + "type": "string", + "description": "Path to the lldb-mi executable or the command if in PATH", + "default": "lldb-mi" + }, + "env": { + "type": "object", + "description": "Environment overriding the lldb-mi (and in turn also the process) environment", + "default": null + }, + "debugger_args": { + "type": "array", + "description": "Additional arguments to pass to LLDB", + "default": [] + }, + "pathSubstitutions": { + "type": "object", + "description": "Help LLDB find your source using path substitutions (LLDB `target.source-map)` variable", + "default": { + "": "" + } + }, + "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", + "enum": [ + "disabled", + "parseText", + "prettyPrinters" + ] + }, + "printCalls": { + "type": "boolean", + "description": "Prints all lldb calls to the console", + "default": false + }, + "showDevDebugOutput": { + "type": "boolean", + "description": "Prints all lldb responses to the console", + "default": false + }, + "autorun": { + "type": "array", + "description": "lldb commands to run when starting to debug", + "default": [] + }, + "stopAtEntry": { + "type": ["boolean", "string"], + "description": "Whether debugger should stop at application entry point", + "default": false + }, + "ssh": { + "required": [ + "host", + "cwd", + "user" + ], + "type": "object", + "description": "If this is set then the extension will connect to an ssh host and run lldb there", + "properties": { + "host": { + "type": "string", + "description": "Remote host name/ip to connect to" + }, + "port": { + "type": ["number", "string"], + "description": "Remote port number", + "default": 22 + }, + "user": { + "type": "string", + "description": "Username to connect as" + }, + "password": { + "type": "string", + "description": "Plain text password (unsafe; if possible use keyfile instead)" + }, + "keyfile": { + "type": "string", + "description": "Absolute path to private key" + }, + "useAgent": { + "type": "boolean", + "description": "Auto-detect the running SSH agent (via SSH_AUTH_SOCK environment variable) and use it to perform authentication", + "default": false + }, + "sourceFileMap": { + "type": "object", + "description": "Mapping of source paths (from GDB on ssh remote) to local (IDE) paths.", + "default": { + "": "" + } + }, + "cwd": { + "type": "string", + "description": "Working directory for the debugger.\nIf `ssh.sourceFileMap` is not set, then this is also the project path on the remote for mapping with `cwd`." + }, + "forwardX11": { + "type": "boolean", + "description": "If true, the server will redirect x11 to the local host", + "default": true + }, + "x11host": { + "type": "string", + "description": "Hostname/ip to redirect X11 data to", + "default": "localhost" + }, + "x11port": { + "type": ["number", "string"], + "description": "Port to redirect X11 data to (by default port = display + 6000)", + "default": 6000 + }, + "remotex11screen": { + "type": "number", + "description": "Screen to start the application on the remote side", + "default": 0 + }, + "bootstrap": { + "type": "string", + "description": "Content will be executed on the SSH host before the debugger call." + } + } + } + } + }, + "attach": { + "required": [ + "target" + ], + "properties": { + "target": { + "type": "string", + "description": "PID of running program or program name" + }, + "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", + "enum": [ + "disabled", + "parseText", + "prettyPrinters" + ] + }, + "printCalls": { + "type": "boolean", + "description": "Prints all LLDB calls to the console", + "default": false + }, + "showDevDebugOutput": { + "type": "boolean", + "description": "Prints all LLDB responses to the console", + "default": false + }, + "executable": { + "type": "string", + "description": "Path of executable for debugging symbols" + }, + "lldbmipath": { + "type": "string", + "description": "Path to the lldb-mi executable or the command if in PATH", + "default": "lldb-mi" + }, + "env": { + "type": "object", + "description": "Environment overriding the lldb-mi (and in turn also the process) environment", + "default": null + }, + "debugger_args": { + "type": "array", + "description": "Additional arguments to pass to LLDB", + "default": [] + }, + "pathSubstitutions": { + "type": "object", + "description": "Help LLDB find your source using path substitutions (LLDB `target.source-map)` variable", + "default": { + "": "" + } + }, + "cwd": { + "type": "string", + "description": "project path", + "default": "${workspaceRoot}" + }, + "autorun": { + "type": "array", + "description": "lldb commands to run when starting to debug", + "default": [] + }, + "stopAtConnect": { + "type": "boolean", + "description": "Whether debugger should stop after connecting to target", + "default": false + }, + "stopAtEntry": { + "type": ["boolean", "string"], + "description": "Whether debugger should stop at application entry point", + "default": false + } + } + } + }, + "initialConfigurations": [ + { + "name": "Debug", + "type": "lldb-mi", + "request": "launch", + "target": "./bin/executable", + "cwd": "${workspaceRoot}", + "valuesFormatting": "parseText" + } + ], + "configurationSnippets": [ + { + "label": "LLDB: Launch Program", + "description": "Starts the program using lldb-mi", + "body": { + "type": "lldb-mi", + "request": "launch", + "name": "${2:Launch Program}", + "target": "${1:./bin/executable}", + "cwd": "^\"\\${workspaceRoot}\"", + "valuesFormatting": "parseText" + } + }, + { + "label": "LLDB: Attach to PID", + "description": "Attaches to a running program pid using lldb-mi", + "body": { + "type": "lldb-mi", + "request": "attach", + "name": "${2:Attach to PID}", + "target": "${1:[PID]}", + "cwd": "^\"\\${workspaceRoot}\"", + "valuesFormatting": "parseText" + } + }, + { + "label": "LLDB: Launch over SSH", + "description": "Remotely starts the program using lldb-mi", + "body": { + "type": "lldb-mi", + "request": "launch", + "name": "${6:Launch Program (SSH)}", + "target": "${1:./bin/executable}", + "cwd": "^\"\\${workspaceRoot}\"", + "ssh": { + "host": "${2:127.0.0.1}", + "cwd": "${3:/home/remote_user/project/}", + "keyfile": "${4:/home/my_user/.ssh/id_rsa}", + "user": "${5:remote_user}" + }, + "valuesFormatting": "parseText" + } + }, + { + "label": "LLDB: Launch GUI over SSH with X11 forwarding", + "description": "Remotely starts the program using lldb-mi with X11 forwarding", + "body": { + "type": "lldb-mi", + "request": "launch", + "name": "${6:Launch Program (SSH + X11)}", + "target": "${1:./bin/executable}", + "cwd": "^\"\\${workspaceRoot}\"", + "ssh": { + "host": "${2:127.0.0.1}", + "cwd": "${3:/home/remote_user/project/}", + "keyfile": "${4:/home/my_user/.ssh/id_rsa}", + "user": "${5:remote_user}", + "forwardX11": true, + "x11host": "localhost", + "x11port": 6000 + }, + "valuesFormatting": "parseText" + } + } + ] + }, + { + "type": "mago-mi", + "program": "./out/src/mago.js", + "runtime": "node", + "label": "Mago-MI", + "variables": { + "FileBasenameNoExt": "code-debug.getFileBasenameNoExt", + "FileNameNoExt": "code-debug.getFileNameNoExt" + }, + "configurationAttributes": { + "launch": { + "required": [ + "target" + ], + "properties": { + "target": { + "type": "string", + "description": "Path of executable" + }, + "arguments": { + "type": "string", + "description": "Arguments to append after the executable" + }, + "cwd": { + "type": "string", + "description": "project path" + }, + "magomipath": { + "type": "string", + "description": "Path to the mago-mi executable or the command if in PATH", + "default": "mago-mi" + }, + "env": { + "type": "object", + "description": "Environment overriding the mago-mi (and in turn also the process) environment", + "default": null + }, + "debugger_args": { + "type": "array", + "description": "Additional arguments to pass to mago", + "default": [] + }, + "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", + "enum": [ + "disabled", + "parseText", + "prettyPrinters" + ] + }, + "printCalls": { + "type": "boolean", + "description": "Prints all mago calls to the console", + "default": false + }, + "showDevDebugOutput": { + "type": "boolean", + "description": "Prints all mago responses to the console", + "default": false + }, + "autorun": { + "type": "array", + "description": "mago commands to run when starting to debug", + "default": [] + } + } + }, + "attach": { + "required": [ + "target" + ], + "properties": { + "target": { + "type": "string", + "description": "PID of running program or program name" + }, + "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", + "enum": [ + "disabled", + "parseText", + "prettyPrinters" + ] + }, + "printCalls": { + "type": "boolean", + "description": "Prints all mago calls to the console", + "default": false + }, + "showDevDebugOutput": { + "type": "boolean", + "description": "Prints all mago responses to the console", + "default": false + }, + "executable": { + "type": "string", + "description": "Path of executable for debugging symbols" + }, + "magomipath": { + "type": "string", + "description": "Path to the mago-mi executable or the command if in PATH", + "default": "mago-mi" + }, + "env": { + "type": "object", + "description": "Environment overriding the mago-mi (and in turn also the process) environment", + "default": null + }, + "debugger_args": { + "type": "array", + "description": "Additional arguments to pass to mago", + "default": [] + }, + "cwd": { + "type": "string", + "description": "project path", + "default": "${workspaceRoot}" + }, + "autorun": { + "type": "array", + "description": "mago commands to run when starting to debug", + "default": [] + }, + "stopAtConnect": { + "type": "boolean", + "description": "Whether debugger should stop after connecting to target", + "default": false + } + } + } + }, + "initialConfigurations": [ + { + "name": "Debug", + "type": "mago-mi", + "request": "launch", + "target": "./bin/executable", + "cwd": "${workspaceRoot}", + "valuesFormatting": "parseText" + } + ], + "configurationSnippets": [ + { + "label": "Mago: Launch Program", + "description": "Starts the program using mago-mi", + "body": { + "type": "mago-mi", + "request": "launch", + "name": "${2:Launch Program}", + "target": "${1:./bin/executable}", + "cwd": "^\"\\${workspaceRoot}\"", + "valuesFormatting": "parseText" + } + }, + { + "label": "Mago: Attach to PID", + "description": "Attaches to a running program pid using mago-mi", + "body": { + "type": "mago-mi", + "request": "attach", + "name": "${2:Attach to PID}", + "target": "${1:[PID]}", + "cwd": "^\"\\${workspaceRoot}\"", + "valuesFormatting": "parseText" + } + } + ] + } + ] + }, + "scripts": { + "vscode:prepublish": "tsc -p ./", + "compile": "tsc -watch -p ./" + }, + "dependencies": { + "ssh2": "^1.6.0", + "vscode-debugadapter": "^1.45.0", + "vscode-debugprotocol": "^1.45.0" + }, + "devDependencies": { + "@types/mocha": "^5.2.6", + "@types/node": "^11.11.3", + "@types/vscode": "^1.55.0", + "mocha": "^9.1.3", + "tslint": "^5.20.1", + "typescript": "^3.9.3", + "vscode-test": "1.3.0" + }, + "__metadata": { + "id": "2fd22b8e-b3b8-4e7f-9a28-a5e2d1bdd0d4", + "publisherDisplayName": "WebFreak", + "publisherId": "e054f80a-50f9-4d80-85c8-6ff87eef6c35" + } +} diff --git a/src/backend/backend.ts b/src/backend/backend.ts new file mode 100644 index 0000000..10532a4 --- /dev/null +++ b/src/backend/backend.ts @@ -0,0 +1,165 @@ +import { MINode } from "./mi_parse"; +import { DebugProtocol } from "vscode-debugprotocol/lib/debugProtocol"; + +export type ValuesFormattingMode = "disabled" | "parseText" | "prettyPrinters"; + +export interface Breakpoint { + file?: string; + line?: number; + raw?: string; + condition: string; + countCondition?: string; +} + +export interface Thread { + id: number; + targetId: string; + name?: string; +} + +export interface Stack { + level: number; + address: string; + function: string; + fileName: string; + file: string; + line: number; +} + +export interface Variable { + name: string; + valueStr: string; + type: string; + raw?: any; +} + +export interface SSHArguments { + forwardX11: boolean; + host: string; + keyfile: string; + password: string; + useAgent: boolean; + cwd: string; + port: number; + user: string; + remotex11screen: number; + x11port: number; + x11host: string; + bootstrap: string; + sourceFileMap: { [index: string]: string }; +} + +export interface IBackend { + load(cwd: string, target: string, procArgs: string, separateConsole: string): Thenable; + ssh(args: SSHArguments, cwd: string, target: string, procArgs: string, separateConsole: string, attach: boolean): Thenable; + attach(cwd: string, executable: string, target: string): Thenable; + connect(cwd: string, executable: string, target: string): Thenable; + start(runToStart: boolean): Thenable; + stop(); + detach(); + interrupt(): Thenable; + continue(): Thenable; + next(): Thenable; + step(): Thenable; + stepOut(): Thenable; + loadBreakPoints(breakpoints: Breakpoint[]): Thenable<[boolean, Breakpoint][]>; + addBreakPoint(breakpoint: Breakpoint): Thenable<[boolean, Breakpoint]>; + removeBreakPoint(breakpoint: Breakpoint): Thenable; + clearBreakPoints(source?: string): Thenable; + getThreads(): Thenable; + getStack(startFrame: number, maxLevels: number, thread: number): Thenable; + getStackVariables(thread: number, frame: number): Thenable; + evalExpression(name: string, thread: number, frame: number): Thenable; + isReady(): boolean; + changeVariable(name: string, rawValue: string): Thenable; + examineMemory(from: number, to: number): Thenable; +} + +export class VariableObject { + name: string; + exp: string; + numchild: number; + type: string; + value: string; + threadId: string; + frozen: boolean; + dynamic: boolean; + displayhint: string; + hasMore: boolean; + id: number; + constructor(node: any) { + this.name = MINode.valueOf(node, "name"); + this.exp = MINode.valueOf(node, "exp"); + this.numchild = parseInt(MINode.valueOf(node, "numchild")); + this.type = MINode.valueOf(node, "type"); + this.value = MINode.valueOf(node, "value"); + this.threadId = MINode.valueOf(node, "thread-id"); + this.frozen = !!MINode.valueOf(node, "frozen"); + this.dynamic = !!MINode.valueOf(node, "dynamic"); + this.displayhint = MINode.valueOf(node, "displayhint"); + // TODO: use has_more when it's > 0 + this.hasMore = !!MINode.valueOf(node, "has_more"); + } + + public applyChanges(node: MINode) { + this.value = MINode.valueOf(node, "value"); + if (!!MINode.valueOf(node, "type_changed")) { + this.type = MINode.valueOf(node, "new_type"); + } + this.dynamic = !!MINode.valueOf(node, "dynamic"); + this.displayhint = MINode.valueOf(node, "displayhint"); + this.hasMore = !!MINode.valueOf(node, "has_more"); + } + + public isCompound(): boolean { + return this.numchild > 0 || + this.value === "{...}" || + (this.dynamic && (this.displayhint === "array" || this.displayhint === "map")); + } + + public toProtocolVariable(): DebugProtocol.Variable { + const res: DebugProtocol.Variable = { + name: this.exp, + evaluateName: this.name, + value: (this.value === void 0) ? "" : this.value, + type: this.type, + variablesReference: this.id + }; + return res; + } +} + +// from https://gist.github.com/justmoon/15511f92e5216fa2624b#gistcomment-1928632 +export interface MIError extends Error { + readonly name: string; + readonly message: string; + readonly source: string; +} +export interface MIErrorConstructor { + new (message: string, source: string): MIError; + readonly prototype: MIError; +} + +export const MIError: MIErrorConstructor = class MIError { + readonly name: string; + readonly message: string; + 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, + }); + Error.captureStackTrace(this, this.constructor); + } + + public toString() { + return `${this.message} (from ${this.source})`; + } +}; +Object.setPrototypeOf(MIError as any, Object.create(Error.prototype)); +MIError.prototype.constructor = MIError; diff --git a/src/backend/gdb_expansion.ts b/src/backend/gdb_expansion.ts new file mode 100644 index 0000000..143e6d5 --- /dev/null +++ b/src/backend/gdb_expansion.ts @@ -0,0 +1,258 @@ +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 errorRegex = /^\<.+?\>/; +const referenceStringRegex = /^(0x[0-9a-fA-F]+\s*)"/; +const referenceRegex = /^0x[0-9a-fA-F]+/; +const cppReferenceRegex = /^@0x[0-9a-fA-F]+/; +const nullpointerRegex = /^0x0+\b/; +const charRegex = /^(\d+) ['"]/; +const numberRegex = /^\d+(\.\d+)?/; +const pointerCombineChar = "."; + +export function isExpandable(value: string): number { + let match; + value = value.trim(); + if (value.length == 0) return 0; + else if (value.startsWith("{...}")) return 2; // lldb string/array + else if (value[0] == '{') return 1; // object + else if (value.startsWith("true")) return 0; + else if (value.startsWith("false")) return 0; + else if (match = nullpointerRegex.exec(value)) return 0; + else if (match = referenceStringRegex.exec(value)) return 0; + else if (match = referenceRegex.exec(value)) return 2; // reference + else if (match = charRegex.exec(value)) return 0; + else if (match = numberRegex.exec(value)) return 0; + else if (match = variableRegex.exec(value)) return 0; + else if (match = errorRegex.exec(value)) return 0; + else return 0; +} + +export function expandValue(variableCreate: Function, value: string, root: string = "", extra: any = undefined): any { + const parseCString = () => { + value = value.trim(); + if (value[0] != '"' && value[0] != '\'') + return ""; + let stringEnd = 1; + let inString = true; + const charStr = value[0]; + let remaining = value.substr(1); + let escaped = false; + while (inString) { + if (escaped) + escaped = false; + else if (remaining[0] == '\\') + escaped = true; + else if (remaining[0] == charStr) + inString = false; + + remaining = remaining.substr(1); + stringEnd++; + } + const str = value.substr(0, stringEnd).trim(); + value = value.substr(stringEnd).trim(); + return str; + }; + + const stack = [root]; + let parseValue, parseCommaResult, parseCommaValue, parseResult, createValue; + let variable = ""; + + const getNamespace = (variable) => { + let namespace = ""; + let prefix = ""; + stack.push(variable); + stack.forEach(name => { + prefix = ""; + if (name != "") { + if (name.startsWith("[")) + namespace = namespace + name; + else { + if (namespace) { + while (name.startsWith("*")) { + prefix += "*"; + name = name.substr(1); + } + namespace = namespace + pointerCombineChar + name; + } else + namespace = name; + } + } + }); + stack.pop(); + return prefix + namespace; + }; + + const parseTupleOrList = () => { + value = value.trim(); + if (value[0] != '{') + return undefined; + const oldContent = value; + value = value.substr(1).trim(); + if (value[0] == '}') { + value = value.substr(1).trim(); + return []; + } + if (value.startsWith("...")) { + value = value.substr(3).trim(); + if (value[0] == '}') { + value = value.substr(1).trim(); + return "<...>"; + } + } + const eqPos = value.indexOf("="); + const newValPos1 = value.indexOf("{"); + const newValPos2 = value.indexOf(","); + let newValPos = newValPos1; + if (newValPos2 != -1 && newValPos2 < newValPos1) + newValPos = newValPos2; + if (newValPos != -1 && eqPos > newValPos || eqPos == -1) { // is value list + const values = []; + stack.push("[0]"); + let val = parseValue(); + stack.pop(); + values.push(createValue("[0]", val)); + const remaining = value; + let i = 0; + while (true) { + stack.push("[" + (++i) + "]"); + if (!(val = parseCommaValue())) { + stack.pop(); + break; + } + stack.pop(); + values.push(createValue("[" + i + "]", val)); + } + value = value.substr(1).trim(); // } + return values; + } + + let result = parseResult(true); + if (result) { + const results = []; + results.push(result); + while (result = parseCommaResult(true)) + results.push(result); + value = value.substr(1).trim(); // } + return results; + } + + return undefined; + }; + + const parsePrimitive = () => { + let primitive: any; + let match; + value = value.trim(); + if (value.length == 0) + primitive = undefined; + else if (value.startsWith("true")) { + primitive = "true"; + value = value.substr(4).trim(); + } else if (value.startsWith("false")) { + primitive = "false"; + value = value.substr(5).trim(); + } else if (match = nullpointerRegex.exec(value)) { + primitive = ""; + value = value.substr(match[0].length).trim(); + } else if (match = referenceStringRegex.exec(value)) { + value = value.substr(match[1].length).trim(); + primitive = parseCString(); + } else if (match = referenceRegex.exec(value)) { + primitive = "*" + match[0]; + value = value.substr(match[0].length).trim(); + } else if (match = cppReferenceRegex.exec(value)) { + primitive = match[0]; + value = value.substr(match[0].length).trim(); + } else if (match = charRegex.exec(value)) { + primitive = match[1]; + value = value.substr(match[0].length - 1); + primitive += " " + parseCString(); + } else if (match = numberRegex.exec(value)) { + primitive = match[0]; + value = value.substr(match[0].length).trim(); + } else if (match = variableRegex.exec(value)) { + primitive = match[0]; + value = value.substr(match[0].length).trim(); + } else if (match = errorRegex.exec(value)) { + primitive = match[0]; + value = value.substr(match[0].length).trim(); + } else { + primitive = value; + } + return primitive; + }; + + parseValue = () => { + value = value.trim(); + if (value[0] == '"') + return parseCString(); + else if (value[0] == '{') + return parseTupleOrList(); + else + return parsePrimitive(); + }; + + parseResult = (pushToStack: boolean = false) => { + value = value.trim(); + const variableMatch = resultRegex.exec(value); + if (!variableMatch) + return undefined; + value = value.substr(variableMatch[0].length).trim(); + const name = variable = variableMatch[1]; + if (pushToStack) + stack.push(variable); + const val = parseValue(); + if (pushToStack) + stack.pop(); + return createValue(name, val); + }; + + createValue = (name, val) => { + let ref = 0; + if (typeof val == "object") { + ref = variableCreate(val); + val = "Object"; + } else if (typeof val == "string" && val.startsWith("*0x")) { + if (extra && MINode.valueOf(extra, "arg") == "1") { + ref = variableCreate(getNamespace("*(" + name), { arg: true }); + val = ""; + } else { + ref = variableCreate(getNamespace("*" + name)); + val = "Object@" + val; + } + } else if (typeof val == "string" && val.startsWith("@0x")) { + ref = variableCreate(getNamespace("*&" + name.substr)); + val = "Ref" + val; + } else if (typeof val == "string" && val.startsWith("<...>")) { + ref = variableCreate(getNamespace(name)); + val = "..."; + } + return { + name: name, + value: val, + variablesReference: ref + }; + }; + + parseCommaValue = () => { + value = value.trim(); + if (value[0] != ',') + return undefined; + value = value.substr(1).trim(); + return parseValue(); + }; + + parseCommaResult = (pushToStack: boolean = false) => { + value = value.trim(); + if (value[0] != ',') + return undefined; + value = value.substr(1).trim(); + return parseResult(pushToStack); + }; + + + value = value.trim(); + return parseValue(); +} diff --git a/src/backend/linux/console.ts b/src/backend/linux/console.ts new file mode 100644 index 0000000..464d697 --- /dev/null +++ b/src/backend/linux/console.ts @@ -0,0 +1,21 @@ +import * as ChildProcess from "child_process"; +import * as fs from "fs"; + +export function spawnTerminalEmulator(preferedEmulator: string): Thenable { + return new Promise((resolve, reject) => { + const ttyFileOutput = "/tmp/vscode-gdb-tty-0" + Math.floor(Math.random() * 100000000).toString(36); + ChildProcess.spawn(preferedEmulator || "x-terminal-emulator", ["-e", "sh -c \"tty > " + ttyFileOutput + " && sleep 4294967294\""]); + let it = 0; + const interval = setInterval(() => { + if (fs.existsSync(ttyFileOutput)) { + clearInterval(interval); + const tty = fs.readFileSync(ttyFileOutput).toString("utf8"); + fs.unlinkSync(ttyFileOutput); + return resolve(tty); + } + it++; + if (it > 500) + reject(); + }, 10); + }); +} diff --git a/src/backend/mi2/mi2.ts b/src/backend/mi2/mi2.ts new file mode 100644 index 0000000..81cce1f --- /dev/null +++ b/src/backend/mi2/mi2.ts @@ -0,0 +1,917 @@ +import { Breakpoint, IBackend, Thread, Stack, SSHArguments, Variable, VariableObject, MIError } from "../backend"; +import * as ChildProcess from "child_process"; +import { EventEmitter } from "events"; +import { parseMI, MINode } from '../mi_parse'; +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"; + +export function escape(str: string) { + return str.replace(/\\/g, "\\\\").replace(/"/g, "\\\""); +} + +const nonOutput = /^(?:\d*|undefined)[\*\+\=]|[\~\@\&\^]/; +const gdbMatch = /(?:\d*|undefined)\(gdb\)/; +const numRegex = /\d+/; + +function couldBeOutput(line: string) { + if (nonOutput.exec(line)) + return false; + return true; +} + +const trace = false; + +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 = {}; + // Duplicate process.env so we don't override it + for (const key in process.env) + if (process.env.hasOwnProperty(key)) + env[key] = process.env[key]; + + // Overwrite with user specified variables + for (const key in procEnv) { + if (procEnv.hasOwnProperty(key)) { + if (procEnv === null) + delete env[key]; + else + env[key] = procEnv[key]; + } + } + this.procEnv = env; + } + } + + load(cwd: string, target: string, procArgs: string, separateConsole: string): Thenable { + if (!path.isAbsolute(target)) + target = path.join(cwd, target); + return new Promise((resolve, reject) => { + this.isSSH = false; + 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)); + const promises = this.initCommands(target, cwd); + if (procArgs && procArgs.length) + promises.push(this.sendCommand("exec-arguments " + procArgs)); + if (process.platform == "win32") { + if (separateConsole !== undefined) + promises.push(this.sendCommand("gdb-set new-console on")); + Promise.all(promises).then(() => { + this.emit("debug-ready"); + resolve(undefined); + }, reject); + } else { + if (separateConsole !== undefined) { + linuxTerm.spawnTerminalEmulator(separateConsole).then(tty => { + promises.push(this.sendCommand("inferior-tty-set " + tty)); + Promise.all(promises).then(() => { + this.emit("debug-ready"); + resolve(undefined); + }, reject); + }); + } else { + Promise.all(promises).then(() => { + this.emit("debug-ready"); + resolve(undefined); + }, reject); + } + } + }); + } + + ssh(args: SSHArguments, cwd: string, target: string, procArgs: string, separateConsole: string, attach: boolean): Thenable { + return new Promise((resolve, reject) => { + this.isSSH = true; + this.sshReady = false; + this.sshConn = new Client(); + + if (separateConsole !== undefined) + this.log("stderr", "WARNING: Output to terminal emulators are not supported over SSH"); + + if (args.forwardX11) { + this.sshConn.on("x11", (info, accept, reject) => { + const xserversock = new net.Socket(); + xserversock.on("error", (err) => { + this.log("stderr", "Could not connect to local X11 server! Did you enable it in your display manager?\n" + err); + }); + xserversock.on("connect", () => { + const xclientsock = accept(); + xclientsock.pipe(xserversock).pipe(xclientsock); + }); + xserversock.connect(args.x11port, args.x11host); + }); + } + + const connectionArgs: any = { + host: args.host, + port: args.port, + username: args.user + }; + + if (args.useAgent) { + connectionArgs.agent = process.env.SSH_AUTH_SOCK; + } else if (args.keyfile) { + if (require("fs").existsSync(args.keyfile)) + connectionArgs.privateKey = require("fs").readFileSync(args.keyfile); + else { + this.log("stderr", "SSH key file does not exist!"); + this.emit("quit"); + reject(); + return; + } + } else { + connectionArgs.password = args.password; + } + + this.sshConn.on("ready", () => { + this.log("stdout", "Running " + this.application + " over ssh..."); + const execArgs: any = {}; + if (args.forwardX11) { + execArgs.x11 = { + single: false, + screen: args.remotex11screen + }; + } + let sshCMD = this.application + " " + this.preargs.concat(this.extraargs || []).join(" "); + if (args.bootstrap) sshCMD = args.bootstrap + " && " + sshCMD; + this.sshConn.exec(sshCMD, execArgs, (err, stream) => { + if (err) { + this.log("stderr", "Could not run " + this.application + "(" + sshCMD +") over ssh!"); + if (err === undefined) { + err = "" + } + this.log("stderr", err.toString()); + this.emit("quit"); + reject(); + return; + } + this.sshReady = true; + this.stream = stream; + stream.on("data", this.stdout.bind(this)); + stream.stderr.on("data", this.stderr.bind(this)); + 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) { + // Attach to local process + promises.push(this.sendCommand("target-attach " + target)); + } else if (procArgs && procArgs.length) + promises.push(this.sendCommand("exec-arguments " + procArgs)); + Promise.all(promises).then(() => { + this.emit("debug-ready"); + resolve(undefined); + }, reject); + }); + }).on("error", (err) => { + this.log("stderr", "Error running " + this.application + " over ssh!"); + if (err === undefined) { + err = "" + } + this.log("stderr", err.toString()); + this.emit("quit"); + reject(); + }).connect(connectionArgs); + }); + } + + protected 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 + // that to determine the path type used by the debugger and use the result of that test to + // select the correct API to check whether the target path is an absolute path. + const debuggerPath = path.posix.isAbsolute(cwd) ? path.posix : path.win32; + + if (!debuggerPath.isAbsolute(target)) + target = debuggerPath.join(cwd, target); + + const cmds = [ + this.sendCommand("gdb-set target-async on", true), + new Promise(resolve => { + this.sendCommand("list-features").then(done => { + this.features = done.result("features"); + resolve(undefined); + }, () => { + // Default to no supported features on error + this.features = []; + resolve(undefined); + }); + }), + this.sendCommand("environment-directory \"" + escape(cwd) + "\"", true) + ]; + if (!attach) + cmds.push(this.sendCommand("file-exec-and-symbols \"" + escape(target) + "\"")); + if (this.prettyPrint) + cmds.push(this.sendCommand("enable-pretty-printing")); + for (let cmd of this.extraCommands) { + cmds.push(this.sendCommand(cmd)); + } + + return cmds; + } + + attach(cwd: string, executable: string, target: string): Thenable { + return new Promise((resolve, reject) => { + let args = []; + if (executable && !path.isAbsolute(executable)) + executable = path.join(cwd, executable); + 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)); + const promises = this.initCommands(target, cwd, true); + if (target.startsWith("extended-remote")) { + promises.push(this.sendCommand("target-select " + target)); + if (executable) + promises.push(this.sendCommand("file-symbol-file \"" + escape(executable) + "\"")); + } else { + // Attach to local process + if (executable) + promises.push(this.sendCommand("file-exec-and-symbols \"" + escape(executable) + "\"")); + promises.push(this.sendCommand("target-attach " + target)); + } + Promise.all(promises).then(() => { + this.emit("debug-ready"); + resolve(undefined); + }, reject); + }); + } + + connect(cwd: string, executable: string, target: string): Thenable { + return new Promise((resolve, reject) => { + let args = []; + if (executable && !path.isAbsolute(executable)) + executable = path.join(cwd, executable); + args = this.preargs.concat(this.extraargs || []); + if (executable) + args = args.concat([executable]); + 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)); + const promises = this.initCommands(target, cwd, true); + promises.push(this.sendCommand("target-select remote " + target)); + Promise.all(promises).then(() => { + this.emit("debug-ready"); + resolve(undefined); + }, reject); + }); + } + + stdout(data) { + if (trace) + this.log("stderr", "stdout: " + data); + if (typeof data == "string") + this.buffer += data; + else + this.buffer += data.toString("utf8"); + const end = this.buffer.lastIndexOf('\n'); + if (end != -1) { + this.onOutput(this.buffer.substr(0, end)); + this.buffer = this.buffer.substr(end + 1); + } + if (this.buffer.length) { + if (this.onOutputPartial(this.buffer)) { + this.buffer = ""; + } + } + } + + stderr(data) { + if (typeof data == "string") + this.errbuf += data; + else + this.errbuf += data.toString("utf8"); + const end = this.errbuf.lastIndexOf('\n'); + if (end != -1) { + this.onOutputStderr(this.errbuf.substr(0, end)); + this.errbuf = this.errbuf.substr(end + 1); + } + if (this.errbuf.length) { + this.logNoNewLine("stderr", this.errbuf); + this.errbuf = ""; + } + } + + onOutputStderr(lines) { + lines = lines.split('\n'); + lines.forEach(line => { + this.log("stderr", line); + }); + } + + onOutputPartial(line) { + if (couldBeOutput(line)) { + this.logNoNewLine("stdout", line); + return true; + } + return false; + } + + onOutput(lines) { + lines = lines.split('\n'); + lines.forEach(line => { + if (couldBeOutput(line)) { + if (!gdbMatch.exec(line)) + this.log("stdout", line); + } else { + const parsed = parseMI(line); + if (this.debugOutput) + this.log("log", "GDB -> App: " + JSON.stringify(parsed)); + let handled = false; + if (parsed.token !== undefined) { + if (this.handlers[parsed.token]) { + this.handlers[parsed.token](parsed); + delete this.handlers[parsed.token]; + handled = true; + } + } + if (!handled && parsed.resultRecords && parsed.resultRecords.resultClass == "error") { + this.log("stderr", parsed.result("msg") || line); + } + if (parsed.outOfBandRecord) { + parsed.outOfBandRecord.forEach(record => { + if (record.isStream) { + this.log(record.type, record.content); + } else { + if (record.type == "exec") { + this.emit("exec-async-output", parsed); + if (record.asyncClass == "running") { + this.status = 'running'; + this.emit("running", parsed); + } + else if (record.asyncClass == "stopped") { + this.status = 'stopped'; + const reason = parsed.record("reason"); + if (reason === undefined) { + if (trace) + this.log("stderr", "stop (no reason given)"); + // attaching to a process stops, but does not provide a reason + // also python generated interrupt seems to only produce this + this.emit("step-other", parsed); + } else { + if (trace) + this.log("stderr", "stop: " + reason); + switch (reason) { + case "breakpoint-hit": + this.emit("breakpoint", parsed); + break; + case "watchpoint-trigger": + case "read-watchpoint-trigger": + case "access-watchpoint-trigger": + this.emit("watchpoint", parsed); + break; + case "function-finished": + // identical result -> send step-end + // this.emit("step-out-end", parsed); + // break; + case "location-reached": + case "end-stepping-range": + this.emit("step-end", parsed); + break; + case "watchpoint-scope": + case "solib-event": + case "syscall-entry": + case "syscall-return": + // TODO: inform the user + this.emit("step-end", parsed); + break; + case "fork": + case "vfork": + case "exec": + // TODO: inform the user, possibly add second inferiour + this.emit("step-end", parsed); + break; + case "signal-received": + this.emit("signal-stop", parsed); + break; + case "exited-normally": + this.emit("exited-normally", parsed); + break; + case "exited": // exit with error code != 0 + this.log("stderr", "Program exited with code " + parsed.record("exit-code")); + this.emit("exited-normally", parsed); + break; + // case "exited-signalled": // consider handling that explicit possible + // this.log("stderr", "Program exited because of signal " + parsed.record("signal")); + // this.emit("stoped", parsed); + // break; + + default: + this.log("console", "Not implemented stop reason (assuming exception): " + reason); + this.emit("stopped", parsed); + break; + } + } + } else + this.log("log", JSON.stringify(parsed)); + } else if (record.type == "notify") { + if (record.asyncClass == "thread-created") { + this.emit("thread-created", parsed); + } else if (record.asyncClass == "thread-exited") { + this.emit("thread-exited", parsed); + } + } + } + }); + handled = true; + } + if (parsed.token == undefined && parsed.resultRecords == undefined && parsed.outOfBandRecord.length == 0) + handled = true; + if (!handled) + this.log("log", "Unhandled: " + JSON.stringify(parsed)); + } + }); + } + + start(runToStart: boolean): Thenable { + const options: string[] = []; + if (runToStart) + options.push("--start"); + const startCommand: string = ["exec-run"].concat(options).join(" "); + return new Promise((resolve, reject) => { + this.log("console", "Running executable"); + this.sendCommand(startCommand).then((info) => { + if (info.resultRecords.resultClass == "running") + resolve(undefined); + else + reject(); + }, reject); + }); + } + + stop() { + if (this.isSSH) { + const proc = this.stream; + const to = setTimeout(() => { + proc.signal("KILL"); + }, 1000); + this.stream.on("exit", function (code) { + clearTimeout(to); + }); + this.sendRaw("-gdb-exit"); + } else { + const proc = this.process; + const to = setTimeout(() => { + process.kill(-proc.pid); + }, 1000); + this.process.on("exit", function (code) { + clearTimeout(to); + }); + this.sendRaw("-gdb-exit"); + } + } + + detach() { + const proc = this.process; + const to = setTimeout(() => { + process.kill(-proc.pid); + }, 1000); + this.process.on("exit", function (code) { + clearTimeout(to); + }); + this.sendRaw("-target-detach"); + } + + interrupt(): Thenable { + if (trace) + this.log("stderr", "interrupt"); + return new Promise((resolve, reject) => { + this.sendCommand("exec-interrupt").then((info) => { + resolve(info.resultRecords.resultClass == "done"); + }, reject); + }); + } + + continue(reverse: boolean = false): Thenable { + if (trace) + this.log("stderr", "continue"); + return new Promise((resolve, reject) => { + this.sendCommand("exec-continue" + (reverse ? " --reverse" : "")).then((info) => { + resolve(info.resultRecords.resultClass == "running"); + }, reject); + }); + } + + next(reverse: boolean = false): Thenable { + if (trace) + this.log("stderr", "next"); + return new Promise((resolve, reject) => { + this.sendCommand("exec-next" + (reverse ? " --reverse" : "")).then((info) => { + resolve(info.resultRecords.resultClass == "running"); + }, reject); + }); + } + + step(reverse: boolean = false): Thenable { + if (trace) + this.log("stderr", "step"); + return new Promise((resolve, reject) => { + this.sendCommand("exec-step" + (reverse ? " --reverse" : "")).then((info) => { + resolve(info.resultRecords.resultClass == "running"); + }, reject); + }); + } + + stepOut(reverse: boolean = false): Thenable { + if (trace) + this.log("stderr", "stepOut"); + return new Promise((resolve, reject) => { + this.sendCommand("exec-finish" + (reverse ? " --reverse" : "")).then((info) => { + resolve(info.resultRecords.resultClass == "running"); + }, reject); + }); + } + + goto(filename: string, line: number): Thenable { + if (trace) + this.log("stderr", "goto"); + return new Promise((resolve, reject) => { + const target: string = '"' + (filename ? escape(filename) + ":" : "") + line + '"'; + this.sendCommand("break-insert -t " + target).then(() => { + this.sendCommand("exec-jump " + target).then((info) => { + resolve(info.resultRecords.resultClass == "running"); + }, reject); + }, reject); + }); + } + + changeVariable(name: string, rawValue: string): Thenable { + if (trace) + this.log("stderr", "changeVariable"); + return this.sendCommand("gdb-set var " + name + "=" + rawValue); + } + + loadBreakPoints(breakpoints: Breakpoint[]): Thenable<[boolean, Breakpoint][]> { + if (trace) + this.log("stderr", "loadBreakPoints"); + const promisses = []; + breakpoints.forEach(breakpoint => { + promisses.push(this.addBreakPoint(breakpoint)); + }); + return Promise.all(promisses); + } + + setBreakPointCondition(bkptNum, condition): Thenable { + if (trace) + this.log("stderr", "setBreakPointCondition"); + return this.sendCommand("break-condition " + bkptNum + " " + condition); + } + + setEntryBreakPoint(entryPoint: string): Thenable { + return this.sendCommand("break-insert -t -f " + entryPoint); + } + + addBreakPoint(breakpoint: Breakpoint): Thenable<[boolean, Breakpoint]> { + if (trace) + this.log("stderr", "addBreakPoint"); + return new Promise((resolve, reject) => { + if (this.breakpoints.has(breakpoint)) + return resolve([false, undefined]); + let location = ""; + if (breakpoint.countCondition) { + if (breakpoint.countCondition[0] == ">") + location += "-i " + numRegex.exec(breakpoint.countCondition.substr(1))[0] + " "; + else { + const match = numRegex.exec(breakpoint.countCondition)[0]; + if (match.length != breakpoint.countCondition.length) { + this.log("stderr", "Unsupported break count expression: '" + breakpoint.countCondition + "'. Only supports 'X' for breaking once after X times or '>X' for ignoring the first X breaks"); + location += "-t "; + } else if (parseInt(match) != 0) + location += "-t -i " + parseInt(match) + " "; + } + } + if (breakpoint.raw) + location += '"' + escape(breakpoint.raw) + '"'; + else + location += '"' + escape(breakpoint.file) + ":" + breakpoint.line + '"'; + this.sendCommand("break-insert -f " + location).then((result) => { + if (result.resultRecords.resultClass == "done") { + const bkptNum = parseInt(result.result("bkpt.number")); + const newBrk = { + file: breakpoint.file ? breakpoint.file : result.result("bkpt.file"), + raw: breakpoint.raw, + line: parseInt(result.result("bkpt.line")), + condition: breakpoint.condition + }; + if (breakpoint.condition) { + this.setBreakPointCondition(bkptNum, breakpoint.condition).then((result) => { + if (result.resultRecords.resultClass == "done") { + this.breakpoints.set(newBrk, bkptNum); + resolve([true, newBrk]); + } else { + resolve([false, undefined]); + } + }, reject); + } else { + this.breakpoints.set(newBrk, bkptNum); + resolve([true, newBrk]); + } + } else { + reject(result); + } + }, reject); + }); + } + + removeBreakPoint(breakpoint: Breakpoint): Thenable { + if (trace) + this.log("stderr", "removeBreakPoint"); + return new Promise((resolve, reject) => { + if (!this.breakpoints.has(breakpoint)) + return resolve(false); + this.sendCommand("break-delete " + this.breakpoints.get(breakpoint)).then((result) => { + if (result.resultRecords.resultClass == "done") { + this.breakpoints.delete(breakpoint); + resolve(true); + } else resolve(false); + }); + }); + } + + clearBreakPoints(source?: string): Thenable { + if (trace) + this.log("stderr", "clearBreakPoints"); + return new Promise((resolve, reject) => { + const promises = []; + const breakpoints = this.breakpoints; + this.breakpoints = new Map(); + breakpoints.forEach((k, index) => { + if (index.file === source) { + promises.push(this.sendCommand("break-delete " + k).then((result) => { + if (result.resultRecords.resultClass == "done") resolve(true); + else resolve(false); + })); + } else { + this.breakpoints.set(index, k); + } + }); + Promise.all(promises).then(resolve, reject); + }); + } + + async getThreads(): Promise { + if (trace) this.log("stderr", "getThreads"); + + const command = "thread-info"; + const result = await this.sendCommand(command); + const threads = result.result("threads"); + const ret: Thread[] = []; + return threads.map(element => { + const ret: Thread = { + id: parseInt(MINode.valueOf(element, "id")), + targetId: MINode.valueOf(element, "target-id") + }; + + ret.name = MINode.valueOf(element, "details") + || undefined; + + return ret; + }); + } + + async getStack(startFrame: number, maxLevels: number, thread: number): Promise { + if (trace) this.log("stderr", "getStack"); + + const options: string[] = []; + + if (thread != 0) + options.push("--thread " + thread); + + const depth: number = (await this.sendCommand(["stack-info-depth"].concat(options).join(" "))).result("depth").valueOf(); + const lowFrame: number = startFrame ? startFrame : 0; + const highFrame: number = (maxLevels ? Math.min(depth, lowFrame + maxLevels) : depth) - 1; + + if (highFrame < lowFrame) + return []; + + options.push(lowFrame.toString()); + options.push(highFrame.toString()); + + const result = await this.sendCommand(["stack-list-frames"].concat(options).join(" ")); + const stack = result.result("stack"); + return stack.map(element => { + 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) { + if (this.isSSH) + file = path.posix.normalize(file); + else + file = path.normalize(file); + } + + let line = 0; + const lnstr = MINode.valueOf(element, "@frame.line"); + if (lnstr) + line = parseInt(lnstr); + const from = parseInt(MINode.valueOf(element, "@frame.from")); + return { + address: addr, + fileName: filename, + file: file, + function: func || from, + level: level, + line: line + }; + }); + } + + async getStackVariables(thread: number, frame: number): Promise { + if (trace) + this.log("stderr", "getStackVariables"); + + const result = await this.sendCommand(`stack-list-variables --thread ${thread} --frame ${frame} --simple-values`); + const variables = result.result("variables"); + const ret: Variable[] = []; + for (const element of variables) { + const key = MINode.valueOf(element, "name"); + const value = MINode.valueOf(element, "value"); + const type = MINode.valueOf(element, "type"); + ret.push({ + name: key, + valueStr: value, + type: type, + raw: element + }); + } + return ret; + } + + examineMemory(from: number, length: number): Thenable { + if (trace) + this.log("stderr", "examineMemory"); + return new Promise((resolve, reject) => { + this.sendCommand("data-read-memory-bytes 0x" + from.toString(16) + " " + length).then((result) => { + resolve(result.result("memory[0].contents")); + }, reject); + }); + } + + record(): Thenable { + if (trace) + this.log("stderr", "record"); + + return new Promise((resolve, reject) => { + + if (this.status != 'stopped') { + resolve(this.isRecord ? {result:'0', oldValue:'1'} : { result:'0', oldValue:'0'}); + this.log("stderr", `WARNING: The program is ${this.status} and cannot ${this.isRecord ? 'stop' : 'start'} reverse debugging.`); + return; + } + + this.isRecord = !this.isRecord; + this.sendCliCommand(this.isRecord ? "record" : "record stop").then((result) => { + if (result.resultRecords.resultClass == "done") { + resolve(this.isRecord ? {result:'1', oldValue:'0'} : { result:'1', oldValue:'1'}); + } + else { + this.isRecord = !this.isRecord; //restore + resolve(this.isRecord ? {result:'0', oldValue:'1'} : { result:'0', oldValue:'0'}); + } + }, reject); + }); + } + async evalExpression(name: string, thread: number, frame: number): Promise { + if (trace) + this.log("stderr", "evalExpression"); + + let command = "data-evaluate-expression "; + if (thread != 0) { + command += `--thread ${thread} --frame ${frame} `; + } + command += name; + + return await this.sendCommand(command); + } + + async varCreate(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}"`); + return new VariableObject(res.result("")); + } + + async varEvalExpression(name: string): Promise { + if (trace) + this.log("stderr", "varEvalExpression"); + return this.sendCommand(`var-evaluate-expression ${this.quote(name)}`); + } + + async varListChildren(name: string): Promise { + if (trace) + this.log("stderr", "varListChildren"); + //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])); + return omg; + } + + async varUpdate(name: string = "*"): Promise { + if (trace) + this.log("stderr", "varUpdate"); + return this.sendCommand(`var-update --all-values ${this.quote(name)}`); + } + + async varAssign(name: string, rawValue: string): Promise { + if (trace) + this.log("stderr", "varAssign"); + return this.sendCommand(`var-assign ${this.quote(name)} ${rawValue}`); + } + + logNoNewLine(type: string, msg: string) { + this.emit("msg", type, msg); + } + + log(type: string, msg: string) { + this.emit("msg", type, msg[msg.length - 1] == '\n' ? msg : (msg + "\n")); + } + + sendUserInput(command: string, threadId: number = 0, frameLevel: number = 0): Thenable { + if (command.startsWith("-")) { + return this.sendCommand(command.substr(1)); + } else { + return this.sendCliCommand(command, threadId, frameLevel); + } + } + + sendRaw(raw: string) { + if (this.printCalls) + this.log("log", raw); + if (this.isSSH) + this.stream.write(raw + "\n"); + else + this.process.stdin.write(raw + "\n"); + } + + sendCliCommand(command: string, threadId: number = 0, frameLevel: number = 0): Thenable { + let miCommand = "interpreter-exec "; + if (threadId != 0) { + miCommand += `--thread ${threadId} --frame ${frameLevel} `; + } + miCommand += `console "${command.replace(/[\\"']/g, '\\$&')}"`; + return this.sendCommand(miCommand); + } + + sendCommand(command: string, suppressFailure: boolean = false): Thenable { + const sel = this.currentToken++; + return new Promise((resolve, reject) => { + this.handlers[sel] = (node: MINode) => { + if (node && node.resultRecords && node.resultRecords.resultClass === "error") { + if (suppressFailure) { + this.log("stderr", `WARNING: Error executing command '${command}'`); + resolve(node); + } else + reject(new MIError(node.result("msg") || "Internal error", command)); + } else + resolve(node); + }; + this.sendRaw(sel + "-" + command); + }); + } + + isReady(): boolean { + return this.isSSH ? this.sshReady : !!this.process; + } + + protected quote(text: string): string { + // only escape if text contains non-word or non-path characters such as whitespace or quotes + return /^-|[^\w\d\/_\-\.]/g.test(text) ? ('"' + escape(text) + '"') : text; + } + + isRecord:boolean = false; + status: 'running' | 'stopped' | 'none' = 'none'; + prettyPrint: boolean = true; + printCalls: boolean; + debugOutput: boolean; + features: string[]; + public procEnv: any; + protected isSSH: boolean; + protected sshReady: boolean; + protected currentToken: number = 1; + protected handlers: { [index: number]: (info: MINode) => any } = {}; + protected breakpoints: Map = new Map(); + protected buffer: string; + protected errbuf: string; + protected process: ChildProcess.ChildProcess; + protected stream; + protected sshConn; +} diff --git a/src/backend/mi2/mi2lldb.ts b/src/backend/mi2/mi2lldb.ts new file mode 100644 index 0000000..514f891 --- /dev/null +++ b/src/backend/mi2/mi2lldb.ts @@ -0,0 +1,72 @@ +import { MI2, escape } from "./mi2"; +import { Breakpoint } from "../backend"; +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) { + // 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 + // that to determine the path type used by the debugger and use the result of that test to + // select the correct API to check whether the target path is an absolute path. + const debuggerPath = path.posix.isAbsolute(cwd) ? path.posix : path.win32; + + if (!debuggerPath.isAbsolute(target)) + target = debuggerPath.join(cwd, target); + + const cmds = [ + this.sendCommand("gdb-set target-async on"), + new Promise(resolve => { + this.sendCommand("list-features").then(done => { + this.features = done.result("features"); + resolve(undefined); + }, err => { + this.features = []; + resolve(undefined); + }); + }) + ]; + if (!attach) + cmds.push(this.sendCommand("file-exec-and-symbols \"" + escape(target) + "\"")); + for (let cmd of this.extraCommands) { + cmds.push(this.sendCliCommand(cmd)); + } + return cmds; + } + + attach(cwd: string, executable: string, target: 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)); + const promises = this.initCommands(target, cwd, true); + promises.push(this.sendCommand("file-exec-and-symbols \"" + escape(executable) + "\"")); + promises.push(this.sendCommand("target-attach " + target)); + Promise.all(promises).then(() => { + this.emit("debug-ready"); + resolve(undefined); + }, reject); + }); + } + + setBreakPointCondition(bkptNum, condition): Thenable { + return this.sendCommand("break-condition " + bkptNum + " \"" + escape(condition) + "\" 1"); + } + + 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 + // LLDB doesn't allow quoting the whole argument but rather only the file + const target: string = (filename ? '"' + escape(filename) + '":' : "") + line; + this.sendCliCommand("jump " + target).then(() => { + this.emit("step-other", null); + resolve(true); + }, reject); + }); + } +} diff --git a/src/backend/mi2/mi2mago.ts b/src/backend/mi2/mi2mago.ts new file mode 100644 index 0000000..78e9f25 --- /dev/null +++ b/src/backend/mi2/mi2mago.ts @@ -0,0 +1,45 @@ +import { MI2_LLDB } from "./mi2lldb"; +import { Stack } from "../backend"; +import { MINode } from "../mi_parse"; + +export class MI2_Mago extends MI2_LLDB { + 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 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, "file"); + const file = MINode.valueOf(element, "fullname"); + let line = 0; + const lnstr = MINode.valueOf(element, "line"); + if (lnstr) + line = parseInt(lnstr); + const from = parseInt(MINode.valueOf(element, "from")); + ret.push({ + address: addr, + fileName: filename || "", + file: file || "", + function: func || from || "", + level: level, + line: line + }); + }; + stack.forEach(element => { + if (element) + if (element[0] == "stack") { + addToStack(element[1]); + } else remaining.push(element); + }); + if (remaining.length) + addToStack(remaining); + resolve(ret); + }, reject); + }); + } +} diff --git a/src/backend/mi_parse.ts b/src/backend/mi_parse.ts new file mode 100644 index 0000000..1bdd9be --- /dev/null +++ b/src/backend/mi_parse.ts @@ -0,0 +1,313 @@ +export interface MIInfo { + token: number; + outOfBandRecord: { isStream: boolean, type: string, asyncClass: string, output: [string, any][], content: string }[]; + resultRecords: { resultClass: string, results: [string, any][] }; +} + +const octalMatch = /^[0-7]{3}/; +function parseString(str: string): string { + const ret = new Buffer(str.length * 4); + let bufIndex = 0; + + if (str[0] != '"' || str[str.length - 1] != '"') + throw new Error("Not a valid string"); + str = str.slice(1, -1); + let escaped = false; + for (let i = 0; i < str.length; i++) { + if (escaped) { + let m; + if (str[i] == '\\') + bufIndex += ret.write('\\', bufIndex); + else if (str[i] == '"') + bufIndex += ret.write('"', bufIndex); + else if (str[i] == '\'') + bufIndex += ret.write('\'', bufIndex); + else if (str[i] == 'n') + bufIndex += ret.write('\n', bufIndex); + else if (str[i] == 'r') + bufIndex += ret.write('\r', bufIndex); + else if (str[i] == 't') + bufIndex += ret.write('\t', bufIndex); + else if (str[i] == 'b') + bufIndex += ret.write('\b', bufIndex); + else if (str[i] == 'f') + bufIndex += ret.write('\f', bufIndex); + else if (str[i] == 'v') + bufIndex += ret.write('\v', bufIndex); + else if (str[i] == '0') + bufIndex += ret.write('\0', bufIndex); + else if (m = octalMatch.exec(str.substr(i))) { + ret.writeUInt8(parseInt(m[0], 8), bufIndex++); + i += 2; + } else + bufIndex += ret.write(str[i], bufIndex); + escaped = false; + } else { + if (str[i] == '\\') + escaped = true; + else if (str[i] == '"') + throw new Error("Not a valid string"); + else + bufIndex += ret.write(str[i], bufIndex); + } + } + return ret.slice(0, bufIndex).toString("utf8"); +} + +export class MINode implements MIInfo { + token: number; + outOfBandRecord: { isStream: boolean, type: string, asyncClass: string, output: [string, any][], content: string }[]; + resultRecords: { resultClass: string, results: [string, any][] }; + + constructor(token: number, info: { isStream: boolean, type: string, asyncClass: string, output: [string, any][], content: string }[], result: { resultClass: string, results: [string, any][] }) { + this.token = token; + this.outOfBandRecord = info; + this.resultRecords = result; + } + + record(path: string): any { + if (!this.outOfBandRecord) + return undefined; + return MINode.valueOf(this.outOfBandRecord[0].output, path); + } + + result(path: string): any { + if (!this.resultRecords) + return undefined; + return MINode.valueOf(this.resultRecords.results, path); + } + + static valueOf(start: any, path: string): any { + if (!start) + return undefined; + const pathRegex = /^\.?([a-zA-Z_\-][a-zA-Z0-9_\-]*)/; + const indexRegex = /^\[(\d+)\](?:$|\.)/; + path = path.trim(); + if (!path) + return start; + let current = start; + do { + let target = pathRegex.exec(path); + if (target) { + path = path.substr(target[0].length); + if (current.length && typeof current != "string") { + const found = []; + for (const element of current) { + if (element[0] == target[1]) { + found.push(element[1]); + } + } + if (found.length > 1) { + current = found; + } else if (found.length == 1) { + current = found[0]; + } else return undefined; + } else return undefined; + } else if (path[0] == '@') { + current = [current]; + path = path.substr(1); + } else { + target = indexRegex.exec(path); + if (target) { + path = path.substr(target[0].length); + const i = parseInt(target[1]); + if (current.length && typeof current != "string" && i >= 0 && i < current.length) { + current = current[i]; + } else if (i == 0) { + } else return undefined; + } else return undefined; + } + path = path.trim(); + } while (path); + return current; + } +} + +const tokenRegex = /^\d+/; +const outOfBandRecordRegex = /^(?:(\d*|undefined)([\*\+\=])|([\~\@\&]))/; +const resultRecordRegex = /^(\d*)\^(done|running|connected|error|exit)/; +const newlineRegex = /^\r\n?/; +const endRegex = /^\(gdb\)\r\n?/; +const variableRegex = /^([a-zA-Z_\-][a-zA-Z0-9_\-]*)/; +const asyncClassRegex = /^[^,\r\n]+/; + +export function parseMI(output: string): MINode { + /* + output ==> + ( + exec-async-output = [ token ] "*" ("stopped" | others) ( "," variable "=" (const | tuple | list) )* \n + status-async-output = [ token ] "+" ("stopped" | others) ( "," variable "=" (const | tuple | list) )* \n + notify-async-output = [ token ] "=" ("stopped" | others) ( "," variable "=" (const | tuple | list) )* \n + console-stream-output = "~" c-string \n + target-stream-output = "@" c-string \n + log-stream-output = "&" c-string \n + )* + [ + [ token ] "^" ("done" | "running" | "connected" | "error" | "exit") ( "," variable "=" (const | tuple | list) )* \n + ] + "(gdb)" \n + */ + + let token = undefined; + const outOfBandRecord = []; + let resultRecords = undefined; + + const asyncRecordType = { + "*": "exec", + "+": "status", + "=": "notify" + }; + const streamRecordType = { + "~": "console", + "@": "target", + "&": "log" + }; + + const parseCString = () => { + if (output[0] != '"') + return ""; + let stringEnd = 1; + let inString = true; + let remaining = output.substr(1); + let escaped = false; + while (inString) { + if (escaped) + escaped = false; + else if (remaining[0] == '\\') + escaped = true; + else if (remaining[0] == '"') + inString = false; + + remaining = remaining.substr(1); + stringEnd++; + } + let str; + try { + str = parseString(output.substr(0, stringEnd)); + } catch (e) { + str = output.substr(0, stringEnd); + } + output = output.substr(stringEnd); + return str; + }; + + let parseValue, parseCommaResult, parseCommaValue, parseResult; + + const parseTupleOrList = () => { + if (output[0] != '{' && output[0] != '[') + return undefined; + const oldContent = output; + const canBeValueList = output[0] == '['; + output = output.substr(1); + if (output[0] == '}' || output[0] == ']') { + output = output.substr(1); // ] or } + return []; + } + if (canBeValueList) { + let value = parseValue(); + if (value !== undefined) { // is value list + const values = []; + values.push(value); + const remaining = output; + while ((value = parseCommaValue()) !== undefined) + values.push(value); + output = output.substr(1); // ] + return values; + } + } + let result = parseResult(); + if (result) { + const results = []; + results.push(result); + while (result = parseCommaResult()) + results.push(result); + output = output.substr(1); // } + return results; + } + output = (canBeValueList ? '[' : '{') + output; + return undefined; + }; + + parseValue = () => { + if (output[0] == '"') + return parseCString(); + else if (output[0] == '{' || output[0] == '[') + return parseTupleOrList(); + else + return undefined; + }; + + parseResult = () => { + const variableMatch = variableRegex.exec(output); + if (!variableMatch) + return undefined; + output = output.substr(variableMatch[0].length + 1); + const variable = variableMatch[1]; + return [variable, parseValue()]; + }; + + parseCommaValue = () => { + if (output[0] != ',') + return undefined; + output = output.substr(1); + return parseValue(); + }; + + parseCommaResult = () => { + if (output[0] != ',') + return undefined; + output = output.substr(1); + return parseResult(); + }; + + let match = undefined; + + while (match = outOfBandRecordRegex.exec(output)) { + output = output.substr(match[0].length); + if (match[1] && token === undefined && match[1] !== "undefined") { + token = parseInt(match[1]); + } + + if (match[2]) { + const classMatch = asyncClassRegex.exec(output); + output = output.substring(classMatch[0].length); + const asyncRecord = { + isStream: false, + type: asyncRecordType[match[2]], + asyncClass: classMatch[0], + output: [] + }; + let result; + while (result = parseCommaResult()) + asyncRecord.output.push(result); + outOfBandRecord.push(asyncRecord); + } else if (match[3]) { + const streamRecord = { + isStream: true, + type: streamRecordType[match[3]], + content: parseCString() + }; + outOfBandRecord.push(streamRecord); + } + + output = output.replace(newlineRegex, ""); + } + + if (match = resultRecordRegex.exec(output)) { + output = output.substr(match[0].length); + if (match[1] && token === undefined) { + token = parseInt(match[1]); + } + resultRecords = { + resultClass: match[2], + results: [] + }; + let result; + while (result = parseCommaResult()) + resultRecords.results.push(result); + + output = output.replace(newlineRegex, ""); + } + + return new MINode(token, outOfBandRecord || [], resultRecords); +} diff --git a/src/frontend/extension.ts b/src/frontend/extension.ts new file mode 100644 index 0000000..4cc7f26 --- /dev/null +++ b/src/frontend/extension.ts @@ -0,0 +1,207 @@ +import * as vscode from "vscode"; +import * as net from "net"; +import * as fs from "fs"; +import * as path from "path"; +import * as os from "os"; + +export function activate(context: vscode.ExtensionContext) { + context.subscriptions.push(vscode.workspace.registerTextDocumentContentProvider("debugmemory", new MemoryContentProvider())); + context.subscriptions.push(vscode.commands.registerCommand("code-debug.examineMemoryLocation", examineMemory)); + context.subscriptions.push(vscode.commands.registerCommand("code-debug.getFileNameNoExt", () => { + if (!vscode.window.activeTextEditor || !vscode.window.activeTextEditor.document || !vscode.window.activeTextEditor.document.fileName) { + vscode.window.showErrorMessage("No editor with valid file name active"); + return; + } + const fileName = vscode.window.activeTextEditor.document.fileName; + const ext = path.extname(fileName); + return fileName.substr(0, fileName.length - ext.length); + })); + context.subscriptions.push(vscode.commands.registerCommand("code-debug.startRecord", handleRecord)); + context.subscriptions.push(vscode.commands.registerCommand("code-debug.stopRecord", handleRecord)); + context.subscriptions.push(vscode.commands.registerCommand("code-debug.getFileBasenameNoExt", () => { + if (!vscode.window.activeTextEditor || !vscode.window.activeTextEditor.document || !vscode.window.activeTextEditor.document.fileName) { + vscode.window.showErrorMessage("No editor with valid file name active"); + return; + } + const fileName = path.basename(vscode.window.activeTextEditor.document.fileName); + const ext = path.extname(fileName); + return fileName.substr(0, fileName.length - ext.length); + })); + + vscode.debug.onDidStartDebugSession(()=>{ + vscode.commands.executeCommand('setContext', 'code-debug.enableStartRecord', true); + }); +} + +const memoryLocationRegex = /^0x[0-9a-f]+$/; + +function getMemoryRange(range: string) { + if (!range) + return undefined; + range = range.replace(/\s+/g, "").toLowerCase(); + let index; + if ((index = range.indexOf("+")) != -1) { + const from = range.substr(0, index); + let length = range.substr(index + 1); + if (!memoryLocationRegex.exec(from)) + return undefined; + if (memoryLocationRegex.exec(length)) + length = parseInt(length.substr(2), 16).toString(); + return "from=" + encodeURIComponent(from) + "&length=" + encodeURIComponent(length); + } else if ((index = range.indexOf("-")) != -1) { + const from = range.substr(0, index); + const to = range.substr(index + 1); + if (!memoryLocationRegex.exec(from)) + return undefined; + if (!memoryLocationRegex.exec(to)) + return undefined; + return "from=" + encodeURIComponent(from) + "&to=" + encodeURIComponent(to); + } else if (memoryLocationRegex.exec(range)) + return "at=" + encodeURIComponent(range); + else return undefined; +} + +function execCmd(func:any){ + const socketlists = path.join(os.tmpdir(), "code-debug-sockets"); + if (!fs.existsSync(socketlists)) { + if (process.platform == "win32") + return vscode.window.showErrorMessage("This command is not available on windows"); + else + return vscode.window.showErrorMessage("No debugging sessions available"); + } + fs.readdir(socketlists, (err, files) => { + if (err) { + if (process.platform == "win32") + return vscode.window.showErrorMessage("This command is not available on windows"); + else + return vscode.window.showErrorMessage("No debugging sessions available"); + } + + if (files.length == 1) + func(files[0]); + else if (files.length > 0) + vscode.window.showQuickPick(files, { placeHolder: "Running debugging instance" }).then(file => func(file)); + else if (process.platform == "win32") + return vscode.window.showErrorMessage("This command is not available on windows"); + else + vscode.window.showErrorMessage("No debugging sessions available"); + }); +} + +function examineMemory() { + const pickedFile = (file) => { + 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))); + }); + }; + execCmd(pickedFile); +} + +function handleRecord() { + const record = (file) => { + const conn = net.connect(path.join(os.tmpdir(), "code-debug-sockets", file)); + conn.write("record"); + conn.once("data", data => { + if (data){ + let ret = JSON.parse(data.toString()); + if (ret && ret.result == '1'){ + console.log('successful to satrt reverse debugging.'); + } + else { + vscode.window.showErrorMessage(`Failed to ${ret.oldValue == '1' ? 'stop' : 'start'} reverse debugging`); + } + vscode.commands.executeCommand('setContext', 'code-debug.enableStartRecord', ret.oldValue == '1' ? true : false); + } + conn.destroy(); + }); + }; + execCmd(record); +} + +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 highlightAt = -1; + const splits = uri.query.split("&"); + if (splits[0].split("=")[0] == "at") { + const loc = parseInt(splits[0].split("=")[1].substr(2), 16); + highlightAt = 64; + from = Math.max(loc - 64, 0); + to = Math.max(loc + 768, 0); + } else if (splits[0].split("=")[0] == "from") { + from = parseInt(splits[0].split("=")[1].substr(2), 16); + if (splits[1].split("=")[0] == "to") { + to = parseInt(splits[1].split("=")[1].substr(2), 16); + } else if (splits[1].split("=")[0] == "length") { + to = from + parseInt(splits[1].split("=")[1]); + } else return reject("Invalid Range"); + } else return reject("Invalid Range"); + if (to < from) + return reject("Negative Range"); + conn.write("examineMemory " + JSON.stringify([from, to - from + 1])); + conn.once("data", data => { + let formattedCode = " 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F\n"; + var index: number = from; + const hexString = data.toString(); + let x = 0; + let asciiLine = ""; + let byteNo = 0; + for (let i = 0; i < hexString.length; i += 2) { + if (x == 0) { + var addr = index.toString(16); + while (addr.length < 16) addr = '0' + addr; + formattedCode += addr + " "; + } + index++; + + const digit = hexString.substr(i, 2); + const digitNum = parseInt(digit, 16); + if (digitNum >= 32 && digitNum <= 126) + asciiLine += String.fromCharCode(digitNum); + else + asciiLine += "."; + + if (highlightAt == byteNo) { + formattedCode = formattedCode.slice(0, -1) + "[" + digit + "]"; + } else { + formattedCode += digit + " "; + } + + if (x == 7) + formattedCode += " "; + + if (++x >= 16) { + formattedCode += " " + asciiLine + "\n"; + x = 0; + asciiLine = ""; + } + byteNo++; + } + if (x > 0) { + for (let i = 0; i <= 16 - x; i++) { + formattedCode += " "; + } + if (x >= 8) + formattedCode = formattedCode.slice(0, -2); + else + formattedCode = formattedCode.slice(0, -1); + formattedCode += asciiLine; + } + resolve(center("Memory Range from 0x" + from.toString(16) + " to 0x" + to.toString(16), 84) + "\n\n" + formattedCode); + conn.destroy(); + }); + }); + } +} + +function center(str: string, width: number): string { + var left = true; + while (str.length < width) { + if (left) str = ' ' + str; + else str = str + ' '; + left = !left; + } + return str; +} diff --git a/src/gdb.ts b/src/gdb.ts new file mode 100644 index 0000000..6934e43 --- /dev/null +++ b/src/gdb.ts @@ -0,0 +1,173 @@ +import { MI2DebugSession, RunCommand } from './mibase'; +import { DebugSession, InitializedEvent, TerminatedEvent, StoppedEvent, OutputEvent, Thread, StackFrame, Scope, Source, Handles } from 'vscode-debugadapter'; +import { DebugProtocol } from 'vscode-debugprotocol'; +import { MI2, escape } from "./backend/mi2/mi2"; +import { SSHArguments, ValuesFormattingMode } from './backend/backend'; + +export interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArguments { + cwd: string; + target: string; + gdbpath: string; + env: any; + debugger_args: string[]; + pathSubstitutions: { [index: string]: string }; + arguments: string; + terminal: string; + autorun: string[]; + stopAtEntry: boolean | string; + ssh: SSHArguments; + valuesFormatting: ValuesFormattingMode; + printCalls: boolean; + showDevDebugOutput: boolean; +} + +export interface AttachRequestArguments extends DebugProtocol.AttachRequestArguments { + cwd: string; + target: string; + gdbpath: string; + env: any; + debugger_args: string[]; + pathSubstitutions: { [index: string]: string }; + executable: string; + remote: boolean; + autorun: string[]; + stopAtConnect: boolean; + stopAtEntry: boolean | string; + ssh: SSHArguments; + valuesFormatting: ValuesFormattingMode; + printCalls: boolean; + showDevDebugOutput: boolean; +} + +class GDBDebugSession extends MI2DebugSession { + protected initializeRequest(response: DebugProtocol.InitializeResponse, args: DebugProtocol.InitializeRequestArguments): void { + response.body.supportsGotoTargetsRequest = true; + response.body.supportsHitConditionalBreakpoints = true; + response.body.supportsConfigurationDoneRequest = true; + response.body.supportsConditionalBreakpoints = true; + response.body.supportsFunctionBreakpoints = true; + response.body.supportsEvaluateForHovers = true; + response.body.supportsSetVariable = true; + response.body.supportsStepBack = 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); + this.setPathSubstitutions(args.pathSubstitutions); + this.initDebugger(); + this.quit = false; + this.attached = false; + this.initialRunCommand = RunCommand.RUN; + this.isSSH = false; + this.started = false; + this.crashed = false; + this.setValuesFormattingMode(args.valuesFormatting); + this.miDebugger.printCalls = !!args.printCalls; + this.miDebugger.debugOutput = !!args.showDevDebugOutput; + this.stopAtEntry = args.stopAtEntry; + if (args.ssh !== undefined) { + if (args.ssh.forwardX11 === undefined) + args.ssh.forwardX11 = true; + if (args.ssh.port === undefined) + args.ssh.port = 22; + if (args.ssh.x11port === undefined) + args.ssh.x11port = 6000; + if (args.ssh.x11host === undefined) + args.ssh.x11host = "localhost"; + if (args.ssh.remotex11screen === undefined) + args.ssh.remotex11screen = 0; + this.isSSH = true; + this.setSourceFileMap(args.ssh.sourceFileMap, args.ssh.cwd, args.cwd); + this.miDebugger.ssh(args.ssh, args.ssh.cwd, args.target, args.arguments, args.terminal, false).then(() => { + if (args.autorun) + args.autorun.forEach(command => { + this.miDebugger.sendUserInput(command); + }); + this.sendResponse(response); + }, err => { + this.sendErrorResponse(response, 102, `Failed to SSH: ${err.toString()}`); + }); + } else { + this.miDebugger.load(args.cwd, args.target, args.arguments, args.terminal).then(() => { + if (args.autorun) + args.autorun.forEach(command => { + this.miDebugger.sendUserInput(command); + }); + this.sendResponse(response); + }, err => { + this.sendErrorResponse(response, 103, `Failed to load MI Debugger: ${err.toString()}`); + }); + } + } + + protected attachRequest(response: DebugProtocol.AttachResponse, args: AttachRequestArguments): void { + this.miDebugger = new MI2(args.gdbpath || "gdb", ["-q", "--interpreter=mi2"], args.debugger_args, args.env); + this.setPathSubstitutions(args.pathSubstitutions); + this.initDebugger(); + this.quit = false; + this.attached = !args.remote; + this.initialRunCommand = !!args.stopAtConnect ? RunCommand.NONE : RunCommand.CONTINUE; + this.isSSH = false; + this.setValuesFormattingMode(args.valuesFormatting); + this.miDebugger.printCalls = !!args.printCalls; + this.miDebugger.debugOutput = !!args.showDevDebugOutput; + this.stopAtEntry = args.stopAtEntry; + if (args.ssh !== undefined) { + if (args.ssh.forwardX11 === undefined) + args.ssh.forwardX11 = true; + if (args.ssh.port === undefined) + args.ssh.port = 22; + if (args.ssh.x11port === undefined) + args.ssh.x11port = 6000; + if (args.ssh.x11host === undefined) + args.ssh.x11host = "localhost"; + if (args.ssh.remotex11screen === undefined) + args.ssh.remotex11screen = 0; + this.isSSH = true; + this.setSourceFileMap(args.ssh.sourceFileMap, args.ssh.cwd, args.cwd); + this.miDebugger.ssh(args.ssh, args.ssh.cwd, args.target, "", undefined, true).then(() => { + if (args.autorun) + args.autorun.forEach(command => { + this.miDebugger.sendUserInput(command); + }); + this.sendResponse(response); + }, err => { + this.sendErrorResponse(response, 102, `Failed to SSH: ${err.toString()}`); + }); + } else { + if (args.remote) { + this.miDebugger.connect(args.cwd, args.executable, args.target).then(() => { + if (args.autorun) + args.autorun.forEach(command => { + this.miDebugger.sendUserInput(command); + }); + this.sendResponse(response); + }, err => { + this.sendErrorResponse(response, 102, `Failed to attach: ${err.toString()}`); + }); + } else { + this.miDebugger.attach(args.cwd, args.executable, args.target).then(() => { + if (args.autorun) + args.autorun.forEach(command => { + this.miDebugger.sendUserInput(command); + }); + this.sendResponse(response); + }, err => { + this.sendErrorResponse(response, 101, `Failed to attach: ${err.toString()}`); + }); + } + } + } + + // Add extra commands for source file path substitution in GDB-specific syntax + protected setPathSubstitutions(substitutions: { [index: string]: string }): void { + if (substitutions) { + Object.keys(substitutions).forEach(source => { + this.miDebugger.extraCommands.push("gdb-set substitute-path \"" + escape(source) + "\" \"" + escape(substitutions[source]) + "\""); + }) + } + } +} + +DebugSession.run(GDBDebugSession); diff --git a/src/lldb.ts b/src/lldb.ts new file mode 100644 index 0000000..9c4a979 --- /dev/null +++ b/src/lldb.ts @@ -0,0 +1,126 @@ +import { MI2DebugSession, RunCommand } from './mibase'; +import { DebugSession, InitializedEvent, TerminatedEvent, StoppedEvent, OutputEvent, Thread, StackFrame, Scope, Source, Handles } from 'vscode-debugadapter'; +import { DebugProtocol } from 'vscode-debugprotocol'; +import { MI2_LLDB } from "./backend/mi2/mi2lldb"; +import { SSHArguments, ValuesFormattingMode } from './backend/backend'; + +export interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArguments { + cwd: string; + target: string; + lldbmipath: string; + env: any; + debugger_args: string[]; + pathSubstitutions: { [index: string]: string }; + arguments: string; + autorun: string[]; + stopAtEntry: boolean | string; + ssh: SSHArguments; + valuesFormatting: ValuesFormattingMode; + printCalls: boolean; + showDevDebugOutput: boolean; +} + +export interface AttachRequestArguments extends DebugProtocol.AttachRequestArguments { + cwd: string; + target: string; + lldbmipath: string; + env: any; + debugger_args: string[]; + pathSubstitutions: { [index: string]: string }; + executable: string; + autorun: string[]; + stopAtConnect: boolean; + stopAtEntry: boolean | string; + valuesFormatting: ValuesFormattingMode; + printCalls: boolean; + showDevDebugOutput: boolean; +} + +class LLDBDebugSession extends MI2DebugSession { + protected initializeRequest(response: DebugProtocol.InitializeResponse, args: DebugProtocol.InitializeRequestArguments): void { + response.body.supportsGotoTargetsRequest = true; + response.body.supportsHitConditionalBreakpoints = true; + response.body.supportsConfigurationDoneRequest = true; + response.body.supportsConditionalBreakpoints = true; + response.body.supportsFunctionBreakpoints = true; + response.body.supportsEvaluateForHovers = true; + 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); + this.setPathSubstitutions(args.pathSubstitutions); + this.initDebugger(); + this.quit = false; + this.attached = false; + this.initialRunCommand = RunCommand.RUN; + this.isSSH = false; + this.started = false; + this.crashed = false; + this.setValuesFormattingMode(args.valuesFormatting); + this.miDebugger.printCalls = !!args.printCalls; + this.miDebugger.debugOutput = !!args.showDevDebugOutput; + this.stopAtEntry = args.stopAtEntry; + if (args.ssh !== undefined) { + if (args.ssh.forwardX11 === undefined) + args.ssh.forwardX11 = true; + if (args.ssh.port === undefined) + args.ssh.port = 22; + if (args.ssh.x11port === undefined) + args.ssh.x11port = 6000; + if (args.ssh.x11host === undefined) + args.ssh.x11host = "localhost"; + if (args.ssh.remotex11screen === undefined) + args.ssh.remotex11screen = 0; + this.isSSH = true; + this.setSourceFileMap(args.ssh.sourceFileMap, args.ssh.cwd, args.cwd); + this.miDebugger.ssh(args.ssh, args.ssh.cwd, args.target, args.arguments, undefined, false).then(() => { + if (args.autorun) + args.autorun.forEach(command => { + this.miDebugger.sendUserInput(command); + }); + this.sendResponse(response); + }); + } else { + this.miDebugger.load(args.cwd, args.target, args.arguments, undefined).then(() => { + if (args.autorun) + args.autorun.forEach(command => { + this.miDebugger.sendUserInput(command); + }); + this.sendResponse(response); + }); + } + } + + protected attachRequest(response: DebugProtocol.AttachResponse, args: AttachRequestArguments): void { + this.miDebugger = new MI2_LLDB(args.lldbmipath || "lldb-mi", [], args.debugger_args, args.env); + this.setPathSubstitutions(args.pathSubstitutions); + this.initDebugger(); + this.quit = false; + this.attached = true; + this.initialRunCommand = !!args.stopAtConnect ? RunCommand.NONE : RunCommand.CONTINUE; + this.isSSH = false; + this.setValuesFormattingMode(args.valuesFormatting); + this.miDebugger.printCalls = !!args.printCalls; + this.miDebugger.debugOutput = !!args.showDevDebugOutput; + this.stopAtEntry = args.stopAtEntry; + this.miDebugger.attach(args.cwd, args.executable, args.target).then(() => { + if (args.autorun) + args.autorun.forEach(command => { + this.miDebugger.sendUserInput(command); + }); + this.sendResponse(response); + }); + } + + // Add extra commands for source file path substitution in LLDB-specific syntax + protected setPathSubstitutions(substitutions: { [index: string]: string }): void { + if (substitutions) { + Object.keys(substitutions).forEach(source => { + this.miDebugger.extraCommands.push("settings append target.source-map " + source + " " + substitutions[source]); + }) + } + } +} + +DebugSession.run(LLDBDebugSession); diff --git a/src/mago.ts b/src/mago.ts new file mode 100644 index 0000000..387138d --- /dev/null +++ b/src/mago.ts @@ -0,0 +1,93 @@ +import { MI2DebugSession, RunCommand } from './mibase'; +import { DebugSession, InitializedEvent, TerminatedEvent, StoppedEvent, OutputEvent, Thread, StackFrame, Scope, Source, Handles } from 'vscode-debugadapter'; +import { DebugProtocol } from 'vscode-debugprotocol'; +import { MI2_Mago } from "./backend/mi2/mi2mago"; +import { SSHArguments, ValuesFormattingMode } from './backend/backend'; + +export interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArguments { + cwd: string; + target: string; + magomipath: string; + env: any; + debugger_args: string[]; + arguments: string; + autorun: string[]; + valuesFormatting: ValuesFormattingMode; + printCalls: boolean; + showDevDebugOutput: boolean; +} + +export interface AttachRequestArguments extends DebugProtocol.AttachRequestArguments { + cwd: string; + target: string; + magomipath: string; + env: any; + debugger_args: string[]; + executable: string; + autorun: string[]; + stopAtConnect: boolean; + valuesFormatting: ValuesFormattingMode; + printCalls: boolean; + showDevDebugOutput: boolean; +} + +class MagoDebugSession extends MI2DebugSession { + public constructor(debuggerLinesStartAt1: boolean, isServer: boolean = false) { + super(debuggerLinesStartAt1, isServer); + } + + protected initializeRequest(response: DebugProtocol.InitializeResponse, args: DebugProtocol.InitializeRequestArguments): void { + response.body.supportsHitConditionalBreakpoints = true; + response.body.supportsConfigurationDoneRequest = true; + response.body.supportsConditionalBreakpoints = true; + response.body.supportsFunctionBreakpoints = true; + response.body.supportsEvaluateForHovers = true; + this.sendResponse(response); + } + + getThreadID() { + 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); + this.initDebugger(); + this.quit = false; + this.attached = false; + this.initialRunCommand = RunCommand.RUN; + this.isSSH = false; + this.started = false; + this.crashed = false; + this.setValuesFormattingMode(args.valuesFormatting); + this.miDebugger.printCalls = !!args.printCalls; + this.miDebugger.debugOutput = !!args.showDevDebugOutput; + this.miDebugger.load(args.cwd, args.target, args.arguments, undefined).then(() => { + if (args.autorun) + args.autorun.forEach(command => { + this.miDebugger.sendUserInput(command); + }); + this.sendResponse(response); + }); + } + + protected attachRequest(response: DebugProtocol.AttachResponse, args: AttachRequestArguments): void { + this.miDebugger = new MI2_Mago(args.magomipath || "mago-mi", [], args.debugger_args, args.env); + this.initDebugger(); + this.quit = false; + this.attached = true; + this.initialRunCommand = !!args.stopAtConnect ? RunCommand.NONE : RunCommand.CONTINUE; + this.isSSH = false; + this.setValuesFormattingMode(args.valuesFormatting); + this.miDebugger.printCalls = !!args.printCalls; + this.miDebugger.debugOutput = !!args.showDevDebugOutput; + this.miDebugger.attach(args.cwd, args.executable, args.target).then(() => { + if (args.autorun) + args.autorun.forEach(command => { + this.miDebugger.sendUserInput(command); + }); + this.sendResponse(response); + }); + } +} + +DebugSession.run(MagoDebugSession); diff --git a/src/mibase.ts b/src/mibase.ts new file mode 100644 index 0000000..5ca012e --- /dev/null +++ b/src/mibase.ts @@ -0,0 +1,798 @@ +import * as DebugAdapter from 'vscode-debugadapter'; +import { DebugSession, InitializedEvent, TerminatedEvent, StoppedEvent, ThreadEvent, OutputEvent, ContinuedEvent, Thread, StackFrame, Scope, Source, Handles } from 'vscode-debugadapter'; +import { DebugProtocol } from 'vscode-debugprotocol'; +import { Breakpoint, IBackend, Variable, VariableObject, ValuesFormattingMode, MIError } from './backend/backend'; +import { MINode } from './backend/mi_parse'; +import { expandValue, isExpandable } from './backend/gdb_expansion'; +import { MI2 } from './backend/mi2/mi2'; +import { posix } from "path"; +import * as systemPath from "path"; +import * as net from "net"; +import * as os from "os"; +import * as fs from "fs"; + +class ExtendedVariable { + constructor(public name, public options) { + } +} + +export enum RunCommand { CONTINUE, RUN, NONE } + +const STACK_HANDLES_START = 1000; +const VAR_HANDLES_START = 512 * 256 + 1000; + +export class MI2DebugSession extends DebugSession { + protected variableHandles = new Handles(VAR_HANDLES_START); + protected variableHandlesReverse: { [id: string]: number } = {}; + protected useVarObjects: boolean; + protected quit: boolean; + protected attached: boolean; + protected initialRunCommand: RunCommand; + protected stopAtEntry: boolean | string; + protected isSSH: boolean; + protected sourceFileMap: Map; + protected started: boolean; + protected crashed: boolean; + protected miDebugger: MI2; + protected commandServer: net.Server; + protected serverPath: string; + + public constructor(debuggerLinesStartAt1: boolean, isServer: boolean = false) { + super(debuggerLinesStartAt1, isServer); + } + + protected async initDebugger() { + this.miDebugger.on("launcherror", this.launchError.bind(this)); + this.miDebugger.on("quit", this.quitEvent.bind(this)); + this.miDebugger.on("exited-normally", this.quitEvent.bind(this)); + this.miDebugger.on("stopped", this.stopEvent.bind(this)); + this.miDebugger.on("msg", this.handleMsg.bind(this)); + this.miDebugger.on("breakpoint", this.handleBreakpoint.bind(this)); + this.miDebugger.on("watchpoint", this.handleBreak.bind(this)); // consider to parse old/new, too (otherwise it is in the console only) + this.miDebugger.on("step-end", this.handleBreak.bind(this)); + // this.miDebugger.on("step-out-end", this.handleBreak.bind(this)); // was combined into step-end + this.miDebugger.on("step-other", this.handleBreak.bind(this)); + this.miDebugger.on("signal-stop", this.handlePause.bind(this)); + this.miDebugger.on("thread-created", this.threadCreatedEvent.bind(this)); + this.miDebugger.on("thread-exited", this.threadExitedEvent.bind(this)); + this.miDebugger.once("debug-ready", (() => this.sendEvent(new InitializedEvent()))); + try { + const socketlists = systemPath.join(os.tmpdir(), "code-debug-sockets"); + if (fs.existsSync(socketlists)) { + await cleanInvalidSocketPath(socketlists); + } + this.commandServer = net.createServer(c => { + c.on("data", data => { + const rawCmd = data.toString(); + const spaceIndex = rawCmd.indexOf(" "); + let func = rawCmd; + let args = []; + if (spaceIndex != -1) { + func = rawCmd.substr(0, spaceIndex); + args = JSON.parse(rawCmd.substr(spaceIndex + 1)); + } + Promise.resolve(this.miDebugger[func].apply(this.miDebugger, args)).then(data => { + c.write(data instanceof Object ? JSON.stringify(data).toString() : data.toString()); + }); + }); + }); + this.commandServer.on("error", err => { + if (process.platform != "win32") + this.handleMsg("stderr", "Code-Debug WARNING: Utility Command Server: Error in command socket " + err.toString() + "\nCode-Debug WARNING: The examine memory location command won't work"); + }); + if (!fs.existsSync(systemPath.join(os.tmpdir(), "code-debug-sockets"))) + fs.mkdirSync(systemPath.join(os.tmpdir(), "code-debug-sockets")); + this.commandServer.listen(this.serverPath = systemPath.join(os.tmpdir(), "code-debug-sockets", ("Debug-Instance-" + Math.floor(Math.random() * 36 * 36 * 36 * 36).toString(36)).toLowerCase())); + } catch (e) { + if (process.platform != "win32") + this.handleMsg("stderr", "Code-Debug WARNING: Utility Command Server: Failed to start " + e.toString() + "\nCode-Debug WARNING: The examine memory location command won't work"); + } + } + + protected setValuesFormattingMode(mode: ValuesFormattingMode) { + switch (mode) { + case "disabled": + this.useVarObjects = true; + this.miDebugger.prettyPrint = false; + break; + case "prettyPrinters": + this.useVarObjects = true; + this.miDebugger.prettyPrint = true; + break; + case "parseText": + default: + this.useVarObjects = false; + this.miDebugger.prettyPrint = false; + } + } + + protected handleMsg(type: string, msg: string) { + if (type == "target") + type = "stdout"; + if (type == "log") + type = "stderr"; + this.sendEvent(new OutputEvent(msg, type)); + } + + protected handleBreakpoint(info: MINode) { + const event = new StoppedEvent("breakpoint", parseInt(info.record("thread-id"))); + (event as DebugProtocol.StoppedEvent).body.allThreadsStopped = info.record("stopped-threads") == "all"; + this.sendEvent(event); + } + + protected handleBreak(info?: MINode) { + const event = new StoppedEvent("step", info ? parseInt(info.record("thread-id")) : 1); + (event as DebugProtocol.StoppedEvent).body.allThreadsStopped = info ? info.record("stopped-threads") == "all" : true; + this.sendEvent(event); + } + + protected handlePause(info: MINode) { + const event = new StoppedEvent("user request", parseInt(info.record("thread-id"))); + (event as DebugProtocol.StoppedEvent).body.allThreadsStopped = info.record("stopped-threads") == "all"; + this.sendEvent(event); + } + + protected stopEvent(info: MINode) { + if (!this.started) + this.crashed = true; + if (!this.quit) { + const event = new StoppedEvent("exception", parseInt(info.record("thread-id"))); + (event as DebugProtocol.StoppedEvent).body.allThreadsStopped = info.record("stopped-threads") == "all"; + this.sendEvent(event); + } + } + + protected threadCreatedEvent(info: MINode) { + this.sendEvent(new ThreadEvent("started", info.record("id"))); + } + + protected threadExitedEvent(info: MINode) { + this.sendEvent(new ThreadEvent("exited", info.record("id"))); + } + + protected quitEvent() { + this.quit = true; + this.sendEvent(new TerminatedEvent()); + + if (this.serverPath) + fs.unlink(this.serverPath, (err) => { + console.error("Failed to unlink debug server"); + }); + } + + protected launchError(err: any) { + this.handleMsg("stderr", "Could not start debugger process, does the program exist in filesystem?\n"); + this.handleMsg("stderr", err.toString() + "\n"); + this.quitEvent(); + } + + protected disconnectRequest(response: DebugProtocol.DisconnectResponse, args: DebugProtocol.DisconnectArguments): void { + if (this.attached) + this.miDebugger.detach(); + else + this.miDebugger.stop(); + this.commandServer.close(); + this.commandServer = undefined; + this.sendResponse(response); + } + + protected async setVariableRequest(response: DebugProtocol.SetVariableResponse, args: DebugProtocol.SetVariableArguments): Promise { + try { + if (this.useVarObjects) { + let name = args.name; + if (args.variablesReference >= VAR_HANDLES_START) { + const parent = this.variableHandles.get(args.variablesReference) as VariableObject; + name = `${parent.name}.${name}`; + } + + const res = await this.miDebugger.varAssign(name, args.value); + response.body = { + value: res.result("value") + }; + } else { + await this.miDebugger.changeVariable(args.name, args.value); + response.body = { + value: args.value + }; + } + this.sendResponse(response); + } catch (err) { + this.sendErrorResponse(response, 11, `Could not continue: ${err}`); + } + } + + protected setFunctionBreakPointsRequest(response: DebugProtocol.SetFunctionBreakpointsResponse, args: DebugProtocol.SetFunctionBreakpointsArguments): void { + const all = []; + 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 = []; + brkpoints.forEach(brkp => { + if (brkp[0]) + finalBrks.push({ line: brkp[1].line }); + }); + response.body = { + breakpoints: finalBrks + }; + this.sendResponse(response); + }, msg => { + this.sendErrorResponse(response, 10, msg.toString()); + }); + } + + protected setBreakPointsRequest(response: DebugProtocol.SetBreakpointsResponse, args: DebugProtocol.SetBreakpointsArguments): void { + this.miDebugger.clearBreakPoints(args.source.path).then(() => { + let path = args.source.path; + if (this.isSSH) { + // convert local path to ssh path + if (path.indexOf("\\") != -1) + path = path.replace(/\\/g, "/").toLowerCase(); + // ideCWD is the local path, gdbCWD is the ssh path, both had the replacing \ -> / done up-front + for (let [ideCWD, gdbCWD] of this.sourceFileMap) { + if (path.startsWith(ideCWD)) { + path = posix.relative(ideCWD, path); + path = posix.join(gdbCWD, path); // we combined a guaranteed path with relative one (works with GDB both on GNU/Linux and Win32) + break; + } + } + } + const all = args.breakpoints.map(brk => { + return this.miDebugger.addBreakPoint({ file: path, line: brk.line, condition: brk.condition, countCondition: brk.hitCondition }); + }); + Promise.all(all).then(brkpoints => { + const finalBrks = []; + brkpoints.forEach(brkp => { + // TODO: Currently all breakpoints returned are marked as verified, + // which leads to verified breakpoints on a broken lldb. + if (brkp[0]) + finalBrks.push(new DebugAdapter.Breakpoint(true, brkp[1].line)); + }); + response.body = { + breakpoints: finalBrks + }; + this.sendResponse(response); + }, msg => { + this.sendErrorResponse(response, 9, msg.toString()); + }); + }, msg => { + this.sendErrorResponse(response, 9, msg.toString()); + }); + } + + protected threadsRequest(response: DebugProtocol.ThreadsResponse): void { + if (!this.miDebugger) { + this.sendResponse(response); + return; + } + this.miDebugger.getThreads().then(threads => { + response.body = { + threads: [] + }; + for (const thread of threads) { + let threadName = thread.name || thread.targetId || ""; + response.body.threads.push(new Thread(thread.id, thread.id + ":" + threadName)); + } + this.sendResponse(response); + }).catch(error => { + this.sendErrorResponse(response, 17, `Could not get threads: ${error}`); + }); + } + + // Supports 65535 threads. + protected threadAndLevelToFrameId(threadId: number, level: number) { + return level << 16 | threadId; + } + protected frameIdToThreadAndLevel(frameId: number) { + return [frameId & 0xffff, frameId >> 16]; + } + + protected 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 => { + let source = undefined; + let path = element.file; + if (path) { + if (this.isSSH) { + // convert ssh path to local path + if (path.indexOf("\\") != -1) + path = path.replace(/\\/g, "/").toLowerCase(); + // ideCWD is the local path, gdbCWD is the ssh path, both had the replacing \ -> / done up-front + for (let [ideCWD, gdbCWD] of this.sourceFileMap) { + if (path.startsWith(gdbCWD)) { + path = posix.relative(gdbCWD, path); // only operates on "/" paths + path = systemPath.resolve(ideCWD, path); // will do the conversion to "\" on Win32 + break; + } + } + } else if (process.platform === "win32") { + if (path.startsWith("\\cygdrive\\") || path.startsWith("/cygdrive/")) { + path = path[10] + ":" + path.substr(11); // replaces /cygdrive/c/foo/bar.txt with c:/foo/bar.txt + } + } + source = new Source(element.fileName, path); + } + + ret.push(new StackFrame( + this.threadAndLevelToFrameId(args.threadId, element.level), + element.function + "@" + element.address, + source, + element.line, + 0)); + }); + response.body = { + stackFrames: ret + }; + this.sendResponse(response); + }, err => { + this.sendErrorResponse(response, 12, `Failed to get Stack Trace: ${err.toString()}`); + }); + } + + protected configurationDoneRequest(response: DebugProtocol.ConfigurationDoneResponse, args: DebugProtocol.ConfigurationDoneArguments): void { + const promises: Thenable[] = []; + let entryPoint: string | undefined = undefined; + let runToStart: boolean = false; + // Setup temporary breakpoint for the entry point if needed. + switch (this.initialRunCommand) { + case RunCommand.CONTINUE: + case RunCommand.NONE: + if (typeof this.stopAtEntry == 'boolean' && this.stopAtEntry) + entryPoint = "main"; // sensible default + else if (typeof this.stopAtEntry == 'string') + entryPoint = this.stopAtEntry; + break; + case RunCommand.RUN: + if (typeof this.stopAtEntry == 'boolean' && this.stopAtEntry) { + if (this.miDebugger.features.includes("exec-run-start-option")) + runToStart = true; + else + entryPoint = "main"; // sensible fallback + } else if (typeof this.stopAtEntry == 'string') + entryPoint = this.stopAtEntry; + break; + default: + throw new Error('Unhandled run command: ' + RunCommand[this.initialRunCommand]); + } + if (entryPoint) + promises.push(this.miDebugger.setEntryBreakPoint(entryPoint)); + switch (this.initialRunCommand) { + case RunCommand.CONTINUE: + promises.push(this.miDebugger.continue().then(() => { + // Some debuggers will provide an out-of-band status that they are stopped + // when attaching (e.g., gdb), so the client assumes we are stopped and gets + // confused if we start running again on our own. + // + // If we don't send this event, the client may start requesting data (such as + // stack frames, local variables, etc.) since they believe the target is + // stopped. Furthermore the client may not be indicating the proper status + // to the user (may indicate stopped when the target is actually running). + this.sendEvent(new ContinuedEvent(1, true)); + })); + break; + case RunCommand.RUN: + promises.push(this.miDebugger.start(runToStart).then(() => { + this.started = true; + if (this.crashed) + this.handlePause(undefined); + })); + break; + case RunCommand.NONE: + // Not all debuggers seem to provide an out-of-band status that they are stopped + // when attaching (e.g., lldb), so the client assumes we are running and gets + // confused when we don't actually run or continue. Therefore, we'll force a + // stopped event to be sent to the client (just in case) to synchronize the state. + const event: DebugProtocol.StoppedEvent = new StoppedEvent("pause", 1); + event.body.description = "paused on attach"; + event.body.allThreadsStopped = true; + this.sendEvent(event); + break; + default: + throw new Error('Unhandled run command: ' + RunCommand[this.initialRunCommand]); + } + Promise.all(promises).then(() => { + this.sendResponse(response); + }).catch(err => { + this.sendErrorResponse(response, 18, `Could not run/continue: ${err.toString()}`); + }); + } + + protected scopesRequest(response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments): void { + const scopes = new Array(); + scopes.push(new Scope("Local", STACK_HANDLES_START + (parseInt(args.frameId as any) || 0), false)); + + response.body = { + scopes: scopes + }; + this.sendResponse(response); + } + + protected async variablesRequest(response: DebugProtocol.VariablesResponse, args: DebugProtocol.VariablesArguments): Promise { + const variables: DebugProtocol.Variable[] = []; + let id: number | string | VariableObject | ExtendedVariable; + if (args.variablesReference < VAR_HANDLES_START) { + id = args.variablesReference - STACK_HANDLES_START; + } else { + id = this.variableHandles.get(args.variablesReference); + } + + const createVariable = (arg, options?) => { + if (options) + return this.variableHandles.create(new ExtendedVariable(arg, options)); + else + return this.variableHandles.create(arg); + }; + + const findOrCreateVariable = (varObj: VariableObject): number => { + let id: number; + if (this.variableHandlesReverse.hasOwnProperty(varObj.name)) { + id = this.variableHandlesReverse[varObj.name]; + } else { + id = createVariable(varObj); + this.variableHandlesReverse[varObj.name] = id; + } + return varObj.isCompound() ? id : 0; + }; + + if (typeof id == "number") { + let stack: Variable[]; + try { + const [threadId, level] = this.frameIdToThreadAndLevel(id); + stack = await this.miDebugger.getStackVariables(threadId, level); + for (const variable of stack) { + if (this.useVarObjects) { + try { + const varObjName = `var_${id}_${variable.name}`; + let varObj: VariableObject; + try { + const changes = await this.miDebugger.varUpdate(varObjName); + const changelist = changes.result("changelist"); + changelist.forEach((change) => { + const name = MINode.valueOf(change, "name"); + const vId = this.variableHandlesReverse[name]; + const v = this.variableHandles.get(vId) as any; + v.applyChanges(change); + }); + 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); + const varId = findOrCreateVariable(varObj); + varObj.exp = variable.name; + varObj.id = varId; + } else { + throw err; + } + } + variables.push(varObj.toProtocolVariable()); + } catch (err) { + variables.push({ + name: variable.name, + value: `<${err}>`, + variablesReference: 0 + }); + } + } else { + if (variable.valueStr !== undefined) { + let expanded = expandValue(createVariable, `{${variable.name}=${variable.valueStr})`, "", variable.raw); + if (expanded) { + if (typeof expanded[0] == "string") + expanded = [ + { + name: "", + value: prettyStringArray(expanded), + variablesReference: 0 + } + ]; + variables.push(expanded[0]); + } + } else + variables.push({ + name: variable.name, + type: variable.type, + value: "", + variablesReference: createVariable(variable.name) + }); + } + } + response.body = { + variables: variables + }; + this.sendResponse(response); + } catch (err) { + this.sendErrorResponse(response, 1, `Could not expand variable: ${err}`); + } + } else if (typeof id == "string") { + // Variable members + let variable; + try { + // TODO: this evals on an (effectively) unknown thread for multithreaded programs. + variable = await this.miDebugger.evalExpression(JSON.stringify(id), 0, 0); + try { + let expanded = expandValue(createVariable, variable.result("value"), id, variable); + if (!expanded) { + this.sendErrorResponse(response, 2, `Could not expand variable`); + } else { + if (typeof expanded[0] == "string") + expanded = [ + { + name: "", + value: prettyStringArray(expanded), + variablesReference: 0 + } + ]; + response.body = { + variables: expanded + }; + this.sendResponse(response); + } + } catch (e) { + this.sendErrorResponse(response, 2, `Could not expand variable: ${e}`); + } + } catch (err) { + this.sendErrorResponse(response, 1, `Could not expand variable: ${err}`); + } + } else if (typeof id == "object") { + if (id instanceof VariableObject) { + // Variable members + let children: VariableObject[]; + try { + children = await this.miDebugger.varListChildren(id.name); + const vars = children.map(child => { + const varId = findOrCreateVariable(child); + child.id = varId; + return child.toProtocolVariable(); + }); + + response.body = { + variables: vars + }; + this.sendResponse(response); + } catch (err) { + this.sendErrorResponse(response, 1, `Could not expand variable: ${err}`); + } + } else if (id instanceof ExtendedVariable) { + const varReq = id; + if (varReq.options.arg) { + const strArr = []; + let argsPart = true; + let arrIndex = 0; + const submit = () => { + response.body = { + variables: strArr + }; + this.sendResponse(response); + }; + const addOne = async () => { + // TODO: this evals on an (effectively) unknown thread for multithreaded programs. + const variable = await this.miDebugger.evalExpression(JSON.stringify(`${varReq.name}+${arrIndex})`), 0, 0); + try { + const expanded = expandValue(createVariable, variable.result("value"), varReq.name, variable); + if (!expanded) { + this.sendErrorResponse(response, 15, `Could not expand variable`); + } else { + if (typeof expanded == "string") { + if (expanded == "") { + if (argsPart) + argsPart = false; + else + return submit(); + } else if (expanded[0] != '"') { + strArr.push({ + name: "[err]", + value: expanded, + variablesReference: 0 + }); + return submit(); + } + strArr.push({ + name: `[${(arrIndex++)}]`, + value: expanded, + variablesReference: 0 + }); + addOne(); + } else { + strArr.push({ + name: "[err]", + value: expanded, + variablesReference: 0 + }); + submit(); + } + } + } catch (e) { + this.sendErrorResponse(response, 14, `Could not expand variable: ${e}`); + } + }; + addOne(); + } else + this.sendErrorResponse(response, 13, `Unimplemented variable request options: ${JSON.stringify(varReq.options)}`); + } else { + response.body = { + variables: id + }; + this.sendResponse(response); + } + } else { + response.body = { + variables: variables + }; + this.sendResponse(response); + } + } + + protected pauseRequest(response: DebugProtocol.ContinueResponse, args: DebugProtocol.ContinueArguments): void { + this.miDebugger.interrupt().then(done => { + this.sendResponse(response); + }, msg => { + this.sendErrorResponse(response, 3, `Could not pause: ${msg}`); + }); + } + + protected reverseContinueRequest(response: DebugProtocol.ReverseContinueResponse, args: DebugProtocol.ReverseContinueArguments): void { + this.miDebugger.continue(true).then(done => { + this.sendResponse(response); + }, msg => { + this.sendErrorResponse(response, 2, `Could not continue: ${msg}`); + }); + } + + protected continueRequest(response: DebugProtocol.ContinueResponse, args: DebugProtocol.ContinueArguments): void { + this.miDebugger.continue().then(done => { + this.sendResponse(response); + }, msg => { + this.sendErrorResponse(response, 2, `Could not continue: ${msg}`); + }); + } + + protected stepBackRequest(response: DebugProtocol.StepBackResponse, args: DebugProtocol.StepBackArguments): void { + this.miDebugger.step(true).then(done => { + this.sendResponse(response); + }, msg => { + this.sendErrorResponse(response, 4, `Could not step back: ${msg} - Try running 'target record-full' before stepping back`); + }); + } + + protected stepInRequest(response: DebugProtocol.NextResponse, args: DebugProtocol.NextArguments): void { + this.miDebugger.step().then(done => { + this.sendResponse(response); + }, msg => { + this.sendErrorResponse(response, 4, `Could not step in: ${msg}`); + }); + } + + protected stepOutRequest(response: DebugProtocol.NextResponse, args: DebugProtocol.NextArguments): void { + this.miDebugger.stepOut().then(done => { + this.sendResponse(response); + }, msg => { + this.sendErrorResponse(response, 5, `Could not step out: ${msg}`); + }); + } + + protected nextRequest(response: DebugProtocol.NextResponse, args: DebugProtocol.NextArguments): void { + this.miDebugger.next().then(done => { + this.sendResponse(response); + }, msg => { + this.sendErrorResponse(response, 6, `Could not step over: ${msg}`); + }); + } + + protected 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) => { + response.body = { + variablesReference: 0, + result: res.result("value") + }; + this.sendResponse(response); + }, msg => { + this.sendErrorResponse(response, 7, msg.toString()); + }); + } else { + this.miDebugger.sendUserInput(args.expression, threadId, level).then(output => { + if (typeof output == "undefined") + response.body = { + result: "", + variablesReference: 0 + }; + else + response.body = { + result: JSON.stringify(output), + variablesReference: 0 + }; + this.sendResponse(response); + }, msg => { + this.sendErrorResponse(response, 8, msg.toString()); + }); + } + } + + protected gotoTargetsRequest(response: DebugProtocol.GotoTargetsResponse, args: DebugProtocol.GotoTargetsArguments): void { + this.miDebugger.goto(args.source.path, args.line).then(done => { + response.body = { + targets: [{ + id: 1, + label: args.source.name, + column: args.column, + line : args.line + }] + }; + this.sendResponse(response); + }, msg => { + this.sendErrorResponse(response, 16, `Could not jump: ${msg}`); + }); + } + + protected gotoRequest(response: DebugProtocol.GotoResponse, args: DebugProtocol.GotoArguments): void { + this.sendResponse(response); + } + + private addSourceFileMapEntry(gdbCWD :string, ideCWD : string): void { + // if it looks like a Win32 path convert to "/"-style for comparisions and to all-lower-case + if (ideCWD.indexOf("\\") != -1) + ideCWD = ideCWD.replace(/\\/g, "/").toLowerCase(); + if (!ideCWD.endsWith("/")) + ideCWD = ideCWD + "/" + // ensure that we only replace complete paths + if (gdbCWD.indexOf("\\") != -1) + gdbCWD = gdbCWD.replace(/\\/g, "/").toLowerCase(); + if (!gdbCWD.endsWith("/")) + gdbCWD = gdbCWD + "/" + this.sourceFileMap.set(ideCWD, gdbCWD); + } + + protected setSourceFileMap(configMap: { [index: string]: string }, fallbackGDB :string, fallbackIDE : string): void { + this.sourceFileMap = new Map(); + if (configMap === undefined) { + this.addSourceFileMapEntry(fallbackGDB, fallbackIDE); + } else { + for (let [gdbPath, localPath] of Object.entries(configMap)) { + this.addSourceFileMapEntry(gdbPath, localPath); + } + } + } + +} + +function prettyStringArray(strings) { + if (typeof strings == "object") { + if (strings.length !== undefined) + return strings.join(", "); + else + return JSON.stringify(strings); + } else return strings; +} + +async function cleanInvalidSocketPath(socketlists:string){ + return new Promise((resolve, reject) => { + fs.readdir(socketlists, (err, files) => { + if (!err) { + if (files.length == 0) resolve(''); + files.forEach((file)=>{ + try { + const conn = net.connect(systemPath.join(socketlists, file)); + conn.setTimeout(200); + conn.on('error',()=>{ + fs.unlink(systemPath.join(socketlists, file), (err) => { + if (err) + console.error("Failed to unlink invalid debug server"); + resolve(''); + }); + }); + } + catch{ + fs.unlink(systemPath.join(socketlists, file), (err) => { + if (err) + console.error("Failed to unlink invalid debug server"); + resolve(''); + }); + } + }) + } + resolve(''); + }); + }); +} diff --git a/src/test/runTest.ts b/src/test/runTest.ts new file mode 100644 index 0000000..76dfd38 --- /dev/null +++ b/src/test/runTest.ts @@ -0,0 +1,24 @@ +import * as path from 'path'; + +import { runTests } from 'vscode-test'; + +async function main() { + try { + // The folder containing the Extension Manifest package.json + // Passed to `--extensionDevelopmentPath` + const extensionDevelopmentPath = path.resolve(__dirname, '../../'); + + // The path to the extension test runner script + // Passed to --extensionTestsPath + const extensionTestsPath = path.resolve(__dirname, './suite/index'); + + // Download VS Code, unzip it and run the integration test + await runTests({ extensionDevelopmentPath, extensionTestsPath }); + } catch (err) { + console.error(err); + console.error('Failed to run tests'); + process.exit(1); + } +} + +main(); \ No newline at end of file diff --git a/src/test/suite/gdb_expansion.test.ts b/src/test/suite/gdb_expansion.test.ts new file mode 100644 index 0000000..ff81ac6 --- /dev/null +++ b/src/test/suite/gdb_expansion.test.ts @@ -0,0 +1,306 @@ +import * as assert from 'assert'; +import { expandValue, isExpandable } from '../../backend/gdb_expansion'; + +suite("GDB Value Expansion", () => { + const variableCreate = (variable) => { return { expanded: variable }; }; + test("Various values", () => { + assert.strictEqual(isExpandable(`false`), 0); + assert.equal(expandValue(variableCreate, `false`), "false"); + assert.strictEqual(isExpandable(`5`), 0); + assert.equal(expandValue(variableCreate, `5`), "5"); + assert.strictEqual(isExpandable(`"hello world!"`), 0); + assert.equal(expandValue(variableCreate, `"hello world!"`), `"hello world!"`); + assert.strictEqual(isExpandable(`0x7fffffffe956 "foobar"`), 0); + assert.equal(expandValue(variableCreate, `0x7fffffffe956 "foobar"`), `"foobar"`); + assert.strictEqual(isExpandable(`0x0`), 0); + assert.equal(expandValue(variableCreate, `0x0`), ""); + assert.strictEqual(isExpandable(`0x000000`), 0); + assert.equal(expandValue(variableCreate, `0x000000`), ""); + assert.strictEqual(isExpandable(`{...}`), 2); + assert.equal(expandValue(variableCreate, `{...}`), "<...>"); + assert.strictEqual(isExpandable(`0x00abc`), 2); + assert.equal(expandValue(variableCreate, `0x007ffff7ecb480`), "*0x007ffff7ecb480"); + assert.strictEqual(isExpandable(`{a = b, c = d}`), 1); + assert.deepEqual(expandValue(variableCreate, `{a = b, c = d}`), [ + { + name: "a", + value: "b", + variablesReference: 0 + }, { + name: "c", + value: "d", + variablesReference: 0 + }]); + assert.strictEqual(isExpandable(`{[0] = 0x400730 "foo", [1] = 0x400735 "bar"}`), 1); + assert.deepEqual(expandValue(variableCreate, `{[0] = 0x400730 "foo", [1] = 0x400735 "bar"}`), [ + { + name: "[0]", + value: "\"foo\"", + variablesReference: 0 + }, { + name: "[1]", + value: "\"bar\"", + variablesReference: 0 + }]); + assert.strictEqual(isExpandable(`{{a = b}}`), 1); + assert.deepEqual(expandValue(variableCreate, `{{a = b}}`), [ + { + name: "[0]", + value: "Object", + variablesReference: { + expanded: [ + { + name: "a", + value: "b", + variablesReference: 0 + } + ] + } + } + ]); + assert.deepEqual(expandValue(variableCreate, `{1, 2, 3, 4}`), [ + { + name: "[0]", + value: "1", + variablesReference: 0 + }, { + name: "[1]", + value: "2", + variablesReference: 0 + }, { + name: "[2]", + value: "3", + variablesReference: 0 + }, { + name: "[3]", + value: "4", + variablesReference: 0 + }]); + }); + test("Error values", () => { + assert.strictEqual(isExpandable(``), 0); + assert.equal(expandValue(variableCreate, ``), ""); + }); + test("Nested values", () => { + assert.strictEqual(isExpandable(`{a = {b = e}, c = d}`), 1); + assert.deepEqual(expandValue(variableCreate, `{a = {b = e}, c = d}`), [ + { + name: "a", + value: "Object", + variablesReference: { + expanded: [ + { + name: "b", + value: "e", + variablesReference: 0 + } + ] + } + }, { + name: "c", + value: "d", + variablesReference: 0 + }]); + }); + test("Simple node", () => { + assert.strictEqual(isExpandable(`{a = false, b = 5, c = 0x0, d = "foobar"}`), 1); + const variables = expandValue(variableCreate, `{a = false, b = 5, c = 0x0, d = "foobar"}`); + assert.equal(variables.length, 4); + assert.equal(variables[0].name, "a"); + assert.equal(variables[0].value, "false"); + assert.equal(variables[1].name, "b"); + assert.equal(variables[1].value, "5"); + assert.equal(variables[2].name, "c"); + assert.equal(variables[2].value, ""); + assert.equal(variables[3].name, "d"); + assert.equal(variables[3].value, `"foobar"`); + }); + test("Complex node", () => { + const node = `{quit = false, _views = {{view = 0x7ffff7ece1e8, renderer = 0x7ffff7eccc50, world = 0x7ffff7ece480}}, deltaTimer = {_flagStarted = false, _timeStart = {length = 0}, _timeMeasured = {length = 0}}, _start = {callbacks = 0x0}, _stop = {callbacks = 0x0}}`; + assert.strictEqual(isExpandable(node), 1); + const variables = expandValue(variableCreate, node); + assert.deepEqual(variables, [ + { + name: "quit", + value: "false", + variablesReference: 0 + }, + { + name: "_views", + value: "Object", + variablesReference: { + expanded: [ + { + name: "[0]", + value: "Object", + variablesReference: { + expanded: [ + { + name: "view", + value: "Object@*0x7ffff7ece1e8", + variablesReference: { expanded: "*_views[0].view" } + }, + { + name: "renderer", + value: "Object@*0x7ffff7eccc50", + variablesReference: { expanded: "*_views[0].renderer" } + }, + { + name: "world", + value: "Object@*0x7ffff7ece480", + variablesReference: { expanded: "*_views[0].world" } + } + ] + } + } + ] + } + }, + { + name: "deltaTimer", + value: "Object", + variablesReference: { + expanded: [ + { + name: "_flagStarted", + value: "false", + variablesReference: 0 + }, + { + name: "_timeStart", + value: "Object", + variablesReference: { + expanded: [ + { + name: "length", + value: "0", + variablesReference: 0 + } + ] + } + }, + { + name: "_timeMeasured", + value: "Object", + variablesReference: { + expanded: [ + { + name: "length", + value: "0", + variablesReference: 0 + } + ] + } + } + ] + } + }, + { + name: "_start", + value: "Object", + variablesReference: { + expanded: [ + { + name: "callbacks", + value: "", + variablesReference: 0 + } + ] + } + }, + { + name: "_stop", + value: "Object", + variablesReference: { + expanded: [ + { + name: "callbacks", + value: "", + variablesReference: 0 + } + ] + } + } + ]); + }); + test("Simple node with errors", () => { + const node = `{_enableMipMaps = false, _minFilter = , _magFilter = , _wrapX = , _wrapY = , _inMode = 6408, _mode = 6408, _id = 1, _width = 1024, _height = 1024}`; + assert.strictEqual(isExpandable(node), 1); + const variables = expandValue(variableCreate, node); + assert.deepEqual(variables, [ + { + name: "_enableMipMaps", + value: "false", + variablesReference: 0 + }, + { + name: "_minFilter", + value: "", + variablesReference: 0 + }, + { + name: "_magFilter", + value: "", + variablesReference: 0 + }, + { + name: "_wrapX", + value: "", + variablesReference: 0 + }, + { + name: "_wrapY", + value: "", + variablesReference: 0 + }, + { + name: "_inMode", + value: "6408", + variablesReference: 0 + }, + { + name: "_mode", + value: "6408", + variablesReference: 0 + }, + { + name: "_id", + value: "1", + variablesReference: 0 + }, + { + name: "_width", + value: "1024", + variablesReference: 0 + }, + { + name: "_height", + value: "1024", + variablesReference: 0 + } + ]); + }); + test("lldb strings", () => { + const node = `{ name = {...} }`; + assert.strictEqual(isExpandable(node), 1); + const variables = expandValue(variableCreate, node); + assert.deepEqual(variables, [ + { + name: "name", + value: "...", + variablesReference: { expanded: "name" } + } + ]); + }); + test("float values", () => { + const node = `{ intval1 = 123, floatval1 = 123.456, intval2 = 3, floatval2 = 234.45 }`; + const variables = expandValue(variableCreate, node); + + assert.deepEqual(variables, [ + { name: "intval1", value: "123", variablesReference: 0 }, + { name: "floatval1", value: "123.456", variablesReference: 0 }, + { name: "intval2", value: "3", variablesReference: 0 }, + { name: "floatval2", value: "234.45", variablesReference: 0 } + ]); + }); +}); diff --git a/src/test/suite/index.ts b/src/test/suite/index.ts new file mode 100644 index 0000000..45ff4b8 --- /dev/null +++ b/src/test/suite/index.ts @@ -0,0 +1,37 @@ +import * as path from 'path'; +import * as Mocha from 'mocha'; +import * as glob from 'glob'; + +export function run(): Promise { + // Create the mocha test + const mocha = new Mocha({ + ui: 'tdd', + useColors: true + }); + + const testsRoot = path.resolve(__dirname, '..'); + + return new Promise((c, e) => { + glob('**/**.test.js', { cwd: testsRoot }, (err, files) => { + if (err) { + return e(err); + } + + // Add files to the test suite + files.forEach(f => mocha.addFile(path.resolve(testsRoot, f))); + + try { + // Run the mocha test + mocha.run(failures => { + if (failures > 0) { + e(new Error(`${failures} tests failed.`)); + } else { + c(); + } + }); + } catch (err) { + e(err); + } + }); + }); +} \ No newline at end of file diff --git a/src/test/suite/mi_parse.test.ts b/src/test/suite/mi_parse.test.ts new file mode 100644 index 0000000..8bf7a89 --- /dev/null +++ b/src/test/suite/mi_parse.test.ts @@ -0,0 +1,207 @@ +import * as assert from 'assert'; +import { parseMI, MINode } from '../../backend/mi_parse'; + +suite("MI Parse", () => { + test("Very simple out of band record", () => { + const parsed = parseMI(`*stopped`); + assert.ok(parsed); + assert.strictEqual(parsed.token, undefined); + assert.strictEqual(parsed.outOfBandRecord.length, 1); + assert.strictEqual(parsed.outOfBandRecord[0].isStream, false); + assert.strictEqual(parsed.outOfBandRecord[0].asyncClass, "stopped"); + assert.strictEqual(parsed.outOfBandRecord[0].output.length, 0); + assert.strictEqual(parsed.resultRecords, undefined); + }); + test("Simple out of band record", () => { + const parsed = parseMI(`4=thread-exited,id="3",group-id="i1"`); + assert.ok(parsed); + assert.equal(parsed.token, 4); + assert.equal(parsed.outOfBandRecord.length, 1); + assert.equal(parsed.outOfBandRecord[0].isStream, false); + assert.equal(parsed.outOfBandRecord[0].asyncClass, "thread-exited"); + assert.equal(parsed.outOfBandRecord[0].output.length, 2); + assert.deepEqual(parsed.outOfBandRecord[0].output[0], ["id", "3"]); + assert.deepEqual(parsed.outOfBandRecord[0].output[1], ["group-id", "i1"]); + assert.equal(parsed.resultRecords, undefined); + }); + test("Console stream output with new line", () => { + const parsed = parseMI(`~"[Thread 0x7fffe993a700 (LWP 11002) exited]\\n"`); + assert.ok(parsed); + assert.equal(parsed.token, undefined); + assert.equal(parsed.outOfBandRecord.length, 1); + assert.equal(parsed.outOfBandRecord[0].isStream, true); + assert.equal(parsed.outOfBandRecord[0].content, "[Thread 0x7fffe993a700 (LWP 11002) exited]\n"); + assert.equal(parsed.resultRecords, undefined); + }); + test("Unicode", () => { + let parsed = parseMI(`~"[Depuraci\\303\\263n de hilo usando libthread_db enabled]\\n"`); + assert.ok(parsed); + assert.equal(parsed.token, undefined); + assert.equal(parsed.outOfBandRecord.length, 1); + assert.equal(parsed.outOfBandRecord[0].isStream, true); + assert.equal(parsed.outOfBandRecord[0].content, "[Depuración de hilo usando libthread_db enabled]\n"); + assert.equal(parsed.resultRecords, undefined); + parsed = parseMI(`~"4\\t std::cout << \\"\\345\\245\\275\\345\\245\\275\\345\\255\\246\\344\\271\\240\\357\\274\\214\\345\\244\\251\\345\\244\\251\\345\\220\\221\\344\\270\\212\\" << std::endl;\\n"`); + assert.ok(parsed); + assert.equal(parsed.token, undefined); + assert.equal(parsed.outOfBandRecord.length, 1); + assert.equal(parsed.outOfBandRecord[0].isStream, true); + assert.equal(parsed.outOfBandRecord[0].content, `4\t std::cout << "好好学习,天天向上" << std::endl;\n`); + assert.equal(parsed.resultRecords, undefined); + }); + test("Empty line", () => { + const parsed = parseMI(``); + assert.ok(parsed); + assert.equal(parsed.token, undefined); + assert.equal(parsed.outOfBandRecord.length, 0); + assert.equal(parsed.resultRecords, undefined); + }); + test("'(gdb)' line", () => { + const parsed = parseMI(`(gdb)`); + assert.ok(parsed); + assert.equal(parsed.token, undefined); + assert.equal(parsed.outOfBandRecord.length, 0); + assert.equal(parsed.resultRecords, undefined); + }); + test("Simple result record", () => { + const parsed = parseMI(`1^running`); + assert.ok(parsed); + assert.equal(parsed.token, 1); + assert.equal(parsed.outOfBandRecord.length, 0); + assert.notEqual(parsed.resultRecords, undefined); + assert.equal(parsed.resultRecords.resultClass, "running"); + assert.equal(parsed.resultRecords.results.length, 0); + }); + test("Advanced out of band record (Breakpoint hit)", () => { + const parsed = parseMI(`*stopped,reason="breakpoint-hit",disp="keep",bkptno="1",frame={addr="0x00000000004e807f",func="D main",args=[{name="args",value="..."}],file="source/app.d",fullname="/path/to/source/app.d",line="157"},thread-id="1",stopped-threads="all",core="0"`); + assert.ok(parsed); + assert.equal(parsed.token, undefined); + assert.equal(parsed.outOfBandRecord.length, 1); + assert.equal(parsed.outOfBandRecord[0].isStream, false); + assert.equal(parsed.outOfBandRecord[0].asyncClass, "stopped"); + assert.equal(parsed.outOfBandRecord[0].output.length, 7); + assert.deepEqual(parsed.outOfBandRecord[0].output[0], ["reason", "breakpoint-hit"]); + assert.deepEqual(parsed.outOfBandRecord[0].output[1], ["disp", "keep"]); + assert.deepEqual(parsed.outOfBandRecord[0].output[2], ["bkptno", "1"]); + const frame = [ + ["addr", "0x00000000004e807f"], + ["func", "D main"], + ["args", [[["name", "args"], ["value", "..."]]]], + ["file", "source/app.d"], + ["fullname", "/path/to/source/app.d"], + ["line", "157"] + ]; + assert.deepEqual(parsed.outOfBandRecord[0].output[3], ["frame", frame]); + assert.deepEqual(parsed.outOfBandRecord[0].output[4], ["thread-id", "1"]); + assert.deepEqual(parsed.outOfBandRecord[0].output[5], ["stopped-threads", "all"]); + assert.deepEqual(parsed.outOfBandRecord[0].output[6], ["core", "0"]); + assert.equal(parsed.resultRecords, undefined); + }); + test("Advanced result record", () => { + const parsed = parseMI(`2^done,asm_insns=[src_and_asm_line={line="134",file="source/app.d",fullname="/path/to/source/app.d",line_asm_insn=[{address="0x00000000004e7da4",func-name="_Dmain",offset="0",inst="push %rbp"},{address="0x00000000004e7da5",func-name="_Dmain",offset="1",inst="mov %rsp,%rbp"}]}]`); + assert.ok(parsed); + assert.equal(parsed.token, 2); + assert.equal(parsed.outOfBandRecord.length, 0); + assert.notEqual(parsed.resultRecords, undefined); + assert.equal(parsed.resultRecords.resultClass, "done"); + assert.equal(parsed.resultRecords.results.length, 1); + const asmInsns = [ + "asm_insns", + [ + [ + "src_and_asm_line", + [ + ["line", "134"], + ["file", "source/app.d"], + ["fullname", "/path/to/source/app.d"], + [ + "line_asm_insn", + [ + [ + ["address", "0x00000000004e7da4"], + ["func-name", "_Dmain"], + ["offset", "0"], + ["inst", "push %rbp"] + ], + [ + ["address", "0x00000000004e7da5"], + ["func-name", "_Dmain"], + ["offset", "1"], + ["inst", "mov %rsp,%rbp"] + ] + ] + ] + ] + ] + ] + ]; + assert.deepEqual(parsed.resultRecords.results[0], asmInsns); + assert.equal(parsed.result("asm_insns.src_and_asm_line.line_asm_insn[1].address"), "0x00000000004e7da5"); + }); + test("valueof children", () => { + const obj = [ + [ + "frame", + [ + ["level", "0"], + ["addr", "0x0000000000435f70"], + ["func", "D main"], + ["file", "source/app.d"], + ["fullname", "/path/to/source/app.d"], + ["line", "5"] + ] + ], + [ + "frame", + [ + ["level", "1"], + ["addr", "0x00000000004372d3"], + ["func", "rt.dmain2._d_run_main()"] + ] + ], + [ + "frame", + [ + ["level", "2"], + ["addr", "0x0000000000437229"], + ["func", "rt.dmain2._d_run_main()"] + ] + ] + ]; + + assert.equal(MINode.valueOf(obj[0], "@frame.level"), "0"); + assert.equal(MINode.valueOf(obj[0], "@frame.addr"), "0x0000000000435f70"); + assert.equal(MINode.valueOf(obj[0], "@frame.func"), "D main"); + assert.equal(MINode.valueOf(obj[0], "@frame.file"), "source/app.d"); + assert.equal(MINode.valueOf(obj[0], "@frame.fullname"), "/path/to/source/app.d"); + assert.equal(MINode.valueOf(obj[0], "@frame.line"), "5"); + + assert.equal(MINode.valueOf(obj[1], "@frame.level"), "1"); + assert.equal(MINode.valueOf(obj[1], "@frame.addr"), "0x00000000004372d3"); + assert.equal(MINode.valueOf(obj[1], "@frame.func"), "rt.dmain2._d_run_main()"); + assert.equal(MINode.valueOf(obj[1], "@frame.file"), undefined); + assert.equal(MINode.valueOf(obj[1], "@frame.fullname"), undefined); + assert.equal(MINode.valueOf(obj[1], "@frame.line"), undefined); + }); + test("empty string values", () => { + const parsed = parseMI(`15^done,register-names=["r0","pc","","xpsr","","control"]`); + const result = parsed.result('register-names'); + assert.deepEqual(result, ["r0", "pc", "", "xpsr", "", "control"]); + }); + test("empty string value first and last", () => { + const parsed = parseMI(`15^done,register-names=["","r0","pc","","xpsr","","control",""]`); + const result = parsed.result('register-names'); + assert.deepEqual(result, ["","r0","pc","","xpsr","","control", ""]); + }); + test("empty array values", () => { + const parsed = parseMI(`15^done,foo={x=[],y="y"}`); + assert.deepEqual(parsed.result('foo.x'), []); + assert.equal(parsed.result('foo.y'), "y"); + }); + test("empty object values", () => { + // GDB may send {} as empty array + const parsed = parseMI(`15^done,foo={x={},y="y"}`); + assert.deepEqual(parsed.result('foo.x'), []); + assert.equal(parsed.result('foo.y'), "y"); + }); +}); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..915ff8f --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es6", + "outDir": "out", + "lib": [ + "es6" + ], + "sourceMap": true, + "rootDir": ".", + "plugins": [ + { + "name": "tslint-language-service" + } + ] + }, + "exclude": [ + "node_modules", + ".vscode-test" + ] +} diff --git a/tslint.json b/tslint.json new file mode 100644 index 0000000..b1ea9a1 --- /dev/null +++ b/tslint.json @@ -0,0 +1,38 @@ +{ + "defaultSeverity": "error", + "extends": "tslint:recommended", + "rules": { + "no-null-keyword": true, + "indent": [true, "tabs"], + /* Rules in tslint:recommended that we don't follow yet. */ + "array-type": false, + "arrow-parens": false, + "arrow-return-shorthand": false, + "ban-types": false, + "class-name": false, + "comment-format": false, + "curly": false, + "interface-name": false, + "max-classes-per-file": false, + "max-line-length": false, + "member-access": false, + "member-ordering": false, + "no-angle-bracket-type-assertion": false, + "no-bitwise": false, + "no-conditional-assignment": false, + "no-consecutive-blank-lines": false, + "no-empty": false, + "no-shadowed-variable": false, + "no-unnecessary-initializer": false, + "object-literal-shorthand": false, + "object-literal-sort-keys": false, + "one-variable-per-declaration": false, + "only-arrow-functions": false, + "ordered-imports": false, + "quotemark": false, + "radix": false, + "space-before-function-paren": false, + "trailing-comma": false, + "triple-equals": false + } +}