From 2dfd15462fab2dbf047a3aea9115c78e54421f83 Mon Sep 17 00:00:00 2001 From: Ben Clayton Date: Thu, 31 Oct 2019 17:06:38 +0000 Subject: [PATCH] Initial drop of cppdap --- .clang-format | 2 + .gitignore | 4 + .gitmodules | 6 + .vscode/c_cpp_properties.json | 18 + .vscode/launch.json | 39 + .vscode/tasks.json | 84 + CMakeLists.txt | 231 +++ CONTRIBUTING | 28 + LICENSE | 202 +++ README.md | 73 + examples/hello_debugger.cpp | 453 ++++++ examples/vscode/package.json | 28 + include/dap/any.h | 182 +++ include/dap/io.h | 95 ++ include/dap/network.h | 55 + include/dap/optional.h | 271 +++ include/dap/protocol.h | 2441 ++++++++++++++++++++++++++++ include/dap/serialization.h | 256 +++ include/dap/session.h | 283 ++++ include/dap/typeinfo.h | 43 + include/dap/typeof.h | 182 +++ include/dap/types.h | 102 ++ include/dap/variant.h | 108 ++ src/any_test.cpp | 126 ++ src/chan.h | 90 + src/chan_test.cpp | 35 + src/content_stream.cpp | 179 ++ src/content_stream.h | 68 + src/content_stream_test.cpp | 47 + src/dap_test.cpp | 21 + src/io.cpp | 257 +++ src/json_serializer.cpp | 242 +++ src/json_serializer.h | 149 ++ src/json_serializer_test.cpp | 93 ++ src/network.cpp | 100 ++ src/network_test.cpp | 74 + src/optional_test.cpp | 162 ++ src/protocol_events.cpp | 114 ++ src/protocol_requests.cpp | 321 ++++ src/protocol_response.cpp | 305 ++++ src/protocol_types.cpp | 345 ++++ src/session.cpp | 475 ++++++ src/session_test.cpp | 386 +++++ src/socket.cpp | 204 +++ src/socket.h | 43 + src/string_buffer.h | 85 + src/typeof.cpp | 68 + src/variant_test.cpp | 94 ++ third_party/googletest | 1 + third_party/json | 1 + tools/protocol_gen/protocol_gen.go | 653 ++++++++ 51 files changed, 9924 insertions(+) create mode 100644 .clang-format create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 .vscode/c_cpp_properties.json create mode 100644 .vscode/launch.json create mode 100644 .vscode/tasks.json create mode 100644 CMakeLists.txt create mode 100644 CONTRIBUTING create mode 100644 LICENSE create mode 100644 README.md create mode 100644 examples/hello_debugger.cpp create mode 100644 examples/vscode/package.json create mode 100644 include/dap/any.h create mode 100644 include/dap/io.h create mode 100644 include/dap/network.h create mode 100644 include/dap/optional.h create mode 100644 include/dap/protocol.h create mode 100644 include/dap/serialization.h create mode 100644 include/dap/session.h create mode 100644 include/dap/typeinfo.h create mode 100644 include/dap/typeof.h create mode 100644 include/dap/types.h create mode 100644 include/dap/variant.h create mode 100644 src/any_test.cpp create mode 100644 src/chan.h create mode 100644 src/chan_test.cpp create mode 100644 src/content_stream.cpp create mode 100644 src/content_stream.h create mode 100644 src/content_stream_test.cpp create mode 100644 src/dap_test.cpp create mode 100644 src/io.cpp create mode 100644 src/json_serializer.cpp create mode 100644 src/json_serializer.h create mode 100644 src/json_serializer_test.cpp create mode 100644 src/network.cpp create mode 100644 src/network_test.cpp create mode 100644 src/optional_test.cpp create mode 100644 src/protocol_events.cpp create mode 100644 src/protocol_requests.cpp create mode 100644 src/protocol_response.cpp create mode 100644 src/protocol_types.cpp create mode 100644 src/session.cpp create mode 100644 src/session_test.cpp create mode 100644 src/socket.cpp create mode 100644 src/socket.h create mode 100644 src/string_buffer.h create mode 100644 src/typeof.cpp create mode 100644 src/variant_test.cpp create mode 160000 third_party/googletest create mode 160000 third_party/json create mode 100644 tools/protocol_gen/protocol_gen.go diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..2fbfe15 --- /dev/null +++ b/.clang-format @@ -0,0 +1,2 @@ +# http://clang.llvm.org/docs/ClangFormatStyleOptions.html +BasedOnStyle: Chromium \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7590faf --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +build/ +.vs/ +.vscode/settings.json +CMakeSettings.json diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..d7640fe --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "third_party/json"] + path = third_party/json + url = https://github.com/nlohmann/json.git +[submodule "third_party/googletest"] + path = third_party/googletest + url = https://github.com/google/googletest.git diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..eee6171 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,18 @@ +{ + "configurations": [ + { + "name": "Linux", + "includePath": [ + "${workspaceFolder}/**", + "${workspaceFolder}/third_party/json/include", + "${workspaceFolder}/third_party/googletest/googlemock/include" + ], + "defines": [], + "compilerPath": "/usr/bin/gcc", + "cStandard": "c11", + "cppStandard": "c++17", + "intelliSenseMode": "clang-x64" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..9159b8e --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,39 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "cppdap: hello_debugger", + "type": "hello_debugger", + "request": "launch" + }, + { + "type": "lldb", + "request": "launch", + "name": "unittests (lldb)", + "program": "${workspaceFolder}/build/dap-unittests", + "cwd": "${workspaceRoot}", + }, + { + "name": "unittests (gdb)", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/build/dap-unittests", + "args": [], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "environment": [], + "externalConsole": false, + "MIMode": "gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + } + ] + } + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..d85f957 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,84 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "label": "make", + "group": { + "kind": "build", + "isDefault": true + }, + "type": "shell", + "command": "sh", + "osx": { + "args": [ + "-c", + "cmake --build . && echo Done" + ] + }, + "linux": { + "args": [ + "-c", + "cmake --build . && echo Done" + ] + }, + "windows": { + "args": [ + "-c", + "cmake --build . && echo Done" + ] + }, + "options": { + "cwd": "${workspaceRoot}/build", + }, + "presentation": { + "echo": false, + "reveal": "always", + "focus": false, + "panel": "shared", + "showReuseMessage": false, + "clear": true, + }, + "problemMatcher": { + "owner": "cpp", + "fileLocation": "absolute", + "pattern": { + "regexp": "^(.*):(\\d+):(\\d+):\\s+(warning|error):\\s+(.*)$", + "file": 1, + "line": 2, + "column": 3, + "severity": 4, + "message": 5 + } + } + }, + { + "label": "cmake", + "type": "shell", + "command": "sh", + "args": [ + "-c", + "mkdir build; cd build; cmake .. -GNinja -DCMAKE_BUILD_TYPE=${input:buildType} -DCPPDAP_BUILD_TESTS=1 -DCPPDAP_BUILD_EXAMPLES=1 -DCPPDAP_INSTALL_VSCODE_EXAMPLES=1 -DCPPDAP_WARNINGS_AS_ERRORS=1", + ], + "options": { + "cwd": "${workspaceRoot}" + }, + "problemMatcher": [], + }, + ], + "inputs": [ + { + "id": "buildType", + "type": "pickString", + "options": [ + "Debug", + "Release", + "MinSizeRel", + "RelWithDebInfo", + ], + "default": "Debug", + "description": "The type of build", + }, + ] +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..9b9704c --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,231 @@ +# Copyright 2019 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +cmake_minimum_required(VERSION 2.8) + +set (CMAKE_CXX_STANDARD 11) + +project(cppdap C CXX) + +########################################################### +# Options +########################################################### +option(CPPDAP_WARNINGS_AS_ERRORS "Treat warnings as errors" OFF) +option(CPPDAP_BUILD_EXAMPLES "Build example applications" OFF) +option(CPPDAP_BUILD_TESTS "Build tests" OFF) +option(CPPDAP_ASAN "Build dap with address sanitizer" OFF) +option(CPPDAP_MSAN "Build dap with memory sanitizer" OFF) +option(CPPDAP_TSAN "Build dap with thread sanitizer" OFF) +option(CPPDAP_INSTALL_VSCODE_EXAMPLES "Build and install dap examples into vscode extensions directory" OFF) +option(CPPDAP_INSTALL "Create dap install target" OFF) + +########################################################### +# Directories +########################################################### +set(CPPDAP_SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src) +set(CPPDAP_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/include) +set(CPPDAP_THIRD_PARTY_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party) +set(JSON_DIR ${CPPDAP_THIRD_PARTY_DIR}/json) +set(GOOGLETEST_DIR ${CPPDAP_THIRD_PARTY_DIR}/googletest) + +########################################################### +# Submodules +########################################################### +if(CPPDAP_BUILD_TESTS) + if(NOT EXISTS ${CPPDAP_THIRD_PARTY_DIR}/googletest/.git) + message(WARNING "third_party/googletest submodule missing.") + message(WARNING "Run: `git submodule update --init` to build tests.") + set(CPPDAP_BUILD_TESTS OFF) + endif() +endif(CPPDAP_BUILD_TESTS) + +########################################################### +# File lists +########################################################### +set(CPPDAP_LIST + ${CPPDAP_SRC_DIR}/content_stream.cpp + ${CPPDAP_SRC_DIR}/io.cpp + ${CPPDAP_SRC_DIR}/json_serializer.cpp + ${CPPDAP_SRC_DIR}/network.cpp + ${CPPDAP_SRC_DIR}/protocol_events.cpp + ${CPPDAP_SRC_DIR}/protocol_requests.cpp + ${CPPDAP_SRC_DIR}/protocol_response.cpp + ${CPPDAP_SRC_DIR}/protocol_types.cpp + ${CPPDAP_SRC_DIR}/session.cpp + ${CPPDAP_SRC_DIR}/socket.cpp + ${CPPDAP_SRC_DIR}/typeof.cpp +) + +########################################################### +# OS libraries +########################################################### +if(CMAKE_SYSTEM_NAME MATCHES "Windows") + set(CPPDAP_OS_LIBS WS2_32) +elseif(CMAKE_SYSTEM_NAME MATCHES "Linux") + set(CPPDAP_OS_LIBS pthread) +elseif(CMAKE_SYSTEM_NAME MATCHES "Darwin") + set(CPPDAP_OS_LIBS) +endif() + +########################################################### +# Functions +########################################################### +function(cppdap_set_target_options target) + # Enable all warnings + if(MSVC) + target_compile_options(${target} PRIVATE "-W4") + else() + target_compile_options(${target} PRIVATE "-Wall") + endif() + + # Disable specific, pedantic warnings + if(MSVC) + target_compile_options(${target} PRIVATE "-D_CRT_SECURE_NO_WARNINGS") + endif() + + # Treat all warnings as errors + if(CPPDAP_WARNINGS_AS_ERRORS) + if(MSVC) + target_compile_options(${target} PRIVATE "/WX") + else() + target_compile_options(${target} PRIVATE "-Werror") + endif() + endif(CPPDAP_WARNINGS_AS_ERRORS) + + if(CPPDAP_ASAN) + target_compile_options(${target} PUBLIC "-fsanitize=address") + target_link_libraries(${target} "-fsanitize=address") + elseif(CPPDAP_MSAN) + target_compile_options(${target} PUBLIC "-fsanitize=memory") + target_link_libraries(${target} "-fsanitize=memory") + elseif(CPPDAP_TSAN) + target_compile_options(${target} PUBLIC "-fsanitize=thread") + target_link_libraries(${target} "-fsanitize=thread") + endif() + + # Error on undefined symbols + # if(NOT MSVC) + # target_compile_options(${target} PRIVATE "-Wl,--no-undefined") + # endif() + + target_include_directories(${target} PRIVATE ${CPPDAP_INCLUDE_DIR}) +endfunction(cppdap_set_target_options) + +########################################################### +# Targets +########################################################### + +# dap +add_library(cppdap STATIC ${CPPDAP_LIST}) +set_target_properties(cppdap PROPERTIES + POSITION_INDEPENDENT_CODE 1 +) + +target_include_directories(cppdap PRIVATE "${JSON_DIR}/include/") + +cppdap_set_target_options(cppdap) + +target_link_libraries(cppdap "${CPPDAP_OS_LIBS}") + +# install +if(CPPDAP_INSTALL) + include(GNUInstallDirs) + + install(DIRECTORY ${CPPDAP_INCLUDE_DIR}/cppdap + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + USE_SOURCE_PERMISSIONS + ) + + install(TARGETS cppdap + EXPORT cppdap-targets + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + ) + + install(EXPORT cppdap-targets + FILE cppdap-config.cmake + NAMESPACE cppdap:: + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/cppdap + ) +endif(CPPDAP_INSTALL) + +# tests +if(CPPDAP_BUILD_TESTS) + set(DAP_TEST_LIST + ${CPPDAP_SRC_DIR}/any_test.cpp + ${CPPDAP_SRC_DIR}/chan_test.cpp + ${CPPDAP_SRC_DIR}/content_stream_test.cpp + ${CPPDAP_SRC_DIR}/dap_test.cpp + ${CPPDAP_SRC_DIR}/json_serializer_test.cpp + ${CPPDAP_SRC_DIR}/network_test.cpp + ${CPPDAP_SRC_DIR}/optional_test.cpp + ${CPPDAP_SRC_DIR}/session_test.cpp + ${CPPDAP_SRC_DIR}/variant_test.cpp + ${GOOGLETEST_DIR}/googletest/src/gtest-all.cc + ) + + set(DAP_TEST_INCLUDE_DIR + ${GOOGLETEST_DIR}/googlemock/include/ + ${GOOGLETEST_DIR}/googletest/ + ${GOOGLETEST_DIR}/googletest/include/ + ${JSON_DIR}/include/ + ) + + add_executable(cppdap-unittests ${DAP_TEST_LIST}) + + set_target_properties(cppdap-unittests PROPERTIES + INCLUDE_DIRECTORIES "${DAP_TEST_INCLUDE_DIR}" + FOLDER "Tests" + ) + + if(MSVC) + # googletest emits warning C4244: 'initializing': conversion from 'double' to 'testing::internal::BiggestInt', possible loss of data + target_compile_options(cppdap-unittests PRIVATE "/wd4244") + endif() + + cppdap_set_target_options(cppdap-unittests) + + target_link_libraries(cppdap-unittests cppdap "${CPPDAP_OS_LIBS}") +endif(CPPDAP_BUILD_TESTS) + +# examples +if(CPPDAP_BUILD_EXAMPLES) + function(build_example target) + add_executable(${target} "${CMAKE_CURRENT_SOURCE_DIR}/examples/${target}.cpp") + set_target_properties(${target} PROPERTIES + FOLDER "Examples" + ) + cppdap_set_target_options(${target}) + target_link_libraries(${target} cppdap "${CPPDAP_OS_LIBS}") + + if(CPPDAP_INSTALL_VSCODE_EXAMPLES) + set(extroot "$ENV{HOME}/.vscode/extensions") + if(EXISTS ${extroot}) + set(extdir "${extroot}/google.cppdap-example-${target}-1.0.0") + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/examples/vscode/package.json ${extdir}/package.json) + add_custom_command(TARGET ${target} + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy $ ${extdir}) + else() + message(WARNING "Could not install vscode example extension as '${extroot}' does not exist") + endif() + endif(CPPDAP_INSTALL_VSCODE_EXAMPLES) + endfunction(build_example) + + build_example(hello_debugger) + +endif(CPPDAP_BUILD_EXAMPLES) diff --git a/CONTRIBUTING b/CONTRIBUTING new file mode 100644 index 0000000..9a86ba0 --- /dev/null +++ b/CONTRIBUTING @@ -0,0 +1,28 @@ +# How to Contribute + +We'd love to accept your patches and contributions to this project. There are +just a few small guidelines you need to follow. + +## Contributor License Agreement + +Contributions to this project must be accompanied by a Contributor License +Agreement. You (or your employer) retain the copyright to your contribution; +this simply gives us permission to use and redistribute your contributions as +part of the project. Head over to to see +your current agreements on file or to sign a new one. + +You generally only need to submit a CLA once, so if you've already submitted one +(even if it was for a different project), you probably don't need to do it +again. + +## Code reviews + +All submissions, including submissions by project members, require review. We +use GitHub pull requests for this purpose. Consult +[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more +information on using pull requests. + +## Community Guidelines + +This project follows +[Google's Open Source Community Guidelines](https://opensource.google/conduct/). diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..7a4a3ea --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..5390dc3 --- /dev/null +++ b/README.md @@ -0,0 +1,73 @@ +# cppdap + +## About + +`cppdap` is a C++11 library (["SDK"](https://microsoft.github.io/debug-adapter-protocol/implementors/sdks/)) implementation of the [Debug Adapter Protocol](https://microsoft.github.io/debug-adapter-protocol/), providing an API for implementing a DAP client or server. + +`cppdap` provides C++ type-safe structures for the full [DAP specification](https://microsoft.github.io/debug-adapter-protocol/specification), and provides a simple way to add custom protocol messages. + +## Fetching dependencies + +`cppdap` provides CMake build files to build the library, unit tests and examples. + +`cppdap` depends on the [`nlohmann/json` library](https://github.com/nlohmann/json), and the unit tests depend on the [`googletest` library](https://github.com/google/googletest). Both are referenced as a git submodules. + +Before building, fetch the git submodules with: + +```bash +cd +git submodule update --init +``` + +## Building + +### Linux and macOS + +Next, generate the build files: + +```bash +cd +mkdir build +cd build +cmake .. +``` + +You may wish to suffix the `cmake ..` line with any of the following flags: + +* `-DCPPDAP_BUILD_TESTS=1` - Builds the `cppdap` unit tests +* `-DCPPDAP_BUILD_EXAMPLES=1` - Builds the `cppdap` examples +* `-DCPPDAP_INSTALL_VSCODE_EXAMPLES=1` - Installs the `cppdap` examples as Visual Studio Code extensions +* `-DCPPDAP_WARNINGS_AS_ERRORS=1` - Treats all compiler warnings as errors. + +Finally, build the project: + +`make` + +### Windows + +`cppdap` can be built using [Visual Studio 2019's CMake integration](https://docs.microsoft.com/en-us/cpp/build/cmake-projects-in-visual-studio?view=vs-2019). + + +### Using `cppdap` in your CMake project + +You can build and link `cppdap` using `add_subdirectory()` in your project's `CMakeLists.txt` file: +```cmake +set(CPPDAP_DIR ) # example : "${CMAKE_CURRENT_SOURCE_DIR}/third_party/cppdap" +add_subdirectory(${CPPDAP_DIR}) +``` + +This will define the `cppdap` library target, which you can pass to `target_link_libraries()`: + +```cmake +target_link_libraries( cppdap) # replace with the name of your project's target +``` + +You will also want to add the `cppdap` public headers to your project's include search paths so you can `#include` the `cppdap` headers: + +```cmake +target_include_directories($ PRIVATE "${CPPDAP_DIR}/include") # replace with the name of your project's target +``` + +--- + +Note: This is not an officially supported Google product diff --git a/examples/hello_debugger.cpp b/examples/hello_debugger.cpp new file mode 100644 index 0000000..dd234f2 --- /dev/null +++ b/examples/hello_debugger.cpp @@ -0,0 +1,453 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// hello_debugger is an example DAP server that provides single line stepping +// through a synthetic file. + +#include "dap/io.h" +#include "dap/protocol.h" +#include "dap/session.h" + +#include +#include +#include +#include + +// Uncomment the line below and change to a file path to +// write all DAP communications to the given path. +// +// #define LOG_TO_FILE "" + +namespace { + +// sourceContent holds the synthetic file source. +constexpr char sourceContent[] = R"(// Hello Debugger! + +This is a synthetic source file provided by the DAP debugger. + +You can set breakpoints, and single line step. + +You may also notice that the locals contains a single variable for the currently executing line number.)"; + +// Total number of newlines in source. +constexpr int numSourceLines = 7; + +// Debugger holds the dummy debugger state and fires events to the EventHandler +// passed to the constructor. +class Debugger { + public: + enum class Event { BreakpointHit, Stepped, Paused }; + using EventHandler = std::function; + + Debugger(const EventHandler&); + + // run() instructs the debugger to continue execution. + void run(); + + // pause() instructs the debugger to pause execution. + void pause(); + + // currentLine() returns the currently executing line number. + int currentLine(); + + // stepForward() instructs the debugger to step forward one line. + void stepForward(); + + // clearBreakpoints() clears all set breakpoints. + void clearBreakpoints(); + + // addBreakpoint() sets a new breakpoint on the given line. + void addBreakpoint(int line); + + private: + EventHandler onEvent; + std::mutex mutex; + int line = 1; + std::unordered_set breakpoints; +}; + +Debugger::Debugger(const EventHandler& onEvent) : onEvent(onEvent) {} + +void Debugger::run() { + std::unique_lock lock(mutex); + for (int i = 0; i < numSourceLines; i++) { + auto l = ((line + i) % numSourceLines) + 1; + if (breakpoints.count(l)) { + line = l; + lock.unlock(); + onEvent(Event::BreakpointHit); + return; + } + } +} + +void Debugger::pause() { + onEvent(Event::Paused); +} + +int Debugger::currentLine() { + std::unique_lock lock(mutex); + return line; +} + +void Debugger::stepForward() { + std::unique_lock lock(mutex); + line = (line % numSourceLines) + 1; + lock.unlock(); + onEvent(Event::Stepped); +} + +void Debugger::clearBreakpoints() { + std::unique_lock lock(mutex); + this->breakpoints.clear(); +} + +void Debugger::addBreakpoint(int l) { + std::unique_lock lock(mutex); + this->breakpoints.emplace(l); +} + +// Event provides a basic wait and signal synchronization primitive. +class Event { + public: + // wait() blocks until the event is fired. + void wait(); + + // fire() sets signals the event, and unblocks any calls to wait(). + void fire(); + + private: + std::mutex mutex; + std::condition_variable cv; + bool fired = false; +}; + +void Event::wait() { + std::unique_lock lock(mutex); + cv.wait(lock, [&] { return fired; }); +} + +void Event::fire() { + std::unique_lock lock(mutex); + fired = true; + cv.notify_all(); +} + +} // anonymous namespace + +// main() entry point to the DAP server. +int main(int, char*[]) { + std::shared_ptr log; +#ifdef LOG_TO_FILE + log = dap::file(LOG_TO_FILE); +#endif + + // Create the DAP session. + // This is used to implement the DAP server. + auto session = dap::Session::create(); + + // Hard-coded identifiers for the one thread, frame, variable and source. + // These numbers have no meaning, and just need to remain constant for the + // duration of the service. + const dap::integer threadId = 100; + const dap::integer frameId = 200; + const dap::integer variablesReferenceId = 300; + const dap::integer sourceReferenceId = 400; + + // Signal events + Event configured; + Event terminate; + + // Event handlers from the Debugger. + auto onDebuggerEvent = [&](Debugger::Event onEvent) { + switch (onEvent) { + case Debugger::Event::Stepped: { + // The debugger has single-line stepped. Inform the client. + dap::StoppedEvent event; + event.reason = "step"; + event.threadId = threadId; + session->send(event); + break; + } + case Debugger::Event::BreakpointHit: { + // The debugger has hit a breakpoint. Inform the client. + dap::StoppedEvent event; + event.reason = "breakpoint"; + event.threadId = threadId; + session->send(event); + break; + } + case Debugger::Event::Paused: { + // The debugger has been suspended. Inform the client. + dap::StoppedEvent event; + event.reason = "pause"; + event.threadId = threadId; + session->send(event); + break; + } + } + }; + + // Construct the debugger. + Debugger debugger(onDebuggerEvent); + + // Handle errors reported by the Session. These errors include protocol + // parsing errors and receiving messages with no handler. + session->onError([&](const char* msg) { + if (log) { + dap::writef(log, "dap::Session error: %s\n", msg); + log->close(); + } + terminate.fire(); + }); + + // The Initialize request is the first message sent from the client and + // the response reports debugger capabilities. + // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Initialize + session->registerHandler([](const dap::InitializeRequest&) { + dap::InitializeResponse response; + response.supportsConfigurationDoneRequest = true; + return response; + }); + + // When the Initialize response has been sent, we need to send the initialized + // event. + // We use the registerSentHandler() to ensure the event is sent *after* the + // initialize response. + // https://microsoft.github.io/debug-adapter-protocol/specification#Events_Initialized + session->registerSentHandler( + [&](const dap::ResponseOrError&) { + session->send(dap::InitializedEvent()); + }); + + // The Threads request queries the debugger's list of active threads. + // This example debugger only exposes a single thread. + // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Threads + session->registerHandler([&](const dap::ThreadsRequest&) { + dap::ThreadsResponse response; + dap::Thread thread; + thread.id = threadId; + thread.name = "TheThread"; + response.threads.push_back(thread); + return response; + }); + + // The StackTrace request reports the stack frames (call stack) for a given + // thread. This example debugger only exposes a single stack frame for the + // single thread. + // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_StackTrace + session->registerHandler( + [&](const dap::StackTraceRequest& request) + -> dap::ResponseOrError { + if (request.threadId != threadId) { + return dap::Error("Unknown threadId '%d'", int(request.threadId)); + } + + dap::Source source; + source.sourceReference = sourceReferenceId; + source.name = "HelloDebuggerSource"; + + dap::StackFrame frame; + frame.line = debugger.currentLine(); + frame.column = 1; + frame.name = "HelloDebugger"; + frame.id = frameId; + frame.source = source; + + dap::StackTraceResponse response; + response.stackFrames.push_back(frame); + return response; + }); + + // The Scopes request reports all the scopes of the given stack frame. + // This example debugger only exposes a single 'Locals' scope for the single + // frame. + // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Scopes + session->registerHandler([&](const dap::ScopesRequest& request) + -> dap::ResponseOrError { + if (request.frameId != frameId) { + return dap::Error("Unknown frameId '%d'", int(request.frameId)); + } + + dap::Scope scope; + scope.name = "Locals"; + scope.presentationHint = "locals"; + scope.variablesReference = variablesReferenceId; + + dap::ScopesResponse response; + response.scopes.push_back(scope); + return response; + }); + + // The Variables request reports all the variables for the given scope. + // This example debugger only exposes a single 'currentLine' variable for the + // single 'Locals' scope. + // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Variables + session->registerHandler([&](const dap::VariablesRequest& request) + -> dap::ResponseOrError { + if (request.variablesReference != variablesReferenceId) { + return dap::Error("Unknown variablesReference '%d'", + int(request.variablesReference)); + } + + dap::Variable currentLineVar; + currentLineVar.name = "currentLine"; + currentLineVar.value = std::to_string(debugger.currentLine()); + currentLineVar.type = "int"; + + dap::VariablesResponse response; + response.variables.push_back(currentLineVar); + return response; + }); + + // The Pause request instructs the debugger to pause execution of one or all + // threads. + // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Pause + session->registerHandler([&](const dap::PauseRequest&) { + debugger.pause(); + return dap::PauseResponse(); + }); + + // The Continue request instructs the debugger to resume execution of one or + // all threads. + // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Continue + session->registerHandler([&](const dap::ContinueRequest&) { + debugger.run(); + return dap::ContinueResponse(); + }); + + // The Next request instructs the debugger to single line step for a specific + // thread. + // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Next + session->registerHandler([&](const dap::NextRequest&) { + debugger.stepForward(); + return dap::NextResponse(); + }); + + // The StepIn request instructs the debugger to step-in for a specific thread. + // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_StepIn + session->registerHandler([&](const dap::StepInRequest&) { + // Step-in treated as step-over as there's only one stack frame. + debugger.stepForward(); + return dap::StepInResponse(); + }); + + // The StepOut request instructs the debugger to step-out for a specific + // thread. + // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_StepOut + session->registerHandler([&](const dap::StepOutRequest&) { + // Step-out is not supported as there's only one stack frame. + return dap::StepOutResponse(); + }); + + // The SetBreakpoints request instructs the debugger to clear and set a number + // of line breakpoints for a specific source file. + // This example debugger only exposes a single source file. + // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_SetBreakpoints + session->registerHandler([&](const dap::SetBreakpointsRequest& request) { + dap::SetBreakpointsResponse response; + + auto breakpoints = request.breakpoints.value({}); + if (request.source.sourceReference.value(0) == sourceReferenceId) { + debugger.clearBreakpoints(); + response.breakpoints.resize(breakpoints.size()); + for (size_t i = 0; i < breakpoints.size(); i++) { + debugger.addBreakpoint(breakpoints[i].line); + response.breakpoints[i].verified = breakpoints[i].line < numSourceLines; + } + } else { + response.breakpoints.resize(breakpoints.size()); + } + + return response; + }); + + // The SetExceptionBreakpoints request configures the debugger's handling of + // thrown exceptions. + // This example debugger does not use any exceptions, so this is a no-op. + // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_SetExceptionBreakpoints + session->registerHandler([&](const dap::SetExceptionBreakpointsRequest&) { + return dap::SetExceptionBreakpointsResponse(); + }); + + // The Source request retrieves the source code for a given source file. + // This example debugger only exposes one synthetic source file. + // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Source + session->registerHandler([&](const dap::SourceRequest& request) + -> dap::ResponseOrError { + if (request.sourceReference != sourceReferenceId) { + return dap::Error("Unknown source reference '%d'", + int(request.sourceReference)); + } + + dap::SourceResponse response; + response.content = sourceContent; + return response; + }); + + // The Launch request is made when the client instructs the debugger adapter + // to start the debuggee. This request contains the launch arguments. + // This example debugger does nothing with this request. + // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Launch + session->registerHandler( + [&](const dap::LaunchRequest&) { return dap::LaunchResponse(); }); + + // Handler for disconnect requests + session->registerHandler([&](const dap::DisconnectRequest& request) { + if (request.terminateDebuggee.value(false)) { + terminate.fire(); + } + return dap::DisconnectResponse(); + }); + + // The ConfigurationDone request is made by the client once all configuration + // requests have been made. + // This example debugger uses this request to 'start' the debugger. + // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_ConfigurationDone + session->registerHandler([&](const dap::ConfigurationDoneRequest&) { + configured.fire(); + return dap::ConfigurationDoneResponse(); + }); + + // All the handlers we care about have now been registered. + // We now bind the session to stdin and stdout to connect to the client. + // After the call to bind() we should start receiving requests, starting with + // the Initialize request. + std::shared_ptr in = dap::file(stdin, false); + std::shared_ptr out = dap::file(stdout, false); + if (log) { + session->bind(spy(in, log), spy(out, log)); + } else { + session->bind(in, out); + } + + // Wait for the ConfigurationDone request to be made. + configured.wait(); + + // Broadcast the existance of the single thread to the client. + dap::ThreadEvent threadStartedEvent; + threadStartedEvent.reason = "started"; + threadStartedEvent.threadId = threadId; + session->send(threadStartedEvent); + + // Start the debugger in a paused state. + // This sends a stopped event to the client. + debugger.pause(); + + // Block until we receive a 'terminateDebuggee' request or encounter a session + // error. + terminate.wait(); + + return 0; +} diff --git a/examples/vscode/package.json b/examples/vscode/package.json new file mode 100644 index 0000000..bd9e378 --- /dev/null +++ b/examples/vscode/package.json @@ -0,0 +1,28 @@ +{ + "name": "cppdap-example-@target@", + "displayName": "cppdap example: @target@", + "description": "cppdap example: @target@", + "version": "1.0.0", + "preview": false, + "publisher": "Google LLC", + "author": { + "name": "Google LLC" + }, + "license": "SEE LICENSE IN LICENSE.txt", + "engines": { + "vscode": "^1.32.0" + }, + "categories": [ + "Debuggers" + ], + "contributes": { + "debuggers": [ + { + "type": "@target@", + "program": "@target@", + "label": "cppdap example: @target@", + "configurationAttributes": {} + } + ] + } +} \ No newline at end of file diff --git a/include/dap/any.h b/include/dap/any.h new file mode 100644 index 0000000..0d2242f --- /dev/null +++ b/include/dap/any.h @@ -0,0 +1,182 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef dap_any_h +#define dap_any_h + +#include "typeinfo.h" + +#include + +namespace dap { + +template +struct TypeOf; + +// any provides a type-safe container for values of any of dap type (boolean, +// integer, number, array, variant, any, null, dap-structs). +class any { + public: + // constructors + inline any() = default; + inline any(const any& other) noexcept; + inline any(any&& other) noexcept; + + template + inline any(const T& val); + + // destructors + inline ~any(); + + // replaces the contained value with a null. + inline void reset(); + + // assignment + inline any& operator=(const any& rhs); + inline any& operator=(any&& rhs) noexcept; + template + inline any& operator=(const T& val); + + // get() returns the contained value of the type T. + // If the any does not contain a value of type T, then get() will assert. + template + inline T& get() const; + + // is() returns true iff the contained value is of type T. + template + inline bool is() const; + + private: + static inline void* alignUp(void* val, size_t alignment); + inline void alloc(size_t size, size_t align); + inline void free(); + inline bool isInBuffer(void* ptr) const; + + void* value = nullptr; + const TypeInfo* type = nullptr; + void* heap = nullptr; // heap allocation + uint8_t buffer[32]; // or internal allocation +}; + +inline any::~any() { + reset(); +} + +template +inline any::any(const T& val) { + *this = val; +} + +any::any(const any& other) noexcept : type(other.type) { + if (other.value != nullptr) { + alloc(type->size(), type->alignment()); + type->copyConstruct(value, other.value); + } +} + +any::any(any&& other) noexcept : value(other.value), type(other.type) { + other.value = nullptr; + other.type = nullptr; +} + +void any::reset() { + if (value != nullptr) { + type->destruct(value); + free(); + } + value = nullptr; + type = nullptr; +} + +any& any::operator=(const any& rhs) { + reset(); + type = rhs.type; + if (rhs.value != nullptr) { + alloc(type->size(), type->alignment()); + type->copyConstruct(value, rhs.value); + } + return *this; +} + +any& any::operator=(any&& rhs) noexcept { + value = rhs.value; + type = rhs.type; + rhs.value = nullptr; + rhs.type = nullptr; + return *this; +} + +template +any& any::operator=(const T& val) { + if (!is()) { + reset(); + type = TypeOf::type(); + alloc(type->size(), type->alignment()); + type->copyConstruct(value, &val); + } else { + *reinterpret_cast(value) = val; + } + return *this; +} + +template +T& any::get() const { + assert(is()); + return *reinterpret_cast(value); +} + +template +bool any::is() const { + return type == TypeOf::type(); +} + +template <> +inline bool any::is() const { + return value == nullptr; +} + +void* any::alignUp(void* val, size_t alignment) { + auto ptr = reinterpret_cast(val); + return reinterpret_cast(alignment * + ((ptr + alignment - 1) / alignment)); +} + +void any::alloc(size_t size, size_t align) { + assert(value == nullptr); + value = alignUp(buffer, align); + if (isInBuffer(reinterpret_cast(value) + size - 1)) { + return; + } + heap = new uint8_t[size + align]; + value = alignUp(heap, align); +} + +void any::free() { + assert(value != nullptr); + if (heap != nullptr) { + delete[] reinterpret_cast(heap); + heap = nullptr; + } + value = nullptr; +} + +bool any::isInBuffer(void* ptr) const { + auto addr = reinterpret_cast(ptr); + return addr >= reinterpret_cast(buffer) && + addr < reinterpret_cast(buffer + sizeof(buffer)); +} + +} // namespace dap + +#endif // dap_any_h diff --git a/include/dap/io.h b/include/dap/io.h new file mode 100644 index 0000000..e1f378d --- /dev/null +++ b/include/dap/io.h @@ -0,0 +1,95 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef dap_io_h +#define dap_io_h + +#include // size_t +#include // std::unique_ptr +#include // std::pair + +namespace dap { + +class Closable { + public: + virtual ~Closable() = default; + + // isOpen() returns true if the stream has not been closed. + virtual bool isOpen() = 0; + + // close() closes the stream. + virtual void close() = 0; +}; + +// Reader is an interface for reading from a byte stream. +class Reader : virtual public Closable { + public: + // read() attempts to read at most n bytes into buffer, returning the number + // of bytes read. + // read() will block until the stream is closed or at least one byte is read. + virtual size_t read(void* buffer, size_t n) = 0; +}; + +// Writer is an interface for writing to a byte stream. +class Writer : virtual public Closable { + public: + // write() writes n bytes from buffer into the stream. + // Returns true on success, or false if there was an error or the stream was + // closed. + virtual bool write(const void* buffer, size_t n) = 0; +}; + +// ReaderWriter is an interface that combines the Reader and Writer interfaces. +class ReaderWriter : public Reader, public Writer { + // create() returns a ReaderWriter that delegates the interface methods on to + // the provided Reader and Writer. + // isOpen() returns true if the Reader and Writer both return true for + // isOpen(). + // close() closes both the Reader and Writer. + static std::shared_ptr create(const std::shared_ptr&, + const std::shared_ptr&); +}; + +// pipe() returns a ReaderWriter where the Writer streams to the Reader. +// Writes are internally buffered. +// Calling close() on either the Reader or Writer will close both ends of the +// stream. +std::shared_ptr pipe(); + +// file() wraps file with a ReaderWriter. +// If closable is false, then a call to ReaderWriter::close() will not close the +// underlying file. +std::shared_ptr file(FILE* file, bool closable = true); + +// file() opens (or creates) the file with the given path. +std::shared_ptr file(const char* path); + +// spy() returns a Reader that copies all reads from the Reader r to the Writer +// s, using the given optional prefix. +std::shared_ptr spy(const std::shared_ptr& r, + const std::shared_ptr& s, + const char* prefix = "\n->"); + +// spy() returns a Writer that copies all writes to the Writer w to the Writer +// s, using the given optional prefix. +std::shared_ptr spy(const std::shared_ptr& w, + const std::shared_ptr& s, + const char* prefix = "\n<-"); + +// writef writes the printf style string to the writer w. +bool writef(const std::shared_ptr& w, const char* msg, ...); + +} // namespace dap + +#endif // dap_io_h diff --git a/include/dap/network.h b/include/dap/network.h new file mode 100644 index 0000000..df00cb8 --- /dev/null +++ b/include/dap/network.h @@ -0,0 +1,55 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef dap_network_h +#define dap_network_h + +#include +#include + +namespace dap { +class ReaderWriter; + +namespace net { + +// connect() connects to the given TCP address and port. +std::shared_ptr connect(const char* addr, int port); + +// Server implements a basic TCP server. +class Server { + public: + using OnError = std::function; + using OnConnect = std::function&)>; + + virtual ~Server() = default; + + // create() constructs and returns a new Server. + static std::unique_ptr create(); + + // start() begins listening for connections on the given port. + // callback will be called for each connection. + // onError will be called for any connection errors. + virtual bool start(int port, + const OnConnect& callback, + const OnError& onError = OnError()) = 0; + + // stop() stops listening for connections. + // stop() is implicitly called on destruction. + virtual void stop() = 0; +}; + +} // namespace net +} // namespace dap + +#endif // dap_network_h diff --git a/include/dap/optional.h b/include/dap/optional.h new file mode 100644 index 0000000..77b0587 --- /dev/null +++ b/include/dap/optional.h @@ -0,0 +1,271 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef dap_optional_h +#define dap_optional_h + +#include +#include + +namespace dap { + +// optional holds an 'optional' contained value. +// This is similar to C++17's std::optional. +template +class optional { + template + using IsConvertibleToT = + typename std::enable_if::value>::type; + + public: + using value_type = T; + + // constructors + inline optional() = default; + inline optional(const optional& other); + inline optional(optional&& other); + template + inline optional(const optional& other); + template + inline optional(optional&& other); + template > + inline optional(U&& value); + + // value() returns the contained value. + // If the optional does not contain a value, then value() will assert. + inline T& value(); + inline const T& value() const; + + // value() returns the contained value, or defaultValue if the optional does + // not contain a value. + inline T& value(const T& defaultValue); + inline const T& value(const T& defaultValue) const; + + // operator bool() returns true if the optional contains a value. + inline explicit operator bool() const noexcept; + + // has_value() returns true if the optional contains a value. + inline bool has_value() const; + + // assignment + inline optional& operator=(const optional& other); + inline optional& operator=(optional&& other) noexcept; + template > + inline optional& operator=(U&& value); + template + inline optional& operator=(const optional& other); + template + inline optional& operator=(optional&& other); + + // value access + inline const T* operator->() const; + inline T* operator->(); + inline const T& operator*() const; + inline T& operator*(); + + private: + T val = {}; + bool set = false; +}; + +template +optional::optional(const optional& other) : val(other.val), set(other.set) {} + +template +optional::optional(optional&& other) + : val(std::move(other.val)), set(other.set) {} + +template +template +optional::optional(const optional& other) : set(other.has_value()) { + if (set) { + val = static_cast(other.value()); + } +} + +template +template +optional::optional(optional&& other) : set(other.has_value()) { + if (set) { + val = static_cast(std::move(other.value())); + } +} + +template +template +optional::optional(U&& value) : val(std::move(value)), set(true) {} + +template +T& optional::value() { + assert(set); + return val; +} + +template +const T& optional::value() const { + assert(set); + return val; +} + +template +T& optional::value(const T& defaultValue) { + if (!has_value()) { + return defaultValue; + } + return val; +} + +template +const T& optional::value(const T& defaultValue) const { + if (!has_value()) { + return defaultValue; + } + return val; +} + +template +optional::operator bool() const noexcept { + return set; +} + +template +bool optional::has_value() const { + return set; +} + +template +optional& optional::operator=(const optional& other) { + val = other.val; + set = other.set; + return *this; +} + +template +optional& optional::operator=(optional&& other) noexcept { + val = std::move(other.val); + set = other.set; + return *this; +} + +template +template +optional& optional::operator=(U&& value) { + val = std::move(value); + set = true; + return *this; +} + +template +template +optional& optional::operator=(const optional& other) { + val = other.val; + set = other.set; + return *this; +} + +template +template +optional& optional::operator=(optional&& other) { + val = std::move(other.val); + set = other.set; + return *this; +} + +template +const T* optional::operator->() const { + assert(set); + return &val; +} + +template +T* optional::operator->() { + assert(set); + return &val; +} + +template +const T& optional::operator*() const { + assert(set); + return val; +} + +template +T& optional::operator*() { + assert(set); + return val; +} + +template +inline bool operator==(const optional& lhs, const optional& rhs) { + if (!lhs.has_value() && !rhs.has_value()) { + return true; + } + if (!lhs.has_value() || !rhs.has_value()) { + return false; + } + return lhs.value() == rhs.value(); +} + +template +inline bool operator!=(const optional& lhs, const optional& rhs) { + return !(lhs == rhs); +} + +template +inline bool operator<(const optional& lhs, const optional& rhs) { + if (!rhs.has_value()) { + return false; + } + if (!lhs.has_value()) { + return true; + } + return lhs.value() < rhs.value(); +} + +template +inline bool operator<=(const optional& lhs, const optional& rhs) { + if (!lhs.has_value()) { + return true; + } + if (!rhs.has_value()) { + return false; + } + return lhs.value() <= rhs.value(); +} + +template +inline bool operator>(const optional& lhs, const optional& rhs) { + if (!lhs.has_value()) { + return false; + } + if (!rhs.has_value()) { + return true; + } + return lhs.value() > rhs.value(); +} + +template +inline bool operator>=(const optional& lhs, const optional& rhs) { + if (!rhs.has_value()) { + return true; + } + if (!lhs.has_value()) { + return false; + } + return lhs.value() >= rhs.value(); +} + +} // namespace dap + +#endif // dap_optional_h diff --git a/include/dap/protocol.h b/include/dap/protocol.h new file mode 100644 index 0000000..1f88cb3 --- /dev/null +++ b/include/dap/protocol.h @@ -0,0 +1,2441 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Generated with protocol_gen.go -- do not edit this file. +// go run scripts/protocol_gen/protocol_gen.go + +#ifndef dap_protocol_h +#define dap_protocol_h + +#include "optional.h" +#include "typeinfo.h" +#include "typeof.h" +#include "variant.h" + +#include +#include +#include + +namespace dap { + +struct Request {}; +struct Response {}; +struct Event {}; + +// Response to 'attach' request. This is just an acknowledgement, so no body +// field is required. +struct AttachResponse : public Response { + AttachResponse(); + ~AttachResponse(); +}; + +DAP_DECLARE_STRUCT_TYPEINFO(AttachResponse); + +// The attach request is sent from the client to the debug adapter to attach to +// a debuggee that is already running. Since attaching is debugger/runtime +// specific, the arguments for this request are not part of this specification. +struct AttachRequest : public Request { + using Response = AttachResponse; + + AttachRequest(); + ~AttachRequest(); + + // Optional data from the previous, restarted session. + // The data is sent as the 'restart' attribute of the 'terminated' event. + // The client should leave the data intact. + optional, boolean, integer, null, number, object, string>> + restart; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(AttachRequest); + +// Names of checksum algorithms that may be supported by a debug adapter. +struct ChecksumAlgorithm { + ChecksumAlgorithm(); + ~ChecksumAlgorithm(); +}; + +DAP_DECLARE_STRUCT_TYPEINFO(ChecksumAlgorithm); + +// The checksum of an item calculated by the specified algorithm. +struct Checksum { + Checksum(); + ~Checksum(); + + // The algorithm used to calculate this checksum. + ChecksumAlgorithm algorithm; + // Value of the checksum. + string checksum; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(Checksum); + +// A Source is a descriptor for source code. It is returned from the debug +// adapter as part of a StackFrame and it is used by clients when specifying +// breakpoints. +struct Source { + Source(); + ~Source(); + + // Optional data that a debug adapter might want to loop through the client. + // The client should leave the data intact and persist it across sessions. The + // client should not interpret the data. + optional, boolean, integer, null, number, object, string>> + adapterData; + // The checksums associated with this file. + optional> checksums; + // The short name of the source. Every source returned from the debug adapter + // has a name. When sending a source to the debug adapter this name is + // optional. + optional name; + // The (optional) origin of this source: possible values 'internal module', + // 'inlined content from source map', etc. + optional origin; + // The path of the source to be shown in the UI. It is only used to locate and + // load the content of the source if no sourceReference is specified (or its + // value is 0). + optional path; + // An optional hint for how to present the source in the UI. A value of + // 'deemphasize' can be used to indicate that the source is not available or + // that it is skipped on stepping. + // + // Must be one of the following enumeration values: + // 'normal', 'emphasize', 'deemphasize' + optional presentationHint; + // If sourceReference > 0 the contents of the source must be retrieved through + // the SourceRequest (even if a path is specified). A sourceReference is only + // valid for a session, so it must not be used to persist a source. The value + // should be less than or equal to 2147483647 (2^31 - 1). + optional sourceReference; + // An optional list of sources that are related to this source. These may be + // the source that generated this source. + optional> sources; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(Source); + +// Information about a Breakpoint created in setBreakpoints or +// setFunctionBreakpoints. +struct Breakpoint { + Breakpoint(); + ~Breakpoint(); + + // An optional start column of the actual range covered by the breakpoint. + optional column; + // An optional end column of the actual range covered by the breakpoint. If no + // end line is given, then the end column is assumed to be in the start line. + optional endColumn; + // An optional end line of the actual range covered by the breakpoint. + optional endLine; + // An optional identifier for the breakpoint. It is needed if breakpoint + // events are used to update or remove breakpoints. + optional id; + // The start line of the actual range covered by the breakpoint. + optional line; + // An optional message about the state of the breakpoint. This is shown to the + // user and can be used to explain why a breakpoint could not be verified. + optional message; + // The source where the breakpoint is located. + optional source; + // If true breakpoint could be set (but not necessarily at the desired + // location). + boolean verified; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(Breakpoint); + +// The event indicates that some information about a breakpoint has changed. +struct BreakpointEvent : public Event { + BreakpointEvent(); + ~BreakpointEvent(); + + // The 'id' attribute is used to find the target breakpoint and the other + // attributes are used as the new values. + Breakpoint breakpoint; + // The reason for the event. + // + // May be one of the following enumeration values: + // 'changed', 'new', 'removed' + string reason; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(BreakpointEvent); + +// Properties of a breakpoint location returned from the 'breakpointLocations' +// request. +struct BreakpointLocation { + BreakpointLocation(); + ~BreakpointLocation(); + + // Optional start column of breakpoint location. + optional column; + // Optional end column of breakpoint location if the location covers a range. + optional endColumn; + // Optional end line of breakpoint location if the location covers a range. + optional endLine; + // Start line of breakpoint location. + integer line; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(BreakpointLocation); + +// Response to 'breakpointLocations' request. +// Contains possible locations for source breakpoints. +struct BreakpointLocationsResponse : public Response { + BreakpointLocationsResponse(); + ~BreakpointLocationsResponse(); + + // Sorted set of possible breakpoint locations. + array breakpoints; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(BreakpointLocationsResponse); + +// The 'breakpointLocations' request returns all possible locations for source +// breakpoints in a given range. +struct BreakpointLocationsRequest : public Request { + using Response = BreakpointLocationsResponse; + + BreakpointLocationsRequest(); + ~BreakpointLocationsRequest(); + + // Optional start column of range to search possible breakpoint locations in. + // If no start column is given, the first column in the start line is assumed. + optional column; + // Optional end column of range to search possible breakpoint locations in. If + // no end column is given, then it is assumed to be in the last column of the + // end line. + optional endColumn; + // Optional end line of range to search possible breakpoint locations in. If + // no end line is given, then the end line is assumed to be the start line. + optional endLine; + // Start line of range to search possible breakpoint locations in. If only the + // line is specified, the request returns all possible locations in that line. + integer line; + // The source location of the breakpoints; either 'source.path' or + // 'source.reference' must be specified. + Source source; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(BreakpointLocationsRequest); + +// Response to 'cancel' request. This is just an acknowledgement, so no body +// field is required. +struct CancelResponse : public Response { + CancelResponse(); + ~CancelResponse(); +}; + +DAP_DECLARE_STRUCT_TYPEINFO(CancelResponse); + +// The 'cancel' request is used by the frontend to indicate that it is no longer +// interested in the result produced by a specific request issued earlier. This +// request has a hint characteristic: a debug adapter can only be expected to +// make a 'best effort' in honouring this request but there are no guarantees. +// The 'cancel' request may return an error if it could not cancel an operation +// but a frontend should refrain from presenting this error to end users. A +// frontend client should only call this request if the capability +// 'supportsCancelRequest' is true. The request that got canceled still needs to +// send a response back. This can either be a normal result ('success' attribute +// true) or an error response ('success' attribute false and the 'message' set +// to 'cancelled'). Returning partial results from a cancelled request is +// possible but please note that a frontend client has no generic way for +// detecting that a response is partial or not. +struct CancelRequest : public Request { + using Response = CancelResponse; + + CancelRequest(); + ~CancelRequest(); + + // The ID (attribute 'seq') of the request to cancel. + optional requestId; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(CancelRequest); + +// A ColumnDescriptor specifies what module attribute to show in a column of the +// ModulesView, how to format it, and what the column's label should be. It is +// only used if the underlying UI actually supports this level of customization. +struct ColumnDescriptor { + ColumnDescriptor(); + ~ColumnDescriptor(); + + // Name of the attribute rendered in this column. + string attributeName; + // Format to use for the rendered values in this column. TBD how the format + // strings looks like. + optional format; + // Header UI label of column. + string label; + // Datatype of values in this column. Defaults to 'string' if not specified. + // + // Must be one of the following enumeration values: + // 'string', 'number', 'boolean', 'unixTimestampUTC' + optional type; + // Width of this column in characters (hint only). + optional width; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(ColumnDescriptor); + +// An ExceptionBreakpointsFilter is shown in the UI as an option for configuring +// how exceptions are dealt with. +struct ExceptionBreakpointsFilter { + ExceptionBreakpointsFilter(); + ~ExceptionBreakpointsFilter(); + + // Initial value of the filter. If not specified a value 'false' is assumed. + optional def; + // The internal ID of the filter. This value is passed to the + // setExceptionBreakpoints request. + string filter; + // The name of the filter. This will be shown in the UI. + string label; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(ExceptionBreakpointsFilter); + +// Information about the capabilities of a debug adapter. +struct Capabilities { + Capabilities(); + ~Capabilities(); + + // The set of additional module information exposed by the debug adapter. + optional> additionalModuleColumns; + // The set of characters that should trigger completion in a REPL. If not + // specified, the UI should assume the '.' character. + optional> completionTriggerCharacters; + // Available filters or options for the setExceptionBreakpoints request. + optional> exceptionBreakpointFilters; + // The debug adapter supports the 'terminateDebuggee' attribute on the + // 'disconnect' request. + optional supportTerminateDebuggee; + // Checksum algorithms supported by the debug adapter. + optional> supportedChecksumAlgorithms; + // The debug adapter supports the 'breakpointLocations' request. + optional supportsBreakpointLocationsRequest; + // The debug adapter supports the 'cancel' request. + optional supportsCancelRequest; + // The debug adapter supports the 'completions' request. + optional supportsCompletionsRequest; + // The debug adapter supports conditional breakpoints. + optional supportsConditionalBreakpoints; + // The debug adapter supports the 'configurationDone' request. + optional supportsConfigurationDoneRequest; + // The debug adapter supports data breakpoints. + optional supportsDataBreakpoints; + // The debug adapter supports the delayed loading of parts of the stack, which + // requires that both the 'startFrame' and 'levels' arguments and the + // 'totalFrames' result of the 'StackTrace' request are supported. + optional supportsDelayedStackTraceLoading; + // The debug adapter supports the 'disassemble' request. + optional supportsDisassembleRequest; + // The debug adapter supports a (side effect free) evaluate request for data + // hovers. + optional supportsEvaluateForHovers; + // The debug adapter supports the 'exceptionInfo' request. + optional supportsExceptionInfoRequest; + // The debug adapter supports 'exceptionOptions' on the + // setExceptionBreakpoints request. + optional supportsExceptionOptions; + // The debug adapter supports function breakpoints. + optional supportsFunctionBreakpoints; + // The debug adapter supports the 'gotoTargets' request. + optional supportsGotoTargetsRequest; + // The debug adapter supports breakpoints that break execution after a + // specified number of hits. + optional supportsHitConditionalBreakpoints; + // The debug adapter supports the 'loadedSources' request. + optional supportsLoadedSourcesRequest; + // The debug adapter supports logpoints by interpreting the 'logMessage' + // attribute of the SourceBreakpoint. + optional supportsLogPoints; + // The debug adapter supports the 'modules' request. + optional supportsModulesRequest; + // The debug adapter supports the 'readMemory' request. + optional supportsReadMemoryRequest; + // The debug adapter supports restarting a frame. + optional supportsRestartFrame; + // The debug adapter supports the 'restart' request. In this case a client + // should not implement 'restart' by terminating and relaunching the adapter + // but by calling the RestartRequest. + optional supportsRestartRequest; + // The debug adapter supports the 'setExpression' request. + optional supportsSetExpression; + // The debug adapter supports setting a variable to a value. + optional supportsSetVariable; + // The debug adapter supports stepping back via the 'stepBack' and + // 'reverseContinue' requests. + optional supportsStepBack; + // The debug adapter supports the 'stepInTargets' request. + optional supportsStepInTargetsRequest; + // The debug adapter supports the 'terminate' request. + optional supportsTerminateRequest; + // The debug adapter supports the 'terminateThreads' request. + optional supportsTerminateThreadsRequest; + // The debug adapter supports a 'format' attribute on the stackTraceRequest, + // variablesRequest, and evaluateRequest. + optional supportsValueFormattingOptions; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(Capabilities); + +// The event indicates that one or more capabilities have changed. +// Since the capabilities are dependent on the frontend and its UI, it might not +// be possible to change that at random times (or too late). Consequently this +// event has a hint characteristic: a frontend can only be expected to make a +// 'best effort' in honouring individual capabilities but there are no +// guarantees. Only changed capabilities need to be included, all other +// capabilities keep their values. +struct CapabilitiesEvent : public Event { + CapabilitiesEvent(); + ~CapabilitiesEvent(); + + // The set of updated capabilities. + Capabilities capabilities; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(CapabilitiesEvent); + +// Some predefined types for the CompletionItem. Please note that not all +// clients have specific icons for all of them. +struct CompletionItemType { + CompletionItemType(); + ~CompletionItemType(); +}; + +DAP_DECLARE_STRUCT_TYPEINFO(CompletionItemType); + +// CompletionItems are the suggestions returned from the CompletionsRequest. +struct CompletionItem { + CompletionItem(); + ~CompletionItem(); + + // The label of this completion item. By default this is also the text that is + // inserted when selecting this completion. + string label; + // This value determines how many characters are overwritten by the completion + // text. If missing the value 0 is assumed which results in the completion + // text being inserted. + optional length; + // A string that should be used when comparing this item with other items. + // When `falsy` the label is used. + optional sortText; + // This value determines the location (in the CompletionsRequest's 'text' + // attribute) where the completion text is added. If missing the text is added + // at the location specified by the CompletionsRequest's 'column' attribute. + optional start; + // If text is not falsy then it is inserted instead of the label. + optional text; + // The item's type. Typically the client uses this information to render the + // item in the UI with an icon. + optional type; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(CompletionItem); + +// Response to 'completions' request. +struct CompletionsResponse : public Response { + CompletionsResponse(); + ~CompletionsResponse(); + + // The possible completions for . + array targets; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(CompletionsResponse); + +// Returns a list of possible completions for a given caret position and text. +// The CompletionsRequest may only be called if the 'supportsCompletionsRequest' +// capability exists and is true. +struct CompletionsRequest : public Request { + using Response = CompletionsResponse; + + CompletionsRequest(); + ~CompletionsRequest(); + + // The character position for which to determine the completion proposals. + integer column; + // Returns completions in the scope of this stack frame. If not specified, the + // completions are returned for the global scope. + optional frameId; + // An optional line for which to determine the completion proposals. If + // missing the first line of the text is assumed. + optional line; + // One or more source lines. Typically this is the text a user has typed into + // the debug console before he asked for completion. + string text; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(CompletionsRequest); + +// Response to 'configurationDone' request. This is just an acknowledgement, so +// no body field is required. +struct ConfigurationDoneResponse : public Response { + ConfigurationDoneResponse(); + ~ConfigurationDoneResponse(); +}; + +DAP_DECLARE_STRUCT_TYPEINFO(ConfigurationDoneResponse); + +// The client of the debug protocol must send this request at the end of the +// sequence of configuration requests (which was started by the 'initialized' +// event). +struct ConfigurationDoneRequest : public Request { + using Response = ConfigurationDoneResponse; + + ConfigurationDoneRequest(); + ~ConfigurationDoneRequest(); +}; + +DAP_DECLARE_STRUCT_TYPEINFO(ConfigurationDoneRequest); + +// Response to 'continue' request. +struct ContinueResponse : public Response { + ContinueResponse(); + ~ContinueResponse(); + + // If true, the 'continue' request has ignored the specified thread and + // continued all threads instead. If this attribute is missing a value of + // 'true' is assumed for backward compatibility. + optional allThreadsContinued; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(ContinueResponse); + +// The request starts the debuggee to run again. +struct ContinueRequest : public Request { + using Response = ContinueResponse; + + ContinueRequest(); + ~ContinueRequest(); + + // Continue execution for the specified thread (if possible). If the backend + // cannot continue on a single thread but will continue on all threads, it + // should set the 'allThreadsContinued' attribute in the response to true. + integer threadId; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(ContinueRequest); + +// The event indicates that the execution of the debuggee has continued. +// Please note: a debug adapter is not expected to send this event in response +// to a request that implies that execution continues, e.g. 'launch' or +// 'continue'. It is only necessary to send a 'continued' event if there was no +// previous request that implied this. +struct ContinuedEvent : public Event { + ContinuedEvent(); + ~ContinuedEvent(); + + // If 'allThreadsContinued' is true, a debug adapter can announce that all + // threads have continued. + optional allThreadsContinued; + // The thread which was continued. + integer threadId; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(ContinuedEvent); + +// This enumeration defines all possible access types for data breakpoints. +struct DataBreakpointAccessType { + DataBreakpointAccessType(); + ~DataBreakpointAccessType(); +}; + +DAP_DECLARE_STRUCT_TYPEINFO(DataBreakpointAccessType); + +// Response to 'dataBreakpointInfo' request. +struct DataBreakpointInfoResponse : public Response { + DataBreakpointInfoResponse(); + ~DataBreakpointInfoResponse(); + + // Optional attribute listing the available access types for a potential data + // breakpoint. A UI frontend could surface this information. + optional> accessTypes; + // Optional attribute indicating that a potential data breakpoint could be + // persisted across sessions. + optional canPersist; + // An identifier for the data on which a data breakpoint can be registered + // with the setDataBreakpoints request or null if no data breakpoint is + // available. + variant dataId; + // UI string that describes on what data the breakpoint is set on or why a + // data breakpoint is not available. + string description; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(DataBreakpointInfoResponse); + +// Obtains information on a possible data breakpoint that could be set on an +// expression or variable. +struct DataBreakpointInfoRequest : public Request { + using Response = DataBreakpointInfoResponse; + + DataBreakpointInfoRequest(); + ~DataBreakpointInfoRequest(); + + // The name of the Variable's child to obtain data breakpoint information for. + // If variableReference isn’t provided, this can be an expression. + string name; + // Reference to the Variable container if the data breakpoint is requested for + // a child of the container. + optional variablesReference; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(DataBreakpointInfoRequest); + +// Represents a single disassembled instruction. +struct DisassembledInstruction { + DisassembledInstruction(); + ~DisassembledInstruction(); + + // The address of the instruction. Treated as a hex value if prefixed with + // '0x', or as a decimal value otherwise. + string address; + // The column within the line that corresponds to this instruction, if any. + optional column; + // The end column of the range that corresponds to this instruction, if any. + optional endColumn; + // The end line of the range that corresponds to this instruction, if any. + optional endLine; + // Text representing the instruction and its operands, in an + // implementation-defined format. + string instruction; + // Optional raw bytes representing the instruction and its operands, in an + // implementation-defined format. + optional instructionBytes; + // The line within the source location that corresponds to this instruction, + // if any. + optional line; + // Source location that corresponds to this instruction, if any. Should always + // be set (if available) on the first instruction returned, but can be omitted + // afterwards if this instruction maps to the same source file as the previous + // instruction. + optional location; + // Name of the symbol that corresponds with the location of this instruction, + // if any. + optional symbol; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(DisassembledInstruction); + +// Response to 'disassemble' request. +struct DisassembleResponse : public Response { + DisassembleResponse(); + ~DisassembleResponse(); + + // The list of disassembled instructions. + array instructions; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(DisassembleResponse); + +// Disassembles code stored at the provided location. +struct DisassembleRequest : public Request { + using Response = DisassembleResponse; + + DisassembleRequest(); + ~DisassembleRequest(); + + // Number of instructions to disassemble starting at the specified location + // and offset. An adapter must return exactly this number of instructions - + // any unavailable instructions should be replaced with an + // implementation-defined 'invalid instruction' value. + integer instructionCount; + // Optional offset (in instructions) to be applied after the byte offset (if + // any) before disassembling. Can be negative. + optional instructionOffset; + // Memory reference to the base location containing the instructions to + // disassemble. + string memoryReference; + // Optional offset (in bytes) to be applied to the reference location before + // disassembling. Can be negative. + optional offset; + // If true, the adapter should attempt to resolve memory addresses and other + // values to symbolic names. + optional resolveSymbols; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(DisassembleRequest); + +// Response to 'disconnect' request. This is just an acknowledgement, so no body +// field is required. +struct DisconnectResponse : public Response { + DisconnectResponse(); + ~DisconnectResponse(); +}; + +DAP_DECLARE_STRUCT_TYPEINFO(DisconnectResponse); + +// The 'disconnect' request is sent from the client to the debug adapter in +// order to stop debugging. It asks the debug adapter to disconnect from the +// debuggee and to terminate the debug adapter. If the debuggee has been started +// with the 'launch' request, the 'disconnect' request terminates the debuggee. +// If the 'attach' request was used to connect to the debuggee, 'disconnect' +// does not terminate the debuggee. This behavior can be controlled with the +// 'terminateDebuggee' argument (if supported by the debug adapter). +struct DisconnectRequest : public Request { + using Response = DisconnectResponse; + + DisconnectRequest(); + ~DisconnectRequest(); + + // A value of true indicates that this 'disconnect' request is part of a + // restart sequence. + optional restart; + // Indicates whether the debuggee should be terminated when the debugger is + // disconnected. If unspecified, the debug adapter is free to do whatever it + // thinks is best. A client can only rely on this attribute being properly + // honored if a debug adapter returns true for the 'supportTerminateDebuggee' + // capability. + optional terminateDebuggee; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(DisconnectRequest); + +// A structured message object. Used to return errors from requests. +struct Message { + Message(); + ~Message(); + + // A format string for the message. Embedded variables have the form '{name}'. + // If variable name starts with an underscore character, the variable does not + // contain user data (PII) and can be safely used for telemetry purposes. + string format; + // Unique identifier for the message. + integer id; + // If true send to telemetry. + optional sendTelemetry; + // If true show user. + optional showUser; + // An optional url where additional information about this message can be + // found. + optional url; + // An optional label that is presented to the user as the UI for opening the + // url. + optional urlLabel; + // An object used as a dictionary for looking up the variables in the format + // string. + optional variables; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(Message); + +// On error (whenever 'success' is false), the body can provide more details. +struct ErrorResponse : public Response { + ErrorResponse(); + ~ErrorResponse(); + + // An optional, structured error message. + optional error; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(ErrorResponse); + +// Optional properties of a variable that can be used to determine how to render +// the variable in the UI. +struct VariablePresentationHint { + VariablePresentationHint(); + ~VariablePresentationHint(); + + // Set of attributes represented as an array of strings. Before introducing + // additional values, try to use the listed values. + optional> attributes; + // The kind of variable. Before introducing additional values, try to use the + // listed values. + // + // May be one of the following enumeration values: + // 'property', 'method', 'class', 'data', 'event', 'baseClass', 'innerClass', + // 'interface', 'mostDerivedClass', 'virtual', 'dataBreakpoint' + optional kind; + // Visibility of variable. Before introducing additional values, try to use + // the listed values. + // + // May be one of the following enumeration values: + // 'public', 'private', 'protected', 'internal', 'final' + optional visibility; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(VariablePresentationHint); + +// Response to 'evaluate' request. +struct EvaluateResponse : public Response { + EvaluateResponse(); + ~EvaluateResponse(); + + // The number of indexed child variables. + // The client can use this optional information to present the variables in a + // paged UI and fetch them in chunks. The value should be less than or equal + // to 2147483647 (2^31 - 1). + optional indexedVariables; + // Memory reference to a location appropriate for this result. For pointer + // type eval results, this is generally a reference to the memory address + // contained in the pointer. + optional memoryReference; + // The number of named child variables. + // The client can use this optional information to present the variables in a + // paged UI and fetch them in chunks. The value should be less than or equal + // to 2147483647 (2^31 - 1). + optional namedVariables; + // Properties of a evaluate result that can be used to determine how to render + // the result in the UI. + optional presentationHint; + // The result of the evaluate request. + string result; + // The optional type of the evaluate result. + optional type; + // If variablesReference is > 0, the evaluate result is structured and its + // children can be retrieved by passing variablesReference to the + // VariablesRequest. The value should be less than or equal to 2147483647 + // (2^31 - 1). + integer variablesReference; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(EvaluateResponse); + +// Provides formatting information for a value. +struct ValueFormat { + ValueFormat(); + ~ValueFormat(); + + // Display the value in hex. + optional hex; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(ValueFormat); + +// Evaluates the given expression in the context of the top most stack frame. +// The expression has access to any variables and arguments that are in scope. +struct EvaluateRequest : public Request { + using Response = EvaluateResponse; + + EvaluateRequest(); + ~EvaluateRequest(); + + // The context in which the evaluate request is run. + // + // May be one of the following enumeration values: + // 'watch', 'repl', 'hover' + optional context; + // The expression to evaluate. + string expression; + // Specifies details on how to format the Evaluate result. + optional format; + // Evaluate the expression in the scope of this stack frame. If not specified, + // the expression is evaluated in the global scope. + optional frameId; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(EvaluateRequest); + +// This enumeration defines all possible conditions when a thrown exception +// should result in a break. never: never breaks, always: always breaks, +// unhandled: breaks when exception unhandled, +// userUnhandled: breaks if the exception is not handled by user code. +struct ExceptionBreakMode { + ExceptionBreakMode(); + ~ExceptionBreakMode(); +}; + +DAP_DECLARE_STRUCT_TYPEINFO(ExceptionBreakMode); + +// Detailed information about an exception that has occurred. +struct ExceptionDetails { + ExceptionDetails(); + ~ExceptionDetails(); + + // Optional expression that can be evaluated in the current scope to obtain + // the exception object. + optional evaluateName; + // Fully-qualified type name of the exception object. + optional fullTypeName; + // Details of the exception contained by this exception, if any. + optional> innerException; + // Message contained in the exception. + optional message; + // Stack trace at the time the exception was thrown. + optional stackTrace; + // Short type name of the exception object. + optional typeName; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(ExceptionDetails); + +// Response to 'exceptionInfo' request. +struct ExceptionInfoResponse : public Response { + ExceptionInfoResponse(); + ~ExceptionInfoResponse(); + + // Mode that caused the exception notification to be raised. + ExceptionBreakMode breakMode; + // Descriptive text for the exception provided by the debug adapter. + optional description; + // Detailed information about the exception. + optional details; + // ID of the exception that was thrown. + string exceptionId; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(ExceptionInfoResponse); + +// Retrieves the details of the exception that caused this event to be raised. +struct ExceptionInfoRequest : public Request { + using Response = ExceptionInfoResponse; + + ExceptionInfoRequest(); + ~ExceptionInfoRequest(); + + // Thread for which exception information should be retrieved. + integer threadId; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(ExceptionInfoRequest); + +// The event indicates that the debuggee has exited and returns its exit code. +struct ExitedEvent : public Event { + ExitedEvent(); + ~ExitedEvent(); + + // The exit code returned from the debuggee. + integer exitCode; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(ExitedEvent); + +// Response to 'goto' request. This is just an acknowledgement, so no body field +// is required. +struct GotoResponse : public Response { + GotoResponse(); + ~GotoResponse(); +}; + +DAP_DECLARE_STRUCT_TYPEINFO(GotoResponse); + +// The request sets the location where the debuggee will continue to run. +// This makes it possible to skip the execution of code or to executed code +// again. The code between the current location and the goto target is not +// executed but skipped. The debug adapter first sends the response and then a +// 'stopped' event with reason 'goto'. +struct GotoRequest : public Request { + using Response = GotoResponse; + + GotoRequest(); + ~GotoRequest(); + + // The location where the debuggee will continue to run. + integer targetId; + // Set the goto target for this thread. + integer threadId; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(GotoRequest); + +// A GotoTarget describes a code location that can be used as a target in the +// 'goto' request. The possible goto targets can be determined via the +// 'gotoTargets' request. +struct GotoTarget { + GotoTarget(); + ~GotoTarget(); + + // An optional column of the goto target. + optional column; + // An optional end column of the range covered by the goto target. + optional endColumn; + // An optional end line of the range covered by the goto target. + optional endLine; + // Unique identifier for a goto target. This is used in the goto request. + integer id; + // Optional memory reference for the instruction pointer value represented by + // this target. + optional instructionPointerReference; + // The name of the goto target (shown in the UI). + string label; + // The line of the goto target. + integer line; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(GotoTarget); + +// Response to 'gotoTargets' request. +struct GotoTargetsResponse : public Response { + GotoTargetsResponse(); + ~GotoTargetsResponse(); + + // The possible goto targets of the specified location. + array targets; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(GotoTargetsResponse); + +// This request retrieves the possible goto targets for the specified source +// location. These targets can be used in the 'goto' request. The GotoTargets +// request may only be called if the 'supportsGotoTargetsRequest' capability +// exists and is true. +struct GotoTargetsRequest : public Request { + using Response = GotoTargetsResponse; + + GotoTargetsRequest(); + ~GotoTargetsRequest(); + + // An optional column location for which the goto targets are determined. + optional column; + // The line location for which the goto targets are determined. + integer line; + // The source location for which the goto targets are determined. + Source source; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(GotoTargetsRequest); + +// Response to 'initialize' request. +struct InitializeResponse : public Response { + InitializeResponse(); + ~InitializeResponse(); + + // The set of additional module information exposed by the debug adapter. + optional> additionalModuleColumns; + // The set of characters that should trigger completion in a REPL. If not + // specified, the UI should assume the '.' character. + optional> completionTriggerCharacters; + // Available filters or options for the setExceptionBreakpoints request. + optional> exceptionBreakpointFilters; + // The debug adapter supports the 'terminateDebuggee' attribute on the + // 'disconnect' request. + optional supportTerminateDebuggee; + // Checksum algorithms supported by the debug adapter. + optional> supportedChecksumAlgorithms; + // The debug adapter supports the 'breakpointLocations' request. + optional supportsBreakpointLocationsRequest; + // The debug adapter supports the 'cancel' request. + optional supportsCancelRequest; + // The debug adapter supports the 'completions' request. + optional supportsCompletionsRequest; + // The debug adapter supports conditional breakpoints. + optional supportsConditionalBreakpoints; + // The debug adapter supports the 'configurationDone' request. + optional supportsConfigurationDoneRequest; + // The debug adapter supports data breakpoints. + optional supportsDataBreakpoints; + // The debug adapter supports the delayed loading of parts of the stack, which + // requires that both the 'startFrame' and 'levels' arguments and the + // 'totalFrames' result of the 'StackTrace' request are supported. + optional supportsDelayedStackTraceLoading; + // The debug adapter supports the 'disassemble' request. + optional supportsDisassembleRequest; + // The debug adapter supports a (side effect free) evaluate request for data + // hovers. + optional supportsEvaluateForHovers; + // The debug adapter supports the 'exceptionInfo' request. + optional supportsExceptionInfoRequest; + // The debug adapter supports 'exceptionOptions' on the + // setExceptionBreakpoints request. + optional supportsExceptionOptions; + // The debug adapter supports function breakpoints. + optional supportsFunctionBreakpoints; + // The debug adapter supports the 'gotoTargets' request. + optional supportsGotoTargetsRequest; + // The debug adapter supports breakpoints that break execution after a + // specified number of hits. + optional supportsHitConditionalBreakpoints; + // The debug adapter supports the 'loadedSources' request. + optional supportsLoadedSourcesRequest; + // The debug adapter supports logpoints by interpreting the 'logMessage' + // attribute of the SourceBreakpoint. + optional supportsLogPoints; + // The debug adapter supports the 'modules' request. + optional supportsModulesRequest; + // The debug adapter supports the 'readMemory' request. + optional supportsReadMemoryRequest; + // The debug adapter supports restarting a frame. + optional supportsRestartFrame; + // The debug adapter supports the 'restart' request. In this case a client + // should not implement 'restart' by terminating and relaunching the adapter + // but by calling the RestartRequest. + optional supportsRestartRequest; + // The debug adapter supports the 'setExpression' request. + optional supportsSetExpression; + // The debug adapter supports setting a variable to a value. + optional supportsSetVariable; + // The debug adapter supports stepping back via the 'stepBack' and + // 'reverseContinue' requests. + optional supportsStepBack; + // The debug adapter supports the 'stepInTargets' request. + optional supportsStepInTargetsRequest; + // The debug adapter supports the 'terminate' request. + optional supportsTerminateRequest; + // The debug adapter supports the 'terminateThreads' request. + optional supportsTerminateThreadsRequest; + // The debug adapter supports a 'format' attribute on the stackTraceRequest, + // variablesRequest, and evaluateRequest. + optional supportsValueFormattingOptions; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(InitializeResponse); + +// The 'initialize' request is sent as the first request from the client to the +// debug adapter in order to configure it with client capabilities and to +// retrieve capabilities from the debug adapter. Until the debug adapter has +// responded to with an 'initialize' response, the client must not send any +// additional requests or events to the debug adapter. In addition the debug +// adapter is not allowed to send any requests or events to the client until it +// has responded with an 'initialize' response. The 'initialize' request may +// only be sent once. +struct InitializeRequest : public Request { + using Response = InitializeResponse; + + InitializeRequest(); + ~InitializeRequest(); + + // The ID of the debug adapter. + string adapterID; + // The ID of the (frontend) client using this adapter. + optional clientID; + // The human readable name of the (frontend) client using this adapter. + optional clientName; + // If true all column numbers are 1-based (default). + optional columnsStartAt1; + // If true all line numbers are 1-based (default). + optional linesStartAt1; + // The ISO-639 locale of the (frontend) client using this adapter, e.g. en-US + // or de-CH. + optional locale; + // Determines in what format paths are specified. The default is 'path', which + // is the native format. + // + // May be one of the following enumeration values: + // 'path', 'uri' + optional pathFormat; + // Client supports memory references. + optional supportsMemoryReferences; + // Client supports the runInTerminal request. + optional supportsRunInTerminalRequest; + // Client supports the paging of variables. + optional supportsVariablePaging; + // Client supports the optional type attribute for variables. + optional supportsVariableType; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(InitializeRequest); + +// This event indicates that the debug adapter is ready to accept configuration +// requests (e.g. SetBreakpointsRequest, SetExceptionBreakpointsRequest). A +// debug adapter is expected to send this event when it is ready to accept +// configuration requests (but not before the 'initialize' request has +// finished). The sequence of events/requests is as follows: +// - adapters sends 'initialized' event (after the 'initialize' request has +// returned) +// - frontend sends zero or more 'setBreakpoints' requests +// - frontend sends one 'setFunctionBreakpoints' request +// - frontend sends a 'setExceptionBreakpoints' request if one or more +// 'exceptionBreakpointFilters' have been defined (or if +// 'supportsConfigurationDoneRequest' is not defined or false) +// - frontend sends other future configuration requests +// - frontend sends one 'configurationDone' request to indicate the end of the +// configuration. +struct InitializedEvent : public Event { + InitializedEvent(); + ~InitializedEvent(); +}; + +DAP_DECLARE_STRUCT_TYPEINFO(InitializedEvent); + +// Response to 'launch' request. This is just an acknowledgement, so no body +// field is required. +struct LaunchResponse : public Response { + LaunchResponse(); + ~LaunchResponse(); +}; + +DAP_DECLARE_STRUCT_TYPEINFO(LaunchResponse); + +// The launch request is sent from the client to the debug adapter to start the +// debuggee with or without debugging (if 'noDebug' is true). Since launching is +// debugger/runtime specific, the arguments for this request are not part of +// this specification. +struct LaunchRequest : public Request { + using Response = LaunchResponse; + + LaunchRequest(); + ~LaunchRequest(); + + // Optional data from the previous, restarted session. + // The data is sent as the 'restart' attribute of the 'terminated' event. + // The client should leave the data intact. + optional, boolean, integer, null, number, object, string>> + restart; + // If noDebug is true the launch request should launch the program without + // enabling debugging. + optional noDebug; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(LaunchRequest); + +// The event indicates that some source has been added, changed, or removed from +// the set of all loaded sources. +struct LoadedSourceEvent : public Event { + LoadedSourceEvent(); + ~LoadedSourceEvent(); + + // The reason for the event. + // + // Must be one of the following enumeration values: + // 'new', 'changed', 'removed' + string reason = "new"; + // The new, changed, or removed source. + Source source; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(LoadedSourceEvent); + +// Response to 'loadedSources' request. +struct LoadedSourcesResponse : public Response { + LoadedSourcesResponse(); + ~LoadedSourcesResponse(); + + // Set of loaded sources. + array sources; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(LoadedSourcesResponse); + +// Retrieves the set of all sources currently loaded by the debugged process. +struct LoadedSourcesRequest : public Request { + using Response = LoadedSourcesResponse; + + LoadedSourcesRequest(); + ~LoadedSourcesRequest(); +}; + +DAP_DECLARE_STRUCT_TYPEINFO(LoadedSourcesRequest); + +// A Module object represents a row in the modules view. +// Two attributes are mandatory: an id identifies a module in the modules view +// and is used in a ModuleEvent for identifying a module for adding, updating or +// deleting. The name is used to minimally render the module in the UI. +// +// Additional attributes can be added to the module. They will show up in the +// module View if they have a corresponding ColumnDescriptor. +// +// To avoid an unnecessary proliferation of additional attributes with similar +// semantics but different names we recommend to re-use attributes from the +// 'recommended' list below first, and only introduce new attributes if nothing +// appropriate could be found. +struct Module { + Module(); + ~Module(); + + // Address range covered by this module. + optional addressRange; + // Module created or modified. + optional dateTimeStamp; + // Unique identifier for the module. + variant id; + // True if the module is optimized. + optional isOptimized; + // True if the module is considered 'user code' by a debugger that supports + // 'Just My Code'. + optional isUserCode; + // A name of the module. + string name; + // optional but recommended attributes. + // always try to use these first before introducing additional attributes. + // + // Logical full path to the module. The exact definition is implementation + // defined, but usually this would be a full path to the on-disk file for the + // module. + optional path; + // Logical full path to the symbol file. The exact definition is + // implementation defined. + optional symbolFilePath; + // User understandable description of if symbols were found for the module + // (ex: 'Symbols Loaded', 'Symbols not found', etc. + optional symbolStatus; + // Version of Module. + optional version; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(Module); + +// The event indicates that some information about a module has changed. +struct ModuleEvent : public Event { + ModuleEvent(); + ~ModuleEvent(); + + // The new, changed, or removed module. In case of 'removed' only the module + // id is used. + Module module; + // The reason for the event. + // + // Must be one of the following enumeration values: + // 'new', 'changed', 'removed' + string reason = "new"; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(ModuleEvent); + +// Response to 'modules' request. +struct ModulesResponse : public Response { + ModulesResponse(); + ~ModulesResponse(); + + // All modules or range of modules. + array modules; + // The total number of modules available. + optional totalModules; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(ModulesResponse); + +// Modules can be retrieved from the debug adapter with the ModulesRequest which +// can either return all modules or a range of modules to support paging. +struct ModulesRequest : public Request { + using Response = ModulesResponse; + + ModulesRequest(); + ~ModulesRequest(); + + // The number of modules to return. If moduleCount is not specified or 0, all + // modules are returned. + optional moduleCount; + // The index of the first module to return; if omitted modules start at 0. + optional startModule; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(ModulesRequest); + +// Response to 'next' request. This is just an acknowledgement, so no body field +// is required. +struct NextResponse : public Response { + NextResponse(); + ~NextResponse(); +}; + +DAP_DECLARE_STRUCT_TYPEINFO(NextResponse); + +// The request starts the debuggee to run again for one step. +// The debug adapter first sends the response and then a 'stopped' event (with +// reason 'step') after the step has completed. +struct NextRequest : public Request { + using Response = NextResponse; + + NextRequest(); + ~NextRequest(); + + // Execute 'next' for this thread. + integer threadId; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(NextRequest); + +// The event indicates that the target has produced some output. +struct OutputEvent : public Event { + OutputEvent(); + ~OutputEvent(); + + // The output category. If not specified, 'console' is assumed. + // + // May be one of the following enumeration values: + // 'console', 'stdout', 'stderr', 'telemetry' + optional category; + // An optional source location column where the output was produced. + optional column; + // Optional data to report. For the 'telemetry' category the data will be sent + // to telemetry, for the other categories the data is shown in JSON format. + optional, boolean, integer, null, number, object, string>> + data; + // An optional source location line where the output was produced. + optional line; + // The output to report. + string output; + // An optional source location where the output was produced. + optional source; + // If an attribute 'variablesReference' exists and its value is > 0, the + // output contains objects which can be retrieved by passing + // 'variablesReference' to the 'variables' request. The value should be less + // than or equal to 2147483647 (2^31 - 1). + optional variablesReference; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(OutputEvent); + +// Response to 'pause' request. This is just an acknowledgement, so no body +// field is required. +struct PauseResponse : public Response { + PauseResponse(); + ~PauseResponse(); +}; + +DAP_DECLARE_STRUCT_TYPEINFO(PauseResponse); + +// The request suspends the debuggee. +// The debug adapter first sends the response and then a 'stopped' event (with +// reason 'pause') after the thread has been paused successfully. +struct PauseRequest : public Request { + using Response = PauseResponse; + + PauseRequest(); + ~PauseRequest(); + + // Pause execution for this thread. + integer threadId; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(PauseRequest); + +// The event indicates that the debugger has begun debugging a new process. +// Either one that it has launched, or one that it has attached to. +struct ProcessEvent : public Event { + ProcessEvent(); + ~ProcessEvent(); + + // If true, the process is running on the same computer as the debug adapter. + optional isLocalProcess; + // The logical name of the process. This is usually the full path to process's + // executable file. Example: /home/example/myproj/program.js. + string name; + // The size of a pointer or address for this process, in bits. This value may + // be used by clients when formatting addresses for display. + optional pointerSize; + // Describes how the debug engine started debugging this process. + // + // Must be one of the following enumeration values: + // 'launch', 'attach', 'attachForSuspendedLaunch' + optional startMethod; + // The system process id of the debugged process. This property will be + // missing for non-system processes. + optional systemProcessId; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(ProcessEvent); + +// Response to 'readMemory' request. +struct ReadMemoryResponse : public Response { + ReadMemoryResponse(); + ~ReadMemoryResponse(); + + // The address of the first byte of data returned. Treated as a hex value if + // prefixed with '0x', or as a decimal value otherwise. + string address; + // The bytes read from memory, encoded using base64. + optional data; + // The number of unreadable bytes encountered after the last successfully read + // byte. This can be used to determine the number of bytes that must be + // skipped before a subsequent 'readMemory' request will succeed. + optional unreadableBytes; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(ReadMemoryResponse); + +// Reads bytes from memory at the provided location. +struct ReadMemoryRequest : public Request { + using Response = ReadMemoryResponse; + + ReadMemoryRequest(); + ~ReadMemoryRequest(); + + // Number of bytes to read at the specified location and offset. + integer count; + // Memory reference to the base location from which data should be read. + string memoryReference; + // Optional offset (in bytes) to be applied to the reference location before + // reading data. Can be negative. + optional offset; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(ReadMemoryRequest); + +// Response to 'restartFrame' request. This is just an acknowledgement, so no +// body field is required. +struct RestartFrameResponse : public Response { + RestartFrameResponse(); + ~RestartFrameResponse(); +}; + +DAP_DECLARE_STRUCT_TYPEINFO(RestartFrameResponse); + +// The request restarts execution of the specified stackframe. +// The debug adapter first sends the response and then a 'stopped' event (with +// reason 'restart') after the restart has completed. +struct RestartFrameRequest : public Request { + using Response = RestartFrameResponse; + + RestartFrameRequest(); + ~RestartFrameRequest(); + + // Restart this stackframe. + integer frameId; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(RestartFrameRequest); + +// Response to 'restart' request. This is just an acknowledgement, so no body +// field is required. +struct RestartResponse : public Response { + RestartResponse(); + ~RestartResponse(); +}; + +DAP_DECLARE_STRUCT_TYPEINFO(RestartResponse); + +// Restarts a debug session. If the capability 'supportsRestartRequest' is +// missing or has the value false, the client will implement 'restart' by +// terminating the debug adapter first and then launching it anew. A debug +// adapter can override this default behaviour by implementing a restart request +// and setting the capability 'supportsRestartRequest' to true. +struct RestartRequest : public Request { + using Response = RestartResponse; + + RestartRequest(); + ~RestartRequest(); +}; + +DAP_DECLARE_STRUCT_TYPEINFO(RestartRequest); + +// Response to 'reverseContinue' request. This is just an acknowledgement, so no +// body field is required. +struct ReverseContinueResponse : public Response { + ReverseContinueResponse(); + ~ReverseContinueResponse(); +}; + +DAP_DECLARE_STRUCT_TYPEINFO(ReverseContinueResponse); + +// The request starts the debuggee to run backward. Clients should only call +// this request if the capability 'supportsStepBack' is true. +struct ReverseContinueRequest : public Request { + using Response = ReverseContinueResponse; + + ReverseContinueRequest(); + ~ReverseContinueRequest(); + + // Execute 'reverseContinue' for this thread. + integer threadId; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(ReverseContinueRequest); + +// Response to 'runInTerminal' request. +struct RunInTerminalResponse : public Response { + RunInTerminalResponse(); + ~RunInTerminalResponse(); + + // The process ID. The value should be less than or equal to 2147483647 (2^31 + // - 1). + optional processId; + // The process ID of the terminal shell. The value should be less than or + // equal to 2147483647 (2^31 - 1). + optional shellProcessId; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(RunInTerminalResponse); + +// This request is sent from the debug adapter to the client to run a command in +// a terminal. This is typically used to launch the debuggee in a terminal +// provided by the client. +struct RunInTerminalRequest : public Request { + using Response = RunInTerminalResponse; + + RunInTerminalRequest(); + ~RunInTerminalRequest(); + + // List of arguments. The first argument is the command to run. + array args; + // Working directory of the command. + string cwd; + // Environment key-value pairs that are added to or removed from the default + // environment. + optional env; + // What kind of terminal to launch. + // + // Must be one of the following enumeration values: + // 'integrated', 'external' + optional kind; + // Optional title of the terminal. + optional title; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(RunInTerminalRequest); + +// A Scope is a named container for variables. Optionally a scope can map to a +// source or a range within a source. +struct Scope { + Scope(); + ~Scope(); + + // Optional start column of the range covered by this scope. + optional column; + // Optional end column of the range covered by this scope. + optional endColumn; + // Optional end line of the range covered by this scope. + optional endLine; + // If true, the number of variables in this scope is large or expensive to + // retrieve. + boolean expensive; + // The number of indexed variables in this scope. + // The client can use this optional information to present the variables in a + // paged UI and fetch them in chunks. + optional indexedVariables; + // Optional start line of the range covered by this scope. + optional line; + // Name of the scope such as 'Arguments', 'Locals', or 'Registers'. This + // string is shown in the UI as is and can be translated. + string name; + // The number of named variables in this scope. + // The client can use this optional information to present the variables in a + // paged UI and fetch them in chunks. + optional namedVariables; + // An optional hint for how to present this scope in the UI. If this attribute + // is missing, the scope is shown with a generic UI. + // + // May be one of the following enumeration values: + // 'arguments', 'locals', 'registers' + optional presentationHint; + // Optional source for this scope. + optional source; + // The variables of this scope can be retrieved by passing the value of + // variablesReference to the VariablesRequest. + integer variablesReference; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(Scope); + +// Response to 'scopes' request. +struct ScopesResponse : public Response { + ScopesResponse(); + ~ScopesResponse(); + + // The scopes of the stackframe. If the array has length zero, there are no + // scopes available. + array scopes; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(ScopesResponse); + +// The request returns the variable scopes for a given stackframe ID. +struct ScopesRequest : public Request { + using Response = ScopesResponse; + + ScopesRequest(); + ~ScopesRequest(); + + // Retrieve the scopes for this stackframe. + integer frameId; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(ScopesRequest); + +// Response to 'setBreakpoints' request. +// Returned is information about each breakpoint created by this request. +// This includes the actual code location and whether the breakpoint could be +// verified. The breakpoints returned are in the same order as the elements of +// the 'breakpoints' (or the deprecated 'lines') array in the arguments. +struct SetBreakpointsResponse : public Response { + SetBreakpointsResponse(); + ~SetBreakpointsResponse(); + + // Information about the breakpoints. The array elements are in the same order + // as the elements of the 'breakpoints' (or the deprecated 'lines') array in + // the arguments. + array breakpoints; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(SetBreakpointsResponse); + +// Properties of a breakpoint or logpoint passed to the setBreakpoints request. +struct SourceBreakpoint { + SourceBreakpoint(); + ~SourceBreakpoint(); + + // An optional source column of the breakpoint. + optional column; + // An optional expression for conditional breakpoints. + optional condition; + // An optional expression that controls how many hits of the breakpoint are + // ignored. The backend is expected to interpret the expression as needed. + optional hitCondition; + // The source line of the breakpoint or logpoint. + integer line; + // If this attribute exists and is non-empty, the backend must not 'break' + // (stop) but log the message instead. Expressions within {} are interpolated. + optional logMessage; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(SourceBreakpoint); + +// Sets multiple breakpoints for a single source and clears all previous +// breakpoints in that source. To clear all breakpoint for a source, specify an +// empty array. When a breakpoint is hit, a 'stopped' event (with reason +// 'breakpoint') is generated. +struct SetBreakpointsRequest : public Request { + using Response = SetBreakpointsResponse; + + SetBreakpointsRequest(); + ~SetBreakpointsRequest(); + + // The code locations of the breakpoints. + optional> breakpoints; + // Deprecated: The code locations of the breakpoints. + optional> lines; + // The source location of the breakpoints; either 'source.path' or + // 'source.reference' must be specified. + Source source; + // A value of true indicates that the underlying source has been modified + // which results in new breakpoint locations. + optional sourceModified; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(SetBreakpointsRequest); + +// Response to 'setDataBreakpoints' request. +// Returned is information about each breakpoint created by this request. +struct SetDataBreakpointsResponse : public Response { + SetDataBreakpointsResponse(); + ~SetDataBreakpointsResponse(); + + // Information about the data breakpoints. The array elements correspond to + // the elements of the input argument 'breakpoints' array. + array breakpoints; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(SetDataBreakpointsResponse); + +// Properties of a data breakpoint passed to the setDataBreakpoints request. +struct DataBreakpoint { + DataBreakpoint(); + ~DataBreakpoint(); + + // The access type of the data. + optional accessType; + // An optional expression for conditional breakpoints. + optional condition; + // An id representing the data. This id is returned from the + // dataBreakpointInfo request. + string dataId; + // An optional expression that controls how many hits of the breakpoint are + // ignored. The backend is expected to interpret the expression as needed. + optional hitCondition; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(DataBreakpoint); + +// Replaces all existing data breakpoints with new data breakpoints. +// To clear all data breakpoints, specify an empty array. +// When a data breakpoint is hit, a 'stopped' event (with reason 'data +// breakpoint') is generated. +struct SetDataBreakpointsRequest : public Request { + using Response = SetDataBreakpointsResponse; + + SetDataBreakpointsRequest(); + ~SetDataBreakpointsRequest(); + + // The contents of this array replaces all existing data breakpoints. An empty + // array clears all data breakpoints. + array breakpoints; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(SetDataBreakpointsRequest); + +// Response to 'setExceptionBreakpoints' request. This is just an +// acknowledgement, so no body field is required. +struct SetExceptionBreakpointsResponse : public Response { + SetExceptionBreakpointsResponse(); + ~SetExceptionBreakpointsResponse(); +}; + +DAP_DECLARE_STRUCT_TYPEINFO(SetExceptionBreakpointsResponse); + +// An ExceptionPathSegment represents a segment in a path that is used to match +// leafs or nodes in a tree of exceptions. If a segment consists of more than +// one name, it matches the names provided if 'negate' is false or missing or it +// matches anything except the names provided if 'negate' is true. +struct ExceptionPathSegment { + ExceptionPathSegment(); + ~ExceptionPathSegment(); + + // Depending on the value of 'negate' the names that should match or not + // match. + array names; + // If false or missing this segment matches the names provided, otherwise it + // matches anything except the names provided. + optional negate; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(ExceptionPathSegment); + +// An ExceptionOptions assigns configuration options to a set of exceptions. +struct ExceptionOptions { + ExceptionOptions(); + ~ExceptionOptions(); + + // Condition when a thrown exception should result in a break. + ExceptionBreakMode breakMode; + // A path that selects a single or multiple exceptions in a tree. If 'path' is + // missing, the whole tree is selected. By convention the first segment of the + // path is a category that is used to group exceptions in the UI. + optional> path; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(ExceptionOptions); + +// The request configures the debuggers response to thrown exceptions. If an +// exception is configured to break, a 'stopped' event is fired (with reason +// 'exception'). +struct SetExceptionBreakpointsRequest : public Request { + using Response = SetExceptionBreakpointsResponse; + + SetExceptionBreakpointsRequest(); + ~SetExceptionBreakpointsRequest(); + + // Configuration options for selected exceptions. + optional> exceptionOptions; + // IDs of checked exception options. The set of IDs is returned via the + // 'exceptionBreakpointFilters' capability. + array filters; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(SetExceptionBreakpointsRequest); + +// Response to 'setExpression' request. +struct SetExpressionResponse : public Response { + SetExpressionResponse(); + ~SetExpressionResponse(); + + // The number of indexed child variables. + // The client can use this optional information to present the variables in a + // paged UI and fetch them in chunks. The value should be less than or equal + // to 2147483647 (2^31 - 1). + optional indexedVariables; + // The number of named child variables. + // The client can use this optional information to present the variables in a + // paged UI and fetch them in chunks. The value should be less than or equal + // to 2147483647 (2^31 - 1). + optional namedVariables; + // Properties of a value that can be used to determine how to render the + // result in the UI. + optional presentationHint; + // The optional type of the value. + optional type; + // The new value of the expression. + string value; + // If variablesReference is > 0, the value is structured and its children can + // be retrieved by passing variablesReference to the VariablesRequest. The + // value should be less than or equal to 2147483647 (2^31 - 1). + optional variablesReference; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(SetExpressionResponse); + +// Evaluates the given 'value' expression and assigns it to the 'expression' +// which must be a modifiable l-value. The expressions have access to any +// variables and arguments that are in scope of the specified frame. +struct SetExpressionRequest : public Request { + using Response = SetExpressionResponse; + + SetExpressionRequest(); + ~SetExpressionRequest(); + + // The l-value expression to assign to. + string expression; + // Specifies how the resulting value should be formatted. + optional format; + // Evaluate the expressions in the scope of this stack frame. If not + // specified, the expressions are evaluated in the global scope. + optional frameId; + // The value expression to assign to the l-value expression. + string value; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(SetExpressionRequest); + +// Response to 'setFunctionBreakpoints' request. +// Returned is information about each breakpoint created by this request. +struct SetFunctionBreakpointsResponse : public Response { + SetFunctionBreakpointsResponse(); + ~SetFunctionBreakpointsResponse(); + + // Information about the breakpoints. The array elements correspond to the + // elements of the 'breakpoints' array. + array breakpoints; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(SetFunctionBreakpointsResponse); + +// Properties of a breakpoint passed to the setFunctionBreakpoints request. +struct FunctionBreakpoint { + FunctionBreakpoint(); + ~FunctionBreakpoint(); + + // An optional expression for conditional breakpoints. + optional condition; + // An optional expression that controls how many hits of the breakpoint are + // ignored. The backend is expected to interpret the expression as needed. + optional hitCondition; + // The name of the function. + string name; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(FunctionBreakpoint); + +// Replaces all existing function breakpoints with new function breakpoints. +// To clear all function breakpoints, specify an empty array. +// When a function breakpoint is hit, a 'stopped' event (with reason 'function +// breakpoint') is generated. +struct SetFunctionBreakpointsRequest : public Request { + using Response = SetFunctionBreakpointsResponse; + + SetFunctionBreakpointsRequest(); + ~SetFunctionBreakpointsRequest(); + + // The function names of the breakpoints. + array breakpoints; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(SetFunctionBreakpointsRequest); + +// Response to 'setVariable' request. +struct SetVariableResponse : public Response { + SetVariableResponse(); + ~SetVariableResponse(); + + // The number of indexed child variables. + // The client can use this optional information to present the variables in a + // paged UI and fetch them in chunks. The value should be less than or equal + // to 2147483647 (2^31 - 1). + optional indexedVariables; + // The number of named child variables. + // The client can use this optional information to present the variables in a + // paged UI and fetch them in chunks. The value should be less than or equal + // to 2147483647 (2^31 - 1). + optional namedVariables; + // The type of the new value. Typically shown in the UI when hovering over the + // value. + optional type; + // The new value of the variable. + string value; + // If variablesReference is > 0, the new value is structured and its children + // can be retrieved by passing variablesReference to the VariablesRequest. The + // value should be less than or equal to 2147483647 (2^31 - 1). + optional variablesReference; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(SetVariableResponse); + +// Set the variable with the given name in the variable container to a new +// value. +struct SetVariableRequest : public Request { + using Response = SetVariableResponse; + + SetVariableRequest(); + ~SetVariableRequest(); + + // Specifies details on how to format the response value. + optional format; + // The name of the variable in the container. + string name; + // The value of the variable. + string value; + // The reference of the variable container. + integer variablesReference; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(SetVariableRequest); + +// Response to 'source' request. +struct SourceResponse : public Response { + SourceResponse(); + ~SourceResponse(); + + // Content of the source reference. + string content; + // Optional content type (mime type) of the source. + optional mimeType; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(SourceResponse); + +// The request retrieves the source code for a given source reference. +struct SourceRequest : public Request { + using Response = SourceResponse; + + SourceRequest(); + ~SourceRequest(); + + // Specifies the source content to load. Either source.path or + // source.sourceReference must be specified. + optional source; + // The reference to the source. This is the same as source.sourceReference. + // This is provided for backward compatibility since old backends do not + // understand the 'source' attribute. + integer sourceReference; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(SourceRequest); + +// A Stackframe contains the source location. +struct StackFrame { + StackFrame(); + ~StackFrame(); + + // The column within the line. If source is null or doesn't exist, column is 0 + // and must be ignored. + integer column; + // An optional end column of the range covered by the stack frame. + optional endColumn; + // An optional end line of the range covered by the stack frame. + optional endLine; + // An identifier for the stack frame. It must be unique across all threads. + // This id can be used to retrieve the scopes of the frame with the + // 'scopesRequest' or to restart the execution of a stackframe. + integer id; + // Optional memory reference for the current instruction pointer in this + // frame. + optional instructionPointerReference; + // The line within the file of the frame. If source is null or doesn't exist, + // line is 0 and must be ignored. + integer line; + // The module associated with this frame, if any. + optional> moduleId; + // The name of the stack frame, typically a method name. + string name; + // An optional hint for how to present this frame in the UI. A value of + // 'label' can be used to indicate that the frame is an artificial frame that + // is used as a visual label or separator. A value of 'subtle' can be used to + // change the appearance of a frame in a 'subtle' way. + // + // Must be one of the following enumeration values: + // 'normal', 'label', 'subtle' + optional presentationHint; + // The optional source of the frame. + optional source; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(StackFrame); + +// Response to 'stackTrace' request. +struct StackTraceResponse : public Response { + StackTraceResponse(); + ~StackTraceResponse(); + + // The frames of the stackframe. If the array has length zero, there are no + // stackframes available. This means that there is no location information + // available. + array stackFrames; + // The total number of frames available. + optional totalFrames; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(StackTraceResponse); + +// Provides formatting information for a stack frame. +struct StackFrameFormat : public ValueFormat { + StackFrameFormat(); + ~StackFrameFormat(); + + // Includes all stack frames, including those the debug adapter might + // otherwise hide. + optional includeAll; + // Displays the line number of the stack frame. + optional line; + // Displays the module of the stack frame. + optional module; + // Displays the names of parameters for the stack frame. + optional parameterNames; + // Displays the types of parameters for the stack frame. + optional parameterTypes; + // Displays the values of parameters for the stack frame. + optional parameterValues; + // Displays parameters for the stack frame. + optional parameters; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(StackFrameFormat); + +// The request returns a stacktrace from the current execution state. +struct StackTraceRequest : public Request { + using Response = StackTraceResponse; + + StackTraceRequest(); + ~StackTraceRequest(); + + // Specifies details on how to format the stack frames. + optional format; + // The maximum number of frames to return. If levels is not specified or 0, + // all frames are returned. + optional levels; + // The index of the first frame to return; if omitted frames start at 0. + optional startFrame; + // Retrieve the stacktrace for this thread. + integer threadId; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(StackTraceRequest); + +// Response to 'stepBack' request. This is just an acknowledgement, so no body +// field is required. +struct StepBackResponse : public Response { + StepBackResponse(); + ~StepBackResponse(); +}; + +DAP_DECLARE_STRUCT_TYPEINFO(StepBackResponse); + +// The request starts the debuggee to run one step backwards. +// The debug adapter first sends the response and then a 'stopped' event (with +// reason 'step') after the step has completed. Clients should only call this +// request if the capability 'supportsStepBack' is true. +struct StepBackRequest : public Request { + using Response = StepBackResponse; + + StepBackRequest(); + ~StepBackRequest(); + + // Execute 'stepBack' for this thread. + integer threadId; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(StepBackRequest); + +// Response to 'stepIn' request. This is just an acknowledgement, so no body +// field is required. +struct StepInResponse : public Response { + StepInResponse(); + ~StepInResponse(); +}; + +DAP_DECLARE_STRUCT_TYPEINFO(StepInResponse); + +// The request starts the debuggee to step into a function/method if possible. +// If it cannot step into a target, 'stepIn' behaves like 'next'. +// The debug adapter first sends the response and then a 'stopped' event (with +// reason 'step') after the step has completed. If there are multiple +// function/method calls (or other targets) on the source line, the optional +// argument 'targetId' can be used to control into which target the 'stepIn' +// should occur. The list of possible targets for a given source line can be +// retrieved via the 'stepInTargets' request. +struct StepInRequest : public Request { + using Response = StepInResponse; + + StepInRequest(); + ~StepInRequest(); + + // Optional id of the target to step into. + optional targetId; + // Execute 'stepIn' for this thread. + integer threadId; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(StepInRequest); + +// A StepInTarget can be used in the 'stepIn' request and determines into which +// single target the stepIn request should step. +struct StepInTarget { + StepInTarget(); + ~StepInTarget(); + + // Unique identifier for a stepIn target. + integer id; + // The name of the stepIn target (shown in the UI). + string label; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(StepInTarget); + +// Response to 'stepInTargets' request. +struct StepInTargetsResponse : public Response { + StepInTargetsResponse(); + ~StepInTargetsResponse(); + + // The possible stepIn targets of the specified source location. + array targets; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(StepInTargetsResponse); + +// This request retrieves the possible stepIn targets for the specified stack +// frame. These targets can be used in the 'stepIn' request. The StepInTargets +// may only be called if the 'supportsStepInTargetsRequest' capability exists +// and is true. +struct StepInTargetsRequest : public Request { + using Response = StepInTargetsResponse; + + StepInTargetsRequest(); + ~StepInTargetsRequest(); + + // The stack frame for which to retrieve the possible stepIn targets. + integer frameId; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(StepInTargetsRequest); + +// Response to 'stepOut' request. This is just an acknowledgement, so no body +// field is required. +struct StepOutResponse : public Response { + StepOutResponse(); + ~StepOutResponse(); +}; + +DAP_DECLARE_STRUCT_TYPEINFO(StepOutResponse); + +// The request starts the debuggee to run again for one step. +// The debug adapter first sends the response and then a 'stopped' event (with +// reason 'step') after the step has completed. +struct StepOutRequest : public Request { + using Response = StepOutResponse; + + StepOutRequest(); + ~StepOutRequest(); + + // Execute 'stepOut' for this thread. + integer threadId; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(StepOutRequest); + +// The event indicates that the execution of the debuggee has stopped due to +// some condition. This can be caused by a break point previously set, a +// stepping action has completed, by executing a debugger statement etc. +struct StoppedEvent : public Event { + StoppedEvent(); + ~StoppedEvent(); + + // If 'allThreadsStopped' is true, a debug adapter can announce that all + // threads have stopped. + // - The client should use this information to enable that all threads can be + // expanded to access their stacktraces. + // - If the attribute is missing or false, only the thread with the given + // threadId can be expanded. + optional allThreadsStopped; + // The full reason for the event, e.g. 'Paused on exception'. This string is + // shown in the UI as is and must be translated. + optional description; + // A value of true hints to the frontend that this event should not change the + // focus. + optional preserveFocusHint; + // The reason for the event. + // For backward compatibility this string is shown in the UI if the + // 'description' attribute is missing (but it must not be translated). + // + // May be one of the following enumeration values: + // 'step', 'breakpoint', 'exception', 'pause', 'entry', 'goto', 'function + // breakpoint', 'data breakpoint' + string reason; + // Additional information. E.g. if reason is 'exception', text contains the + // exception name. This string is shown in the UI. + optional text; + // The thread which was stopped. + optional threadId; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(StoppedEvent); + +// Response to 'terminate' request. This is just an acknowledgement, so no body +// field is required. +struct TerminateResponse : public Response { + TerminateResponse(); + ~TerminateResponse(); +}; + +DAP_DECLARE_STRUCT_TYPEINFO(TerminateResponse); + +// The 'terminate' request is sent from the client to the debug adapter in order +// to give the debuggee a chance for terminating itself. +struct TerminateRequest : public Request { + using Response = TerminateResponse; + + TerminateRequest(); + ~TerminateRequest(); + + // A value of true indicates that this 'terminate' request is part of a + // restart sequence. + optional restart; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(TerminateRequest); + +// Response to 'terminateThreads' request. This is just an acknowledgement, so +// no body field is required. +struct TerminateThreadsResponse : public Response { + TerminateThreadsResponse(); + ~TerminateThreadsResponse(); +}; + +DAP_DECLARE_STRUCT_TYPEINFO(TerminateThreadsResponse); + +// The request terminates the threads with the given ids. +struct TerminateThreadsRequest : public Request { + using Response = TerminateThreadsResponse; + + TerminateThreadsRequest(); + ~TerminateThreadsRequest(); + + // Ids of threads to be terminated. + optional> threadIds; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(TerminateThreadsRequest); + +// The event indicates that debugging of the debuggee has terminated. This does +// **not** mean that the debuggee itself has exited. +struct TerminatedEvent : public Event { + TerminatedEvent(); + ~TerminatedEvent(); + + // A debug adapter may set 'restart' to true (or to an arbitrary object) to + // request that the front end restarts the session. The value is not + // interpreted by the client and passed unmodified as an attribute '__restart' + // to the 'launch' and 'attach' requests. + optional, boolean, integer, null, number, object, string>> + restart; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(TerminatedEvent); + +// The event indicates that a thread has started or exited. +struct ThreadEvent : public Event { + ThreadEvent(); + ~ThreadEvent(); + + // The reason for the event. + // + // May be one of the following enumeration values: + // 'started', 'exited' + string reason; + // The identifier of the thread. + integer threadId; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(ThreadEvent); + +// A Thread +struct Thread { + Thread(); + ~Thread(); + + // Unique identifier for the thread. + integer id; + // A name of the thread. + string name; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(Thread); + +// Response to 'threads' request. +struct ThreadsResponse : public Response { + ThreadsResponse(); + ~ThreadsResponse(); + + // All threads. + array threads; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(ThreadsResponse); + +// The request retrieves a list of all threads. +struct ThreadsRequest : public Request { + using Response = ThreadsResponse; + + ThreadsRequest(); + ~ThreadsRequest(); +}; + +DAP_DECLARE_STRUCT_TYPEINFO(ThreadsRequest); + +// A Variable is a name/value pair. +// Optionally a variable can have a 'type' that is shown if space permits or +// when hovering over the variable's name. An optional 'kind' is used to render +// additional properties of the variable, e.g. different icons can be used to +// indicate that a variable is public or private. If the value is structured +// (has children), a handle is provided to retrieve the children with the +// VariablesRequest. If the number of named or indexed children is large, the +// numbers should be returned via the optional 'namedVariables' and +// 'indexedVariables' attributes. The client can use this optional information +// to present the children in a paged UI and fetch them in chunks. +struct Variable { + Variable(); + ~Variable(); + + // Optional evaluatable name of this variable which can be passed to the + // 'EvaluateRequest' to fetch the variable's value. + optional evaluateName; + // The number of indexed child variables. + // The client can use this optional information to present the children in a + // paged UI and fetch them in chunks. + optional indexedVariables; + // Optional memory reference for the variable if the variable represents + // executable code, such as a function pointer. + optional memoryReference; + // The variable's name. + string name; + // The number of named child variables. + // The client can use this optional information to present the children in a + // paged UI and fetch them in chunks. + optional namedVariables; + // Properties of a variable that can be used to determine how to render the + // variable in the UI. + optional presentationHint; + // The type of the variable's value. Typically shown in the UI when hovering + // over the value. + optional type; + // The variable's value. This can be a multi-line text, e.g. for a function + // the body of a function. + string value; + // If variablesReference is > 0, the variable is structured and its children + // can be retrieved by passing variablesReference to the VariablesRequest. + integer variablesReference; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(Variable); + +// Response to 'variables' request. +struct VariablesResponse : public Response { + VariablesResponse(); + ~VariablesResponse(); + + // All (or a range) of variables for the given variable reference. + array variables; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(VariablesResponse); + +// Retrieves all child variables for the given variable reference. +// An optional filter can be used to limit the fetched children to either named +// or indexed children. +struct VariablesRequest : public Request { + using Response = VariablesResponse; + + VariablesRequest(); + ~VariablesRequest(); + + // The number of variables to return. If count is missing or 0, all variables + // are returned. + optional count; + // Optional filter to limit the child variables to either named or indexed. If + // omitted, both types are fetched. + // + // Must be one of the following enumeration values: + // 'indexed', 'named' + optional filter; + // Specifies details on how to format the Variable values. + optional format; + // The index of the first variable to return; if omitted children start at 0. + optional start; + // The Variable reference. + integer variablesReference; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(VariablesRequest); + +} // namespace dap + +#endif // dap_protocol_h diff --git a/include/dap/serialization.h b/include/dap/serialization.h new file mode 100644 index 0000000..dbb4e68 --- /dev/null +++ b/include/dap/serialization.h @@ -0,0 +1,256 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef dap_serialization_h +#define dap_serialization_h + +#include "typeof.h" +#include "types.h" + +#include + +namespace dap { + +// Field describes a single field of a struct. +struct Field { + std::string name; // name of the field + ptrdiff_t offset; // offset of the field to the base of the struct + const TypeInfo* type; // type of the field +}; + +//////////////////////////////////////////////////////////////////////////////// +// Deserializer +//////////////////////////////////////////////////////////////////////////////// + +// Deserializer is the interface used to decode data from structured storage. +// Methods that return a bool use this to indicate success. +class Deserializer { + public: + // deserialization methods for simple data types. + // If the stored object is not of the correct type, then these function will + // return false. + virtual bool deserialize(boolean*) const = 0; + virtual bool deserialize(integer*) const = 0; + virtual bool deserialize(number*) const = 0; + virtual bool deserialize(string*) const = 0; + virtual bool deserialize(object*) const = 0; + virtual bool deserialize(any*) const = 0; + + // count() returns the number of elements in the array object referenced by + // this Deserializer. + virtual size_t count() const = 0; + + // array() calls the provided std::function for deserializing each array + // element in the array object referenced by this Deserializer. + virtual bool array(const std::function&) const = 0; + + // field() calls the provided std::function for deserializing the field with + // the given name from the struct object referenced by this Deserializer. + virtual bool field(const std::string& name, + const std::function&) const = 0; + + // deserialize() delegates to TypeOf::type()->deserialize(). + template ::has_custom_serialization>> + inline bool deserialize(T*) const; + + // deserialize() decodes an array. + template + inline bool deserialize(dap::array*) const; + + // deserialize() decodes an optional. + template + inline bool deserialize(dap::optional*) const; + + // deserialize() decodes an variant. + template + inline bool deserialize(dap::variant*) const; + + // deserialize() decodes a list of fields and stores them into the object. + inline bool deserialize(void* object, + const std::initializer_list&) const; + + // deserialize() decodes the struct field f with the given name. + template + inline bool field(const std::string& name, T* f) const; +}; + +template +bool Deserializer::deserialize(T* ptr) const { + return TypeOf::type()->deserialize(this, ptr); +} + +template +bool Deserializer::deserialize(dap::array* vec) const { + auto n = count(); + vec->resize(n); + size_t i = 0; + if (!array([&](Deserializer* d) { return d->deserialize(&(*vec)[i++]); })) { + return false; + } + return true; +} + +template +bool Deserializer::deserialize(dap::optional* opt) const { + T v; + if (deserialize(&v)) { + *opt = v; + }; + return true; +} + +template +bool Deserializer::deserialize(dap::variant* var) const { + return deserialize(&var->value); +} + +bool Deserializer::deserialize( + void* object, + const std::initializer_list& fields) const { + for (auto const& f : fields) { + if (!field(f.name, [&](Deserializer* d) { + auto ptr = reinterpret_cast(object) + f.offset; + return f.type->deserialize(d, ptr); + })) { + return false; + } + } + return true; +} + +template +bool Deserializer::field(const std::string& name, T* v) const { + return this->field(name, + [&](const Deserializer* d) { return d->deserialize(v); }); +} + +//////////////////////////////////////////////////////////////////////////////// +// Serializer +//////////////////////////////////////////////////////////////////////////////// + +// Serializer is the interface used to encode data to structured storage. +// A Serializer is associated with a single storage object, whos type and value +// is assigned by a call to serialize(). +// If serialize() is called multiple times on the same Serializer instance, +// the last type and value is stored. +// Methods that return a bool use this to indicate success. +class Serializer { + public: + using FieldSerializer = std::function; + template + using IsFieldSerializer = std::is_convertible; + + // serialization methods for simple data types. + virtual bool serialize(boolean) = 0; + virtual bool serialize(integer) = 0; + virtual bool serialize(number) = 0; + virtual bool serialize(const string&) = 0; + virtual bool serialize(const object&) = 0; + virtual bool serialize(const any&) = 0; + + // array() encodes count array elements to the array object referenced by this + // Serializer. The std::function will be called count times, each time with a + // Serializer that should be used to encode the n'th array element's data. + virtual bool array(size_t count, const std::function&) = 0; + + // field() encodes a field to the struct object referenced by this Serializer. + // The FieldSerializer will be called with a Serializer used to encode the + // field's data. + virtual bool field(const std::string& name, const FieldSerializer&) = 0; + + // remove() deletes the object referenced by this Serializer. + // remove() can be used to serialize optionals with no value assigned. + virtual void remove() = 0; + + // serialize() delegates to TypeOf::type()->serialize(). + template ::has_custom_serialization>> + inline bool serialize(const T&); + + // serialize() encodes the given array. + template + inline bool serialize(const dap::array&); + + // serialize() encodes the given optional. + template + inline bool serialize(const dap::optional& v); + + // serialize() encodes the given variant. + template + inline bool serialize(const dap::variant&); + + // serialize() encodes all the provided fields of the given object. + inline bool serialize(const void* object, + const std::initializer_list&); + + // deserialize() encodes the given string. + inline bool serialize(const char* v); + + // field() encodes the field with the given name and value. + template < + typename T, + typename = typename std::enable_if::value>::type> + inline bool field(const std::string& name, const T& v); +}; + +template +bool Serializer::serialize(const T& object) { + return TypeOf::type()->serialize(this, &object); +} + +template +bool Serializer::serialize(const dap::array& vec) { + auto it = vec.begin(); + return array(vec.size(), [&](Serializer* s) { return s->serialize(*it++); }); +} + +template +bool Serializer::serialize(const dap::optional& opt) { + if (!opt.has_value()) { + remove(); + return true; + } + return serialize(opt.value()); +} + +template +bool Serializer::serialize(const dap::variant& var) { + return serialize(var.value); +} + +bool Serializer::serialize(const void* object, + const std::initializer_list& fields) { + for (auto const& f : fields) { + if (!field(f.name, [&](Serializer* d) { + auto ptr = reinterpret_cast(object) + f.offset; + return f.type->serialize(d, ptr); + })) + return false; + } + return true; +} + +bool Serializer::serialize(const char* v) { + return serialize(std::string(v)); +} + +template +bool Serializer::field(const std::string& name, const T& v) { + return this->field(name, [&](Serializer* s) { return s->serialize(v); }); +} + +} // namespace dap + +#endif // dap_serialization_h diff --git a/include/dap/session.h b/include/dap/session.h new file mode 100644 index 0000000..056c633 --- /dev/null +++ b/include/dap/session.h @@ -0,0 +1,283 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef dap_session_h +#define dap_session_h + +#include "io.h" +#include "typeinfo.h" +#include "typeof.h" + +#include +#include + +namespace dap { + +// Forward declarations +struct Request; +struct Response; +struct Event; + +// internal functionality +namespace detail { +template +struct traits { + static constexpr bool isRequest = std::is_base_of::value; + static constexpr bool isResponse = std::is_base_of::value; + static constexpr bool isEvent = std::is_base_of::value; +}; + +// ArgTy::type resolves to the first argument type of the function F. +// F can be a function, static member function, or lambda. +template +struct ArgTy { + using type = typename ArgTy::type; +}; + +template +struct ArgTy { + using type = typename std::decay::type; +}; + +template +struct ArgTy { + using type = typename std::decay::type; +}; +} // namespace detail + +//////////////////////////////////////////////////////////////////////////////// +// Error +//////////////////////////////////////////////////////////////////////////////// + +// Error represents an error message in response to a DAP request. +struct Error { + Error() = default; + Error(const std::string& error); + Error(const char* msg, ...); + + // operator bool() returns true if there is an error. + inline operator bool() const { return message.size() > 0; } + + std::string message; // empty represents success. +}; + +//////////////////////////////////////////////////////////////////////////////// +// ResponseOrError +//////////////////////////////////////////////////////////////////////////////// + +// ResponseOrError holds either the response to a DAP request or an error +// message. +template +struct ResponseOrError { + using Request = T; + + inline ResponseOrError() = default; + inline ResponseOrError(const T& response); + inline ResponseOrError(const Error& error); + inline ResponseOrError(const ResponseOrError& other); + + T response; + Error error; // empty represents success. +}; + +template +ResponseOrError::ResponseOrError(const T& response) : response(response) {} +template +ResponseOrError::ResponseOrError(const Error& error) : error(error) {} +template +ResponseOrError::ResponseOrError(const ResponseOrError& other) + : response(other.response), error(other.error) {} + +//////////////////////////////////////////////////////////////////////////////// +// Session +//////////////////////////////////////////////////////////////////////////////// + +// Session implements a DAP client or server endpoint. +// The general usage is as follows: +// (1) Create a session with Session::create(). +// (2) Register request and event handlers with registerHandler(). +// (3) Optionally register a protocol error handler with onError(). +// (3) Bind the session to the remote endpoint with bind(). +// (4) Send requests or events with send(). +class Session { + template + using IsRequest = typename std::enable_if::isRequest>::type; + + template + using IsEvent = typename std::enable_if::isEvent>::type; + + template + using ArgTy = typename detail::ArgTy::type; + + public: + virtual ~Session() = default; + + // ErrorHandler is the type of callback function used for reporting protocol + // errors. + using ErrorHandler = std::function; + + // create() constructs and returns a new Session. + static std::unique_ptr create(); + + // onError() registers a error handler that will be called whenever a protocol + // error is encountered. + // Only one error handler can be bound at any given time, and later calls + // will replace the existing error handler. + virtual void onError(const ErrorHandler&) = 0; + + // registerHandler() registers a request handler for a specific request type. + // The function F must have one of the following signatures: + // ResponseOrError(const RequestType&) + // ResponseType(const RequestType&) + // Error(const RequestType&) + template > + inline IsRequest registerHandler(F&& handler); + + // registerHandler() registers a event handler for a specific event type. + // The function F must have the following signature: + // void(const EventType&) + template > + inline IsEvent registerHandler(F&& handler); + + // registerSentHandler() registers the function F to be called when a response + // of the specific type has been sent. + // The function F must have the following signature: + // void(const ResponseOrError&) + template ::Request> + inline void registerSentHandler(F&& handler); + + // send() sends the request to the connected endpoint and returns a + // std::future that is assigned the request response or error. + template > + std::future> send(const T& request); + + // send() sends the event to the connected endpoint. + template > + void send(const T& event); + + // bind() connects this Session to an endpoint. + // bind() can only be called once. Repeated calls will raise an error, but + // otherwise will do nothing. + virtual void bind(const std::shared_ptr&, + const std::shared_ptr&) = 0; + inline void bind(const std::shared_ptr&); + + protected: + using RequestSuccessCallback = + std::function; + + using RequestErrorCallback = + std::function; + + using GenericResponseHandler = std::function; + + using GenericRequestHandler = + std::function; + + using GenericEventHandler = std::function; + + using GenericResponseSentHandler = + std::function; + + virtual void registerHandler(const TypeInfo* typeinfo, + const GenericRequestHandler& handler) = 0; + + virtual void registerHandler(const TypeInfo* typeinfo, + const GenericEventHandler& handler) = 0; + + virtual void registerHandler(const TypeInfo* typeinfo, + const GenericResponseSentHandler& handler) = 0; + + virtual bool send(const dap::TypeInfo* typeinfo, + const void* request, + const GenericResponseHandler& responseHandler) = 0; + + virtual bool send(const TypeInfo*, const void* event) = 0; +}; + +template +Session::IsRequest Session::registerHandler(F&& handler) { + using ResponseType = typename T::Response; + auto cb = [handler](const void* args, const RequestSuccessCallback& onSuccess, + const RequestErrorCallback& onError) { + ResponseOrError res = + handler(*reinterpret_cast(args)); + if (res.error) { + onError(TypeOf::type(), res.error); + } else { + onSuccess(TypeOf::type(), &res.response); + } + }; + const TypeInfo* typeinfo = TypeOf::type(); + registerHandler(typeinfo, cb); +} + +template +Session::IsEvent Session::registerHandler(F&& handler) { + auto cb = [handler](const void* args) { + handler(*reinterpret_cast(args)); + }; + const TypeInfo* typeinfo = TypeOf::type(); + registerHandler(typeinfo, cb); +} + +template +void Session::registerSentHandler(F&& handler) { + auto cb = [handler](const void* response, const Error* error) { + if (error != nullptr) { + handler(ResponseOrError(*error)); + } else { + handler(ResponseOrError(*reinterpret_cast(response))); + } + }; + const TypeInfo* typeinfo = TypeOf::type(); + registerHandler(typeinfo, cb); +} + +template +std::future> Session::send( + const T& request) { + using Response = typename T::Response; + auto promise = std::make_shared>>(); + const TypeInfo* typeinfo = TypeOf::type(); + auto sent = + send(typeinfo, &request, [=](const void* result, const Error* error) { + if (error != nullptr) { + promise->set_value(ResponseOrError(*error)); + } else { + promise->set_value(ResponseOrError( + *reinterpret_cast(result))); + } + }); + if (!sent) { + promise->set_value(Error("Failed to send request")); + } + return promise->get_future(); +} + +template +void Session::send(const T& event) { + const TypeInfo* typeinfo = TypeOf::type(); + send(typeinfo, &event); +} + +void Session::bind(const std::shared_ptr& rw) { + bind(rw, rw); +} + +} // namespace dap + +#endif // dap_session_h diff --git a/include/dap/typeinfo.h b/include/dap/typeinfo.h new file mode 100644 index 0000000..fbef4e8 --- /dev/null +++ b/include/dap/typeinfo.h @@ -0,0 +1,43 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef dap_typeinfo_h +#define dap_typeinfo_h + +#include +#include + +namespace dap { + +class any; +class Deserializer; +class Serializer; + +// The TypeInfo interface provides basic runtime type information about DAP +// types. TypeInfo is used by the serialization system to encode and decode DAP +// requests, responses, events and structs. +struct TypeInfo { + virtual std::string name() const = 0; + virtual size_t size() const = 0; + virtual size_t alignment() const = 0; + virtual void construct(void*) const = 0; + virtual void copyConstruct(void* dst, const void* src) const = 0; + virtual void destruct(void*) const = 0; + virtual bool deserialize(const Deserializer*, void*) const = 0; + virtual bool serialize(Serializer*, const void*) const = 0; +}; + +} // namespace dap + +#endif // dap_typeinfo_h diff --git a/include/dap/typeof.h b/include/dap/typeof.h new file mode 100644 index 0000000..3fd7cb0 --- /dev/null +++ b/include/dap/typeof.h @@ -0,0 +1,182 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef dap_typeof_h +#define dap_typeof_h + +#include "typeinfo.h" +#include "types.h" + +#include "serialization.h" + +namespace dap { + +// BasicTypeInfo is an implementation of the TypeInfo interface for the simple +// template type T. +template +struct BasicTypeInfo : public TypeInfo { + BasicTypeInfo(const std::string& name) : name_(name) {} + + // TypeInfo compliance + inline std::string name() const { return name_; } + inline size_t size() const { return sizeof(T); } + inline size_t alignment() const { return alignof(T); } + inline void construct(void* ptr) const { new (ptr) T(); } + inline void copyConstruct(void* dst, const void* src) const { + new (dst) T(*reinterpret_cast(src)); + } + inline void destruct(void* ptr) const { reinterpret_cast(ptr)->~T(); } + inline bool deserialize(const Deserializer* d, void* ptr) const { + return d->deserialize(reinterpret_cast(ptr)); + } + inline bool serialize(Serializer* s, const void* ptr) const { + return s->serialize(*reinterpret_cast(ptr)); + } + + private: + std::string name_; +}; + +// TypeOf has a template specialization for each DAP type, each declaring a +// const TypeInfo* type() static member function that describes type T. +template +struct TypeOf {}; + +template <> +struct TypeOf { + static const TypeInfo* type(); +}; + +template <> +struct TypeOf { + static const TypeInfo* type(); +}; + +template <> +struct TypeOf { + static const TypeInfo* type(); +}; + +template <> +struct TypeOf { + static const TypeInfo* type(); +}; + +template <> +struct TypeOf { + static const TypeInfo* type(); +}; + +template <> +struct TypeOf { + static const TypeInfo* type(); +}; + +template <> +struct TypeOf { + static const TypeInfo* type(); +}; + +template +struct TypeOf> { + static inline const TypeInfo* type() { + static BasicTypeInfo> typeinfo("array<" + + TypeOf::type()->name() + ">"); + return &typeinfo; + } +}; + +template +struct TypeOf> { + static inline const TypeInfo* type() { + static BasicTypeInfo> typeinfo("variant"); + return &typeinfo; + } +}; + +template +struct TypeOf> { + static inline const TypeInfo* type() { + static BasicTypeInfo> typeinfo("optional<" + + TypeOf::type()->name() + ">"); + return &typeinfo; + } +}; + +// DAP_OFFSETOF() macro is a generalization of the offsetof() macro defined in +// . It evaluates to the offset of the given field, with fewer +// restrictions than offsetof(). We cast the address '32' and subtract it again, +// because null-dereference is undefined behavior. +#define DAP_OFFSETOF(s, m) \ + ((int)(size_t) & reinterpret_cast((((s*)32)->m)) - 32) + +// internal functionality +namespace detail { +template +M member_type(M T::*); +} // namespace detail + +// DAP_TYPEOF() returns the type of the struct (s) member (m). +#define DAP_TYPEOF(s, m) decltype(detail::member_type(&s::m)) + +// DAP_FIELD() declares a structure field for the DAP_IMPLEMENT_STRUCT_TYPEINFO +// macro. +// FIELD is the name of the struct field. +// NAME is the serialized name of the field, as described by the DAP +// specification. +#define DAP_FIELD(FIELD, NAME) \ + dap::Field { \ + NAME, DAP_OFFSETOF(StructTy, FIELD), \ + TypeOf::type(), \ + } + +// DAP_DECLARE_STRUCT_TYPEINFO() declares a TypeOf<> specialization for STRUCT. +#define DAP_DECLARE_STRUCT_TYPEINFO(STRUCT) \ + template <> \ + struct TypeOf { \ + static constexpr bool has_custom_serialization = true; \ + static const TypeInfo* type(); \ + } + +// DAP_DECLARE_STRUCT_TYPEINFO() implements the type() member function for the +// TypeOf<> specialization for STRUCT. +// STRUCT is the structure typename. +// NAME is the serialized name of the structure, as described by the DAP +// specification. The variadic (...) parameters should be a repeated list of +// DAP_FIELD()s, one for each field of the struct. +#define DAP_IMPLEMENT_STRUCT_TYPEINFO(STRUCT, NAME, ...) \ + const TypeInfo* TypeOf::type() { \ + using StructTy = STRUCT; \ + struct TI : BasicTypeInfo { \ + TI() : BasicTypeInfo(NAME) {} \ + bool deserialize(const Deserializer* d, void* ptr) const override { \ + return d->deserialize(ptr, {__VA_ARGS__}); \ + } \ + bool serialize(Serializer* s, const void* ptr) const override { \ + return s->serialize(ptr, {__VA_ARGS__}); \ + } \ + }; \ + static TI typeinfo; \ + return &typeinfo; \ + } + +// DAP_STRUCT_TYPEINFO() is a helper for declaring and implementing a TypeOf<> +// specialization for STRUCT in a single statement. +#define DAP_STRUCT_TYPEINFO(STRUCT, NAME, ...) \ + DAP_DECLARE_STRUCT_TYPEINFO(STRUCT); \ + DAP_IMPLEMENT_STRUCT_TYPEINFO(STRUCT, NAME, __VA_ARGS__) + +} // namespace dap + +#endif // dap_typeof_h diff --git a/include/dap/types.h b/include/dap/types.h new file mode 100644 index 0000000..8d02e0f --- /dev/null +++ b/include/dap/types.h @@ -0,0 +1,102 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This file holds the basic serializable types used by the debug adapter +// protocol. + +#ifndef dap_types_h +#define dap_types_h + +#include "any.h" +#include "optional.h" +#include "variant.h" + +#include +#include + +namespace dap { + +// string is a sequence of characters. +// string defaults to an empty string. +using string = std::string; + +// boolean holds a true or false value. +// boolean defaults to false. +class boolean { + public: + inline boolean() : val(false) {} + inline boolean(bool i) : val(i) {} + inline operator bool() const { return val; } + inline boolean& operator=(bool i) { + val = i; + return *this; + } + + private: + bool val; +}; + +// integer holds a whole signed number. +// integer defaults to 0. +class integer { + public: + inline integer() : val(0) {} + inline integer(int i) : val(i) {} + inline operator int() const { return val; } + inline integer& operator=(int i) { + val = i; + return *this; + } + inline integer operator++(int) { + auto copy = *this; + val++; + return copy; + } + + private: + int val; +}; + +// number holds a 64-bit floating point number. +// number defaults to 0. +class number { + public: + inline number() : val(0.0) {} + inline number(double i) : val(i) {} + inline operator double() const { return val; } + inline number& operator=(double i) { + val = i; + return *this; + } + + private: + double val; +}; + +// array is a list of items of type T. +// array defaults to an empty list. +template +using array = std::vector; + +// object is a map of string to any. +// object defaults to an empty map. +using object = std::unordered_map; + +// null represents no value. +// null is used by any to check for no-value. +using null = std::nullptr_t; + +} // namespace dap + +#endif // dap_types_h diff --git a/include/dap/variant.h b/include/dap/variant.h new file mode 100644 index 0000000..637787c --- /dev/null +++ b/include/dap/variant.h @@ -0,0 +1,108 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef dap_variant_h +#define dap_variant_h + +#include "any.h" + +namespace dap { + +// internal functionality +namespace detail { +template +struct TypeIsIn { + static constexpr bool value = false; +}; + +template +struct TypeIsIn { + static constexpr bool value = + std::is_same::value || TypeIsIn::value; +}; +} // namespace detail + +// variant represents a type-safe union of DAP types. +// variant can hold a value of any of the template argument types. +// variant defaults to a default-constructed T0. +template +class variant { + public: + // constructors + inline variant(); + template + inline variant(const T& val); + + // assignment + template + inline variant& operator=(const T& val); + + // get() returns the contained value of the type T. + // If the any does not contain a value of type T, then get() will assert. + template + inline T& get() const; + + // is() returns true iff the contained value is of type T. + template + inline bool is() const; + + // accepts() returns true iff the variant accepts values of type T. + template + static constexpr bool accepts(); + + private: + friend class Serializer; + friend class Deserializer; + any value; +}; + +template +variant::variant() : value(T0()) {} + +template +template +variant::variant(const T& value) : value(value) { + static_assert(accepts(), "variant does not accept template type T"); +} + +template +template +variant& variant::operator=(const T& v) { + static_assert(accepts(), "variant does not accept template type T"); + value = v; + return *this; +} + +template +template +T& variant::get() const { + static_assert(accepts(), "variant does not accept template type T"); + return value.get(); +} + +template +template +bool variant::is() const { + return value.is(); +} + +template +template +constexpr bool variant::accepts() { + return detail::TypeIsIn::value; +} + +} // namespace dap + +#endif // dap_variant_h diff --git a/src/any_test.cpp b/src/any_test.cpp new file mode 100644 index 0000000..35b92d5 --- /dev/null +++ b/src/any_test.cpp @@ -0,0 +1,126 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dap/any.h" +#include "dap/typeof.h" +#include "dap/types.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace dap { + +struct AnyTestObject { + dap::integer i; + dap::number n; +}; + +DAP_STRUCT_TYPEINFO(AnyTestObject, + "AnyTestObject", + DAP_FIELD(i, "i"), + DAP_FIELD(n, "n")); + +} // namespace dap + +TEST(Any, EmptyConstruct) { + dap::any any; + ASSERT_TRUE(any.is()); + ASSERT_FALSE(any.is()); + ASSERT_FALSE(any.is()); + ASSERT_FALSE(any.is()); + ASSERT_FALSE(any.is()); + ASSERT_FALSE(any.is>()); + ASSERT_FALSE(any.is()); +} + +TEST(Any, Boolean) { + dap::any any(dap::boolean(true)); + ASSERT_TRUE(any.is()); + ASSERT_EQ(any.get(), dap::boolean(true)); +} + +TEST(Any, Integer) { + dap::any any(dap::integer(10)); + ASSERT_TRUE(any.is()); + ASSERT_EQ(any.get(), dap::integer(10)); +} + +TEST(Any, Number) { + dap::any any(dap::number(123.0f)); + ASSERT_TRUE(any.is()); + ASSERT_EQ(any.get(), dap::number(123.0f)); +} + +TEST(Any, Array) { + using array = dap::array; + dap::any any(array({10, 20, 30})); + ASSERT_TRUE(any.is()); + ASSERT_EQ(any.get(), array({10, 20, 30})); +} + +TEST(Any, Object) { + dap::object o; + o["one"] = dap::integer(1); + o["two"] = dap::integer(2); + o["three"] = dap::integer(3); + dap::any any(o); + ASSERT_TRUE(any.is()); + if (any.is()) { + auto got = any.get(); + ASSERT_EQ(got.size(), 3); + ASSERT_EQ(got.count("one"), 1); + ASSERT_EQ(got.count("two"), 1); + ASSERT_EQ(got.count("three"), 1); + ASSERT_TRUE(got["one"].is()); + ASSERT_TRUE(got["two"].is()); + ASSERT_TRUE(got["three"].is()); + ASSERT_EQ(got["one"].get(), dap::integer(1)); + ASSERT_EQ(got["two"].get(), dap::integer(2)); + ASSERT_EQ(got["three"].get(), dap::integer(3)); + } +} + +TEST(Any, TestObject) { + dap::any any(dap::AnyTestObject{5, 3.0}); + ASSERT_TRUE(any.is()); + ASSERT_EQ(any.get().i, 5); + ASSERT_EQ(any.get().n, 3.0); +} + +TEST(Any, Assign) { + dap::any any; + any = dap::integer(10); + ASSERT_TRUE(any.is()); + ASSERT_FALSE(any.is()); + ASSERT_FALSE(any.is()); + ASSERT_EQ(any.get(), dap::integer(10)); + any = dap::boolean(true); + ASSERT_FALSE(any.is()); + ASSERT_TRUE(any.is()); + ASSERT_FALSE(any.is()); + ASSERT_EQ(any.get(), dap::boolean(true)); + any = dap::AnyTestObject{5, 3.0}; + ASSERT_FALSE(any.is()); + ASSERT_FALSE(any.is()); + ASSERT_TRUE(any.is()); + ASSERT_EQ(any.get().i, 5); + ASSERT_EQ(any.get().n, 3.0); +} + +TEST(Any, Reset) { + dap::any any(dap::integer(10)); + ASSERT_TRUE(any.is()); + any.reset(); + ASSERT_FALSE(any.is()); +} diff --git a/src/chan.h b/src/chan.h new file mode 100644 index 0000000..c4183c2 --- /dev/null +++ b/src/chan.h @@ -0,0 +1,90 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef dap_chan_h +#define dap_chan_h + +#include "dap/optional.h" + +#include +#include +#include + +namespace dap { + +template +struct Chan { + public: + void reset(); + void close(); + optional take(); + void put(T&& in); + void put(const T& in); + + private: + bool closed = false; + std::queue queue; + std::condition_variable cv; + std::mutex mutex; +}; + +template +void Chan::reset() { + std::unique_lock lock(mutex); + queue = {}; + closed = false; +} + +template +void Chan::close() { + std::unique_lock lock(mutex); + closed = true; + cv.notify_all(); +} + +template +optional Chan::take() { + std::unique_lock lock(mutex); + cv.wait(lock, [&] { return queue.size() > 0 || closed; }); + if (queue.size() == 0) { + return optional(); + } + auto out = std::move(queue.front()); + queue.pop(); + return optional(std::move(out)); +} + +template +void Chan::put(T&& in) { + std::unique_lock lock(mutex); + auto notify = queue.size() == 0 && !closed; + queue.push(std::move(in)); + if (notify) { + cv.notify_all(); + } +} + +template +void Chan::put(const T& in) { + std::unique_lock lock(mutex); + auto notify = queue.size() == 0 && !closed; + queue.push(in); + if (notify) { + cv.notify_all(); + } +} + +} // namespace dap + +#endif // dap_chan_h \ No newline at end of file diff --git a/src/chan_test.cpp b/src/chan_test.cpp new file mode 100644 index 0000000..4d7e0a4 --- /dev/null +++ b/src/chan_test.cpp @@ -0,0 +1,35 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "chan.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include + +TEST(ChanTest, PutTakeClose) { + dap::Chan chan; + auto thread = std::thread([&] { + chan.put(10); + chan.put(20); + chan.put(30); + chan.close(); + }); + EXPECT_EQ(chan.take(), dap::optional(10)); + EXPECT_EQ(chan.take(), dap::optional(20)); + EXPECT_EQ(chan.take(), dap::optional(30)); + EXPECT_EQ(chan.take(), dap::optional()); + thread.join(); +} diff --git a/src/content_stream.cpp b/src/content_stream.cpp new file mode 100644 index 0000000..d528669 --- /dev/null +++ b/src/content_stream.cpp @@ -0,0 +1,179 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "content_stream.h" + +#include "dap/io.h" + +#include // strlen +#include // std::min + +namespace dap { + +//////////////////////////////////////////////////////////////////////////////// +// ContentReader +//////////////////////////////////////////////////////////////////////////////// +ContentReader::ContentReader(const std::shared_ptr& reader) + : reader(reader) {} + +ContentReader& ContentReader::operator=(ContentReader&& rhs) noexcept { + buf = std::move(rhs.buf); + reader = std::move(rhs.reader); + return *this; +} + +bool ContentReader::isOpen() { + return reader ? reader->isOpen() : false; +} + +void ContentReader::close() { + if (reader) { + reader->close(); + } +} + +std::string ContentReader::read() { + // Find Content-Length header prefix + if (!scan("Content-Length:")) { + return ""; + } + // Skip whitespace and tabs + while (matchAny(" \t")) { + } + // Parse length + size_t len = 0; + while (true) { + auto c = matchAny("0123456789"); + if (c == 0) { + break; + } + len *= 10; + len += size_t(c) - size_t('0'); + } + if (len == 0) { + return ""; + } + // Expect \r\n\r\n + if (!match("\r\n\r\n")) { + return ""; + } + // Read message + if (!buffer(len)) { + return ""; + } + std::string out; + out.reserve(len); + for (size_t i = 0; i < len; i++) { + out.push_back(static_cast(buf.front())); + buf.pop_front(); + } + return out; +} + +bool ContentReader::scan(const uint8_t* seq, size_t len) { + while (buffer(len)) { + if (match(seq, len)) { + return true; + } + buf.pop_front(); + } + return false; +} + +bool ContentReader::scan(const char* str) { + auto len = strlen(str); + return scan(reinterpret_cast(str), len); +} + +bool ContentReader::match(const uint8_t* seq, size_t len) { + if (!buffer(len)) { + return false; + } + auto it = buf.begin(); + for (size_t i = 0; i < len; i++, it++) { + if (*it != seq[i]) { + return false; + } + } + for (size_t i = 0; i < len; i++) { + buf.pop_front(); + } + return true; +} + +bool ContentReader::match(const char* str) { + auto len = strlen(str); + return match(reinterpret_cast(str), len); +} + +char ContentReader::matchAny(const char* chars) { + if (!buffer(1)) { + return false; + } + int c = buf.front(); + if (auto p = strchr(chars, c)) { + buf.pop_front(); + return *p; + } + return 0; +} + +bool ContentReader::buffer(size_t bytes) { + if (bytes < buf.size()) { + return true; + } + bytes -= buf.size(); + while (bytes > 0) { + uint8_t chunk[256]; + auto c = std::min(sizeof(chunk), bytes); + if (reader->read(chunk, c) <= 0) { + return false; + } + for (size_t i = 0; i < c; i++) { + buf.push_back(chunk[i]); + } + bytes -= c; + } + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +// ContentWriter +//////////////////////////////////////////////////////////////////////////////// +ContentWriter::ContentWriter(const std::shared_ptr& rhs) + : writer(rhs) {} + +ContentWriter& ContentWriter::operator=(ContentWriter&& rhs) noexcept { + writer = std::move(rhs.writer); + return *this; +} + +bool ContentWriter::isOpen() { + return writer ? writer->isOpen() : false; +} + +void ContentWriter::close() { + if (writer) { + writer->close(); + } +} + +bool ContentWriter::write(const std::string& msg) const { + auto header = + std::string("Content-Length: ") + std::to_string(msg.size()) + "\r\n\r\n"; + return writer->write(header.data(), header.size()) && + writer->write(msg.data(), msg.size()); +} + +} // namespace dap \ No newline at end of file diff --git a/src/content_stream.h b/src/content_stream.h new file mode 100644 index 0000000..f01fef7 --- /dev/null +++ b/src/content_stream.h @@ -0,0 +1,68 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef dap_content_stream_h +#define dap_content_stream_h + +#include +#include +#include + +#include + +namespace dap { + +// Forward declarations +class Reader; +class Writer; + +class ContentReader { + public: + ContentReader() = default; + ContentReader(const std::shared_ptr&); + ContentReader& operator=(ContentReader&&) noexcept; + + bool isOpen(); + void close(); + std::string read(); + + private: + bool scan(const uint8_t* seq, size_t len); + bool scan(const char* str); + bool match(const uint8_t* seq, size_t len); + bool match(const char* str); + char matchAny(const char* chars); + bool buffer(size_t bytes); + + std::shared_ptr reader; + std::deque buf; +}; + +class ContentWriter { + public: + ContentWriter() = default; + ContentWriter(const std::shared_ptr&); + ContentWriter& operator=(ContentWriter&&) noexcept; + + bool isOpen(); + void close(); + bool write(const std::string&) const; + + private: + std::shared_ptr writer; +}; + +} // namespace dap + +#endif // dap_content_stream_h diff --git a/src/content_stream_test.cpp b/src/content_stream_test.cpp new file mode 100644 index 0000000..df29b2e --- /dev/null +++ b/src/content_stream_test.cpp @@ -0,0 +1,47 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "content_stream.h" + +#include "string_buffer.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +TEST(ContentStreamTest, Write) { + auto sb = dap::StringBuffer::create(); + auto ptr = sb.get(); + dap::ContentWriter cw(std::move(sb)); + cw.write("Content payload number one"); + cw.write("Content payload number two"); + cw.write("Content payload number three"); + ASSERT_EQ(ptr->string(), + "Content-Length: 26\r\n\r\nContent payload number one" + "Content-Length: 26\r\n\r\nContent payload number two" + "Content-Length: 28\r\n\r\nContent payload number three"); +} + +TEST(ContentStreamTest, Read) { + auto sb = dap::StringBuffer::create(); + sb->write("Content-Length: 26\r\n\r\nContent payload number one"); + sb->write("some unrecognised garbage"); + sb->write("Content-Length: 26\r\n\r\nContent payload number two"); + sb->write("some more unrecognised garbage"); + sb->write("Content-Length: 28\r\n\r\nContent payload number three"); + dap::ContentReader cs(std::move(sb)); + ASSERT_EQ(cs.read(), "Content payload number one"); + ASSERT_EQ(cs.read(), "Content payload number two"); + ASSERT_EQ(cs.read(), "Content payload number three"); + ASSERT_EQ(cs.read(), ""); +} diff --git a/src/dap_test.cpp b/src/dap_test.cpp new file mode 100644 index 0000000..c895d0f --- /dev/null +++ b/src/dap_test.cpp @@ -0,0 +1,21 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/src/io.cpp b/src/io.cpp new file mode 100644 index 0000000..00665dd --- /dev/null +++ b/src/io.cpp @@ -0,0 +1,257 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dap/io.h" + +#include +#include +#include +#include +#include +#include + +namespace { + +class Pipe : public dap::ReaderWriter { + public: + // dap::ReaderWriter compliance + bool isOpen() override { + std::unique_lock lock(mutex); + return !closed; + } + + void close() override { + std::unique_lock lock(mutex); + closed = true; + cv.notify_all(); + } + + size_t read(void* buffer, size_t bytes) override { + std::unique_lock lock(mutex); + auto out = reinterpret_cast(buffer); + size_t n = 0; + while (true) { + cv.wait(lock, [&] { return closed || data.size() > 0; }); + if (closed) { + return n; + } + for (; n < bytes && data.size() > 0; n++) { + out[n] = data.front(); + data.pop_front(); + } + if (n == bytes) { + return n; + } + } + } + + bool write(const void* buffer, size_t bytes) override { + std::unique_lock lock(mutex); + if (closed) { + return false; + } + if (bytes == 0) { + return true; + } + auto notify = data.size() == 0; + auto src = reinterpret_cast(buffer); + for (size_t i = 0; i < bytes; i++) { + data.emplace_back(src[i]); + } + if (notify) { + cv.notify_all(); + } + return true; + } + + private: + std::mutex mutex; + std::condition_variable cv; + std::deque data; + bool closed = false; +}; + +class RW : public dap::ReaderWriter { + public: + RW(const std::shared_ptr& r, const std::shared_ptr& w) + : r(r), w(w) {} + + // dap::ReaderWriter compliance + bool isOpen() override { return r->isOpen() && w->isOpen(); } + void close() override { + r->close(); + w->close(); + } + size_t read(void* buffer, size_t n) override { return r->read(buffer, n); } + bool write(const void* buffer, size_t n) override { + return w->write(buffer, n); + } + + private: + const std::shared_ptr r; + const std::shared_ptr w; +}; + +class File : public dap::ReaderWriter { + public: + File(FILE* f, bool closable) : f(f), closable(closable) {} + + ~File() { close(); } + + // dap::ReaderWriter compliance + bool isOpen() override { return !closed; } + void close() override { + if (closable) { + if (!closed.exchange(true)) { + fclose(f); + } + } + } + size_t read(void* buffer, size_t n) override { + std::unique_lock lock(readMutex); + auto out = reinterpret_cast(buffer); + for (size_t i = 0; i < n; i++) { + int c = fgetc(f); + if (c == EOF) { + return i; + } + out[i] = char(c); + } + return n; + // return fread(buffer, 1, n, f); + } + bool write(const void* buffer, size_t n) override { + std::unique_lock lock(writeMutex); + if (fwrite(buffer, 1, n, f) == n) { + fflush(f); + return true; + } + return false; + } + + private: + FILE* const f; + std::mutex readMutex; + std::mutex writeMutex; + std::atomic closed; + const bool closable; +}; + +class ReaderSpy : public dap::Reader { + public: + ReaderSpy(const std::shared_ptr& r, + const std::shared_ptr& s, + const std::string& prefix) + : r(r), s(s), prefix(prefix) {} + + // dap::Reader compliance + bool isOpen() override { return r->isOpen(); } + void close() override { r->close(); } + size_t read(void* buffer, size_t n) override { + auto c = r->read(buffer, n); + if (c > 0) { + auto chars = reinterpret_cast(buffer); + std::string buf = prefix; + buf.append(chars, chars + c); + s->write(buf.data(), buf.size()); + } + return c; + } + + private: + const std::shared_ptr r; + const std::shared_ptr s; + const std::string prefix; +}; + +class WriterSpy : public dap::Writer { + public: + WriterSpy(const std::shared_ptr& w, + const std::shared_ptr& s, + const std::string& prefix) + : w(w), s(s), prefix(prefix) {} + + // dap::Writer compliance + bool isOpen() override { return w->isOpen(); } + void close() override { w->close(); } + bool write(const void* buffer, size_t n) override { + if (!w->write(buffer, n)) { + return false; + } + auto chars = reinterpret_cast(buffer); + std::string buf = prefix; + buf.append(chars, chars + n); + s->write(buf.data(), buf.size()); + return true; + } + + private: + const std::shared_ptr w; + const std::shared_ptr s; + const std::string prefix; +}; + +} // anonymous namespace + +namespace dap { + +std::shared_ptr ReaderWriter::create( + const std::shared_ptr& r, + const std::shared_ptr& w) { + return std::make_shared(r, w); +} + +std::shared_ptr pipe() { + return std::make_shared(); +} + +std::shared_ptr file(FILE* f, bool closable /* = true */) { + return std::make_shared(f, closable); +} + +std::shared_ptr file(const char* path) { + if (auto f = fopen(path, "wb")) { + return std::make_shared(f, true); + } + return nullptr; +} + +// spy() returns a Reader that copies all reads from the Reader r to the Writer +// s, using the given optional prefix. +std::shared_ptr spy(const std::shared_ptr& r, + const std::shared_ptr& s, + const char* prefix /* = "\n<-" */) { + return std::make_shared(r, s, prefix); +} + +// spy() returns a Writer that copies all writes to the Writer w to the Writer +// s, using the given optional prefix. +std::shared_ptr spy(const std::shared_ptr& w, + const std::shared_ptr& s, + const char* prefix /* = "\n->" */) { + return std::make_shared(w, s, prefix); +} + +bool writef(const std::shared_ptr& w, const char* msg, ...) { + char buf[2048]; + + va_list vararg; + va_start(vararg, msg); + vsnprintf(buf, sizeof(buf), msg, vararg); + va_end(vararg); + + return w->write(buf, strlen(buf)); +} + +} // namespace dap \ No newline at end of file diff --git a/src/json_serializer.cpp b/src/json_serializer.cpp new file mode 100644 index 0000000..d3db29c --- /dev/null +++ b/src/json_serializer.cpp @@ -0,0 +1,242 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "json_serializer.h" + +#include + +namespace { + +struct NullDeserializer : public dap::Deserializer { + static NullDeserializer instance; + + bool deserialize(dap::boolean*) const override { return false; } + bool deserialize(dap::integer*) const override { return false; } + bool deserialize(dap::number*) const override { return false; } + bool deserialize(dap::string*) const override { return false; } + bool deserialize(dap::object*) const override { return false; } + bool deserialize(dap::any*) const override { return false; } + size_t count() const override { return 0; } + bool array(const std::function&) const override { + return false; + } + bool field(const std::string&, + const std::function&) const override { + return false; + } +}; + +NullDeserializer NullDeserializer::instance; + +} // anonymous namespace + +namespace dap { +namespace json { + +Deserializer::Deserializer(const std::string& str) + : json(new nlohmann::json(nlohmann::json::parse(str))), ownsJson(true) {} + +Deserializer::Deserializer(const nlohmann::json* json) + : json(json), ownsJson(false) {} + +Deserializer::~Deserializer() { + if (ownsJson) { + delete json; + } +} + +bool Deserializer::deserialize(dap::boolean* v) const { + if (!json->is_boolean()) { + return false; + } + *v = json->get(); + return true; +} + +bool Deserializer::deserialize(dap::integer* v) const { + if (!json->is_number_integer()) { + return false; + } + *v = json->get(); + return true; +} + +bool Deserializer::deserialize(dap::number* v) const { + if (!json->is_number()) { + return false; + } + *v = json->get(); + return true; +} + +bool Deserializer::deserialize(dap::string* v) const { + if (!json->is_string()) { + return false; + } + *v = json->get(); + return true; +} + +bool Deserializer::deserialize(dap::object* v) const { + v->reserve(json->size()); + for (auto& el : json->items()) { + Deserializer d(&el.value()); + dap::any val; + if (!d.deserialize(&val)) { + return false; + } + (*v)[el.key()] = val; + } + return true; +} + +bool Deserializer::deserialize(dap::any* v) const { + if (json->is_boolean()) { + *v = dap::boolean(json->get()); + } else if (json->is_number_float()) { + *v = dap::number(json->get()); + } else if (json->is_number_integer()) { + *v = dap::integer(json->get()); + } else if (json->is_string()) { + *v = json->get(); + } else if (json->is_null()) { + *v = null(); + } else { + return false; + } + return true; +} + +size_t Deserializer::count() const { + return json->size(); +} + +bool Deserializer::array( + const std::function& cb) const { + if (!json->is_array()) { + return false; + } + for (size_t i = 0; i < json->size(); i++) { + Deserializer d(&(*json)[i]); + if (!cb(&d)) { + return false; + } + } + return true; +} + +bool Deserializer::field( + const std::string& name, + const std::function& cb) const { + if (!json->is_structured()) { + return false; + } + auto it = json->find(name); + if (it == json->end()) { + return cb(&NullDeserializer::instance); + } + auto obj = *it; + Deserializer d(&obj); + return cb(&d); +} + +Serializer::Serializer() : json(new nlohmann::json()), ownsJson(true) {} + +Serializer::Serializer(nlohmann::json* json) : json(json), ownsJson(false) {} + +Serializer::~Serializer() { + if (ownsJson) { + delete json; + } +} + +std::string Serializer::dump() const { + return json->dump(); +} + +bool Serializer::serialize(dap::boolean v) { + *json = (bool)v; + return true; +} + +bool Serializer::serialize(dap::integer v) { + *json = (int)v; + return true; +} + +bool Serializer::serialize(dap::number v) { + *json = (double)v; + return true; +} + +bool Serializer::serialize(const dap::string& v) { + *json = v; + return true; +} + +bool Serializer::serialize(const dap::object& v) { + for (auto& it : v) { + Serializer s(&(*json)[it.first]); + if (!s.serialize(it.second)) { + return false; + } + } + return true; +} + +bool Serializer::serialize(const dap::any& v) { + if (v.is()) { + *json = (bool)v.get(); + } else if (v.is()) { + *json = (int)v.get(); + } else if (v.is()) { + *json = (double)v.get(); + } else if (v.is()) { + *json = v.get(); + } else if (v.is()) { + } else { + return false; + } + + return true; +} + +bool Serializer::array(size_t count, + const std::function& cb) { + *json = std::vector(); + for (size_t i = 0; i < count; i++) { + Serializer s(&(*json)[i]); + if (!cb(&s)) { + return false; + } + } + return true; +} + +bool Serializer::field(const std::string& name, + const std::function& cb) { + Serializer s(&(*json)[name]); + auto res = cb(&s); + if (s.removed) { + json->erase(name); + } + return res; +} + +void Serializer::remove() { + removed = true; +} + +} // namespace json +} // namespace dap diff --git a/src/json_serializer.h b/src/json_serializer.h new file mode 100644 index 0000000..6c7cc16 --- /dev/null +++ b/src/json_serializer.h @@ -0,0 +1,149 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef dap_json_serializer_h +#define dap_json_serializer_h + +#include "dap/protocol.h" +#include "dap/serialization.h" +#include "dap/types.h" + +#include + +namespace dap { +namespace json { + +struct Deserializer : public dap::Deserializer { + explicit Deserializer(const std::string&); + ~Deserializer(); + + // dap::Deserializer compliance + bool deserialize(boolean* v) const override; + bool deserialize(integer* v) const override; + bool deserialize(number* v) const override; + bool deserialize(string* v) const override; + bool deserialize(object* v) const override; + bool deserialize(any* v) const override; + size_t count() const override; + bool array(const std::function&) const override; + bool field(const std::string& name, + const std::function&) const override; + + // Unhide base overloads + template + inline bool field(const std::string& name, T* v) { + return dap::Deserializer::field(name, v); + } + + template ::has_custom_serialization>> + inline bool deserialize(T* v) const { + return dap::Deserializer::deserialize(v); + } + + template + inline bool deserialize(dap::array* v) const { + return dap::Deserializer::deserialize(v); + } + + template + inline bool deserialize(dap::optional* v) const { + return dap::Deserializer::deserialize(v); + } + + template + inline bool deserialize(dap::variant* v) const { + return dap::Deserializer::deserialize(v); + } + + inline bool deserialize(void* o, + const std::initializer_list& f) const { + return dap::Deserializer::deserialize(o, f); + } + + template + inline bool field(const std::string& name, T* v) const { + return dap::Deserializer::deserialize(name, v); + } + + private: + Deserializer(const nlohmann::json*); + const nlohmann::json* const json; + const bool ownsJson; +}; + +struct Serializer : public dap::Serializer { + Serializer(); + ~Serializer(); + + std::string dump() const; + + // dap::Serializer compliance + bool serialize(boolean v) override; + bool serialize(integer v) override; + bool serialize(number v) override; + bool serialize(const string& v) override; + bool serialize(const object& v) override; + bool serialize(const any& v) override; + bool array(size_t count, + const std::function&) override; + bool field(const std::string& name, const FieldSerializer&) override; + void remove() override; + + // Unhide base overloads + template < + typename T, + typename = typename std::enable_if::value>::type> + inline bool field(const std::string& name, const T& v) { + return dap::Serializer::field(name, v); + } + + template ::has_custom_serialization>> + inline bool serialize(const T& v) { + return dap::Serializer::serialize(v); + } + + template + inline bool serialize(const dap::array& v) { + return dap::Serializer::serialize(v); + } + + template + inline bool serialize(const dap::optional& v) { + return dap::Serializer::serialize(v); + } + + template + inline bool serialize(const dap::variant& v) { + return dap::Serializer::serialize(v); + } + + inline bool serialize(const void* o, const std::initializer_list& f) { + return dap::Serializer::serialize(o, f); + } + + inline bool serialize(const char* v) { return dap::Serializer::serialize(v); } + + private: + Serializer(nlohmann::json*); + nlohmann::json* const json; + const bool ownsJson; + bool removed = false; +}; + +} // namespace json +} // namespace dap + +#endif // dap_json_serializer_h \ No newline at end of file diff --git a/src/json_serializer_test.cpp b/src/json_serializer_test.cpp new file mode 100644 index 0000000..5072bcc --- /dev/null +++ b/src/json_serializer_test.cpp @@ -0,0 +1,93 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "json_serializer.h" + +#include "dap/typeinfo.h" +#include "dap/typeof.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include + +namespace dap { + +struct JSONInnerTestObject { + integer i; +}; + +DAP_STRUCT_TYPEINFO(JSONInnerTestObject, + "json-inner-test-object", + DAP_FIELD(i, "i")); + +struct JSONTestObject { + boolean b; + integer i; + number n; + array a; + object o; + string s; + optional o1; + optional o2; + JSONInnerTestObject inner; +}; + +DAP_STRUCT_TYPEINFO(JSONTestObject, + "json-test-object", + DAP_FIELD(b, "b"), + DAP_FIELD(i, "i"), + DAP_FIELD(n, "n"), + DAP_FIELD(a, "a"), + DAP_FIELD(o, "o"), + DAP_FIELD(s, "s"), + DAP_FIELD(o1, "o1"), + DAP_FIELD(o2, "o2"), + DAP_FIELD(inner, "inner")); + +TEST(JSONSerializer, Decode) {} + +} // namespace dap + +TEST(JSONSerializer, SerializeDeserialize) { + dap::JSONTestObject encoded; + encoded.b = true; + encoded.i = 32; + encoded.n = 123.456; + encoded.a = {2, 4, 6, 8}; + encoded.o["one"] = dap::integer(1); + encoded.o["two"] = dap::number(2); + encoded.s = "hello world"; + encoded.o2 = 42; + encoded.inner.i = 70; + + dap::json::Serializer s; + ASSERT_TRUE(s.serialize(encoded)); + + dap::JSONTestObject decoded; + dap::json::Deserializer d(s.dump()); + ASSERT_TRUE(d.deserialize(&decoded)); + + ASSERT_EQ(encoded.b, decoded.b); + ASSERT_EQ(encoded.i, decoded.i); + ASSERT_EQ(encoded.n, decoded.n); + ASSERT_EQ(encoded.a, decoded.a); + ASSERT_EQ(encoded.o["one"].get(), + decoded.o["one"].get()); + ASSERT_EQ(encoded.o["two"].get(), + decoded.o["two"].get()); + ASSERT_EQ(encoded.s, decoded.s); + ASSERT_EQ(encoded.o2, decoded.o2); + ASSERT_EQ(encoded.inner.i, decoded.inner.i); +} diff --git a/src/network.cpp b/src/network.cpp new file mode 100644 index 0000000..ff2a311 --- /dev/null +++ b/src/network.cpp @@ -0,0 +1,100 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dap/network.h" + +#include "socket.h" + +#include +#include +#include + +namespace { + +class Impl : public dap::net::Server { + public: + Impl() {} + + ~Impl() { stop(); } + + bool start(int port, + const OnConnect& onConnect, + const OnError& onError) override { + std::unique_lock lock(mutex); + stopWithLock(); + socket = std::unique_ptr( + new dap::Socket("localhost", std::to_string(port).c_str())); + + if (!socket->isOpen()) { + onError("Failed to open socket"); + return false; + } + + running = true; + thread = std::thread([=] { + do { + if (auto rw = socket->accept()) { + onConnect(rw); + continue; + } + if (!isRunning()) { + onError("Failed to accept connection"); + } + } while (false); + }); + + return true; + } + + void stop() override { + std::unique_lock lock(mutex); + stopWithLock(); + } + + private: + bool isRunning() { + std::unique_lock lock(mutex); + return running; + } + + void stopWithLock() { + if (running) { + socket->close(); + thread.join(); + running = false; + } + } + + std::mutex mutex; + std::thread thread; + std::unique_ptr socket; + bool running = false; + OnError errorHandler; +}; + +} // anonymous namespace + +namespace dap { +namespace net { + +std::unique_ptr Server::create() { + return std::unique_ptr(new Impl()); +} + +std::shared_ptr connect(const char* addr, int port) { + return Socket::connect(addr, std::to_string(port).c_str()); +} + +} // namespace net +} // namespace dap diff --git a/src/network_test.cpp b/src/network_test.cpp new file mode 100644 index 0000000..c33c4b8 --- /dev/null +++ b/src/network_test.cpp @@ -0,0 +1,74 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dap/network.h" +#include "dap/io.h" + +#include "chan.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include +#include + +namespace { + +bool write(const std::shared_ptr& w, const std::string& s) { + return w->write(s.data(), s.size()) && w->write("\0", 1); +} + +std::string read(const std::shared_ptr& r) { + char c; + std::string s; + while (r->read(&c, sizeof(c)) > 0) { + if (c == '\0') { + return s; + } + s += c; + } + return r->isOpen() ? "" : ""; +} + +} // anonymous namespace + +TEST(Network, ClientServer) { + const int port = 19021; + dap::Chan done; + auto server = dap::net::Server::create(); + if (!server->start(port, + [&](const std::shared_ptr& rw) { + ASSERT_EQ(read(rw), "client to server"); + ASSERT_TRUE(write(rw, "server to client")); + done.put(true); + }, + [&](const char* err) { + FAIL() << "Server error: " << err; + })) { + FAIL() << "Couldn't start server"; + return; + } + + for (int i = 0; i < 10; i++) { + if (auto client = dap::net::connect("localhost", port)) { + ASSERT_TRUE(write(client, "client to server")); + ASSERT_EQ(read(client), "server to client"); + break; + } + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + + done.take(); + server.reset(); +} diff --git a/src/optional_test.cpp b/src/optional_test.cpp new file mode 100644 index 0000000..4d78379 --- /dev/null +++ b/src/optional_test.cpp @@ -0,0 +1,162 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dap/optional.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +TEST(Optional, EmptyConstruct) { + dap::optional opt; + ASSERT_FALSE(opt); + ASSERT_FALSE(opt.has_value()); +} + +TEST(Optional, ValueConstruct) { + dap::optional opt(0); + ASSERT_TRUE(opt); + ASSERT_TRUE(opt.has_value()); +} + +TEST(Optional, CopyConstruct) { + dap::optional a(10); + dap::optional b(a); + ASSERT_EQ(a, b); + ASSERT_EQ(b.value(), 10); +} + +TEST(Optional, CopyCastConstruct) { + dap::optional a(10); + dap::optional b(a); + ASSERT_EQ(a, b); + ASSERT_EQ(b.value(), (uint16_t)10); +} + +TEST(Optional, MoveConstruct) { + dap::optional a(10); + dap::optional b(std::move(a)); + ASSERT_EQ(b.value(), 10); +} + +TEST(Optional, MoveCastConstruct) { + dap::optional a(10); + dap::optional b(std::move(a)); + ASSERT_EQ(b.value(), (uint16_t)10); +} + +TEST(Optional, AssignValue) { + dap::optional a; + a = 10; + ASSERT_EQ(a.value(), 10); +} + +TEST(Optional, AssignOptional) { + dap::optional a; + dap::optional b(10); + a = b; + ASSERT_EQ(a.value(), 10); +} + +TEST(Optional, MoveAssignOptional) { + dap::optional a; + dap::optional b(10); + a = std::move(b); + ASSERT_EQ(a.value(), 10); +} + +TEST(Optional, StarDeref) { + dap::optional a(10); + ASSERT_EQ(*a, 10); +} + +TEST(Optional, StarDerefConst) { + const dap::optional a(10); + ASSERT_EQ(*a, 10); +} + +TEST(Optional, ArrowDeref) { + struct S { + int i; + }; + dap::optional a(S{10}); + ASSERT_EQ(a->i, 10); +} + +TEST(Optional, ArrowDerefConst) { + struct S { + int i; + }; + const dap::optional a(S{10}); + ASSERT_EQ(a->i, 10); +} + +TEST(Optional, Value) { + const dap::optional a(10); + ASSERT_EQ(a.value(), 10); +} + +TEST(Optional, ValueDefault) { + const dap::optional a; + const dap::optional b(20); + ASSERT_EQ(a.value(10), 10); + ASSERT_EQ(b.value(10), 20); +} + +TEST(Optional, CompareLT) { + ASSERT_FALSE(dap::optional(5) < dap::optional(3)); + ASSERT_FALSE(dap::optional(5) < dap::optional(5)); + ASSERT_TRUE(dap::optional(5) < dap::optional(10)); + ASSERT_TRUE(dap::optional() < dap::optional(10)); + ASSERT_FALSE(dap::optional() < dap::optional()); +} + +TEST(Optional, CompareLE) { + ASSERT_FALSE(dap::optional(5) <= dap::optional(3)); + ASSERT_TRUE(dap::optional(5) <= dap::optional(5)); + ASSERT_TRUE(dap::optional(5) <= dap::optional(10)); + ASSERT_TRUE(dap::optional() <= dap::optional(10)); + ASSERT_TRUE(dap::optional() <= dap::optional()); +} + +TEST(Optional, CompareGT) { + ASSERT_TRUE(dap::optional(5) > dap::optional(3)); + ASSERT_FALSE(dap::optional(5) > dap::optional(5)); + ASSERT_FALSE(dap::optional(5) > dap::optional(10)); + ASSERT_FALSE(dap::optional() > dap::optional(10)); + ASSERT_FALSE(dap::optional() > dap::optional()); +} + +TEST(Optional, CompareGE) { + ASSERT_TRUE(dap::optional(5) >= dap::optional(3)); + ASSERT_TRUE(dap::optional(5) >= dap::optional(5)); + ASSERT_FALSE(dap::optional(5) >= dap::optional(10)); + ASSERT_FALSE(dap::optional() >= dap::optional(10)); + ASSERT_TRUE(dap::optional() >= dap::optional()); +} + +TEST(Optional, CompareEQ) { + ASSERT_FALSE(dap::optional(5) == dap::optional(3)); + ASSERT_TRUE(dap::optional(5) == dap::optional(5)); + ASSERT_FALSE(dap::optional(5) == dap::optional(10)); + ASSERT_FALSE(dap::optional() == dap::optional(10)); + ASSERT_TRUE(dap::optional() == dap::optional()); +} + +TEST(Optional, CompareNEQ) { + ASSERT_TRUE(dap::optional(5) != dap::optional(3)); + ASSERT_FALSE(dap::optional(5) != dap::optional(5)); + ASSERT_TRUE(dap::optional(5) != dap::optional(10)); + ASSERT_TRUE(dap::optional() != dap::optional(10)); + ASSERT_FALSE(dap::optional() != dap::optional()); +} diff --git a/src/protocol_events.cpp b/src/protocol_events.cpp new file mode 100644 index 0000000..80e9703 --- /dev/null +++ b/src/protocol_events.cpp @@ -0,0 +1,114 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Generated with protocol_gen.go -- do not edit this file. +// go run scripts/protocol_gen/protocol_gen.go + +#include "dap/protocol.h" + +namespace dap { + +BreakpointEvent::BreakpointEvent() = default; +BreakpointEvent::~BreakpointEvent() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(BreakpointEvent, + "breakpoint", + DAP_FIELD(breakpoint, "breakpoint"), + DAP_FIELD(reason, "reason")); + +CapabilitiesEvent::CapabilitiesEvent() = default; +CapabilitiesEvent::~CapabilitiesEvent() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(CapabilitiesEvent, + "capabilities", + DAP_FIELD(capabilities, "capabilities")); + +ContinuedEvent::ContinuedEvent() = default; +ContinuedEvent::~ContinuedEvent() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(ContinuedEvent, + "continued", + DAP_FIELD(allThreadsContinued, + "allThreadsContinued"), + DAP_FIELD(threadId, "threadId")); + +ExitedEvent::ExitedEvent() = default; +ExitedEvent::~ExitedEvent() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(ExitedEvent, + "exited", + DAP_FIELD(exitCode, "exitCode")); + +InitializedEvent::InitializedEvent() = default; +InitializedEvent::~InitializedEvent() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(InitializedEvent, "initialized"); + +LoadedSourceEvent::LoadedSourceEvent() = default; +LoadedSourceEvent::~LoadedSourceEvent() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(LoadedSourceEvent, + "loadedSource", + DAP_FIELD(reason, "reason"), + DAP_FIELD(source, "source")); + +ModuleEvent::ModuleEvent() = default; +ModuleEvent::~ModuleEvent() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(ModuleEvent, + "module", + DAP_FIELD(module, "module"), + DAP_FIELD(reason, "reason")); + +OutputEvent::OutputEvent() = default; +OutputEvent::~OutputEvent() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(OutputEvent, + "output", + DAP_FIELD(category, "category"), + DAP_FIELD(column, "column"), + DAP_FIELD(data, "data"), + DAP_FIELD(line, "line"), + DAP_FIELD(output, "output"), + DAP_FIELD(source, "source"), + DAP_FIELD(variablesReference, + "variablesReference")); + +ProcessEvent::ProcessEvent() = default; +ProcessEvent::~ProcessEvent() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(ProcessEvent, + "process", + DAP_FIELD(isLocalProcess, "isLocalProcess"), + DAP_FIELD(name, "name"), + DAP_FIELD(pointerSize, "pointerSize"), + DAP_FIELD(startMethod, "startMethod"), + DAP_FIELD(systemProcessId, "systemProcessId")); + +StoppedEvent::StoppedEvent() = default; +StoppedEvent::~StoppedEvent() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(StoppedEvent, + "stopped", + DAP_FIELD(allThreadsStopped, "allThreadsStopped"), + DAP_FIELD(description, "description"), + DAP_FIELD(preserveFocusHint, "preserveFocusHint"), + DAP_FIELD(reason, "reason"), + DAP_FIELD(text, "text"), + DAP_FIELD(threadId, "threadId")); + +TerminatedEvent::TerminatedEvent() = default; +TerminatedEvent::~TerminatedEvent() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(TerminatedEvent, + "terminated", + DAP_FIELD(restart, "restart")); + +ThreadEvent::ThreadEvent() = default; +ThreadEvent::~ThreadEvent() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(ThreadEvent, + "thread", + DAP_FIELD(reason, "reason"), + DAP_FIELD(threadId, "threadId")); + +} // namespace dap diff --git a/src/protocol_requests.cpp b/src/protocol_requests.cpp new file mode 100644 index 0000000..23e3bdb --- /dev/null +++ b/src/protocol_requests.cpp @@ -0,0 +1,321 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Generated with protocol_gen.go -- do not edit this file. +// go run scripts/protocol_gen/protocol_gen.go + +#include "dap/protocol.h" + +namespace dap { + +AttachRequest::AttachRequest() = default; +AttachRequest::~AttachRequest() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(AttachRequest, + "attach", + DAP_FIELD(restart, "__restart")); + +BreakpointLocationsRequest::BreakpointLocationsRequest() = default; +BreakpointLocationsRequest::~BreakpointLocationsRequest() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(BreakpointLocationsRequest, + "breakpointLocations", + DAP_FIELD(column, "column"), + DAP_FIELD(endColumn, "endColumn"), + DAP_FIELD(endLine, "endLine"), + DAP_FIELD(line, "line"), + DAP_FIELD(source, "source")); + +CancelRequest::CancelRequest() = default; +CancelRequest::~CancelRequest() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(CancelRequest, + "cancel", + DAP_FIELD(requestId, "requestId")); + +CompletionsRequest::CompletionsRequest() = default; +CompletionsRequest::~CompletionsRequest() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(CompletionsRequest, + "completions", + DAP_FIELD(column, "column"), + DAP_FIELD(frameId, "frameId"), + DAP_FIELD(line, "line"), + DAP_FIELD(text, "text")); + +ConfigurationDoneRequest::ConfigurationDoneRequest() = default; +ConfigurationDoneRequest::~ConfigurationDoneRequest() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(ConfigurationDoneRequest, "configurationDone"); + +ContinueRequest::ContinueRequest() = default; +ContinueRequest::~ContinueRequest() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(ContinueRequest, + "continue", + DAP_FIELD(threadId, "threadId")); + +DataBreakpointInfoRequest::DataBreakpointInfoRequest() = default; +DataBreakpointInfoRequest::~DataBreakpointInfoRequest() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(DataBreakpointInfoRequest, + "dataBreakpointInfo", + DAP_FIELD(name, "name"), + DAP_FIELD(variablesReference, + "variablesReference")); + +DisassembleRequest::DisassembleRequest() = default; +DisassembleRequest::~DisassembleRequest() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(DisassembleRequest, + "disassemble", + DAP_FIELD(instructionCount, "instructionCount"), + DAP_FIELD(instructionOffset, "instructionOffset"), + DAP_FIELD(memoryReference, "memoryReference"), + DAP_FIELD(offset, "offset"), + DAP_FIELD(resolveSymbols, "resolveSymbols")); + +DisconnectRequest::DisconnectRequest() = default; +DisconnectRequest::~DisconnectRequest() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(DisconnectRequest, + "disconnect", + DAP_FIELD(restart, "restart"), + DAP_FIELD(terminateDebuggee, + "terminateDebuggee")); + +EvaluateRequest::EvaluateRequest() = default; +EvaluateRequest::~EvaluateRequest() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(EvaluateRequest, + "evaluate", + DAP_FIELD(context, "context"), + DAP_FIELD(expression, "expression"), + DAP_FIELD(format, "format"), + DAP_FIELD(frameId, "frameId")); + +ExceptionInfoRequest::ExceptionInfoRequest() = default; +ExceptionInfoRequest::~ExceptionInfoRequest() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(ExceptionInfoRequest, + "exceptionInfo", + DAP_FIELD(threadId, "threadId")); + +GotoRequest::GotoRequest() = default; +GotoRequest::~GotoRequest() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(GotoRequest, + "goto", + DAP_FIELD(targetId, "targetId"), + DAP_FIELD(threadId, "threadId")); + +GotoTargetsRequest::GotoTargetsRequest() = default; +GotoTargetsRequest::~GotoTargetsRequest() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(GotoTargetsRequest, + "gotoTargets", + DAP_FIELD(column, "column"), + DAP_FIELD(line, "line"), + DAP_FIELD(source, "source")); + +InitializeRequest::InitializeRequest() = default; +InitializeRequest::~InitializeRequest() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO( + InitializeRequest, + "initialize", + DAP_FIELD(adapterID, "adapterID"), + DAP_FIELD(clientID, "clientID"), + DAP_FIELD(clientName, "clientName"), + DAP_FIELD(columnsStartAt1, "columnsStartAt1"), + DAP_FIELD(linesStartAt1, "linesStartAt1"), + DAP_FIELD(locale, "locale"), + DAP_FIELD(pathFormat, "pathFormat"), + DAP_FIELD(supportsMemoryReferences, "supportsMemoryReferences"), + DAP_FIELD(supportsRunInTerminalRequest, "supportsRunInTerminalRequest"), + DAP_FIELD(supportsVariablePaging, "supportsVariablePaging"), + DAP_FIELD(supportsVariableType, "supportsVariableType")); + +LaunchRequest::LaunchRequest() = default; +LaunchRequest::~LaunchRequest() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(LaunchRequest, + "launch", + DAP_FIELD(restart, "__restart"), + DAP_FIELD(noDebug, "noDebug")); + +LoadedSourcesRequest::LoadedSourcesRequest() = default; +LoadedSourcesRequest::~LoadedSourcesRequest() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(LoadedSourcesRequest, "loadedSources"); + +ModulesRequest::ModulesRequest() = default; +ModulesRequest::~ModulesRequest() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(ModulesRequest, + "modules", + DAP_FIELD(moduleCount, "moduleCount"), + DAP_FIELD(startModule, "startModule")); + +NextRequest::NextRequest() = default; +NextRequest::~NextRequest() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(NextRequest, + "next", + DAP_FIELD(threadId, "threadId")); + +PauseRequest::PauseRequest() = default; +PauseRequest::~PauseRequest() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(PauseRequest, + "pause", + DAP_FIELD(threadId, "threadId")); + +ReadMemoryRequest::ReadMemoryRequest() = default; +ReadMemoryRequest::~ReadMemoryRequest() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(ReadMemoryRequest, + "readMemory", + DAP_FIELD(count, "count"), + DAP_FIELD(memoryReference, "memoryReference"), + DAP_FIELD(offset, "offset")); + +RestartFrameRequest::RestartFrameRequest() = default; +RestartFrameRequest::~RestartFrameRequest() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(RestartFrameRequest, + "restartFrame", + DAP_FIELD(frameId, "frameId")); + +RestartRequest::RestartRequest() = default; +RestartRequest::~RestartRequest() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(RestartRequest, "restart"); + +ReverseContinueRequest::ReverseContinueRequest() = default; +ReverseContinueRequest::~ReverseContinueRequest() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(ReverseContinueRequest, + "reverseContinue", + DAP_FIELD(threadId, "threadId")); + +RunInTerminalRequest::RunInTerminalRequest() = default; +RunInTerminalRequest::~RunInTerminalRequest() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(RunInTerminalRequest, + "runInTerminal", + DAP_FIELD(args, "args"), + DAP_FIELD(cwd, "cwd"), + DAP_FIELD(env, "env"), + DAP_FIELD(kind, "kind"), + DAP_FIELD(title, "title")); + +ScopesRequest::ScopesRequest() = default; +ScopesRequest::~ScopesRequest() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(ScopesRequest, + "scopes", + DAP_FIELD(frameId, "frameId")); + +SetBreakpointsRequest::SetBreakpointsRequest() = default; +SetBreakpointsRequest::~SetBreakpointsRequest() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(SetBreakpointsRequest, + "setBreakpoints", + DAP_FIELD(breakpoints, "breakpoints"), + DAP_FIELD(lines, "lines"), + DAP_FIELD(source, "source"), + DAP_FIELD(sourceModified, "sourceModified")); + +SetDataBreakpointsRequest::SetDataBreakpointsRequest() = default; +SetDataBreakpointsRequest::~SetDataBreakpointsRequest() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(SetDataBreakpointsRequest, + "setDataBreakpoints", + DAP_FIELD(breakpoints, "breakpoints")); + +SetExceptionBreakpointsRequest::SetExceptionBreakpointsRequest() = default; +SetExceptionBreakpointsRequest::~SetExceptionBreakpointsRequest() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(SetExceptionBreakpointsRequest, + "setExceptionBreakpoints", + DAP_FIELD(exceptionOptions, "exceptionOptions"), + DAP_FIELD(filters, "filters")); + +SetExpressionRequest::SetExpressionRequest() = default; +SetExpressionRequest::~SetExpressionRequest() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(SetExpressionRequest, + "setExpression", + DAP_FIELD(expression, "expression"), + DAP_FIELD(format, "format"), + DAP_FIELD(frameId, "frameId"), + DAP_FIELD(value, "value")); + +SetFunctionBreakpointsRequest::SetFunctionBreakpointsRequest() = default; +SetFunctionBreakpointsRequest::~SetFunctionBreakpointsRequest() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(SetFunctionBreakpointsRequest, + "setFunctionBreakpoints", + DAP_FIELD(breakpoints, "breakpoints")); + +SetVariableRequest::SetVariableRequest() = default; +SetVariableRequest::~SetVariableRequest() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(SetVariableRequest, + "setVariable", + DAP_FIELD(format, "format"), + DAP_FIELD(name, "name"), + DAP_FIELD(value, "value"), + DAP_FIELD(variablesReference, + "variablesReference")); + +SourceRequest::SourceRequest() = default; +SourceRequest::~SourceRequest() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(SourceRequest, + "source", + DAP_FIELD(source, "source"), + DAP_FIELD(sourceReference, "sourceReference")); + +StackTraceRequest::StackTraceRequest() = default; +StackTraceRequest::~StackTraceRequest() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(StackTraceRequest, + "stackTrace", + DAP_FIELD(format, "format"), + DAP_FIELD(levels, "levels"), + DAP_FIELD(startFrame, "startFrame"), + DAP_FIELD(threadId, "threadId")); + +StepBackRequest::StepBackRequest() = default; +StepBackRequest::~StepBackRequest() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(StepBackRequest, + "stepBack", + DAP_FIELD(threadId, "threadId")); + +StepInRequest::StepInRequest() = default; +StepInRequest::~StepInRequest() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(StepInRequest, + "stepIn", + DAP_FIELD(targetId, "targetId"), + DAP_FIELD(threadId, "threadId")); + +StepInTargetsRequest::StepInTargetsRequest() = default; +StepInTargetsRequest::~StepInTargetsRequest() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(StepInTargetsRequest, + "stepInTargets", + DAP_FIELD(frameId, "frameId")); + +StepOutRequest::StepOutRequest() = default; +StepOutRequest::~StepOutRequest() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(StepOutRequest, + "stepOut", + DAP_FIELD(threadId, "threadId")); + +TerminateRequest::TerminateRequest() = default; +TerminateRequest::~TerminateRequest() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(TerminateRequest, + "terminate", + DAP_FIELD(restart, "restart")); + +TerminateThreadsRequest::TerminateThreadsRequest() = default; +TerminateThreadsRequest::~TerminateThreadsRequest() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(TerminateThreadsRequest, + "terminateThreads", + DAP_FIELD(threadIds, "threadIds")); + +ThreadsRequest::ThreadsRequest() = default; +ThreadsRequest::~ThreadsRequest() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(ThreadsRequest, "threads"); + +VariablesRequest::VariablesRequest() = default; +VariablesRequest::~VariablesRequest() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(VariablesRequest, + "variables", + DAP_FIELD(count, "count"), + DAP_FIELD(filter, "filter"), + DAP_FIELD(format, "format"), + DAP_FIELD(start, "start"), + DAP_FIELD(variablesReference, + "variablesReference")); + +} // namespace dap diff --git a/src/protocol_response.cpp b/src/protocol_response.cpp new file mode 100644 index 0000000..eeb2218 --- /dev/null +++ b/src/protocol_response.cpp @@ -0,0 +1,305 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Generated with protocol_gen.go -- do not edit this file. +// go run scripts/protocol_gen/protocol_gen.go + +#include "dap/protocol.h" + +namespace dap { + +AttachResponse::AttachResponse() = default; +AttachResponse::~AttachResponse() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(AttachResponse, ""); + +BreakpointLocationsResponse::BreakpointLocationsResponse() = default; +BreakpointLocationsResponse::~BreakpointLocationsResponse() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(BreakpointLocationsResponse, + "", + DAP_FIELD(breakpoints, "breakpoints")); + +CancelResponse::CancelResponse() = default; +CancelResponse::~CancelResponse() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(CancelResponse, ""); + +CompletionsResponse::CompletionsResponse() = default; +CompletionsResponse::~CompletionsResponse() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(CompletionsResponse, + "", + DAP_FIELD(targets, "targets")); + +ConfigurationDoneResponse::ConfigurationDoneResponse() = default; +ConfigurationDoneResponse::~ConfigurationDoneResponse() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(ConfigurationDoneResponse, ""); + +ContinueResponse::ContinueResponse() = default; +ContinueResponse::~ContinueResponse() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(ContinueResponse, + "", + DAP_FIELD(allThreadsContinued, + "allThreadsContinued")); + +DataBreakpointInfoResponse::DataBreakpointInfoResponse() = default; +DataBreakpointInfoResponse::~DataBreakpointInfoResponse() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(DataBreakpointInfoResponse, + "", + DAP_FIELD(accessTypes, "accessTypes"), + DAP_FIELD(canPersist, "canPersist"), + DAP_FIELD(dataId, "dataId"), + DAP_FIELD(description, "description")); + +DisassembleResponse::DisassembleResponse() = default; +DisassembleResponse::~DisassembleResponse() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(DisassembleResponse, + "", + DAP_FIELD(instructions, "instructions")); + +DisconnectResponse::DisconnectResponse() = default; +DisconnectResponse::~DisconnectResponse() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(DisconnectResponse, ""); + +ErrorResponse::ErrorResponse() = default; +ErrorResponse::~ErrorResponse() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(ErrorResponse, "", DAP_FIELD(error, "error")); + +EvaluateResponse::EvaluateResponse() = default; +EvaluateResponse::~EvaluateResponse() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(EvaluateResponse, + "", + DAP_FIELD(indexedVariables, "indexedVariables"), + DAP_FIELD(memoryReference, "memoryReference"), + DAP_FIELD(namedVariables, "namedVariables"), + DAP_FIELD(presentationHint, "presentationHint"), + DAP_FIELD(result, "result"), + DAP_FIELD(type, "type"), + DAP_FIELD(variablesReference, + "variablesReference")); + +ExceptionInfoResponse::ExceptionInfoResponse() = default; +ExceptionInfoResponse::~ExceptionInfoResponse() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(ExceptionInfoResponse, + "", + DAP_FIELD(breakMode, "breakMode"), + DAP_FIELD(description, "description"), + DAP_FIELD(details, "details"), + DAP_FIELD(exceptionId, "exceptionId")); + +GotoResponse::GotoResponse() = default; +GotoResponse::~GotoResponse() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(GotoResponse, ""); + +GotoTargetsResponse::GotoTargetsResponse() = default; +GotoTargetsResponse::~GotoTargetsResponse() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(GotoTargetsResponse, + "", + DAP_FIELD(targets, "targets")); + +InitializeResponse::InitializeResponse() = default; +InitializeResponse::~InitializeResponse() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO( + InitializeResponse, + "", + DAP_FIELD(additionalModuleColumns, "additionalModuleColumns"), + DAP_FIELD(completionTriggerCharacters, "completionTriggerCharacters"), + DAP_FIELD(exceptionBreakpointFilters, "exceptionBreakpointFilters"), + DAP_FIELD(supportTerminateDebuggee, "supportTerminateDebuggee"), + DAP_FIELD(supportedChecksumAlgorithms, "supportedChecksumAlgorithms"), + DAP_FIELD(supportsBreakpointLocationsRequest, + "supportsBreakpointLocationsRequest"), + DAP_FIELD(supportsCancelRequest, "supportsCancelRequest"), + DAP_FIELD(supportsCompletionsRequest, "supportsCompletionsRequest"), + DAP_FIELD(supportsConditionalBreakpoints, "supportsConditionalBreakpoints"), + DAP_FIELD(supportsConfigurationDoneRequest, + "supportsConfigurationDoneRequest"), + DAP_FIELD(supportsDataBreakpoints, "supportsDataBreakpoints"), + DAP_FIELD(supportsDelayedStackTraceLoading, + "supportsDelayedStackTraceLoading"), + DAP_FIELD(supportsDisassembleRequest, "supportsDisassembleRequest"), + DAP_FIELD(supportsEvaluateForHovers, "supportsEvaluateForHovers"), + DAP_FIELD(supportsExceptionInfoRequest, "supportsExceptionInfoRequest"), + DAP_FIELD(supportsExceptionOptions, "supportsExceptionOptions"), + DAP_FIELD(supportsFunctionBreakpoints, "supportsFunctionBreakpoints"), + DAP_FIELD(supportsGotoTargetsRequest, "supportsGotoTargetsRequest"), + DAP_FIELD(supportsHitConditionalBreakpoints, + "supportsHitConditionalBreakpoints"), + DAP_FIELD(supportsLoadedSourcesRequest, "supportsLoadedSourcesRequest"), + DAP_FIELD(supportsLogPoints, "supportsLogPoints"), + DAP_FIELD(supportsModulesRequest, "supportsModulesRequest"), + DAP_FIELD(supportsReadMemoryRequest, "supportsReadMemoryRequest"), + DAP_FIELD(supportsRestartFrame, "supportsRestartFrame"), + DAP_FIELD(supportsRestartRequest, "supportsRestartRequest"), + DAP_FIELD(supportsSetExpression, "supportsSetExpression"), + DAP_FIELD(supportsSetVariable, "supportsSetVariable"), + DAP_FIELD(supportsStepBack, "supportsStepBack"), + DAP_FIELD(supportsStepInTargetsRequest, "supportsStepInTargetsRequest"), + DAP_FIELD(supportsTerminateRequest, "supportsTerminateRequest"), + DAP_FIELD(supportsTerminateThreadsRequest, + "supportsTerminateThreadsRequest"), + DAP_FIELD(supportsValueFormattingOptions, + "supportsValueFormattingOptions")); + +LaunchResponse::LaunchResponse() = default; +LaunchResponse::~LaunchResponse() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(LaunchResponse, ""); + +LoadedSourcesResponse::LoadedSourcesResponse() = default; +LoadedSourcesResponse::~LoadedSourcesResponse() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(LoadedSourcesResponse, + "", + DAP_FIELD(sources, "sources")); + +ModulesResponse::ModulesResponse() = default; +ModulesResponse::~ModulesResponse() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(ModulesResponse, + "", + DAP_FIELD(modules, "modules"), + DAP_FIELD(totalModules, "totalModules")); + +NextResponse::NextResponse() = default; +NextResponse::~NextResponse() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(NextResponse, ""); + +PauseResponse::PauseResponse() = default; +PauseResponse::~PauseResponse() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(PauseResponse, ""); + +ReadMemoryResponse::ReadMemoryResponse() = default; +ReadMemoryResponse::~ReadMemoryResponse() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(ReadMemoryResponse, + "", + DAP_FIELD(address, "address"), + DAP_FIELD(data, "data"), + DAP_FIELD(unreadableBytes, "unreadableBytes")); + +RestartFrameResponse::RestartFrameResponse() = default; +RestartFrameResponse::~RestartFrameResponse() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(RestartFrameResponse, ""); + +RestartResponse::RestartResponse() = default; +RestartResponse::~RestartResponse() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(RestartResponse, ""); + +ReverseContinueResponse::ReverseContinueResponse() = default; +ReverseContinueResponse::~ReverseContinueResponse() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(ReverseContinueResponse, ""); + +RunInTerminalResponse::RunInTerminalResponse() = default; +RunInTerminalResponse::~RunInTerminalResponse() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(RunInTerminalResponse, + "", + DAP_FIELD(processId, "processId"), + DAP_FIELD(shellProcessId, "shellProcessId")); + +ScopesResponse::ScopesResponse() = default; +ScopesResponse::~ScopesResponse() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(ScopesResponse, "", DAP_FIELD(scopes, "scopes")); + +SetBreakpointsResponse::SetBreakpointsResponse() = default; +SetBreakpointsResponse::~SetBreakpointsResponse() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(SetBreakpointsResponse, + "", + DAP_FIELD(breakpoints, "breakpoints")); + +SetDataBreakpointsResponse::SetDataBreakpointsResponse() = default; +SetDataBreakpointsResponse::~SetDataBreakpointsResponse() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(SetDataBreakpointsResponse, + "", + DAP_FIELD(breakpoints, "breakpoints")); + +SetExceptionBreakpointsResponse::SetExceptionBreakpointsResponse() = default; +SetExceptionBreakpointsResponse::~SetExceptionBreakpointsResponse() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(SetExceptionBreakpointsResponse, ""); + +SetExpressionResponse::SetExpressionResponse() = default; +SetExpressionResponse::~SetExpressionResponse() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(SetExpressionResponse, + "", + DAP_FIELD(indexedVariables, "indexedVariables"), + DAP_FIELD(namedVariables, "namedVariables"), + DAP_FIELD(presentationHint, "presentationHint"), + DAP_FIELD(type, "type"), + DAP_FIELD(value, "value"), + DAP_FIELD(variablesReference, + "variablesReference")); + +SetFunctionBreakpointsResponse::SetFunctionBreakpointsResponse() = default; +SetFunctionBreakpointsResponse::~SetFunctionBreakpointsResponse() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(SetFunctionBreakpointsResponse, + "", + DAP_FIELD(breakpoints, "breakpoints")); + +SetVariableResponse::SetVariableResponse() = default; +SetVariableResponse::~SetVariableResponse() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(SetVariableResponse, + "", + DAP_FIELD(indexedVariables, "indexedVariables"), + DAP_FIELD(namedVariables, "namedVariables"), + DAP_FIELD(type, "type"), + DAP_FIELD(value, "value"), + DAP_FIELD(variablesReference, + "variablesReference")); + +SourceResponse::SourceResponse() = default; +SourceResponse::~SourceResponse() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(SourceResponse, + "", + DAP_FIELD(content, "content"), + DAP_FIELD(mimeType, "mimeType")); + +StackTraceResponse::StackTraceResponse() = default; +StackTraceResponse::~StackTraceResponse() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(StackTraceResponse, + "", + DAP_FIELD(stackFrames, "stackFrames"), + DAP_FIELD(totalFrames, "totalFrames")); + +StepBackResponse::StepBackResponse() = default; +StepBackResponse::~StepBackResponse() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(StepBackResponse, ""); + +StepInResponse::StepInResponse() = default; +StepInResponse::~StepInResponse() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(StepInResponse, ""); + +StepInTargetsResponse::StepInTargetsResponse() = default; +StepInTargetsResponse::~StepInTargetsResponse() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(StepInTargetsResponse, + "", + DAP_FIELD(targets, "targets")); + +StepOutResponse::StepOutResponse() = default; +StepOutResponse::~StepOutResponse() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(StepOutResponse, ""); + +TerminateResponse::TerminateResponse() = default; +TerminateResponse::~TerminateResponse() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(TerminateResponse, ""); + +TerminateThreadsResponse::TerminateThreadsResponse() = default; +TerminateThreadsResponse::~TerminateThreadsResponse() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(TerminateThreadsResponse, ""); + +ThreadsResponse::ThreadsResponse() = default; +ThreadsResponse::~ThreadsResponse() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(ThreadsResponse, + "", + DAP_FIELD(threads, "threads")); + +VariablesResponse::VariablesResponse() = default; +VariablesResponse::~VariablesResponse() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(VariablesResponse, + "", + DAP_FIELD(variables, "variables")); + +} // namespace dap diff --git a/src/protocol_types.cpp b/src/protocol_types.cpp new file mode 100644 index 0000000..198f62e --- /dev/null +++ b/src/protocol_types.cpp @@ -0,0 +1,345 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Generated with protocol_gen.go -- do not edit this file. +// go run scripts/protocol_gen/protocol_gen.go + +#include "dap/protocol.h" + +namespace dap { + +ChecksumAlgorithm::ChecksumAlgorithm() = default; +ChecksumAlgorithm::~ChecksumAlgorithm() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(ChecksumAlgorithm, ""); + +Checksum::Checksum() = default; +Checksum::~Checksum() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(Checksum, + "", + DAP_FIELD(algorithm, "algorithm"), + DAP_FIELD(checksum, "checksum")); + +Source::Source() = default; +Source::~Source() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(Source, + "", + DAP_FIELD(adapterData, "adapterData"), + DAP_FIELD(checksums, "checksums"), + DAP_FIELD(name, "name"), + DAP_FIELD(origin, "origin"), + DAP_FIELD(path, "path"), + DAP_FIELD(presentationHint, "presentationHint"), + DAP_FIELD(sourceReference, "sourceReference"), + DAP_FIELD(sources, "sources")); + +Breakpoint::Breakpoint() = default; +Breakpoint::~Breakpoint() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(Breakpoint, + "", + DAP_FIELD(column, "column"), + DAP_FIELD(endColumn, "endColumn"), + DAP_FIELD(endLine, "endLine"), + DAP_FIELD(id, "id"), + DAP_FIELD(line, "line"), + DAP_FIELD(message, "message"), + DAP_FIELD(source, "source"), + DAP_FIELD(verified, "verified")); + +BreakpointLocation::BreakpointLocation() = default; +BreakpointLocation::~BreakpointLocation() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(BreakpointLocation, + "", + DAP_FIELD(column, "column"), + DAP_FIELD(endColumn, "endColumn"), + DAP_FIELD(endLine, "endLine"), + DAP_FIELD(line, "line")); + +ColumnDescriptor::ColumnDescriptor() = default; +ColumnDescriptor::~ColumnDescriptor() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(ColumnDescriptor, + "", + DAP_FIELD(attributeName, "attributeName"), + DAP_FIELD(format, "format"), + DAP_FIELD(label, "label"), + DAP_FIELD(type, "type"), + DAP_FIELD(width, "width")); + +ExceptionBreakpointsFilter::ExceptionBreakpointsFilter() = default; +ExceptionBreakpointsFilter::~ExceptionBreakpointsFilter() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(ExceptionBreakpointsFilter, + "", + DAP_FIELD(def, "default"), + DAP_FIELD(filter, "filter"), + DAP_FIELD(label, "label")); + +Capabilities::Capabilities() = default; +Capabilities::~Capabilities() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO( + Capabilities, + "", + DAP_FIELD(additionalModuleColumns, "additionalModuleColumns"), + DAP_FIELD(completionTriggerCharacters, "completionTriggerCharacters"), + DAP_FIELD(exceptionBreakpointFilters, "exceptionBreakpointFilters"), + DAP_FIELD(supportTerminateDebuggee, "supportTerminateDebuggee"), + DAP_FIELD(supportedChecksumAlgorithms, "supportedChecksumAlgorithms"), + DAP_FIELD(supportsBreakpointLocationsRequest, + "supportsBreakpointLocationsRequest"), + DAP_FIELD(supportsCancelRequest, "supportsCancelRequest"), + DAP_FIELD(supportsCompletionsRequest, "supportsCompletionsRequest"), + DAP_FIELD(supportsConditionalBreakpoints, "supportsConditionalBreakpoints"), + DAP_FIELD(supportsConfigurationDoneRequest, + "supportsConfigurationDoneRequest"), + DAP_FIELD(supportsDataBreakpoints, "supportsDataBreakpoints"), + DAP_FIELD(supportsDelayedStackTraceLoading, + "supportsDelayedStackTraceLoading"), + DAP_FIELD(supportsDisassembleRequest, "supportsDisassembleRequest"), + DAP_FIELD(supportsEvaluateForHovers, "supportsEvaluateForHovers"), + DAP_FIELD(supportsExceptionInfoRequest, "supportsExceptionInfoRequest"), + DAP_FIELD(supportsExceptionOptions, "supportsExceptionOptions"), + DAP_FIELD(supportsFunctionBreakpoints, "supportsFunctionBreakpoints"), + DAP_FIELD(supportsGotoTargetsRequest, "supportsGotoTargetsRequest"), + DAP_FIELD(supportsHitConditionalBreakpoints, + "supportsHitConditionalBreakpoints"), + DAP_FIELD(supportsLoadedSourcesRequest, "supportsLoadedSourcesRequest"), + DAP_FIELD(supportsLogPoints, "supportsLogPoints"), + DAP_FIELD(supportsModulesRequest, "supportsModulesRequest"), + DAP_FIELD(supportsReadMemoryRequest, "supportsReadMemoryRequest"), + DAP_FIELD(supportsRestartFrame, "supportsRestartFrame"), + DAP_FIELD(supportsRestartRequest, "supportsRestartRequest"), + DAP_FIELD(supportsSetExpression, "supportsSetExpression"), + DAP_FIELD(supportsSetVariable, "supportsSetVariable"), + DAP_FIELD(supportsStepBack, "supportsStepBack"), + DAP_FIELD(supportsStepInTargetsRequest, "supportsStepInTargetsRequest"), + DAP_FIELD(supportsTerminateRequest, "supportsTerminateRequest"), + DAP_FIELD(supportsTerminateThreadsRequest, + "supportsTerminateThreadsRequest"), + DAP_FIELD(supportsValueFormattingOptions, + "supportsValueFormattingOptions")); + +CompletionItemType::CompletionItemType() = default; +CompletionItemType::~CompletionItemType() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(CompletionItemType, ""); + +CompletionItem::CompletionItem() = default; +CompletionItem::~CompletionItem() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(CompletionItem, + "", + DAP_FIELD(label, "label"), + DAP_FIELD(length, "length"), + DAP_FIELD(sortText, "sortText"), + DAP_FIELD(start, "start"), + DAP_FIELD(text, "text"), + DAP_FIELD(type, "type")); + +DataBreakpointAccessType::DataBreakpointAccessType() = default; +DataBreakpointAccessType::~DataBreakpointAccessType() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(DataBreakpointAccessType, ""); + +DisassembledInstruction::DisassembledInstruction() = default; +DisassembledInstruction::~DisassembledInstruction() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(DisassembledInstruction, + "", + DAP_FIELD(address, "address"), + DAP_FIELD(column, "column"), + DAP_FIELD(endColumn, "endColumn"), + DAP_FIELD(endLine, "endLine"), + DAP_FIELD(instruction, "instruction"), + DAP_FIELD(instructionBytes, "instructionBytes"), + DAP_FIELD(line, "line"), + DAP_FIELD(location, "location"), + DAP_FIELD(symbol, "symbol")); + +Message::Message() = default; +Message::~Message() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(Message, + "", + DAP_FIELD(format, "format"), + DAP_FIELD(id, "id"), + DAP_FIELD(sendTelemetry, "sendTelemetry"), + DAP_FIELD(showUser, "showUser"), + DAP_FIELD(url, "url"), + DAP_FIELD(urlLabel, "urlLabel"), + DAP_FIELD(variables, "variables")); + +VariablePresentationHint::VariablePresentationHint() = default; +VariablePresentationHint::~VariablePresentationHint() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(VariablePresentationHint, + "", + DAP_FIELD(attributes, "attributes"), + DAP_FIELD(kind, "kind"), + DAP_FIELD(visibility, "visibility")); + +ValueFormat::ValueFormat() = default; +ValueFormat::~ValueFormat() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(ValueFormat, "", DAP_FIELD(hex, "hex")); + +ExceptionBreakMode::ExceptionBreakMode() = default; +ExceptionBreakMode::~ExceptionBreakMode() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(ExceptionBreakMode, ""); + +ExceptionDetails::ExceptionDetails() = default; +ExceptionDetails::~ExceptionDetails() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(ExceptionDetails, + "", + DAP_FIELD(evaluateName, "evaluateName"), + DAP_FIELD(fullTypeName, "fullTypeName"), + DAP_FIELD(innerException, "innerException"), + DAP_FIELD(message, "message"), + DAP_FIELD(stackTrace, "stackTrace"), + DAP_FIELD(typeName, "typeName")); + +GotoTarget::GotoTarget() = default; +GotoTarget::~GotoTarget() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(GotoTarget, + "", + DAP_FIELD(column, "column"), + DAP_FIELD(endColumn, "endColumn"), + DAP_FIELD(endLine, "endLine"), + DAP_FIELD(id, "id"), + DAP_FIELD(instructionPointerReference, + "instructionPointerReference"), + DAP_FIELD(label, "label"), + DAP_FIELD(line, "line")); + +Module::Module() = default; +Module::~Module() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(Module, + "", + DAP_FIELD(addressRange, "addressRange"), + DAP_FIELD(dateTimeStamp, "dateTimeStamp"), + DAP_FIELD(id, "id"), + DAP_FIELD(isOptimized, "isOptimized"), + DAP_FIELD(isUserCode, "isUserCode"), + DAP_FIELD(name, "name"), + DAP_FIELD(path, "path"), + DAP_FIELD(symbolFilePath, "symbolFilePath"), + DAP_FIELD(symbolStatus, "symbolStatus"), + DAP_FIELD(version, "version")); + +Scope::Scope() = default; +Scope::~Scope() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(Scope, + "", + DAP_FIELD(column, "column"), + DAP_FIELD(endColumn, "endColumn"), + DAP_FIELD(endLine, "endLine"), + DAP_FIELD(expensive, "expensive"), + DAP_FIELD(indexedVariables, "indexedVariables"), + DAP_FIELD(line, "line"), + DAP_FIELD(name, "name"), + DAP_FIELD(namedVariables, "namedVariables"), + DAP_FIELD(presentationHint, "presentationHint"), + DAP_FIELD(source, "source"), + DAP_FIELD(variablesReference, + "variablesReference")); + +SourceBreakpoint::SourceBreakpoint() = default; +SourceBreakpoint::~SourceBreakpoint() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(SourceBreakpoint, + "", + DAP_FIELD(column, "column"), + DAP_FIELD(condition, "condition"), + DAP_FIELD(hitCondition, "hitCondition"), + DAP_FIELD(line, "line"), + DAP_FIELD(logMessage, "logMessage")); + +DataBreakpoint::DataBreakpoint() = default; +DataBreakpoint::~DataBreakpoint() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(DataBreakpoint, + "", + DAP_FIELD(accessType, "accessType"), + DAP_FIELD(condition, "condition"), + DAP_FIELD(dataId, "dataId"), + DAP_FIELD(hitCondition, "hitCondition")); + +ExceptionPathSegment::ExceptionPathSegment() = default; +ExceptionPathSegment::~ExceptionPathSegment() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(ExceptionPathSegment, + "", + DAP_FIELD(names, "names"), + DAP_FIELD(negate, "negate")); + +ExceptionOptions::ExceptionOptions() = default; +ExceptionOptions::~ExceptionOptions() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(ExceptionOptions, + "", + DAP_FIELD(breakMode, "breakMode"), + DAP_FIELD(path, "path")); + +FunctionBreakpoint::FunctionBreakpoint() = default; +FunctionBreakpoint::~FunctionBreakpoint() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(FunctionBreakpoint, + "", + DAP_FIELD(condition, "condition"), + DAP_FIELD(hitCondition, "hitCondition"), + DAP_FIELD(name, "name")); + +StackFrame::StackFrame() = default; +StackFrame::~StackFrame() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(StackFrame, + "", + DAP_FIELD(column, "column"), + DAP_FIELD(endColumn, "endColumn"), + DAP_FIELD(endLine, "endLine"), + DAP_FIELD(id, "id"), + DAP_FIELD(instructionPointerReference, + "instructionPointerReference"), + DAP_FIELD(line, "line"), + DAP_FIELD(moduleId, "moduleId"), + DAP_FIELD(name, "name"), + DAP_FIELD(presentationHint, "presentationHint"), + DAP_FIELD(source, "source")); + +StackFrameFormat::StackFrameFormat() = default; +StackFrameFormat::~StackFrameFormat() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(StackFrameFormat, + "", + DAP_FIELD(includeAll, "includeAll"), + DAP_FIELD(line, "line"), + DAP_FIELD(module, "module"), + DAP_FIELD(parameterNames, "parameterNames"), + DAP_FIELD(parameterTypes, "parameterTypes"), + DAP_FIELD(parameterValues, "parameterValues"), + DAP_FIELD(parameters, "parameters")); + +StepInTarget::StepInTarget() = default; +StepInTarget::~StepInTarget() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(StepInTarget, + "", + DAP_FIELD(id, "id"), + DAP_FIELD(label, "label")); + +Thread::Thread() = default; +Thread::~Thread() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(Thread, + "", + DAP_FIELD(id, "id"), + DAP_FIELD(name, "name")); + +Variable::Variable() = default; +Variable::~Variable() = default; +DAP_IMPLEMENT_STRUCT_TYPEINFO(Variable, + "", + DAP_FIELD(evaluateName, "evaluateName"), + DAP_FIELD(indexedVariables, "indexedVariables"), + DAP_FIELD(memoryReference, "memoryReference"), + DAP_FIELD(name, "name"), + DAP_FIELD(namedVariables, "namedVariables"), + DAP_FIELD(presentationHint, "presentationHint"), + DAP_FIELD(type, "type"), + DAP_FIELD(value, "value"), + DAP_FIELD(variablesReference, + "variablesReference")); + +} // namespace dap diff --git a/src/session.cpp b/src/session.cpp new file mode 100644 index 0000000..57c8c9a --- /dev/null +++ b/src/session.cpp @@ -0,0 +1,475 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "content_stream.h" + +#include "dap/any.h" +#include "dap/session.h" + +#include "chan.h" +#include "json_serializer.h" +#include "socket.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +class Impl : public dap::Session { + public: + void onError(const ErrorHandler& handler) override { handlers.put(handler); } + + void registerHandler(const dap::TypeInfo* typeinfo, + const GenericRequestHandler& handler) override { + handlers.put(typeinfo, handler); + } + + void registerHandler(const dap::TypeInfo* typeinfo, + const GenericEventHandler& handler) override { + handlers.put(typeinfo, handler); + } + + void registerHandler(const dap::TypeInfo* typeinfo, + const GenericResponseSentHandler& handler) override { + handlers.put(typeinfo, handler); + } + + void bind(const std::shared_ptr& r, + const std::shared_ptr& w) override { + if (isBound.exchange(true)) { + handlers.error("Session is already bound!"); + return; + } + + reader = dap::ContentReader(r); + writer = dap::ContentWriter(w); + + recvThread = std::thread([this] { + while (reader.isOpen()) { + auto request = reader.read(); + if (request.size() > 0) { + if (auto payload = processMessage(request)) { + inbox.put(std::move(payload)); + } + } + } + }); + + dispatchThread = std::thread([this] { + while (auto payload = inbox.take()) { + payload.value()(); + } + }); + } + + bool send(const dap::TypeInfo* typeinfo, + const void* request, + const GenericResponseHandler& responseHandler) override { + int seq = nextSeq++; + + handlers.put(seq, typeinfo, responseHandler); + + dap::json::Serializer s; + s.field("seq", dap::integer(seq)); + s.field("type", "request"); + s.field("command", typeinfo->name()); + s.field("arguments", [&](dap::Serializer* s) { + return typeinfo->serialize(s, request); + }); + return send(s.dump()); + } + + bool send(const dap::TypeInfo* typeinfo, const void* event) override { + dap::json::Serializer s; + s.field("seq", dap::integer(nextSeq++)); + s.field("type", "event"); + s.field("event", typeinfo->name()); + s.field("body", + [&](dap::Serializer* s) { return typeinfo->serialize(s, event); }); + return send(s.dump()); + } + + ~Impl() { + inbox.close(); + reader.close(); + writer.close(); + if (recvThread.joinable()) { + recvThread.join(); + } + if (dispatchThread.joinable()) { + dispatchThread.join(); + } + } + + private: + using Payload = std::function; + + class EventHandlers { + public: + void put(const ErrorHandler& handler) { + std::unique_lock lock(errorMutex); + errorHandler = handler; + } + + void error(const char* format, ...) { + va_list vararg; + va_start(vararg, format); + std::unique_lock lock(errorMutex); + errorLocked(format, vararg); + va_end(vararg); + } + + std::pair request( + const std::string& name) { + std::unique_lock lock(requestMutex); + auto it = requestMap.find(name); + return (it != requestMap.end()) ? it->second : decltype(it->second){}; + } + + void put(const dap::TypeInfo* typeinfo, + const GenericRequestHandler& handler) { + std::unique_lock lock(requestMutex); + auto added = + requestMap + .emplace(typeinfo->name(), std::make_pair(typeinfo, handler)) + .second; + if (!added) { + errorfLocked("Request handler for '%s' already registered", + typeinfo->name().c_str()); + } + } + + std::pair response(int seq) { + std::unique_lock lock(responseMutex); + auto responseIt = responseMap.find(seq); + if (responseIt == responseMap.end()) { + errorfLocked("Unknown response with sequence %d", seq); + return {}; + } + auto out = std::move(responseIt->second); + responseMap.erase(seq); + return out; + } + + void put(int seq, + const dap::TypeInfo* typeinfo, + const GenericResponseHandler& handler) { + std::unique_lock lock(responseMutex); + auto added = + responseMap.emplace(seq, std::make_pair(typeinfo, handler)).second; + if (!added) { + errorfLocked("Response handler for sequence %d already registered", + seq); + } + } + + std::pair event( + const std::string& name) { + std::unique_lock lock(eventMutex); + auto it = eventMap.find(name); + return (it != eventMap.end()) ? it->second : decltype(it->second){}; + } + + void put(const dap::TypeInfo* typeinfo, + const GenericEventHandler& handler) { + std::unique_lock lock(eventMutex); + auto added = + eventMap.emplace(typeinfo->name(), std::make_pair(typeinfo, handler)) + .second; + if (!added) { + errorfLocked("Event handler for '%s' already registered", + typeinfo->name().c_str()); + } + } + + GenericResponseSentHandler responseSent(const dap::TypeInfo* typeinfo) { + std::unique_lock lock(responseSentMutex); + auto it = responseSentMap.find(typeinfo); + return (it != responseSentMap.end()) ? it->second + : decltype(it->second){}; + } + + void put(const dap::TypeInfo* typeinfo, + const GenericResponseSentHandler& handler) { + std::unique_lock lock(responseSentMutex); + auto added = responseSentMap.emplace(typeinfo, handler).second; + if (!added) { + errorfLocked("Response sent handler for '%s' already registered", + typeinfo->name().c_str()); + } + } + + private: + void errorfLocked(const char* format, ...) { + va_list vararg; + va_start(vararg, format); + errorLocked(format, vararg); + va_end(vararg); + } + + void errorLocked(const char* format, va_list args) { + char buf[2048]; + vsnprintf(buf, sizeof(buf), format, args); + if (errorHandler) { + errorHandler(buf); + } + } + + std::mutex errorMutex; + ErrorHandler errorHandler; + + std::mutex requestMutex; + std::unordered_map> + requestMap; + + std::mutex responseMutex; + std::unordered_map> + responseMap; + + std::mutex eventMutex; + std::unordered_map> + eventMap; + + std::mutex responseSentMutex; + std::unordered_map + responseSentMap; + }; // EventHandlers + + Payload processMessage(const std::string& str) { + auto d = dap::json::Deserializer(str); + dap::string type; + if (!d.field("type", &type)) { + handlers.error("Message missing string 'type' field"); + return {}; + } + + dap::integer sequence = 0; + if (!d.field("seq", &sequence)) { + handlers.error("Message missing number 'seq' field"); + return {}; + } + + if (type == "request") { + return processRequest(&d, sequence); + } else if (type == "event") { + return processEvent(&d); + } else if (type == "response") { + processResponse(&d); + return {}; + } else { + handlers.error("Unknown message type '%s'", type.c_str()); + } + + return {}; + } + + Payload processRequest(dap::json::Deserializer* d, dap::integer sequence) { + dap::string command; + if (!d->field("command", &command)) { + handlers.error("Request missing string 'command' field"); + return {}; + } + + const dap::TypeInfo* typeinfo; + GenericRequestHandler handler; + std::tie(typeinfo, handler) = handlers.request(command); + if (!typeinfo) { + handlers.error("No request handler registered for command '%s'", + command.c_str()); + return {}; + } + + auto data = new uint8_t[typeinfo->size()]; + typeinfo->construct(data); + + if (!d->field("arguments", [&](dap::Deserializer* d) { + return typeinfo->deserialize(d, data); + })) { + handlers.error("Failed to deserialize request"); + typeinfo->destruct(data); + delete[] data; + return {}; + } + + return [=] { + handler(data, + [&](const dap::TypeInfo* typeinfo, const void* data) { + // onSuccess + dap::json::Serializer s; + s.field("seq", dap::integer(nextSeq++)); + s.field("type", "response"); + s.field("request_seq", sequence); + s.field("success", dap::boolean(true)); + s.field("command", command); + s.field("body", [&](dap::Serializer* s) { + return typeinfo->serialize(s, data); + }); + send(s.dump()); + + if (auto handler = handlers.responseSent(typeinfo)) { + handler(data, nullptr); + } + }, + [&](const dap::TypeInfo* typeinfo, const dap::Error& error) { + // onError + dap::json::Serializer s; + s.field("seq", dap::integer(nextSeq++)); + s.field("type", "response"); + s.field("request_seq", sequence); + s.field("success", dap::boolean(false)); + s.field("command", command); + s.field("message", error.message); + send(s.dump()); + + if (auto handler = handlers.responseSent(typeinfo)) { + handler(nullptr, &error); + } + }); + typeinfo->destruct(data); + delete[] data; + }; + } + + Payload processEvent(dap::json::Deserializer* d) { + dap::string event; + if (!d->field("event", &event)) { + handlers.error("Event missing string 'event' field"); + return {}; + } + + const dap::TypeInfo* typeinfo; + GenericEventHandler handler; + std::tie(typeinfo, handler) = handlers.event(event); + if (!typeinfo) { + handlers.error("No event handler registered for event '%s'", + event.c_str()); + return {}; + } + + auto data = new uint8_t[typeinfo->size()]; + typeinfo->construct(data); + + if (!d->field("body", [&](dap::Deserializer* d) { + return typeinfo->deserialize(d, data); + })) { + handlers.error("Failed to deserialize event '%s' body", event.c_str()); + typeinfo->destruct(data); + delete[] data; + return {}; + } + + return [=] { + handler(data); + typeinfo->destruct(data); + delete[] data; + }; + } + + void processResponse(const dap::Deserializer* d) { + dap::integer requestSeq = 0; + if (!d->field("request_seq", &requestSeq)) { + handlers.error("Response missing int 'request_seq' field"); + return; + } + + const dap::TypeInfo* typeinfo; + GenericResponseHandler handler; + std::tie(typeinfo, handler) = handlers.response(requestSeq); + if (!typeinfo) { + handlers.error("Unknown response with sequence %d", requestSeq); + return; + } + + dap::boolean success = false; + if (!d->field("success", &success)) { + handlers.error("Response missing int 'success' field"); + return; + } + + if (success) { + auto data = std::unique_ptr(new uint8_t[typeinfo->size()]); + typeinfo->construct(data.get()); + + if (!d->field("body", [&](const dap::Deserializer* d) { + return typeinfo->deserialize(d, data.get()); + })) { + handlers.error("Failed to deserialize request"); + return; + } + + handler(data.get(), nullptr); + typeinfo->destruct(data.get()); + } else { + std::string message; + if (!d->field("message", &message)) { + handlers.error("Failed to deserialize message"); + return; + } + auto error = dap::Error("%s", message.c_str()); + handler(nullptr, &error); + } + } + + bool send(const std::string& s) { + std::unique_lock lock(sendMutex); + if (!writer.isOpen()) { + handlers.error("Send failed as the writer is closed"); + return false; + } + return writer.write(s); + } + + std::atomic isBound = {false}; + dap::ContentReader reader; + dap::ContentWriter writer; + + std::atomic shutdown = {false}; + EventHandlers handlers; + std::thread recvThread; + std::thread dispatchThread; + dap::Chan inbox; + std::atomic nextSeq = {1}; + std::mutex sendMutex; +}; + +} // anonymous namespace + +namespace dap { + +Error::Error(const std::string& message) : message(message) {} + +Error::Error(const char* msg, ...) { + char buf[2048]; + va_list vararg; + va_start(vararg, msg); + vsnprintf(buf, sizeof(buf), msg, vararg); + va_end(vararg); + message = buf; +} + +std::unique_ptr Session::create() { + return std::unique_ptr(new Impl()); +} + +} // namespace dap diff --git a/src/session_test.cpp b/src/session_test.cpp new file mode 100644 index 0000000..247c8a4 --- /dev/null +++ b/src/session_test.cpp @@ -0,0 +1,386 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dap/session.h" +#include "dap/io.h" +#include "dap/protocol.h" + +#include "chan.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include +#include +#include + +namespace dap { + +struct TestResponse : public Response { + boolean b; + integer i; + number n; + array a; + object o; + string s; + optional o1; + optional o2; +}; + +DAP_STRUCT_TYPEINFO(TestResponse, + "test-response", + DAP_FIELD(b, "b"), + DAP_FIELD(i, "i"), + DAP_FIELD(n, "n"), + DAP_FIELD(a, "a"), + DAP_FIELD(o, "o"), + DAP_FIELD(s, "s"), + DAP_FIELD(o1, "o1"), + DAP_FIELD(o2, "o2")); + +struct TestRequest : public Request { + using Response = TestResponse; + + boolean b; + integer i; + number n; + array a; + object o; + string s; + optional o1; + optional o2; +}; + +DAP_STRUCT_TYPEINFO(TestRequest, + "test-request", + DAP_FIELD(b, "b"), + DAP_FIELD(i, "i"), + DAP_FIELD(n, "n"), + DAP_FIELD(a, "a"), + DAP_FIELD(o, "o"), + DAP_FIELD(s, "s"), + DAP_FIELD(o1, "o1"), + DAP_FIELD(o2, "o2")); + +struct TestEvent : public Event { + boolean b; + integer i; + number n; + array a; + object o; + string s; + optional o1; + optional o2; +}; + +DAP_STRUCT_TYPEINFO(TestEvent, + "test-event", + DAP_FIELD(b, "b"), + DAP_FIELD(i, "i"), + DAP_FIELD(n, "n"), + DAP_FIELD(a, "a"), + DAP_FIELD(o, "o"), + DAP_FIELD(s, "s"), + DAP_FIELD(o1, "o1"), + DAP_FIELD(o2, "o2")); + +}; // namespace dap + +namespace { + +dap::TestRequest createRequest() { + dap::TestRequest request; + request.b = false; + request.i = 72; + request.n = 9.87; + request.a = {2, 5, 7, 8}; + request.o = { + std::make_pair("a", dap::integer(1)), + std::make_pair("b", dap::number(2)), + std::make_pair("c", dap::string("3")), + }; + request.s = "request"; + request.o2 = 42; + return request; +} + +dap::TestResponse createResponse() { + dap::TestResponse response; + response.b = true; + response.i = 99; + response.n = 123.456; + response.a = {5, 4, 3, 2, 1}; + response.o = { + std::make_pair("one", dap::integer(1)), + std::make_pair("two", dap::number(2)), + std::make_pair("three", dap::string("3")), + }; + response.s = "ROGER"; + response.o1 = 50; + return response; +} + +dap::TestEvent createEvent() { + dap::TestEvent event; + event.b = false; + event.i = 72; + event.n = 9.87; + event.a = {2, 5, 7, 8}; + event.o = { + std::make_pair("a", dap::integer(1)), + std::make_pair("b", dap::number(2)), + std::make_pair("c", dap::string("3")), + }; + event.s = "event"; + event.o2 = 42; + return event; +} + +} // anonymous namespace + +class SessionTest : public testing::Test { + public: + void bind() { + auto client2server = dap::pipe(); + auto server2client = dap::pipe(); + client->bind(server2client, client2server); + server->bind(client2server, server2client); + } + + std::unique_ptr client = dap::Session::create(); + std::unique_ptr server = dap::Session::create(); +}; + +TEST_F(SessionTest, Request) { + dap::TestRequest received; + server->registerHandler([&](const dap::TestRequest& req) { + received = req; + return createResponse(); + }); + + bind(); + + auto request = createRequest(); + client->send(request).get(); + + // Check request was received correctly. + ASSERT_EQ(received.b, request.b); + ASSERT_EQ(received.i, request.i); + ASSERT_EQ(received.n, request.n); + ASSERT_EQ(received.a, request.a); + ASSERT_EQ(received.o.size(), 3); + ASSERT_EQ(received.o["a"].get(), + request.o["a"].get()); + ASSERT_EQ(received.o["b"].get(), + request.o["b"].get()); + ASSERT_EQ(received.o["c"].get(), + request.o["c"].get()); + ASSERT_EQ(received.s, request.s); + ASSERT_EQ(received.o1, request.o1); + ASSERT_EQ(received.o2, request.o2); +} + +TEST_F(SessionTest, RequestResponseSuccess) { + server->registerHandler( + [&](const dap::TestRequest&) { return createResponse(); }); + + bind(); + + auto request = createRequest(); + auto response = client->send(request); + + auto got = response.get(); + + // Check response was received correctly. + ASSERT_EQ(got.error, false); + ASSERT_EQ(got.response.b, dap::boolean(true)); + ASSERT_EQ(got.response.i, dap::integer(99)); + ASSERT_EQ(got.response.n, dap::number(123.456)); + ASSERT_EQ(got.response.a, dap::array({5, 4, 3, 2, 1})); + ASSERT_EQ(got.response.o.size(), 3); + ASSERT_EQ(got.response.o["one"].get(), dap::integer(1)); + ASSERT_EQ(got.response.o["two"].get(), dap::number(2)); + ASSERT_EQ(got.response.o["three"].get(), dap::string("3")); + ASSERT_EQ(got.response.s, "ROGER"); + ASSERT_EQ(got.response.o1, dap::optional(50)); + ASSERT_FALSE(got.response.o2.has_value()); +} + +TEST_F(SessionTest, RequestResponseOrError) { + server->registerHandler( + [&](const dap::TestRequest&) -> dap::ResponseOrError { + return dap::Error("Oh noes!"); + }); + + bind(); + + auto response = client->send(createRequest()); + + auto got = response.get(); + + // Check response was received correctly. + ASSERT_EQ(got.error, true); + ASSERT_EQ(got.error.message, "Oh noes!"); +} + +TEST_F(SessionTest, RequestResponseError) { + server->registerHandler( + [&](const dap::TestRequest&) { return dap::Error("Oh noes!"); }); + + bind(); + + auto response = client->send(createRequest()); + + auto got = response.get(); + + // Check response was received correctly. + ASSERT_EQ(got.error, true); + ASSERT_EQ(got.error.message, "Oh noes!"); +} + +TEST_F(SessionTest, ResponseSentHandlerSuccess) { + const auto response = createResponse(); + + dap::Chan> chan; + server->registerHandler([&](const dap::TestRequest&) { return response; }); + server->registerSentHandler( + [&](const dap::ResponseOrError r) { chan.put(r); }); + + bind(); + + client->send(createRequest()); + + auto got = chan.take().value(); + ASSERT_EQ(got.error, false); + ASSERT_EQ(got.response.b, dap::boolean(true)); + ASSERT_EQ(got.response.i, dap::integer(99)); + ASSERT_EQ(got.response.n, dap::number(123.456)); + ASSERT_EQ(got.response.a, dap::array({5, 4, 3, 2, 1})); + ASSERT_EQ(got.response.o.size(), 3); + ASSERT_EQ(got.response.o["one"].get(), dap::integer(1)); + ASSERT_EQ(got.response.o["two"].get(), dap::number(2)); + ASSERT_EQ(got.response.o["three"].get(), dap::string("3")); + ASSERT_EQ(got.response.s, "ROGER"); + ASSERT_EQ(got.response.o1, dap::optional(50)); + ASSERT_FALSE(got.response.o2.has_value()); +} + +TEST_F(SessionTest, ResponseSentHandlerError) { + dap::Chan> chan; + server->registerHandler( + [&](const dap::TestRequest&) { return dap::Error("Oh noes!"); }); + server->registerSentHandler( + [&](const dap::ResponseOrError r) { chan.put(r); }); + + bind(); + + client->send(createRequest()); + + auto got = chan.take().value(); + ASSERT_EQ(got.error, true); + ASSERT_EQ(got.error.message, "Oh noes!"); +} + +TEST_F(SessionTest, Event) { + dap::Chan received; + server->registerHandler([&](const dap::TestEvent& e) { received.put(e); }); + + bind(); + + auto event = createEvent(); + client->send(event); + + // Check event was received correctly. + auto got = received.take().value(); + + ASSERT_EQ(got.b, event.b); + ASSERT_EQ(got.i, event.i); + ASSERT_EQ(got.n, event.n); + ASSERT_EQ(got.a, event.a); + ASSERT_EQ(got.o.size(), 3); + ASSERT_EQ(got.o["a"].get(), event.o["a"].get()); + ASSERT_EQ(got.o["b"].get(), event.o["b"].get()); + ASSERT_EQ(got.o["c"].get(), event.o["c"].get()); + ASSERT_EQ(got.s, event.s); + ASSERT_EQ(got.o1, event.o1); + ASSERT_EQ(got.o2, event.o2); +} + +TEST_F(SessionTest, RegisterHandlerFunction) { + struct S { + static dap::TestResponse requestA(const dap::TestRequest&) { return {}; } + static dap::Error requestB(const dap::TestRequest&) { return {}; } + static dap::ResponseOrError requestC( + const dap::TestRequest&) { + return dap::Error(); + } + static void event(const dap::TestEvent&) {} + static void sent(const dap::ResponseOrError&) {} + }; + client->registerHandler(&S::requestA); + client->registerHandler(&S::requestB); + client->registerHandler(&S::requestC); + client->registerHandler(&S::event); + client->registerSentHandler(&S::sent); +} + +TEST_F(SessionTest, SendRequestNoBind) { + bool errored = false; + client->onError([&](const std::string&) { errored = true; }); + auto res = client->send(createRequest()).get(); + ASSERT_TRUE(errored); + ASSERT_TRUE(res.error); +} + +TEST_F(SessionTest, SendEventNoBind) { + bool errored = false; + client->onError([&](const std::string&) { errored = true; }); + client->send(createEvent()); + ASSERT_TRUE(errored); +} + +TEST_F(SessionTest, Concurrency) { + std::atomic numEventsHandled = {0}; + std::atomic done = {false}; + + server->registerHandler( + [](const dap::TestRequest&) { return dap::TestResponse(); }); + + server->registerHandler([&](const dap::TestEvent&) { + if (numEventsHandled++ > 10000) { + done = true; + } + }); + + bind(); + + constexpr int numThreads = 32; + std::array threads; + + for (int i = 0; i < numThreads; i++) { + threads[i] = std::thread([&] { + while (!done) { + client->send(createEvent()); + client->send(createRequest()); + } + }); + } + + for (int i = 0; i < numThreads; i++) { + threads[i].join(); + } + + client.reset(); + server.reset(); +} diff --git a/src/socket.cpp b/src/socket.cpp new file mode 100644 index 0000000..fe564d2 --- /dev/null +++ b/src/socket.cpp @@ -0,0 +1,204 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "socket.h" + +#if defined(_WIN32) +#include +#include +#else +#include +#include +#include +#include +#include +#endif + +#if defined(_WIN32) +#include +namespace { +std::atomic wsaInitCount = {0}; +} // anonymous namespace +#else +namespace { +using SOCKET = int; +} // anonymous namespace +#endif + +namespace { +constexpr SOCKET InvalidSocket = static_cast(-1); +} // anonymous namespace + +class dap::Socket::Shared : public dap::ReaderWriter { + public: + static void init() { +#if defined(_WIN32) + if (wsaInitCount++ == 0) { + WSADATA winsockData; + (void)WSAStartup(MAKEWORD(2, 2), &winsockData); + } +#endif + } + + static void term() { +#if defined(_WIN32) + if (--wsaInitCount == 0) { + WSACleanup(); + } +#endif + } + + static std::shared_ptr create(const char* address, const char* port) { + init(); + + addrinfo hints = {}; + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + hints.ai_flags = AI_PASSIVE; + + addrinfo* info = nullptr; + getaddrinfo(address, port, &hints, &info); + + if (info) { + auto socket = + ::socket(info->ai_family, info->ai_socktype, info->ai_protocol); + return std::make_shared(*info, socket); + } + + term(); + return nullptr; + } + + Shared(SOCKET socket) : info({}), sock(socket) {} + Shared(const addrinfo& info, SOCKET socket) : info(info), sock(socket) {} + + ~Shared() { + close(); + term(); + } + + SOCKET socket() { return sock.load(); } + + // dap::ReaderWriter compliance + bool isOpen() { + SOCKET s = socket(); + if (s == InvalidSocket) { + return false; + } + + char error = 0; + socklen_t len = sizeof(error); + getsockopt(s, SOL_SOCKET, SO_ERROR, &error, &len); + if (error != 0) { + sock.compare_exchange_weak(s, InvalidSocket); + return false; + } + + return true; + } + + void close() { + SOCKET s = sock.exchange(InvalidSocket); + if (s != InvalidSocket) { +#if defined(_WIN32) + closesocket(s); +#else + ::shutdown(s, SHUT_RDWR); + ::close(s); +#endif + } + } + + size_t read(void* buffer, size_t bytes) { + SOCKET s = socket(); + if (s == InvalidSocket) { + return 0; + } + return recv(s, reinterpret_cast(buffer), static_cast(bytes), 0); + } + + bool write(const void* buffer, size_t bytes) { + SOCKET s = socket(); + if (s == InvalidSocket) { + return false; + } + if (bytes == 0) { + return true; + } + return ::send(s, reinterpret_cast(buffer), + static_cast(bytes), 0) > 0; + } + + const addrinfo info; + + private: + std::atomic sock = {InvalidSocket}; +}; + +namespace dap { + +Socket::Socket(const char* address, const char* port) + : shared(Shared::create(address, port)) { + if (!shared) { + return; + } + auto socket = shared->socket(); + + if (bind(socket, shared->info.ai_addr, (int)shared->info.ai_addrlen) != 0) { + shared.reset(); + return; + } + + if (listen(socket, 1) != 0) { + shared.reset(); + return; + } +} + +std::shared_ptr Socket::accept() const { + if (shared) { + SOCKET socket = shared->socket(); + if (socket != InvalidSocket) { + return std::make_shared(::accept(socket, 0, 0)); + } + } + + return {}; +} + +bool Socket::isOpen() const { + if (shared) { + return shared->isOpen(); + } + return false; +} + +void Socket::close() const { + if (shared) { + shared->close(); + } +} + +std::shared_ptr Socket::connect(const char* address, + const char* port) { + auto shared = Shared::create(address, port); + if (::connect(shared->socket(), shared->info.ai_addr, + (int)shared->info.ai_addrlen) == 0) { + return shared; + } + return {}; +} + +} // namespace dap diff --git a/src/socket.h b/src/socket.h new file mode 100644 index 0000000..ea722c6 --- /dev/null +++ b/src/socket.h @@ -0,0 +1,43 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef dap_socket_h +#define dap_socket_h + +#include "dap/io.h" + +#include +#include + +namespace dap { + +class Socket { + public: + class Shared; + + static std::shared_ptr connect(const char* address, + const char* port); + + Socket(const char* address, const char* port); + bool isOpen() const; + std::shared_ptr accept() const; + void close() const; + + private: + std::shared_ptr shared; +}; + +} // namespace dap + +#endif // dap_socket_h diff --git a/src/string_buffer.h b/src/string_buffer.h new file mode 100644 index 0000000..cdd6c41 --- /dev/null +++ b/src/string_buffer.h @@ -0,0 +1,85 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef dap_string_buffer_h +#define dap_string_buffer_h + +#include "dap/io.h" + +#include // std::min +#include // memcpy +#include // std::unique_ptr +#include + +namespace dap { + +class StringBuffer : public virtual Reader, public virtual Writer { + public: + static inline std::unique_ptr create(); + + inline bool write(const std::string& s); + inline std::string string() const; + + // Reader / Writer compilance + inline bool isOpen() override; + inline void close() override; + inline size_t read(void* buffer, size_t bytes) override; + inline bool write(const void* buffer, size_t bytes) override; + + private: + std::string str; + bool closed = false; +}; + +bool StringBuffer::isOpen() { + return !closed; +} +void StringBuffer::close() { + closed = true; +} + +std::unique_ptr StringBuffer::create() { + return std::unique_ptr(new StringBuffer()); +} + +bool StringBuffer::write(const std::string& s) { + return write(s.data(), s.size()); +} + +std::string StringBuffer::string() const { + return str; +} + +size_t StringBuffer::read(void* buffer, size_t bytes) { + if (closed || bytes == 0 || str.size() == 0) { + return 0; + } + auto len = std::min(bytes, str.size()); + memcpy(buffer, str.data(), len); + str = std::string(str.begin() + len, str.end()); + return len; +} + +bool StringBuffer::write(const void* buffer, size_t bytes) { + if (closed) { + return false; + } + auto chars = reinterpret_cast(buffer); + str.append(chars, chars + bytes); + return true; +} + +} // namespace dap + +#endif // dap_string_buffer_h diff --git a/src/typeof.cpp b/src/typeof.cpp new file mode 100644 index 0000000..9987419 --- /dev/null +++ b/src/typeof.cpp @@ -0,0 +1,68 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dap/typeof.h" + +namespace dap { + +const TypeInfo* TypeOf::type() { + static BasicTypeInfo typeinfo("boolean"); + return &typeinfo; +} + +const TypeInfo* TypeOf::type() { + static BasicTypeInfo typeinfo("string"); + return &typeinfo; +} + +const TypeInfo* TypeOf::type() { + static BasicTypeInfo typeinfo("integer"); + return &typeinfo; +} + +const TypeInfo* TypeOf::type() { + static BasicTypeInfo typeinfo("number"); + return &typeinfo; +} + +const TypeInfo* TypeOf::type() { + static BasicTypeInfo typeinfo("object"); + return &typeinfo; +} + +const TypeInfo* TypeOf::type() { + static BasicTypeInfo typeinfo("any"); + return &typeinfo; +} + +const TypeInfo* TypeOf::type() { + struct TI : public TypeInfo { + inline std::string name() const { return "null"; } + inline size_t size() const { return sizeof(null); } + inline size_t alignment() const { return alignof(null); } + inline void construct(void* ptr) const { new (ptr) null(); } + inline void copyConstruct(void* dst, const void* src) const { + new (dst) null(*reinterpret_cast(src)); + } + inline void destruct(void* ptr) const { + reinterpret_cast(ptr)->~null(); + } + inline bool deserialize(const Deserializer*, void*) const { return true; } + inline bool serialize(Serializer*, const void*) const { return true; } + }; + static TI typeinfo; + return &typeinfo; +} // namespace dap + +} // namespace dap diff --git a/src/variant_test.cpp b/src/variant_test.cpp new file mode 100644 index 0000000..5a1f4eb --- /dev/null +++ b/src/variant_test.cpp @@ -0,0 +1,94 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dap/variant.h" +#include "dap/typeof.h" +#include "dap/types.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace dap { + +struct VariantTestObject { + dap::integer i; + dap::number n; +}; + +DAP_STRUCT_TYPEINFO(VariantTestObject, + "VariantTestObject", + DAP_FIELD(i, "i"), + DAP_FIELD(n, "n")); + +} // namespace dap + +TEST(Variant, EmptyConstruct) { + dap::variant variant; + ASSERT_TRUE(variant.is()); + ASSERT_FALSE(variant.is()); + ASSERT_FALSE(variant.is()); +} + +TEST(Variant, Boolean) { + dap::variant variant( + dap::boolean(true)); + ASSERT_TRUE(variant.is()); + ASSERT_EQ(variant.get(), dap::boolean(true)); +} + +TEST(Variant, Integer) { + dap::variant variant( + dap::integer(10)); + ASSERT_TRUE(variant.is()); + ASSERT_EQ(variant.get(), dap::integer(10)); +} + +TEST(Variant, TestObject) { + dap::variant variant( + dap::VariantTestObject{5, 3.0}); + ASSERT_TRUE(variant.is()); + ASSERT_EQ(variant.get().i, 5); + ASSERT_EQ(variant.get().n, 3.0); +} + +TEST(Variant, Assign) { + dap::variant variant( + dap::integer(10)); + variant = dap::integer(10); + ASSERT_TRUE(variant.is()); + ASSERT_FALSE(variant.is()); + ASSERT_FALSE(variant.is()); + ASSERT_EQ(variant.get(), dap::integer(10)); + variant = dap::boolean(true); + ASSERT_FALSE(variant.is()); + ASSERT_TRUE(variant.is()); + ASSERT_FALSE(variant.is()); + ASSERT_EQ(variant.get(), dap::boolean(true)); + variant = dap::VariantTestObject{5, 3.0}; + ASSERT_FALSE(variant.is()); + ASSERT_FALSE(variant.is()); + ASSERT_TRUE(variant.is()); + ASSERT_EQ(variant.get().i, 5); + ASSERT_EQ(variant.get().n, 3.0); +} + +TEST(Variant, Accepts) { + using variant = + dap::variant; + ASSERT_TRUE(variant::accepts()); + ASSERT_TRUE(variant::accepts()); + ASSERT_TRUE(variant::accepts()); + ASSERT_FALSE(variant::accepts()); + ASSERT_FALSE(variant::accepts()); +} diff --git a/third_party/googletest b/third_party/googletest new file mode 160000 index 0000000..0a03480 --- /dev/null +++ b/third_party/googletest @@ -0,0 +1 @@ +Subproject commit 0a03480824b4fc7883255dbd2fd8940c9f81e22e diff --git a/third_party/json b/third_party/json new file mode 160000 index 0000000..f272ad5 --- /dev/null +++ b/third_party/json @@ -0,0 +1 @@ +Subproject commit f272ad533d32a40a3b2154a76f1ae9a45eacd6d3 diff --git a/tools/protocol_gen/protocol_gen.go b/tools/protocol_gen/protocol_gen.go new file mode 100644 index 0000000..d036418 --- /dev/null +++ b/tools/protocol_gen/protocol_gen.go @@ -0,0 +1,653 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// protocol_gen (re)generates the cppdap .h and .cpp files that describe the +// DAP protocol. +package main + +import ( + "bytes" + "encoding/json" + "flag" + "fmt" + "io" + "io/ioutil" + "net/http" + "os" + "os/exec" + "path" + "reflect" + "runtime" + "sort" + "strings" +) + +var ( + cache = flag.String("cache", "", "File cache of the .json schema") +) + +const ( + jsonURL = "https://raw.githubusercontent.com/microsoft/vscode-debugadapter-node/master/debugProtocol.json" + + commonPrologue = `// Copyright 2019 Google LLC + // + // Licensed under the Apache License, Version 2.0 (the "License"); + // you may not use this file except in compliance with the License. + // You may obtain a copy of the License at + // + // https://www.apache.org/licenses/LICENSE-2.0 + // + // Unless required by applicable law or agreed to in writing, software + // distributed under the License is distributed on an "AS IS" BASIS, + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + // See the License for the specific language governing permissions and + // limitations under the License. + + // Generated with protocol_gen.go -- do not edit this file. + // go run scripts/protocol_gen/protocol_gen.go +` + + headerPrologue = commonPrologue + ` +#ifndef dap_protocol_h +#define dap_protocol_h + +#include "optional.h" +#include "typeinfo.h" +#include "typeof.h" +#include "variant.h" + +#include +#include +#include + +namespace dap { + +struct Request {}; +struct Response {}; +struct Event {}; + +` + + headerEpilogue = `} // namespace dap + +#endif // dap_protocol_h +` + + cppPrologue = commonPrologue + ` + +#include "dap/protocol.h" + +namespace dap { + +` + + cppEpilogue = `} // namespace dap +` +) + +func main() { + flag.Parse() + if err := run(); err != nil { + fmt.Fprintf(os.Stderr, "%v\n", err) + os.Exit(1) + } +} + +type root struct { + Schema string `json:"$schema"` + Title string `json:"title"` + Description string `json:"description"` + Ty string `json:"type"` + Definitions map[string]definition `json:"definitions"` +} + +func (r *root) definitions() []namedDefinition { + sortedDefinitions := make([]namedDefinition, 0, len(r.Definitions)) + for name, def := range r.Definitions { + sortedDefinitions = append(sortedDefinitions, namedDefinition{name, def}) + } + sort.Slice(sortedDefinitions, func(i, j int) bool { return sortedDefinitions[i].name < sortedDefinitions[j].name }) + return sortedDefinitions +} + +func (r *root) getRef(ref string) (namedDefinition, error) { + if !strings.HasPrefix(ref, "#/definitions/") { + return namedDefinition{}, fmt.Errorf("Unknown $ref '%s'", ref) + } + name := strings.TrimPrefix(ref, "#/definitions/") + def, ok := r.Definitions[name] + if !ok { + return namedDefinition{}, fmt.Errorf("Unknown $ref '%s'", ref) + } + return namedDefinition{name, def}, nil +} + +type namedDefinition struct { + name string + def definition +} + +type definition struct { + Ty string `json:"type"` + Title string `json:"title"` + Description string `json:"description"` + Properties properties `json:"properties"` + Required []string `json:"required"` + AllOf []definition `json:"allOf"` + Ref string `json:"$ref"` +} + +type properties map[string]property + +func (p *properties) foreach(cb func(string, property) error) error { + type namedProperty struct { + name string + property property + } + sorted := make([]namedProperty, 0, len(*p)) + for name, property := range *p { + sorted = append(sorted, namedProperty{name, property}) + } + sort.Slice(sorted, func(i, j int) bool { return sorted[i].name < sorted[j].name }) + for _, entry := range sorted { + if err := cb(entry.name, entry.property); err != nil { + return err + } + } + return nil +} + +type property struct { + typed + Description string `json:"description"` +} + +func (p *property) properties(r *root) (properties, []string, error) { + if p.Ref == "" { + return p.Properties, p.Required, nil + } + + d, err := r.getRef(p.Ref) + if err != nil { + return nil, nil, err + } + return d.def.Properties, d.def.Required, nil +} + +type typed struct { + Ty interface{} `json:"type"` + Items *typed `json:"items"` + Ref string `json:"$ref"` + Properties properties `json:"properties"` + Required []string `json:"required"` + ClosedEnum []string `json:"enum"` + OpenEnum []string `json:"_enum"` +} + +func (t typed) typename(r *root, refs *[]string) (string, error) { + if t.Ref != "" { + d, err := r.getRef(t.Ref) + if err != nil { + return "", err + } + *refs = append(*refs, d.name) + return d.name, nil + } + + if t.Ty == nil { + return "", fmt.Errorf("No type specified") + } + + var typeof func(v reflect.Value) (string, error) + typeof = func(v reflect.Value) (string, error) { + if v.Kind() == reflect.Interface { + v = v.Elem() + } + switch v.Kind() { + case reflect.String: + ty := v.Interface().(string) + switch ty { + case "boolean", "string", "integer", "number", "object", "null": + return ty, nil + case "array": + if t.Items != nil { + el, err := t.Items.typename(r, refs) + if err != nil { + return "", err + } + return fmt.Sprintf("array<%s>", el), nil + } + return "array", nil + default: + return "", fmt.Errorf("Unhandled property type '%v'", ty) + } + + case reflect.Slice, reflect.Array: + ty := "variant<" + for i := 0; i < v.Len(); i++ { + if i > 0 { + ty += ", " + } + el, err := typeof(v.Index(i)) + if err != nil { + return "", err + } + ty += el + } + ty += ">" + return ty, nil + } + + return "", fmt.Errorf("Unsupported type '%v' kind: %v", v.Interface(), v.Kind()) + } + + return typeof(reflect.ValueOf(t.Ty)) +} + +type cppField struct { + desc string + ty string + name string + defaultValue string + optional bool +} + +type cppStruct struct { + desc string + name string + typename string + base string + fields []cppField + deps []string + emit bool + typedefs []cppTypedef + ty structType +} + +type cppTypedef struct { + from string + to string +} + +func sanitize(s string) string { + s = strings.Trim(s, "_") + switch s { + case "default": + return "def" + default: + return s + } +} + +func (s *cppStruct) writeHeader(w io.Writer) { + if s.desc != "" { + io.WriteString(w, "// ") + io.WriteString(w, strings.ReplaceAll(s.desc, "\n", "\n// ")) + io.WriteString(w, "\n") + } + io.WriteString(w, "struct ") + io.WriteString(w, s.name) + if s.base != "" { + io.WriteString(w, " : public ") + io.WriteString(w, s.base) + } + io.WriteString(w, " {") + + // typedefs + for _, t := range s.typedefs { + io.WriteString(w, "\n using ") + io.WriteString(w, t.from) + io.WriteString(w, " = ") + io.WriteString(w, t.to) + io.WriteString(w, ";") + } + + // constructor + io.WriteString(w, "\n\n ") + io.WriteString(w, s.name) + io.WriteString(w, "();") + + // destructor + io.WriteString(w, "\n ~") + io.WriteString(w, s.name) + io.WriteString(w, "();\n") + + for _, f := range s.fields { + if f.desc != "" { + io.WriteString(w, "\n // ") + io.WriteString(w, strings.ReplaceAll(f.desc, "\n", "\n // ")) + } + io.WriteString(w, "\n ") + if f.optional { + io.WriteString(w, "optional<") + io.WriteString(w, f.ty) + io.WriteString(w, ">") + } else { + io.WriteString(w, f.ty) + } + io.WriteString(w, " ") + io.WriteString(w, sanitize(f.name)) + if !f.optional && f.defaultValue != "" { + io.WriteString(w, " = ") + io.WriteString(w, f.defaultValue) + } + io.WriteString(w, ";") + } + + io.WriteString(w, "\n};\n\n") + + io.WriteString(w, "DAP_DECLARE_STRUCT_TYPEINFO(") + io.WriteString(w, s.name) + io.WriteString(w, ");\n\n") +} + +func (s *cppStruct) writeCPP(w io.Writer) { + // constructor + io.WriteString(w, s.name) + io.WriteString(w, "::") + io.WriteString(w, s.name) + io.WriteString(w, "() = default;\n") + + // destructor + io.WriteString(w, s.name) + io.WriteString(w, "::~") + io.WriteString(w, s.name) + io.WriteString(w, "() = default;\n") + + // typeinfo + io.WriteString(w, "DAP_IMPLEMENT_STRUCT_TYPEINFO(") + io.WriteString(w, s.name) + io.WriteString(w, ",\n \"") + io.WriteString(w, s.typename) + io.WriteString(w, "\"") + for _, f := range s.fields { + io.WriteString(w, ",\n ") + io.WriteString(w, "DAP_FIELD(") + io.WriteString(w, sanitize(f.name)) + io.WriteString(w, ", \"") + io.WriteString(w, f.name) + io.WriteString(w, "\")") + } + io.WriteString(w, ");\n\n") +} + +func buildStructs(r *root) ([]*cppStruct, error) { + ignore := map[string]bool{ + // These are handled internally. + "ProtocolMessage": true, + "Request": true, + "Event": true, + "Response": true, + } + + out := []*cppStruct{} + for _, entry := range r.definitions() { + defName, def := entry.name, entry.def + if ignore[defName] { + continue + } + + base := "" + if len(def.AllOf) > 1 && def.AllOf[0].Ref != "" { + ref, err := r.getRef(def.AllOf[0].Ref) + if err != nil { + return nil, err + } + base = ref.name + if len(def.AllOf) > 2 { + return nil, fmt.Errorf("Cannot handle allOf with more than 2 entries") + } + def = def.AllOf[1] + } + + s := cppStruct{ + desc: def.Description, + name: defName, + base: base, + } + + var props properties + var required []string + var err error + switch base { + case "Request": + if arguments, ok := def.Properties["arguments"]; ok { + props, required, err = arguments.properties(r) + } + if command, ok := def.Properties["command"]; ok { + s.typename = command.ClosedEnum[0] + } + response := strings.TrimSuffix(s.name, "Request") + "Response" + s.deps = append(s.deps, response) + s.typedefs = append(s.typedefs, cppTypedef{"Response", response}) + s.emit = true + s.ty = request + case "Response": + if body, ok := def.Properties["body"]; ok { + props, required, err = body.properties(r) + } + s.emit = true + s.ty = response + case "Event": + if body, ok := def.Properties["body"]; ok { + props, required, err = body.properties(r) + } + if command, ok := def.Properties["event"]; ok { + s.typename = command.ClosedEnum[0] + } + s.emit = true + s.ty = event + default: + props = def.Properties + required = def.Required + s.ty = types + } + if err != nil { + return nil, err + } + + if err = props.foreach(func(propName string, property property) error { + ty, err := property.typename(r, &s.deps) + if err != nil { + return fmt.Errorf("While processing %v.%v: %v", defName, propName, err) + } + + optional := true + for _, r := range required { + if propName == r { + optional = false + } + } + + desc := property.Description + defaultValue := "" + + if len(property.ClosedEnum) > 0 { + desc += "\n\nMust be one of the following enumeration values:\n" + for i, enum := range property.ClosedEnum { + if i > 0 { + desc += ", " + } + desc += "'" + enum + "'" + } + defaultValue = `"` + property.ClosedEnum[0] + `"` + } + + if len(property.OpenEnum) > 0 { + desc += "\n\nMay be one of the following enumeration values:\n" + for i, enum := range property.OpenEnum { + if i > 0 { + desc += ", " + } + desc += "'" + enum + "'" + } + } + + s.fields = append(s.fields, cppField{ + desc: desc, + defaultValue: defaultValue, + ty: ty, + name: propName, + optional: optional, + }) + + return nil + }); err != nil { + return nil, err + } + + out = append(out, &s) + } + + return out, nil +} + +type structType string + +const ( + request = structType("request") + response = structType("response") + event = structType("event") + types = structType("types") +) + +type cppFilePaths map[structType]string + +type cppFiles map[structType]*os.File + +func run() error { + data, err := loadJSONFile() + if err != nil { + return err + } + r := root{} + d := json.NewDecoder(bytes.NewReader(data)) + if err := d.Decode(&r); err != nil { + return err + } + + hPath, cppPaths := outputPaths() + if err := emitFiles(&r, hPath, cppPaths); err != nil { + return err + } + + if clangfmt, err := exec.LookPath("clang-format"); err == nil { + if err := exec.Command(clangfmt, "-i", hPath).Run(); err != nil { + return err + } + for _, p := range cppPaths { + if err := exec.Command(clangfmt, "-i", p).Run(); err != nil { + return err + } + } + } + + return nil +} + +func emitFiles(r *root, hPath string, cppPaths map[structType]string) error { + h, err := os.Create(hPath) + if err != nil { + return err + } + defer h.Close() + cppFiles := map[structType]*os.File{} + for ty, p := range cppPaths { + f, err := os.Create(p) + if err != nil { + return err + } + cppFiles[ty] = f + defer f.Close() + } + + h.WriteString(headerPrologue) + for _, f := range cppFiles { + f.WriteString(cppPrologue) + } + + structs, err := buildStructs(r) + if err != nil { + return err + } + + structsByName := map[string]*cppStruct{} + for _, s := range structs { + structsByName[s.name] = s + } + + seen := map[string]bool{} + var emit func(*cppStruct) + emit = func(s *cppStruct) { + if seen[s.name] { + return + } + seen[s.name] = true + for _, dep := range s.deps { + emit(structsByName[dep]) + } + s.writeHeader(h) + s.writeCPP(cppFiles[s.ty]) + } + + // emit message types. + // Referenced structs will be transitively emitted. + for _, s := range structs { + switch s.ty { + case request, response, event: + emit(s) + } + } + + h.WriteString(headerEpilogue) + for _, f := range cppFiles { + f.WriteString(cppEpilogue) + } + + return nil +} + +func loadJSONFile() ([]byte, error) { + if *cache != "" { + data, err := ioutil.ReadFile(*cache) + if err == nil { + return data, nil + } + } + resp, err := http.Get(jsonURL) + if err != nil { + return nil, err + } + data, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + if *cache != "" { + ioutil.WriteFile(*cache, data, 0777) + } + return data, nil +} + +func outputPaths() (string, cppFilePaths) { + _, thisFile, _, _ := runtime.Caller(1) + thisDir := path.Dir(thisFile) + h := path.Join(thisDir, "../../include/dap/protocol.h") + cpp := cppFilePaths{ + request: path.Join(thisDir, "../../src/protocol_requests.cpp"), + response: path.Join(thisDir, "../../src/protocol_response.cpp"), + event: path.Join(thisDir, "../../src/protocol_events.cpp"), + types: path.Join(thisDir, "../../src/protocol_types.cpp"), + } + return h, cpp +}